@elizaos/plugin-finances 2.0.3-beta.6 → 2.0.3-beta.7

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 (103) hide show
  1. package/dist/actions/finances.d.ts +38 -0
  2. package/dist/actions/finances.d.ts.map +1 -0
  3. package/dist/actions/finances.js +368 -0
  4. package/dist/actions/finances.js.map +1 -0
  5. package/dist/components/finances/FinancesSpatialView.d.ts +80 -0
  6. package/dist/components/finances/FinancesSpatialView.d.ts.map +1 -0
  7. package/dist/components/finances/FinancesSpatialView.js +157 -0
  8. package/dist/components/finances/FinancesSpatialView.js.map +1 -0
  9. package/dist/components/finances/FinancesView.d.ts +97 -0
  10. package/dist/components/finances/FinancesView.d.ts.map +1 -0
  11. package/dist/components/finances/FinancesView.js +231 -0
  12. package/dist/components/finances/FinancesView.js.map +1 -0
  13. package/dist/components/finances/finances-view-bundle.d.ts +10 -0
  14. package/dist/components/finances/finances-view-bundle.d.ts.map +1 -0
  15. package/dist/components/finances/finances-view-bundle.js +5 -0
  16. package/dist/components/finances/finances-view-bundle.js.map +1 -0
  17. package/dist/db/finances-repository.d.ts +51 -0
  18. package/dist/db/finances-repository.d.ts.map +1 -0
  19. package/dist/db/finances-repository.js +521 -0
  20. package/dist/db/finances-repository.js.map +1 -0
  21. package/dist/db/index.d.ts +3 -0
  22. package/dist/db/index.d.ts.map +1 -0
  23. package/dist/db/index.js +6 -0
  24. package/dist/db/index.js.map +1 -0
  25. package/dist/db/schema.d.ts +2615 -0
  26. package/dist/db/schema.d.ts.map +1 -0
  27. package/dist/db/schema.js +133 -0
  28. package/dist/db/schema.js.map +1 -0
  29. package/dist/db/sql.d.ts +65 -0
  30. package/dist/db/sql.d.ts.map +1 -0
  31. package/dist/db/sql.js +182 -0
  32. package/dist/db/sql.js.map +1 -0
  33. package/dist/finance-normalize.d.ts +24 -0
  34. package/dist/finance-normalize.d.ts.map +1 -0
  35. package/dist/finance-normalize.js +66 -0
  36. package/dist/finance-normalize.js.map +1 -0
  37. package/dist/finances-service.d.ts +179 -0
  38. package/dist/finances-service.d.ts.map +1 -0
  39. package/dist/finances-service.js +1122 -0
  40. package/dist/finances-service.js.map +1 -0
  41. package/dist/index.d.ts +32 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +109 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/payment-csv-import.d.ts +23 -0
  46. package/dist/payment-csv-import.d.ts.map +1 -0
  47. package/dist/payment-csv-import.js +271 -0
  48. package/dist/payment-csv-import.js.map +1 -0
  49. package/dist/payment-recurrence.d.ts +14 -0
  50. package/dist/payment-recurrence.d.ts.map +1 -0
  51. package/dist/payment-recurrence.js +190 -0
  52. package/dist/payment-recurrence.js.map +1 -0
  53. package/dist/payment-types.d.ts +158 -0
  54. package/dist/payment-types.d.ts.map +1 -0
  55. package/dist/payment-types.js +1 -0
  56. package/dist/payment-types.js.map +1 -0
  57. package/dist/plugin.d.ts +15 -0
  58. package/dist/plugin.d.ts.map +1 -0
  59. package/dist/plugin.js +31 -0
  60. package/dist/plugin.js.map +1 -0
  61. package/dist/register-terminal-view.d.ts +15 -0
  62. package/dist/register-terminal-view.d.ts.map +1 -0
  63. package/dist/register-terminal-view.js +21 -0
  64. package/dist/register-terminal-view.js.map +1 -0
  65. package/dist/register.d.ts +9 -0
  66. package/dist/register.d.ts.map +1 -0
  67. package/dist/register.js +5 -0
  68. package/dist/register.js.map +1 -0
  69. package/dist/services/browser-bridge-seam.d.ts +40 -0
  70. package/dist/services/browser-bridge-seam.d.ts.map +1 -0
  71. package/dist/services/browser-bridge-seam.js +39 -0
  72. package/dist/services/browser-bridge-seam.js.map +1 -0
  73. package/dist/services/gmail-seam.d.ts +40 -0
  74. package/dist/services/gmail-seam.d.ts.map +1 -0
  75. package/dist/services/gmail-seam.js +208 -0
  76. package/dist/services/gmail-seam.js.map +1 -0
  77. package/dist/services/migration.d.ts +65 -0
  78. package/dist/services/migration.d.ts.map +1 -0
  79. package/dist/services/migration.js +116 -0
  80. package/dist/services/migration.js.map +1 -0
  81. package/dist/services/subscriptions-service.d.ts +76 -0
  82. package/dist/services/subscriptions-service.d.ts.map +1 -0
  83. package/dist/services/subscriptions-service.js +1002 -0
  84. package/dist/services/subscriptions-service.js.map +1 -0
  85. package/dist/subscriptions-playbooks.d.ts +79 -0
  86. package/dist/subscriptions-playbooks.d.ts.map +1 -0
  87. package/dist/subscriptions-playbooks.js +871 -0
  88. package/dist/subscriptions-playbooks.js.map +1 -0
  89. package/dist/subscriptions-types.d.ts +80 -0
  90. package/dist/subscriptions-types.d.ts.map +1 -0
  91. package/dist/subscriptions-types.js +1 -0
  92. package/dist/subscriptions-types.js.map +1 -0
  93. package/dist/token-encryption.d.ts +42 -0
  94. package/dist/token-encryption.d.ts.map +1 -0
  95. package/dist/token-encryption.js +96 -0
  96. package/dist/token-encryption.js.map +1 -0
  97. package/dist/types.d.ts +55 -0
  98. package/dist/types.d.ts.map +1 -0
  99. package/dist/types.js +18 -0
  100. package/dist/types.js.map +1 -0
  101. package/dist/views/bundle.js +411 -0
  102. package/dist/views/bundle.js.map +1 -0
  103. package/package.json +11 -11
