@elizaos/plugin-finances 2.0.3-beta.5 → 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.
- package/dist/actions/finances.d.ts +38 -0
- package/dist/actions/finances.d.ts.map +1 -0
- package/dist/actions/finances.js +368 -0
- package/dist/actions/finances.js.map +1 -0
- package/dist/components/finances/FinancesSpatialView.d.ts +80 -0
- package/dist/components/finances/FinancesSpatialView.d.ts.map +1 -0
- package/dist/components/finances/FinancesSpatialView.js +157 -0
- package/dist/components/finances/FinancesSpatialView.js.map +1 -0
- package/dist/components/finances/FinancesView.d.ts +97 -0
- package/dist/components/finances/FinancesView.d.ts.map +1 -0
- package/dist/components/finances/FinancesView.js +231 -0
- package/dist/components/finances/FinancesView.js.map +1 -0
- package/dist/components/finances/finances-view-bundle.d.ts +10 -0
- package/dist/components/finances/finances-view-bundle.d.ts.map +1 -0
- package/dist/components/finances/finances-view-bundle.js +5 -0
- package/dist/components/finances/finances-view-bundle.js.map +1 -0
- package/dist/db/finances-repository.d.ts +51 -0
- package/dist/db/finances-repository.d.ts.map +1 -0
- package/dist/db/finances-repository.js +521 -0
- package/dist/db/finances-repository.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +6 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +2615 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +133 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/sql.d.ts +65 -0
- package/dist/db/sql.d.ts.map +1 -0
- package/dist/db/sql.js +182 -0
- package/dist/db/sql.js.map +1 -0
- package/dist/finance-normalize.d.ts +24 -0
- package/dist/finance-normalize.d.ts.map +1 -0
- package/dist/finance-normalize.js +66 -0
- package/dist/finance-normalize.js.map +1 -0
- package/dist/finances-service.d.ts +179 -0
- package/dist/finances-service.d.ts.map +1 -0
- package/dist/finances-service.js +1122 -0
- package/dist/finances-service.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -0
- package/dist/payment-csv-import.d.ts +23 -0
- package/dist/payment-csv-import.d.ts.map +1 -0
- package/dist/payment-csv-import.js +271 -0
- package/dist/payment-csv-import.js.map +1 -0
- package/dist/payment-recurrence.d.ts +14 -0
- package/dist/payment-recurrence.d.ts.map +1 -0
- package/dist/payment-recurrence.js +190 -0
- package/dist/payment-recurrence.js.map +1 -0
- package/dist/payment-types.d.ts +158 -0
- package/dist/payment-types.d.ts.map +1 -0
- package/dist/payment-types.js +1 -0
- package/dist/payment-types.js.map +1 -0
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +31 -0
- package/dist/plugin.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +21 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +9 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +5 -0
- package/dist/register.js.map +1 -0
- package/dist/services/browser-bridge-seam.d.ts +40 -0
- package/dist/services/browser-bridge-seam.d.ts.map +1 -0
- package/dist/services/browser-bridge-seam.js +39 -0
- package/dist/services/browser-bridge-seam.js.map +1 -0
- package/dist/services/gmail-seam.d.ts +40 -0
- package/dist/services/gmail-seam.d.ts.map +1 -0
- package/dist/services/gmail-seam.js +208 -0
- package/dist/services/gmail-seam.js.map +1 -0
- package/dist/services/migration.d.ts +65 -0
- package/dist/services/migration.d.ts.map +1 -0
- package/dist/services/migration.js +116 -0
- package/dist/services/migration.js.map +1 -0
- package/dist/services/subscriptions-service.d.ts +76 -0
- package/dist/services/subscriptions-service.d.ts.map +1 -0
- package/dist/services/subscriptions-service.js +1002 -0
- package/dist/services/subscriptions-service.js.map +1 -0
- package/dist/subscriptions-playbooks.d.ts +79 -0
- package/dist/subscriptions-playbooks.d.ts.map +1 -0
- package/dist/subscriptions-playbooks.js +871 -0
- package/dist/subscriptions-playbooks.js.map +1 -0
- package/dist/subscriptions-types.d.ts +80 -0
- package/dist/subscriptions-types.d.ts.map +1 -0
- package/dist/subscriptions-types.js +1 -0
- package/dist/subscriptions-types.js.map +1 -0
- package/dist/token-encryption.d.ts +42 -0
- package/dist/token-encryption.d.ts.map +1 -0
- package/dist/token-encryption.js +96 -0
- package/dist/token-encryption.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/views/bundle.js +411 -0
- package/dist/views/bundle.js.map +1 -0
- 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"}
|