@@ -0,0 +1,39 @@
1
+ import {
2
+ BROWSER_BRIDGE_ROUTE_SERVICE_TYPE
3
+ } from "@elizaos/plugin-browser";
4
+ import { fail } from "../finance-normalize.js";
5
+ function requireBrowserBridgeService(runtime) {
6
+ const service = runtime.getService(BROWSER_BRIDGE_ROUTE_SERVICE_TYPE);
7
+ if (!service || typeof service !== "object") {
8
+ fail(
9
+ 503,
10
+ "Browser bridge service is not registered. Enable the Agent Browser Bridge host plugin before using subscription cancellation in a browser."
11
+ );
12
+ }
13
+ return service;
14
+ }
15
+ function createSubscriptionsBrowserGateway(runtime, ownerEntityId) {
16
+ return {
17
+ async listBrowserCompanions() {
18
+ return requireBrowserBridgeService(runtime).listBrowserCompanions(
19
+ ownerEntityId
20
+ );
21
+ },
22
+ async createBrowserSession(request) {
23
+ return requireBrowserBridgeService(runtime).createBrowserSession(
24
+ request,
25
+ ownerEntityId
26
+ );
27
+ },
28
+ async getBrowserSession(sessionId) {
29
+ return requireBrowserBridgeService(runtime).getBrowserSession(
30
+ sessionId,
31
+ ownerEntityId
32
+ );
33
+ }
34
+ };
35
+ }
36
+ export {
37
+ createSubscriptionsBrowserGateway
38
+ };
39
+ //# sourceMappingURL=browser-bridge-seam.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/browser-bridge-seam.ts"],"sourcesContent":["/**\n * Browser-bridge runtime-service seam for the subscriptions back-end.\n *\n * Subscription cancellation drives the user's real Chrome / Safari through the\n * Agent Browser Bridge companion: it lists connected companions, creates a\n * browser session from a cancellation playbook, and polls that session's\n * status. `@elizaos/plugin-browser` owns the contract for this — it exports the\n * `BrowserBridgeRouteService` interface and the\n * `BROWSER_BRIDGE_ROUTE_SERVICE_TYPE` (\"lifeops_browser_plugin\") service type;\n * a host plugin (today, plugin-personal-assistant) registers the implementor\n * that persists companions and sessions.\n *\n * This module resolves that runtime service by service-type and exposes only\n * the narrow surface the subscriptions path needs. It carries no dependency on\n * `@elizaos/plugin-personal-assistant`: the contract lives entirely in\n * `@elizaos/plugin-browser`, the same way the BrowserService bridge target\n * resolves it.\n */\n\nimport type { IAgentRuntime } from \"@elizaos/core\";\nimport {\n BROWSER_BRIDGE_ROUTE_SERVICE_TYPE,\n type BrowserBridgeCompanionStatus,\n type BrowserBridgeRouteService,\n} from \"@elizaos/plugin-browser\";\nimport type {\n CreateLifeOpsBrowserSessionRequest,\n LifeOpsBrowserSession,\n} from \"@elizaos/plugin-browser/lifeops-session-contracts\";\nimport { fail } from \"../finance-normalize.js\";\n\n/**\n * The browser-bridge surface the subscriptions back-end needs, bound to the\n * registered `lifeops_browser_plugin` runtime service for the resolved owner.\n */\nexport interface SubscriptionsBrowserGateway {\n /** Connected + pending Agent Browser Bridge companions for the owner. */\n listBrowserCompanions(): Promise<BrowserBridgeCompanionStatus[]>;\n /** Create a browser session that runs the cancellation playbook. */\n createBrowserSession(\n request: CreateLifeOpsBrowserSessionRequest,\n ): Promise<LifeOpsBrowserSession>;\n /** Fetch a browser session by id (used to reconcile cancellation status). */\n getBrowserSession(sessionId: string): Promise<LifeOpsBrowserSession>;\n}\n\nfunction requireBrowserBridgeService(\n runtime: IAgentRuntime,\n): BrowserBridgeRouteService {\n const service = runtime.getService(BROWSER_BRIDGE_ROUTE_SERVICE_TYPE);\n if (!service || typeof service !== \"object\") {\n fail(\n 503,\n \"Browser bridge service is not registered. Enable the Agent Browser Bridge host plugin before using subscription cancellation in a browser.\",\n );\n }\n return service as unknown as BrowserBridgeRouteService;\n}\n\n/**\n * Build the subscriptions browser gateway bound to a runtime + owner. The owner\n * entity id is forwarded to the host service so companion + session scoping\n * matches the owner the finances service resolved.\n */\nexport function createSubscriptionsBrowserGateway(\n runtime: IAgentRuntime,\n ownerEntityId: string | null,\n): SubscriptionsBrowserGateway {\n return {\n async listBrowserCompanions(): Promise<BrowserBridgeCompanionStatus[]> {\n return requireBrowserBridgeService(runtime).listBrowserCompanions(\n ownerEntityId,\n );\n },\n async createBrowserSession(\n request: CreateLifeOpsBrowserSessionRequest,\n ): Promise<LifeOpsBrowserSession> {\n return requireBrowserBridgeService(runtime).createBrowserSession(\n request,\n ownerEntityId,\n );\n },\n async getBrowserSession(sessionId: string): Promise<LifeOpsBrowserSession> {\n return requireBrowserBridgeService(runtime).getBrowserSession(\n sessionId,\n ownerEntityId,\n );\n },\n };\n}\n"],"mappings":"AAoBA;AAAA,EACE;AAAA,OAGK;AAKP,SAAS,YAAY;AAiBrB,SAAS,4BACP,SAC2B;AAC3B,QAAM,UAAU,QAAQ,WAAW,iCAAiC;AACpE,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,kCACd,SACA,eAC6B;AAC7B,SAAO;AAAA,IACL,MAAM,wBAAiE;AACrE,aAAO,4BAA4B,OAAO,EAAE;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM,qBACJ,SACgC;AAChC,aAAO,4BAA4B,OAAO,EAAE;AAAA,QAC1C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM,kBAAkB,WAAmD;AACzE,aAAO,4BAA4B,OAAO,EAAE;AAAA,QAC1C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Gmail runtime-service seam for the subscriptions back-end.
3
+ *
4
+ * Subscription discovery scans the owner's recent Gmail for receipts /
5
+ * renewals and scores them against the cancellation playbooks. It does NOT
6
+ * need cross-channel triage — only a date-windowed Gmail search. This module
7
+ * resolves the `@elizaos/plugin-google` runtime service
8
+ * (`runtime.getService("google")`), derives the owner's Gmail grant from the
9
+ * connector-account metadata + scopes, and exposes the single search the
10
+ * subscriptions path uses.
11
+ *
12
+ * It is the focused sibling of `@elizaos/plugin-inbox`'s `google-gmail-seam`
13
+ * and carries no dependency on `@elizaos/plugin-personal-assistant`: grant
14
+ * derivation is reproduced here from the connector-account metadata (the same
15
+ * mapping PA performs) so the grant is self-contained.
16
+ */
17
+ import { type IAgentRuntime } from "@elizaos/core";
18
+ import type { LifeOpsGmailMessageSummary } from "@elizaos/shared";
19
+ /**
20
+ * The Gmail surface the subscriptions back-end needs, resolved from the runtime
21
+ * so the service does not reach into `runtime.getService` itself.
22
+ */
23
+ export interface SubscriptionsGmailGateway {
24
+ /**
25
+ * Search recent owner Gmail for subscription evidence. `windowDays` bounds
26
+ * the search to the discovery window; messages are returned newest-first.
27
+ * Throws (409/403/503) when Google is unconnected or Gmail is not granted.
28
+ */
29
+ searchSubscriptionMessages(args: {
30
+ windowDays: number;
31
+ maxResults: number;
32
+ now?: Date;
33
+ }): Promise<LifeOpsGmailMessageSummary[]>;
34
+ }
35
+ /**
36
+ * Build the subscriptions Gmail gateway bound to a runtime. The owner mailbox
37
+ * is scanned (the subscriptions path operates on the owner's receipts).
38
+ */
39
+ export declare function createSubscriptionsGmailGateway(runtime: IAgentRuntime, agentId: string): SubscriptionsGmailGateway;
40
+ //# sourceMappingURL=gmail-seam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-seam.d.ts","sourceRoot":"","sources":["../../src/services/gmail-seam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAKvB,OAAO,KAAK,EAGV,0BAA0B,EAE3B,MAAM,iBAAiB,CAAC;AA4PzB;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,0BAA0B,CAAC,IAAI,EAAE;QAC/B,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,IAAI,CAAC;KACZ,GAAG,OAAO,CAAC,0BAA0B,EAAE,CAAC,CAAC;CAC3C;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,MAAM,GACd,yBAAyB,CAgC3B"}
@@ -0,0 +1,208 @@
1
+ import {
2
+ getConnectorAccountManager
3
+ } from "@elizaos/core";
4
+ import { fail } from "../finance-normalize.js";
5
+ const GOOGLE_CONNECTOR_ACCOUNT_GRANT_PREFIX = "connector-account:";
6
+ function isRecord(value) {
7
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8
+ }
9
+ function stringValue(value) {
10
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
11
+ }
12
+ function stringArray(value) {
13
+ return Array.isArray(value) ? value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean) : [];
14
+ }
15
+ function accountMetadata(account) {
16
+ return isRecord(account.metadata) ? account.metadata : {};
17
+ }
18
+ function googleSideForAccount(account) {
19
+ return account.role === "AGENT" ? "agent" : "owner";
20
+ }
21
+ function googleCapabilitiesForAccount(account) {
22
+ const meta = accountMetadata(account);
23
+ const scopes = stringArray(meta.grantedScopes);
24
+ const capabilities = /* @__PURE__ */ new Set([
25
+ "google.basic_identity"
26
+ ]);
27
+ if (scopes.some((scope) => scope.includes("gmail.readonly"))) {
28
+ capabilities.add("google.gmail.triage");
29
+ }
30
+ if (scopes.some((scope) => scope.includes("gmail.send"))) {
31
+ capabilities.add("google.gmail.send");
32
+ capabilities.add("google.gmail.triage");
33
+ }
34
+ if (scopes.some(
35
+ (scope) => scope.includes("gmail.modify") || scope.includes("gmail.settings")
36
+ )) {
37
+ capabilities.add("google.gmail.manage");
38
+ capabilities.add("google.gmail.triage");
39
+ }
40
+ return [...capabilities];
41
+ }
42
+ function googleAccountEmail(account) {
43
+ const meta = accountMetadata(account);
44
+ return (stringValue(meta.email) ?? stringValue(account.displayHandle) ?? null)?.toLowerCase() ?? null;
45
+ }
46
+ function grantIdForAccount(accountId) {
47
+ return `${GOOGLE_CONNECTOR_ACCOUNT_GRANT_PREFIX}${accountId}`;
48
+ }
49
+ function accountIdFromGrantId(grantId) {
50
+ const normalized = stringValue(grantId);
51
+ if (!normalized) return null;
52
+ return normalized.startsWith(GOOGLE_CONNECTOR_ACCOUNT_GRANT_PREFIX) ? normalized.slice(GOOGLE_CONNECTOR_ACCOUNT_GRANT_PREFIX.length) : normalized;
53
+ }
54
+ function grantFromAccount(args) {
55
+ const { account, agentId } = args;
56
+ const capabilities = googleCapabilitiesForAccount(account);
57
+ const meta = accountMetadata(account);
58
+ const createdAt = new Date(account.createdAt).toISOString();
59
+ const updatedAt = new Date(account.updatedAt).toISOString();
60
+ return {
61
+ id: grantIdForAccount(account.id),
62
+ agentId,
63
+ provider: "google",
64
+ side: googleSideForAccount(account),
65
+ identity: {},
66
+ grantedScopes: stringArray(meta.grantedScopes),
67
+ capabilities,
68
+ tokenRef: null,
69
+ mode: "local",
70
+ executionTarget: "local",
71
+ sourceOfTruth: "connector_account",
72
+ preferredByAgent: meta.isDefault === true,
73
+ cloudConnectionId: null,
74
+ connectorAccountId: account.id,
75
+ identityEmail: googleAccountEmail(account),
76
+ metadata: {
77
+ ...meta,
78
+ connectorAccountId: account.id,
79
+ connectorAccountProvider: "google"
80
+ },
81
+ lastRefreshAt: updatedAt,
82
+ createdAt,
83
+ updatedAt
84
+ };
85
+ }
86
+ async function listGoogleConnectorAccounts(runtime, requestedSide) {
87
+ const manager = getConnectorAccountManager(runtime);
88
+ const accounts = await manager.listAccounts("google");
89
+ return accounts.filter(
90
+ (account) => account.status !== "disabled" && account.status !== "revoked"
91
+ ).filter(
92
+ (account) => requestedSide ? googleSideForAccount(account) === requestedSide : true
93
+ );
94
+ }
95
+ async function resolveGoogleConnectorAccount(args) {
96
+ const accounts = await listGoogleConnectorAccounts(
97
+ args.runtime,
98
+ args.requestedSide
99
+ );
100
+ return accounts.find(
101
+ (account) => account.status === "connected" && accountMetadata(account).isDefault === true
102
+ ) ?? accounts.find((account) => account.status === "connected") ?? accounts[0] ?? null;
103
+ }
104
+ function requireGoogleWorkspaceService(runtime) {
105
+ const service = runtime.getService("google");
106
+ if (!isRecord(service)) {
107
+ fail(
108
+ 503,
109
+ "Google Workspace service is not registered. Enable @elizaos/plugin-google before scanning Gmail for subscriptions."
110
+ );
111
+ }
112
+ return service;
113
+ }
114
+ function requireSearchMessages(runtime) {
115
+ const service = requireGoogleWorkspaceService(runtime);
116
+ const searchMessages = service.searchMessages;
117
+ if (typeof searchMessages !== "function") {
118
+ fail(
119
+ 501,
120
+ "@elizaos/plugin-google does not expose searchMessages for subscription discovery."
121
+ );
122
+ }
123
+ return searchMessages.bind(
124
+ service
125
+ );
126
+ }
127
+ function accountIdForGrant(grant) {
128
+ return stringValue(grant.connectorAccountId) ?? accountIdFromGrantId(grant.id) ?? fail(
129
+ 409,
130
+ "Google connector account id is missing. Reconnect Google through connector account management."
131
+ );
132
+ }
133
+ function gmailMessageFromGoogle(args) {
134
+ const { message, grant, agentId, syncedAt } = args;
135
+ const labels = message.labelIds ?? [];
136
+ const fromName = message.from?.name?.trim();
137
+ const fromEmail = message.from?.email?.trim() ?? null;
138
+ const externalId = message.id;
139
+ const receivedAt = message.receivedAt ?? syncedAt;
140
+ return {
141
+ id: `${agentId}:google:${grant.side}:gmail:${externalId}`,
142
+ externalId,
143
+ agentId,
144
+ provider: "google",
145
+ side: grant.side,
146
+ threadId: message.threadId ?? externalId,
147
+ subject: message.subject ?? "(no subject)",
148
+ from: fromName || fromEmail || "Unknown sender",
149
+ fromEmail,
150
+ replyTo: message.replyTo?.email ?? null,
151
+ to: (message.to ?? []).map((item) => item.email),
152
+ cc: (message.cc ?? []).map((item) => item.email),
153
+ snippet: message.snippet ?? message.bodyText?.slice(0, 240) ?? "",
154
+ receivedAt,
155
+ isUnread: labels.includes("UNREAD"),
156
+ isImportant: labels.includes("IMPORTANT"),
157
+ likelyReplyNeeded: labels.includes("INBOX") && !labels.includes("SENT"),
158
+ triageScore: labels.includes("IMPORTANT") ? 90 : labels.includes("UNREAD") ? 70 : 40,
159
+ triageReason: labels.includes("IMPORTANT") ? "Marked important in Gmail." : labels.includes("UNREAD") ? "Unread inbox message." : "Recent Gmail message.",
160
+ labels,
161
+ htmlLink: null,
162
+ metadata: {
163
+ googlePlugin: true,
164
+ headers: message.headers ?? {},
165
+ bodyHtml: message.bodyHtml
166
+ },
167
+ syncedAt,
168
+ updatedAt: syncedAt,
169
+ connectorAccountId: grant.connectorAccountId ?? void 0,
170
+ grantId: grant.id,
171
+ accountEmail: grant.identityEmail ?? void 0
172
+ };
173
+ }
174
+ function createSubscriptionsGmailGateway(runtime, agentId) {
175
+ return {
176
+ async searchSubscriptionMessages(args) {
177
+ const account = await resolveGoogleConnectorAccount({
178
+ runtime,
179
+ requestedSide: "owner"
180
+ });
181
+ if (account?.status !== "connected") {
182
+ fail(409, "Google Gmail is not connected.");
183
+ }
184
+ const grant = grantFromAccount({ account, agentId });
185
+ if (!grant.capabilities.includes("google.gmail.triage")) {
186
+ fail(403, "Google Gmail triage access has not been granted.");
187
+ }
188
+ const searchMessages = requireSearchMessages(runtime);
189
+ const syncedAt = (args.now ?? /* @__PURE__ */ new Date()).toISOString();
190
+ const windowDays = Math.max(
191
+ 1,
192
+ Math.min(365, Math.trunc(args.windowDays))
193
+ );
194
+ const googleMessages = await searchMessages({
195
+ accountId: accountIdForGrant(grant),
196
+ query: `in:inbox newer_than:${windowDays}d`,
197
+ limit: args.maxResults
198
+ });
199
+ return googleMessages.map(
200
+ (message) => gmailMessageFromGoogle({ message, grant, agentId, syncedAt })
201
+ );
202
+ }
203
+ };
204
+ }
205
+ export {
206
+ createSubscriptionsGmailGateway
207
+ };
208
+ //# sourceMappingURL=gmail-seam.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/gmail-seam.ts"],"sourcesContent":["/**\n * Gmail runtime-service seam for the subscriptions back-end.\n *\n * Subscription discovery scans the owner's recent Gmail for receipts /\n * renewals and scores them against the cancellation playbooks. It does NOT\n * need cross-channel triage — only a date-windowed Gmail search. This module\n * resolves the `@elizaos/plugin-google` runtime service\n * (`runtime.getService(\"google\")`), derives the owner's Gmail grant from the\n * connector-account metadata + scopes, and exposes the single search the\n * subscriptions path uses.\n *\n * It is the focused sibling of `@elizaos/plugin-inbox`'s `google-gmail-seam`\n * and carries no dependency on `@elizaos/plugin-personal-assistant`: grant\n * derivation is reproduced here from the connector-account metadata (the same\n * mapping PA performs) so the grant is self-contained.\n */\n\nimport {\n type ConnectorAccount,\n getConnectorAccountManager,\n type IAgentRuntime,\n} from \"@elizaos/core\";\nimport type {\n GoogleMessageSummary,\n IGoogleWorkspaceService,\n} from \"@elizaos/plugin-google\";\nimport type {\n LifeOpsConnectorGrant,\n LifeOpsConnectorSide,\n LifeOpsGmailMessageSummary,\n LifeOpsGoogleCapability,\n} from \"@elizaos/shared\";\nimport { fail } from \"../finance-normalize.js\";\n\nconst GOOGLE_CONNECTOR_ACCOUNT_GRANT_PREFIX = \"connector-account:\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction stringValue(value: unknown): string | null {\n return typeof value === \"string\" && value.trim().length > 0\n ? value.trim()\n : null;\n}\n\nfunction stringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value\n .map((item) => (typeof item === \"string\" ? item.trim() : \"\"))\n .filter(Boolean)\n : [];\n}\n\nfunction accountMetadata(account: ConnectorAccount): Record<string, unknown> {\n return isRecord(account.metadata) ? account.metadata : {};\n}\n\nfunction googleSideForAccount(\n account: Pick<ConnectorAccount, \"role\">,\n): LifeOpsConnectorSide {\n return account.role === \"AGENT\" ? \"agent\" : \"owner\";\n}\n\nfunction googleCapabilitiesForAccount(\n account: ConnectorAccount,\n): LifeOpsGoogleCapability[] {\n const meta = accountMetadata(account);\n const scopes = stringArray(meta.grantedScopes);\n const capabilities = new Set<LifeOpsGoogleCapability>([\n \"google.basic_identity\",\n ]);\n if (scopes.some((scope) => scope.includes(\"gmail.readonly\"))) {\n capabilities.add(\"google.gmail.triage\");\n }\n if (scopes.some((scope) => scope.includes(\"gmail.send\"))) {\n capabilities.add(\"google.gmail.send\");\n capabilities.add(\"google.gmail.triage\");\n }\n if (\n scopes.some(\n (scope) =>\n scope.includes(\"gmail.modify\") || scope.includes(\"gmail.settings\"),\n )\n ) {\n capabilities.add(\"google.gmail.manage\");\n capabilities.add(\"google.gmail.triage\");\n }\n return [...capabilities];\n}\n\nfunction googleAccountEmail(account: ConnectorAccount): string | null {\n const meta = accountMetadata(account);\n return (\n (\n stringValue(meta.email) ??\n stringValue(account.displayHandle) ??\n null\n )?.toLowerCase() ?? null\n );\n}\n\nfunction grantIdForAccount(accountId: string): string {\n return `${GOOGLE_CONNECTOR_ACCOUNT_GRANT_PREFIX}${accountId}`;\n}\n\nfunction accountIdFromGrantId(\n grantId: string | null | undefined,\n): string | null {\n const normalized = stringValue(grantId);\n if (!normalized) return null;\n return normalized.startsWith(GOOGLE_CONNECTOR_ACCOUNT_GRANT_PREFIX)\n ? normalized.slice(GOOGLE_CONNECTOR_ACCOUNT_GRANT_PREFIX.length)\n : normalized;\n}\n\nfunction grantFromAccount(args: {\n account: ConnectorAccount;\n agentId: string;\n}): LifeOpsConnectorGrant {\n const { account, agentId } = args;\n const capabilities = googleCapabilitiesForAccount(account);\n const meta = accountMetadata(account);\n const createdAt = new Date(account.createdAt).toISOString();\n const updatedAt = new Date(account.updatedAt).toISOString();\n return {\n id: grantIdForAccount(account.id),\n agentId,\n provider: \"google\",\n side: googleSideForAccount(account),\n identity: {},\n grantedScopes: stringArray(meta.grantedScopes),\n capabilities,\n tokenRef: null,\n mode: \"local\",\n executionTarget: \"local\",\n sourceOfTruth: \"connector_account\",\n preferredByAgent: meta.isDefault === true,\n cloudConnectionId: null,\n connectorAccountId: account.id,\n identityEmail: googleAccountEmail(account),\n metadata: {\n ...meta,\n connectorAccountId: account.id,\n connectorAccountProvider: \"google\",\n },\n lastRefreshAt: updatedAt,\n createdAt,\n updatedAt,\n } as LifeOpsConnectorGrant;\n}\n\nasync function listGoogleConnectorAccounts(\n runtime: IAgentRuntime,\n requestedSide?: LifeOpsConnectorSide,\n): Promise<ConnectorAccount[]> {\n const manager = getConnectorAccountManager(runtime);\n const accounts = await manager.listAccounts(\"google\");\n return accounts\n .filter(\n (account) =>\n account.status !== \"disabled\" && account.status !== \"revoked\",\n )\n .filter((account) =>\n requestedSide ? googleSideForAccount(account) === requestedSide : true,\n );\n}\n\nasync function resolveGoogleConnectorAccount(args: {\n runtime: IAgentRuntime;\n requestedSide?: LifeOpsConnectorSide;\n}): Promise<ConnectorAccount | null> {\n const accounts = await listGoogleConnectorAccounts(\n args.runtime,\n args.requestedSide,\n );\n return (\n accounts.find(\n (account) =>\n account.status === \"connected\" &&\n accountMetadata(account).isDefault === true,\n ) ??\n accounts.find((account) => account.status === \"connected\") ??\n accounts[0] ??\n null\n );\n}\n\nfunction requireGoogleWorkspaceService(\n runtime: IAgentRuntime,\n): Record<string, unknown> {\n const service = runtime.getService(\"google\");\n if (!isRecord(service)) {\n fail(\n 503,\n \"Google Workspace service is not registered. Enable @elizaos/plugin-google before scanning Gmail for subscriptions.\",\n );\n }\n return service;\n}\n\nfunction requireSearchMessages(\n runtime: IAgentRuntime,\n): IGoogleWorkspaceService[\"searchMessages\"] {\n const service = requireGoogleWorkspaceService(runtime);\n const searchMessages = service.searchMessages;\n if (typeof searchMessages !== \"function\") {\n fail(\n 501,\n \"@elizaos/plugin-google does not expose searchMessages for subscription discovery.\",\n );\n }\n return searchMessages.bind(\n service,\n ) as IGoogleWorkspaceService[\"searchMessages\"];\n}\n\nfunction accountIdForGrant(grant: LifeOpsConnectorGrant): string {\n return (\n stringValue(grant.connectorAccountId) ??\n accountIdFromGrantId(grant.id) ??\n fail(\n 409,\n \"Google connector account id is missing. Reconnect Google through connector account management.\",\n )\n );\n}\n\nfunction gmailMessageFromGoogle(args: {\n message: GoogleMessageSummary;\n grant: LifeOpsConnectorGrant;\n agentId: string;\n syncedAt: string;\n}): LifeOpsGmailMessageSummary {\n const { message, grant, agentId, syncedAt } = args;\n const labels = message.labelIds ?? [];\n const fromName = message.from?.name?.trim();\n const fromEmail = message.from?.email?.trim() ?? null;\n const externalId = message.id;\n const receivedAt = message.receivedAt ?? syncedAt;\n return {\n id: `${agentId}:google:${grant.side}:gmail:${externalId}`,\n externalId,\n agentId,\n provider: \"google\",\n side: grant.side,\n threadId: message.threadId ?? externalId,\n subject: message.subject ?? \"(no subject)\",\n from: fromName || fromEmail || \"Unknown sender\",\n fromEmail,\n replyTo: message.replyTo?.email ?? null,\n to: (message.to ?? []).map((item) => item.email),\n cc: (message.cc ?? []).map((item) => item.email),\n snippet: message.snippet ?? message.bodyText?.slice(0, 240) ?? \"\",\n receivedAt,\n isUnread: labels.includes(\"UNREAD\"),\n isImportant: labels.includes(\"IMPORTANT\"),\n likelyReplyNeeded: labels.includes(\"INBOX\") && !labels.includes(\"SENT\"),\n triageScore: labels.includes(\"IMPORTANT\")\n ? 90\n : labels.includes(\"UNREAD\")\n ? 70\n : 40,\n triageReason: labels.includes(\"IMPORTANT\")\n ? \"Marked important in Gmail.\"\n : labels.includes(\"UNREAD\")\n ? \"Unread inbox message.\"\n : \"Recent Gmail message.\",\n labels,\n htmlLink: null,\n metadata: {\n googlePlugin: true,\n headers: message.headers ?? {},\n bodyHtml: message.bodyHtml,\n },\n syncedAt,\n updatedAt: syncedAt,\n connectorAccountId: grant.connectorAccountId ?? undefined,\n grantId: grant.id,\n accountEmail: grant.identityEmail ?? undefined,\n };\n}\n\n/**\n * The Gmail surface the subscriptions back-end needs, resolved from the runtime\n * so the service does not reach into `runtime.getService` itself.\n */\nexport interface SubscriptionsGmailGateway {\n /**\n * Search recent owner Gmail for subscription evidence. `windowDays` bounds\n * the search to the discovery window; messages are returned newest-first.\n * Throws (409/403/503) when Google is unconnected or Gmail is not granted.\n */\n searchSubscriptionMessages(args: {\n windowDays: number;\n maxResults: number;\n now?: Date;\n }): Promise<LifeOpsGmailMessageSummary[]>;\n}\n\n/**\n * Build the subscriptions Gmail gateway bound to a runtime. The owner mailbox\n * is scanned (the subscriptions path operates on the owner's receipts).\n */\nexport function createSubscriptionsGmailGateway(\n runtime: IAgentRuntime,\n agentId: string,\n): SubscriptionsGmailGateway {\n return {\n async searchSubscriptionMessages(\n args,\n ): Promise<LifeOpsGmailMessageSummary[]> {\n const account = await resolveGoogleConnectorAccount({\n runtime,\n requestedSide: \"owner\",\n });\n if (account?.status !== \"connected\") {\n fail(409, \"Google Gmail is not connected.\");\n }\n const grant = grantFromAccount({ account, agentId });\n if (!grant.capabilities.includes(\"google.gmail.triage\")) {\n fail(403, \"Google Gmail triage access has not been granted.\");\n }\n const searchMessages = requireSearchMessages(runtime);\n const syncedAt = (args.now ?? new Date()).toISOString();\n const windowDays = Math.max(\n 1,\n Math.min(365, Math.trunc(args.windowDays)),\n );\n const googleMessages = await searchMessages({\n accountId: accountIdForGrant(grant),\n query: `in:inbox newer_than:${windowDays}d`,\n limit: args.maxResults,\n });\n return googleMessages.map((message) =>\n gmailMessageFromGoogle({ message, grant, agentId, syncedAt }),\n );\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Non-destructive data migration for the finance tables carved out of
3
+ * @elizaos/plugin-personal-assistant.
4
+ *
5
+ * The five finance tables (`life_payment_sources`, `life_payment_transactions`,
6
+ * `life_subscription_audits`, `life_subscription_candidates`,
7
+ * `life_subscription_cancellations`) used to live in the `app_lifeops`
8
+ * PostgreSQL schema, created by plugin-personal-assistant. They now live in
9
+ * `app_finances`, created by this plugin's drizzle schema. Existing installs
10
+ * still hold the owner's finance rows in `app_lifeops`, so on first boot we
11
+ * copy them across — once, idempotently, and WITHOUT ever touching the source.
12
+ *
13
+ * Guards (per table, independently):
14
+ * 1. Skip if the source table does not exist (fresh install / already dropped).
15
+ * 2. Skip if the target table is non-empty (migration already ran, or the
16
+ * plugin owns live data).
17
+ * 3. Otherwise copy every source row that is not already present in the
18
+ * target (a doubly-safe NOT EXISTS guard on the primary key).
19
+ *
20
+ * The source table is NEVER dropped or altered. The target schema is created
21
+ * defensively (`CREATE SCHEMA IF NOT EXISTS`) in case the migration runner
22
+ * has not yet applied — the drizzle runner also issues this, so it is a no-op
23
+ * in the normal path.
24
+ */
25
+ import { type IAgentRuntime, Service } from "@elizaos/core";
26
+ export declare const FINANCES_LOG_PREFIX = "[Finances]";
27
+ export declare const FINANCES_MIGRATION_SERVICE_TYPE = "finances_migration";
28
+ /** Tables to copy, in the order their foreign-key-like references read best. */
29
+ export declare const MIGRATED_FINANCE_TABLES: readonly ["life_payment_sources", "life_payment_transactions", "life_subscription_audits", "life_subscription_candidates", "life_subscription_cancellations"];
30
+ export type MigratedFinanceTable = (typeof MIGRATED_FINANCE_TABLES)[number];
31
+ /**
32
+ * Minimal SQL executor contract. Returns the result rows of a query (empty for
33
+ * statements). Real implementation goes through the runtime drizzle handle;
34
+ * tests inject a fake.
35
+ */
36
+ export type SqlExecutor = (sql: string) => Promise<Array<Record<string, unknown>>>;
37
+ export interface TableMigrationResult {
38
+ table: MigratedFinanceTable;
39
+ /** `"copied"` ran the INSERT; otherwise the reason it was skipped. */
40
+ outcome: "copied" | "source-missing" | "target-non-empty";
41
+ }
42
+ /**
43
+ * Copy a single table from `app_lifeops` to `app_finances`, applying the three
44
+ * guards. Pure aside from the injected executor — the unit tests drive this
45
+ * directly.
46
+ */
47
+ export declare function migrateFinanceTable(exec: SqlExecutor, table: MigratedFinanceTable): Promise<TableMigrationResult>;
48
+ /**
49
+ * Run the guarded copy for every finance table. `CREATE SCHEMA IF NOT EXISTS`
50
+ * first so the target is guaranteed to exist even if the migration runner has
51
+ * not yet applied. Returns the per-table outcome for observability/testing.
52
+ */
53
+ export declare function migrateFinanceTables(exec: SqlExecutor): Promise<TableMigrationResult[]>;
54
+ /**
55
+ * Service whose `start()` performs the one-time, guarded, non-destructive copy
56
+ * of the owner's finance rows from `app_lifeops` into `app_finances`.
57
+ */
58
+ export declare class FinancesMigrationService extends Service {
59
+ static readonly serviceType = "finances_migration";
60
+ capabilityDescription: string;
61
+ static start(runtime: IAgentRuntime): Promise<FinancesMigrationService>;
62
+ private run;
63
+ stop(): Promise<void>;
64
+ }
65
+ //# sourceMappingURL=migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/services/migration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAU,OAAO,EAAE,MAAM,eAAe,CAAC;AAEpE,eAAO,MAAM,mBAAmB,eAAe,CAAC;AAChD,eAAO,MAAM,+BAA+B,uBAAuB,CAAC;AAKpE,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,+JAM1B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,GAAG,EAAE,MAAM,KACR,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,oBAAoB,CAAC;IAC5B,sEAAsE;IACtE,OAAO,EAAE,QAAQ,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;CAC3D;AA4BD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,oBAAoB,CAAC,CAoB/B;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAOjC;AAmCD;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,OAAO;IACnD,gBAAyB,WAAW,wBAAmC;IAE9D,qBAAqB,SAC6F;WAE9G,KAAK,CAChB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,wBAAwB,CAAC;YAMtB,GAAG;IAqBF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CACrC"}
@@ -0,0 +1,116 @@
1
+ import { logger, Service } from "@elizaos/core";
2
+ const FINANCES_LOG_PREFIX = "[Finances]";
3
+ const FINANCES_MIGRATION_SERVICE_TYPE = "finances_migration";
4
+ const SOURCE_SCHEMA = "app_lifeops";
5
+ const TARGET_SCHEMA = "app_finances";
6
+ const MIGRATED_FINANCE_TABLES = [
7
+ "life_payment_sources",
8
+ "life_payment_transactions",
9
+ "life_subscription_audits",
10
+ "life_subscription_candidates",
11
+ "life_subscription_cancellations"
12
+ ];
13
+ function quoteIdent(name) {
14
+ return `"${name.replace(/"/g, '""')}"`;
15
+ }
16
+ async function sourceTableExists(exec, table) {
17
+ const rows = await exec(
18
+ `SELECT to_regclass('${SOURCE_SCHEMA}.${table}') IS NOT NULL AS present`
19
+ );
20
+ return rows[0]?.present === true || rows[0]?.present === "true";
21
+ }
22
+ async function targetTableIsEmpty(exec, table) {
23
+ const rows = await exec(
24
+ `SELECT NOT EXISTS (SELECT 1 FROM ${TARGET_SCHEMA}.${quoteIdent(table)}) AS empty`
25
+ );
26
+ return rows[0]?.empty === true || rows[0]?.empty === "true";
27
+ }
28
+ async function migrateFinanceTable(exec, table) {
29
+ if (!await sourceTableExists(exec, table)) {
30
+ return { table, outcome: "source-missing" };
31
+ }
32
+ if (!await targetTableIsEmpty(exec, table)) {
33
+ return { table, outcome: "target-non-empty" };
34
+ }
35
+ const target = `${TARGET_SCHEMA}.${quoteIdent(table)}`;
36
+ const source = `${SOURCE_SCHEMA}.${quoteIdent(table)}`;
37
+ await exec(
38
+ `INSERT INTO ${target}
39
+ SELECT s.* FROM ${source} AS s
40
+ WHERE NOT EXISTS (
41
+ SELECT 1 FROM ${target} AS t WHERE t.id = s.id
42
+ )`
43
+ );
44
+ return { table, outcome: "copied" };
45
+ }
46
+ async function migrateFinanceTables(exec) {
47
+ await exec(`CREATE SCHEMA IF NOT EXISTS ${TARGET_SCHEMA}`);
48
+ const results = [];
49
+ for (const table of MIGRATED_FINANCE_TABLES) {
50
+ results.push(await migrateFinanceTable(exec, table));
51
+ }
52
+ return results;
53
+ }
54
+ function getRuntimeDb(runtime) {
55
+ const db = runtime.db;
56
+ if (!db || typeof db.execute !== "function") {
57
+ throw new Error(
58
+ `${FINANCES_LOG_PREFIX} runtime.db is unavailable \u2014 @elizaos/plugin-sql must be loaded before @elizaos/plugin-finances.`
59
+ );
60
+ }
61
+ return db;
62
+ }
63
+ function extractRows(result) {
64
+ if (Array.isArray(result)) {
65
+ return result.filter(
66
+ (row) => typeof row === "object" && row !== null && !Array.isArray(row)
67
+ );
68
+ }
69
+ if (result && typeof result === "object" && "rows" in result) {
70
+ const rows = result.rows;
71
+ if (Array.isArray(rows)) {
72
+ return rows.filter(
73
+ (row) => typeof row === "object" && row !== null && !Array.isArray(row)
74
+ );
75
+ }
76
+ }
77
+ return [];
78
+ }
79
+ class FinancesMigrationService extends Service {
80
+ static serviceType = FINANCES_MIGRATION_SERVICE_TYPE;
81
+ capabilityDescription = "Non-destructive one-time copy of finance rows from app_lifeops into app_finances during the plugin-finances carve-out.";
82
+ static async start(runtime) {
83
+ const service = new FinancesMigrationService(runtime);
84
+ await service.run();
85
+ return service;
86
+ }
87
+ async run() {
88
+ const db = getRuntimeDb(this.runtime);
89
+ const { sql } = await import("drizzle-orm");
90
+ const exec = async (statement) => extractRows(await db.execute(sql.raw(statement)));
91
+ const results = await migrateFinanceTables(exec);
92
+ const copied = results.filter((r) => r.outcome === "copied");
93
+ if (copied.length > 0) {
94
+ logger.info(
95
+ { tables: copied.map((r) => r.table) },
96
+ `${FINANCES_LOG_PREFIX} copied ${copied.length} finance table(s) from ${SOURCE_SCHEMA} to ${TARGET_SCHEMA}`
97
+ );
98
+ } else {
99
+ logger.debug(
100
+ { results },
101
+ `${FINANCES_LOG_PREFIX} no finance tables required copying (already migrated or fresh install)`
102
+ );
103
+ }
104
+ }
105
+ async stop() {
106
+ }
107
+ }
108
+ export {
109
+ FINANCES_LOG_PREFIX,
110
+ FINANCES_MIGRATION_SERVICE_TYPE,
111
+ FinancesMigrationService,
112
+ MIGRATED_FINANCE_TABLES,
113
+ migrateFinanceTable,
114
+ migrateFinanceTables
115
+ };
116
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/migration.ts"],"sourcesContent":["/**\n * Non-destructive data migration for the finance tables carved out of\n * @elizaos/plugin-personal-assistant.\n *\n * The five finance tables (`life_payment_sources`, `life_payment_transactions`,\n * `life_subscription_audits`, `life_subscription_candidates`,\n * `life_subscription_cancellations`) used to live in the `app_lifeops`\n * PostgreSQL schema, created by plugin-personal-assistant. They now live in\n * `app_finances`, created by this plugin's drizzle schema. Existing installs\n * still hold the owner's finance rows in `app_lifeops`, so on first boot we\n * copy them across — once, idempotently, and WITHOUT ever touching the source.\n *\n * Guards (per table, independently):\n * 1. Skip if the source table does not exist (fresh install / already dropped).\n * 2. Skip if the target table is non-empty (migration already ran, or the\n * plugin owns live data).\n * 3. Otherwise copy every source row that is not already present in the\n * target (a doubly-safe NOT EXISTS guard on the primary key).\n *\n * The source table is NEVER dropped or altered. The target schema is created\n * defensively (`CREATE SCHEMA IF NOT EXISTS`) in case the migration runner\n * has not yet applied — the drizzle runner also issues this, so it is a no-op\n * in the normal path.\n */\n\nimport { type IAgentRuntime, logger, Service } from \"@elizaos/core\";\n\nexport const FINANCES_LOG_PREFIX = \"[Finances]\";\nexport const FINANCES_MIGRATION_SERVICE_TYPE = \"finances_migration\";\n\nconst SOURCE_SCHEMA = \"app_lifeops\";\nconst TARGET_SCHEMA = \"app_finances\";\n\n/** Tables to copy, in the order their foreign-key-like references read best. */\nexport const MIGRATED_FINANCE_TABLES = [\n \"life_payment_sources\",\n \"life_payment_transactions\",\n \"life_subscription_audits\",\n \"life_subscription_candidates\",\n \"life_subscription_cancellations\",\n] as const;\n\nexport type MigratedFinanceTable = (typeof MIGRATED_FINANCE_TABLES)[number];\n\n/**\n * Minimal SQL executor contract. Returns the result rows of a query (empty for\n * statements). Real implementation goes through the runtime drizzle handle;\n * tests inject a fake.\n */\nexport type SqlExecutor = (\n sql: string,\n) => Promise<Array<Record<string, unknown>>>;\n\nexport interface TableMigrationResult {\n table: MigratedFinanceTable;\n /** `\"copied\"` ran the INSERT; otherwise the reason it was skipped. */\n outcome: \"copied\" | \"source-missing\" | \"target-non-empty\";\n}\n\nfunction quoteIdent(name: string): string {\n // Identifiers here are compile-time literals (schema/table names), never user\n // input — but quote defensively so a stray name can never break out.\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\nasync function sourceTableExists(\n exec: SqlExecutor,\n table: MigratedFinanceTable,\n): Promise<boolean> {\n const rows = await exec(\n `SELECT to_regclass('${SOURCE_SCHEMA}.${table}') IS NOT NULL AS present`,\n );\n return rows[0]?.present === true || rows[0]?.present === \"true\";\n}\n\nasync function targetTableIsEmpty(\n exec: SqlExecutor,\n table: MigratedFinanceTable,\n): Promise<boolean> {\n const rows = await exec(\n `SELECT NOT EXISTS (SELECT 1 FROM ${TARGET_SCHEMA}.${quoteIdent(table)}) AS empty`,\n );\n return rows[0]?.empty === true || rows[0]?.empty === \"true\";\n}\n\n/**\n * Copy a single table from `app_lifeops` to `app_finances`, applying the three\n * guards. Pure aside from the injected executor — the unit tests drive this\n * directly.\n */\nexport async function migrateFinanceTable(\n exec: SqlExecutor,\n table: MigratedFinanceTable,\n): Promise<TableMigrationResult> {\n if (!(await sourceTableExists(exec, table))) {\n return { table, outcome: \"source-missing\" };\n }\n if (!(await targetTableIsEmpty(exec, table))) {\n return { table, outcome: \"target-non-empty\" };\n }\n\n const target = `${TARGET_SCHEMA}.${quoteIdent(table)}`;\n const source = `${SOURCE_SCHEMA}.${quoteIdent(table)}`;\n // NOT EXISTS on the primary key is redundant given the empty-target guard,\n // but keeps the INSERT idempotent even under a concurrent re-run.\n await exec(\n `INSERT INTO ${target}\n SELECT s.* FROM ${source} AS s\n WHERE NOT EXISTS (\n SELECT 1 FROM ${target} AS t WHERE t.id = s.id\n )`,\n );\n return { table, outcome: \"copied\" };\n}\n\n/**\n * Run the guarded copy for every finance table. `CREATE SCHEMA IF NOT EXISTS`\n * first so the target is guaranteed to exist even if the migration runner has\n * not yet applied. Returns the per-table outcome for observability/testing.\n */\nexport async function migrateFinanceTables(\n exec: SqlExecutor,\n): Promise<TableMigrationResult[]> {\n await exec(`CREATE SCHEMA IF NOT EXISTS ${TARGET_SCHEMA}`);\n const results: TableMigrationResult[] = [];\n for (const table of MIGRATED_FINANCE_TABLES) {\n results.push(await migrateFinanceTable(exec, table));\n }\n return results;\n}\n\ntype RuntimeDb = {\n execute: (query: unknown) => Promise<unknown>;\n};\n\nfunction getRuntimeDb(runtime: IAgentRuntime): RuntimeDb {\n const db = runtime.db as RuntimeDb | undefined;\n if (!db || typeof db.execute !== \"function\") {\n throw new Error(\n `${FINANCES_LOG_PREFIX} runtime.db is unavailable — @elizaos/plugin-sql must be loaded before @elizaos/plugin-finances.`,\n );\n }\n return db;\n}\n\nfunction extractRows(result: unknown): Array<Record<string, unknown>> {\n if (Array.isArray(result)) {\n return result.filter(\n (row): row is Record<string, unknown> =>\n typeof row === \"object\" && row !== null && !Array.isArray(row),\n );\n }\n if (result && typeof result === \"object\" && \"rows\" in result) {\n const rows = (result as { rows: unknown }).rows;\n if (Array.isArray(rows)) {\n return rows.filter(\n (row): row is Record<string, unknown> =>\n typeof row === \"object\" && row !== null && !Array.isArray(row),\n );\n }\n }\n return [];\n}\n\n/**\n * Service whose `start()` performs the one-time, guarded, non-destructive copy\n * of the owner's finance rows from `app_lifeops` into `app_finances`.\n */\nexport class FinancesMigrationService extends Service {\n static override readonly serviceType = FINANCES_MIGRATION_SERVICE_TYPE;\n\n override capabilityDescription =\n \"Non-destructive one-time copy of finance rows from app_lifeops into app_finances during the plugin-finances carve-out.\";\n\n static async start(\n runtime: IAgentRuntime,\n ): Promise<FinancesMigrationService> {\n const service = new FinancesMigrationService(runtime);\n await service.run();\n return service;\n }\n\n private async run(): Promise<void> {\n const db = getRuntimeDb(this.runtime);\n const { sql } = await import(\"drizzle-orm\");\n const exec: SqlExecutor = async (statement) =>\n extractRows(await db.execute(sql.raw(statement)));\n\n const results = await migrateFinanceTables(exec);\n const copied = results.filter((r) => r.outcome === \"copied\");\n if (copied.length > 0) {\n logger.info(\n { tables: copied.map((r) => r.table) },\n `${FINANCES_LOG_PREFIX} copied ${copied.length} finance table(s) from ${SOURCE_SCHEMA} to ${TARGET_SCHEMA}`,\n );\n } else {\n logger.debug(\n { results },\n `${FINANCES_LOG_PREFIX} no finance tables required copying (already migrated or fresh install)`,\n );\n }\n }\n\n override async stop(): Promise<void> {}\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * SubscriptionsService — the subscription audit / cancellation back-end.
3
+ *
4
+ * Standalone successor to plugin-personal-assistant's `withSubscriptions`
5
+ * LifeOps mixin. It holds its own runtime + {@link FinancesRepository} (the
6
+ * finance/subscription tables already live in `app_finances`) and reaches the
7
+ * cross-domain surfaces it needs through runtime-service seams rather than PA
8
+ * internals:
9
+ *
10
+ * - **Gmail** ({@link SubscriptionsGmailGateway}) — date-windowed owner Gmail
11
+ * search via `@elizaos/plugin-google`, for subscription-evidence discovery.
12
+ * - **Browser bridge** ({@link SubscriptionsBrowserGateway}) — companion list
13
+ * + session create/poll via the `lifeops_browser_plugin` runtime service
14
+ * contract owned by `@elizaos/plugin-browser`, for `user_browser`
15
+ * cancellation.
16
+ * - **computer-use** — the `computeruse` runtime service, for `agent_browser`
17
+ * cancellation playback.
18
+ *
19
+ * Behavior and the data it returns are preserved verbatim from the original
20
+ * mixin. This service carries no dependency on
21
+ * `@elizaos/plugin-personal-assistant`.
22
+ */
23
+ import { type IAgentRuntime } from "@elizaos/core";
24
+ import { FinancesRepository } from "../db/finances-repository.js";
25
+ import { type LifeOpsSubscriptionPlaybook } from "../subscriptions-playbooks.js";
26
+ import type { LifeOpsSubscriptionAuditSummary, LifeOpsSubscriptionCancellationRequest, LifeOpsSubscriptionCancellationSummary, LifeOpsSubscriptionDiscoveryRequest, LifeOpsSubscriptionExecutor } from "../subscriptions-types.js";
27
+ import { type SubscriptionsBrowserGateway } from "./browser-bridge-seam.js";
28
+ import { type SubscriptionsGmailGateway } from "./gmail-seam.js";
29
+ /** Optional construction options (mirrors the finances service shape). */
30
+ export type SubscriptionsServiceOptions = {
31
+ ownerEntityId?: string | null;
32
+ /** Injectable for tests — defaults to the runtime-resolved Gmail seam. */
33
+ gmailGateway?: SubscriptionsGmailGateway;
34
+ /** Injectable for tests — defaults to the runtime-resolved browser seam. */
35
+ browserGateway?: SubscriptionsBrowserGateway;
36
+ };
37
+ export declare class SubscriptionsService {
38
+ readonly runtime: IAgentRuntime;
39
+ readonly repository: FinancesRepository;
40
+ readonly ownerEntityId: string | null;
41
+ private readonly gmail;
42
+ private readonly browser;
43
+ constructor(runtime: IAgentRuntime, options?: SubscriptionsServiceOptions);
44
+ agentId(): string;
45
+ private logSubscriptionsWarn;
46
+ listSubscriptionPlaybooks(): Promise<LifeOpsSubscriptionPlaybook[]>;
47
+ /**
48
+ * Best-effort merchant→playbook lookup used by the Payments dashboard to
49
+ * deep-link from a recurring charge row to the cancellation flow. Returns
50
+ * a *trimmed* playbook descriptor (no `steps`) so callers don't render
51
+ * automation internals.
52
+ */
53
+ findSubscriptionPlaybookForMerchant(merchant: string): {
54
+ key: string;
55
+ serviceName: string;
56
+ managementUrl: string;
57
+ executorPreference: LifeOpsSubscriptionPlaybook["executorPreference"];
58
+ } | null;
59
+ getLatestSubscriptionAudit(): Promise<LifeOpsSubscriptionAuditSummary | null>;
60
+ auditSubscriptions(request?: LifeOpsSubscriptionDiscoveryRequest): Promise<LifeOpsSubscriptionAuditSummary>;
61
+ getSubscriptionCancellationStatus(args: {
62
+ cancellationId?: string | null;
63
+ serviceName?: string | null;
64
+ serviceSlug?: string | null;
65
+ }): Promise<LifeOpsSubscriptionCancellationSummary | null>;
66
+ cancelSubscription(request: LifeOpsSubscriptionCancellationRequest): Promise<LifeOpsSubscriptionCancellationSummary>;
67
+ summarizeSubscriptionAudit(summary: LifeOpsSubscriptionAuditSummary): string;
68
+ summarizeSubscriptionCancellation(summary: LifeOpsSubscriptionCancellationSummary): string;
69
+ resolveSubscriptionIntent(text: string): {
70
+ mode: "audit" | "cancel" | "status" | null;
71
+ serviceName?: string;
72
+ serviceSlug?: string;
73
+ executor?: LifeOpsSubscriptionExecutor;
74
+ };
75
+ }
76
+ //# sourceMappingURL=subscriptions-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions-service.d.ts","sourceRoot":"","sources":["../../src/services/subscriptions-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAU,MAAM,eAAe,CAAC;AAU3D,OAAO,EAIL,kBAAkB,EACnB,MAAM,8BAA8B,CAAC;AAQtC,OAAO,EAEL,KAAK,2BAA2B,EAIjC,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAEV,+BAA+B,EAE/B,sCAAsC,EACtC,sCAAsC,EAEtC,mCAAmC,EACnC,2BAA2B,EAC5B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,iBAAiB,CAAC;AAEzB,0EAA0E;AAC1E,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,yBAAyB,CAAC;IACzC,4EAA4E;IAC5E,cAAc,CAAC,EAAE,2BAA2B,CAAC;CAC9C,CAAC;AAsnBF,qBAAa,oBAAoB;aAOb,OAAO,EAAE,aAAa;IANxC,SAAgB,UAAU,EAAE,kBAAkB,CAAC;IAC/C,SAAgB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;gBAGpC,OAAO,EAAE,aAAa,EACtC,OAAO,GAAE,2BAAgC;IAY3C,OAAO,IAAI,MAAM;IAIjB,OAAO,CAAC,oBAAoB;IAWtB,yBAAyB,IAAI,OAAO,CAAC,2BAA2B,EAAE,CAAC;IAIzE;;;;;OAKG;IACH,mCAAmC,CAAC,QAAQ,EAAE,MAAM,GAAG;QACrD,GAAG,EAAE,MAAM,CAAC;QACZ,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,kBAAkB,EAAE,2BAA2B,CAAC,oBAAoB,CAAC,CAAC;KACvE,GAAG,IAAI;IAaF,0BAA0B,IAAI,OAAO,CAAC,+BAA+B,GAAG,IAAI,CAAC;IAc7E,kBAAkB,CACtB,OAAO,GAAE,mCAAwC,GAChD,OAAO,CAAC,+BAA+B,CAAC;IAwIrC,iCAAiC,CAAC,IAAI,EAAE;QAC5C,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7B,GAAG,OAAO,CAAC,sCAAsC,GAAG,IAAI,CAAC;IAuEpD,kBAAkB,CACtB,OAAO,EAAE,sCAAsC,GAC9C,OAAO,CAAC,sCAAsC,CAAC;IAuTlD,0BAA0B,CAAC,OAAO,EAAE,+BAA+B,GAAG,MAAM;IAgB5E,iCAAiC,CAC/B,OAAO,EAAE,sCAAsC,GAC9C,MAAM;IAiBT,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG;QACvC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;QAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,2BAA2B,CAAC;KACxC;CAuCF"}