@lhremote/core 0.7.0 → 0.8.0

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 (288) hide show
  1. package/dist/db/index.d.ts +1 -1
  2. package/dist/db/index.d.ts.map +1 -1
  3. package/dist/db/index.js +1 -1
  4. package/dist/db/index.js.map +1 -1
  5. package/dist/db/repositories/action-budget.d.ts +29 -0
  6. package/dist/db/repositories/action-budget.d.ts.map +1 -0
  7. package/dist/db/repositories/action-budget.js +100 -0
  8. package/dist/db/repositories/action-budget.js.map +1 -0
  9. package/dist/db/repositories/action-budget.test.d.ts +2 -0
  10. package/dist/db/repositories/action-budget.test.d.ts.map +1 -0
  11. package/dist/db/repositories/action-budget.test.js +181 -0
  12. package/dist/db/repositories/action-budget.test.js.map +1 -0
  13. package/dist/db/repositories/campaign-hard-delete.integration.test.d.ts +2 -0
  14. package/dist/db/repositories/campaign-hard-delete.integration.test.d.ts.map +1 -0
  15. package/dist/db/repositories/campaign-hard-delete.integration.test.js +78 -0
  16. package/dist/db/repositories/campaign-hard-delete.integration.test.js.map +1 -0
  17. package/dist/db/repositories/campaign.d.ts +14 -0
  18. package/dist/db/repositories/campaign.d.ts.map +1 -1
  19. package/dist/db/repositories/campaign.js +125 -0
  20. package/dist/db/repositories/campaign.js.map +1 -1
  21. package/dist/db/repositories/index.d.ts +1 -0
  22. package/dist/db/repositories/index.d.ts.map +1 -1
  23. package/dist/db/repositories/index.js +1 -0
  24. package/dist/db/repositories/index.js.map +1 -1
  25. package/dist/index.d.ts +5 -4
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +24 -4
  28. package/dist/index.js.map +1 -1
  29. package/dist/linkedin/__tests__/dom-automation.integration.test.d.ts +2 -0
  30. package/dist/linkedin/__tests__/dom-automation.integration.test.d.ts.map +1 -0
  31. package/dist/linkedin/__tests__/dom-automation.integration.test.js +160 -0
  32. package/dist/linkedin/__tests__/dom-automation.integration.test.js.map +1 -0
  33. package/dist/linkedin/__tests__/selectors.integration.test.d.ts +2 -0
  34. package/dist/linkedin/__tests__/selectors.integration.test.d.ts.map +1 -0
  35. package/dist/linkedin/__tests__/selectors.integration.test.js +258 -0
  36. package/dist/linkedin/__tests__/selectors.integration.test.js.map +1 -0
  37. package/dist/linkedin/dom-automation.d.ts +67 -0
  38. package/dist/linkedin/dom-automation.d.ts.map +1 -0
  39. package/dist/linkedin/dom-automation.js +139 -0
  40. package/dist/linkedin/dom-automation.js.map +1 -0
  41. package/dist/linkedin/index.d.ts +3 -0
  42. package/dist/linkedin/index.d.ts.map +1 -0
  43. package/dist/linkedin/index.js +5 -0
  44. package/dist/linkedin/index.js.map +1 -0
  45. package/dist/linkedin/selectors.d.ts +67 -0
  46. package/dist/linkedin/selectors.d.ts.map +1 -0
  47. package/dist/linkedin/selectors.js +74 -0
  48. package/dist/linkedin/selectors.js.map +1 -0
  49. package/dist/operations/campaign-delete.d.ts +6 -2
  50. package/dist/operations/campaign-delete.d.ts.map +1 -1
  51. package/dist/operations/campaign-delete.js +9 -2
  52. package/dist/operations/campaign-delete.js.map +1 -1
  53. package/dist/operations/campaign-delete.test.js +41 -1
  54. package/dist/operations/campaign-delete.test.js.map +1 -1
  55. package/dist/operations/campaign-erase.d.ts +19 -0
  56. package/dist/operations/campaign-erase.d.ts.map +1 -0
  57. package/dist/operations/campaign-erase.js +22 -0
  58. package/dist/operations/campaign-erase.js.map +1 -0
  59. package/dist/operations/campaign-erase.test.d.ts +2 -0
  60. package/dist/operations/campaign-erase.test.d.ts.map +1 -0
  61. package/dist/operations/campaign-erase.test.js +101 -0
  62. package/dist/operations/campaign-erase.test.js.map +1 -0
  63. package/dist/operations/comment-on-post.d.ts +34 -0
  64. package/dist/operations/comment-on-post.d.ts.map +1 -0
  65. package/dist/operations/comment-on-post.js +108 -0
  66. package/dist/operations/comment-on-post.js.map +1 -0
  67. package/dist/operations/comment-on-post.test.d.ts +2 -0
  68. package/dist/operations/comment-on-post.test.d.ts.map +1 -0
  69. package/dist/operations/comment-on-post.test.js +240 -0
  70. package/dist/operations/comment-on-post.test.js.map +1 -0
  71. package/dist/operations/dismiss-errors.d.ts +22 -0
  72. package/dist/operations/dismiss-errors.d.ts.map +1 -0
  73. package/dist/operations/dismiss-errors.js +55 -0
  74. package/dist/operations/dismiss-errors.js.map +1 -0
  75. package/dist/operations/dismiss-errors.test.d.ts +2 -0
  76. package/dist/operations/dismiss-errors.test.d.ts.map +1 -0
  77. package/dist/operations/dismiss-errors.test.js +136 -0
  78. package/dist/operations/dismiss-errors.test.js.map +1 -0
  79. package/dist/operations/endorse-skills.d.ts +10 -0
  80. package/dist/operations/endorse-skills.d.ts.map +1 -0
  81. package/dist/operations/endorse-skills.js +12 -0
  82. package/dist/operations/endorse-skills.js.map +1 -0
  83. package/dist/operations/enrich-profile.d.ts +18 -0
  84. package/dist/operations/enrich-profile.d.ts.map +1 -0
  85. package/dist/operations/enrich-profile.js +15 -0
  86. package/dist/operations/enrich-profile.js.map +1 -0
  87. package/dist/operations/ephemeral-action.d.ts +22 -0
  88. package/dist/operations/ephemeral-action.d.ts.map +1 -0
  89. package/dist/operations/ephemeral-action.js +31 -0
  90. package/dist/operations/ephemeral-action.js.map +1 -0
  91. package/dist/operations/follow-person.d.ts +9 -0
  92. package/dist/operations/follow-person.d.ts.map +1 -0
  93. package/dist/operations/follow-person.js +11 -0
  94. package/dist/operations/follow-person.js.map +1 -0
  95. package/dist/operations/get-action-budget.d.ts +6 -0
  96. package/dist/operations/get-action-budget.d.ts.map +1 -0
  97. package/dist/operations/get-action-budget.js +22 -0
  98. package/dist/operations/get-action-budget.js.map +1 -0
  99. package/dist/operations/get-action-budget.test.d.ts +2 -0
  100. package/dist/operations/get-action-budget.test.d.ts.map +1 -0
  101. package/dist/operations/get-action-budget.test.js +79 -0
  102. package/dist/operations/get-action-budget.test.js.map +1 -0
  103. package/dist/operations/get-errors.d.ts +9 -3
  104. package/dist/operations/get-errors.d.ts.map +1 -1
  105. package/dist/operations/get-errors.js +41 -4
  106. package/dist/operations/get-errors.js.map +1 -1
  107. package/dist/operations/get-errors.test.js +108 -42
  108. package/dist/operations/get-errors.test.js.map +1 -1
  109. package/dist/operations/get-feed.d.ts +33 -0
  110. package/dist/operations/get-feed.d.ts.map +1 -0
  111. package/dist/operations/get-feed.js +220 -0
  112. package/dist/operations/get-feed.js.map +1 -0
  113. package/dist/operations/get-feed.test.d.ts +2 -0
  114. package/dist/operations/get-feed.test.d.ts.map +1 -0
  115. package/dist/operations/get-feed.test.js +276 -0
  116. package/dist/operations/get-feed.test.js.map +1 -0
  117. package/dist/operations/get-post-engagers.d.ts +40 -0
  118. package/dist/operations/get-post-engagers.d.ts.map +1 -0
  119. package/dist/operations/get-post-engagers.js +128 -0
  120. package/dist/operations/get-post-engagers.js.map +1 -0
  121. package/dist/operations/get-post-engagers.test.d.ts +2 -0
  122. package/dist/operations/get-post-engagers.test.d.ts.map +1 -0
  123. package/dist/operations/get-post-engagers.test.js +19 -0
  124. package/dist/operations/get-post-engagers.test.js.map +1 -0
  125. package/dist/operations/get-post-stats.d.ts +38 -0
  126. package/dist/operations/get-post-stats.d.ts.map +1 -0
  127. package/dist/operations/get-post-stats.js +100 -0
  128. package/dist/operations/get-post-stats.js.map +1 -0
  129. package/dist/operations/get-post-stats.test.d.ts +2 -0
  130. package/dist/operations/get-post-stats.test.d.ts.map +1 -0
  131. package/dist/operations/get-post-stats.test.js +34 -0
  132. package/dist/operations/get-post-stats.test.js.map +1 -0
  133. package/dist/operations/get-post.d.ts +183 -0
  134. package/dist/operations/get-post.d.ts.map +1 -0
  135. package/dist/operations/get-post.js +270 -0
  136. package/dist/operations/get-post.js.map +1 -0
  137. package/dist/operations/get-post.test.d.ts +2 -0
  138. package/dist/operations/get-post.test.d.ts.map +1 -0
  139. package/dist/operations/get-post.test.js +449 -0
  140. package/dist/operations/get-post.test.js.map +1 -0
  141. package/dist/operations/get-profile-activity.d.ts +129 -0
  142. package/dist/operations/get-profile-activity.d.ts.map +1 -0
  143. package/dist/operations/get-profile-activity.js +181 -0
  144. package/dist/operations/get-profile-activity.js.map +1 -0
  145. package/dist/operations/get-profile-activity.test.d.ts +2 -0
  146. package/dist/operations/get-profile-activity.test.d.ts.map +1 -0
  147. package/dist/operations/get-profile-activity.test.js +205 -0
  148. package/dist/operations/get-profile-activity.test.js.map +1 -0
  149. package/dist/operations/get-throttle-status.d.ts +6 -0
  150. package/dist/operations/get-throttle-status.d.ts.map +1 -0
  151. package/dist/operations/get-throttle-status.js +30 -0
  152. package/dist/operations/get-throttle-status.js.map +1 -0
  153. package/dist/operations/get-throttle-status.test.d.ts +2 -0
  154. package/dist/operations/get-throttle-status.test.d.ts.map +1 -0
  155. package/dist/operations/get-throttle-status.test.js +62 -0
  156. package/dist/operations/get-throttle-status.test.js.map +1 -0
  157. package/dist/operations/index.d.ts +22 -0
  158. package/dist/operations/index.d.ts.map +1 -1
  159. package/dist/operations/index.js +32 -0
  160. package/dist/operations/index.js.map +1 -1
  161. package/dist/operations/like-person-posts.d.ts +14 -0
  162. package/dist/operations/like-person-posts.d.ts.map +1 -0
  163. package/dist/operations/like-person-posts.js +28 -0
  164. package/dist/operations/like-person-posts.js.map +1 -0
  165. package/dist/operations/message-person.d.ts +11 -0
  166. package/dist/operations/message-person.d.ts.map +1 -0
  167. package/dist/operations/message-person.js +19 -0
  168. package/dist/operations/message-person.js.map +1 -0
  169. package/dist/operations/react-to-post.d.ts +38 -0
  170. package/dist/operations/react-to-post.d.ts.map +1 -0
  171. package/dist/operations/react-to-post.js +82 -0
  172. package/dist/operations/react-to-post.js.map +1 -0
  173. package/dist/operations/react-to-post.test.d.ts +2 -0
  174. package/dist/operations/react-to-post.test.d.ts.map +1 -0
  175. package/dist/operations/react-to-post.test.js +154 -0
  176. package/dist/operations/react-to-post.test.js.map +1 -0
  177. package/dist/operations/remove-connection.d.ts +6 -0
  178. package/dist/operations/remove-connection.d.ts.map +1 -0
  179. package/dist/operations/remove-connection.js +7 -0
  180. package/dist/operations/remove-connection.js.map +1 -0
  181. package/dist/operations/search-posts.d.ts +143 -0
  182. package/dist/operations/search-posts.d.ts.map +1 -0
  183. package/dist/operations/search-posts.js +220 -0
  184. package/dist/operations/search-posts.js.map +1 -0
  185. package/dist/operations/search-posts.test.d.ts +2 -0
  186. package/dist/operations/search-posts.test.d.ts.map +1 -0
  187. package/dist/operations/search-posts.test.js +261 -0
  188. package/dist/operations/search-posts.test.js.map +1 -0
  189. package/dist/operations/send-inmail.d.ts +11 -0
  190. package/dist/operations/send-inmail.d.ts.map +1 -0
  191. package/dist/operations/send-inmail.js +19 -0
  192. package/dist/operations/send-inmail.js.map +1 -0
  193. package/dist/operations/send-invite.d.ts +9 -0
  194. package/dist/operations/send-invite.d.ts.map +1 -0
  195. package/dist/operations/send-invite.js +14 -0
  196. package/dist/operations/send-invite.js.map +1 -0
  197. package/dist/operations/visit-profile.d.ts +14 -0
  198. package/dist/operations/visit-profile.d.ts.map +1 -0
  199. package/dist/operations/visit-profile.js +49 -0
  200. package/dist/operations/visit-profile.js.map +1 -0
  201. package/dist/operations/visit-profile.test.d.ts +2 -0
  202. package/dist/operations/visit-profile.test.d.ts.map +1 -0
  203. package/dist/operations/visit-profile.test.js +193 -0
  204. package/dist/operations/visit-profile.test.js.map +1 -0
  205. package/dist/services/campaign.d.ts +10 -0
  206. package/dist/services/campaign.d.ts.map +1 -1
  207. package/dist/services/campaign.js +16 -0
  208. package/dist/services/campaign.js.map +1 -1
  209. package/dist/services/collection.d.ts +5 -2
  210. package/dist/services/collection.d.ts.map +1 -1
  211. package/dist/services/collection.js +24 -5
  212. package/dist/services/collection.js.map +1 -1
  213. package/dist/services/collection.test.js +51 -9
  214. package/dist/services/collection.test.js.map +1 -1
  215. package/dist/services/ephemeral-campaign.d.ts +64 -0
  216. package/dist/services/ephemeral-campaign.d.ts.map +1 -0
  217. package/dist/services/ephemeral-campaign.js +210 -0
  218. package/dist/services/ephemeral-campaign.js.map +1 -0
  219. package/dist/services/ephemeral-campaign.test.d.ts +2 -0
  220. package/dist/services/ephemeral-campaign.test.d.ts.map +1 -0
  221. package/dist/services/ephemeral-campaign.test.js +333 -0
  222. package/dist/services/ephemeral-campaign.test.js.map +1 -0
  223. package/dist/services/errors.d.ts +10 -0
  224. package/dist/services/errors.d.ts.map +1 -1
  225. package/dist/services/errors.js +20 -0
  226. package/dist/services/errors.js.map +1 -1
  227. package/dist/services/index.d.ts +3 -1
  228. package/dist/services/index.d.ts.map +1 -1
  229. package/dist/services/index.js +3 -1
  230. package/dist/services/index.js.map +1 -1
  231. package/dist/services/instance-context.d.ts.map +1 -1
  232. package/dist/services/instance-context.js +9 -1
  233. package/dist/services/instance-context.js.map +1 -1
  234. package/dist/services/instance-context.test.js +77 -1
  235. package/dist/services/instance-context.test.js.map +1 -1
  236. package/dist/services/instance.d.ts +31 -0
  237. package/dist/services/instance.d.ts.map +1 -1
  238. package/dist/services/instance.js +123 -0
  239. package/dist/services/instance.js.map +1 -1
  240. package/dist/services/instance.test.js +70 -0
  241. package/dist/services/instance.test.js.map +1 -1
  242. package/dist/services/launcher.d.ts +7 -0
  243. package/dist/services/launcher.d.ts.map +1 -1
  244. package/dist/services/launcher.js +18 -1
  245. package/dist/services/launcher.js.map +1 -1
  246. package/dist/types/action-budget.d.ts +52 -0
  247. package/dist/types/action-budget.d.ts.map +1 -0
  248. package/dist/types/action-budget.js +4 -0
  249. package/dist/types/action-budget.js.map +1 -0
  250. package/dist/types/campaign.d.ts +24 -0
  251. package/dist/types/campaign.d.ts.map +1 -1
  252. package/dist/types/feed.d.ts +32 -0
  253. package/dist/types/feed.d.ts.map +1 -0
  254. package/dist/types/feed.js +4 -0
  255. package/dist/types/feed.js.map +1 -0
  256. package/dist/types/index.d.ts +7 -2
  257. package/dist/types/index.d.ts.map +1 -1
  258. package/dist/types/post-analytics.d.ts +40 -0
  259. package/dist/types/post-analytics.d.ts.map +1 -0
  260. package/dist/types/post-analytics.js +4 -0
  261. package/dist/types/post-analytics.js.map +1 -0
  262. package/dist/types/post.d.ts +43 -0
  263. package/dist/types/post.d.ts.map +1 -0
  264. package/dist/types/post.js +4 -0
  265. package/dist/types/post.js.map +1 -0
  266. package/dist/types/search-posts.d.ts +22 -0
  267. package/dist/types/search-posts.d.ts.map +1 -0
  268. package/dist/types/search-posts.js +4 -0
  269. package/dist/types/search-posts.js.map +1 -0
  270. package/dist/types/ui-health.d.ts +16 -0
  271. package/dist/types/ui-health.d.ts.map +1 -1
  272. package/dist/voyager/index.d.ts +2 -0
  273. package/dist/voyager/index.d.ts.map +1 -0
  274. package/dist/voyager/index.js +4 -0
  275. package/dist/voyager/index.js.map +1 -0
  276. package/dist/voyager/interceptor.d.ts +100 -0
  277. package/dist/voyager/interceptor.d.ts.map +1 -0
  278. package/dist/voyager/interceptor.integration.test.d.ts +2 -0
  279. package/dist/voyager/interceptor.integration.test.d.ts.map +1 -0
  280. package/dist/voyager/interceptor.integration.test.js +89 -0
  281. package/dist/voyager/interceptor.integration.test.js.map +1 -0
  282. package/dist/voyager/interceptor.js +235 -0
  283. package/dist/voyager/interceptor.js.map +1 -0
  284. package/dist/voyager/interceptor.test.d.ts +2 -0
  285. package/dist/voyager/interceptor.test.d.ts.map +1 -0
  286. package/dist/voyager/interceptor.test.js +372 -0
  287. package/dist/voyager/interceptor.test.js.map +1 -0
  288. package/package.json +1 -1
@@ -0,0 +1,210 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Oleksii PELYKH
3
+ import { ProfileRepository } from "../db/index.js";
4
+ import { delay } from "../utils/delay.js";
5
+ import { CampaignService } from "./campaign.js";
6
+ import { CampaignExecutionError, CampaignTimeoutError } from "./errors.js";
7
+ /** Default timeout for ephemeral action execution (5 minutes). */
8
+ const DEFAULT_TIMEOUT = 300_000;
9
+ /** Default interval between status polls (2 seconds). */
10
+ const DEFAULT_POLL_INTERVAL = 2_000;
11
+ /** Prefix for ephemeral campaign names. */
12
+ const CAMPAIGN_NAME_PREFIX = "[ephemeral]";
13
+ /**
14
+ * Executes a single LinkedHelper action on a single person without
15
+ * manual campaign setup.
16
+ *
17
+ * Creates a temporary campaign, adds the requested action, imports the
18
+ * target person, starts the campaign runner, polls for completion,
19
+ * extracts the result, then hard-deletes the campaign (or archives it
20
+ * if `keepCampaign: true`).
21
+ */
22
+ export class EphemeralCampaignService {
23
+ campaignService;
24
+ profileRepo;
25
+ constructor(instance, db) {
26
+ this.campaignService = new CampaignService(instance, db);
27
+ this.profileRepo = new ProfileRepository(db);
28
+ }
29
+ /**
30
+ * Execute a single action on a single person using a temporary campaign.
31
+ *
32
+ * @param actionType - Action type identifier (e.g., 'MessageToPerson', 'InvitePerson').
33
+ * @param target - Person ID (number) or LinkedIn profile URL (string).
34
+ * @param actionSettings - Action-specific settings (e.g., message template).
35
+ * @param options - Execution options (timeout, keepCampaign, etc.).
36
+ * @returns Action result with success status, person ID, and action results.
37
+ *
38
+ * @throws {CampaignExecutionError} if the target cannot be resolved, import fails,
39
+ * or the campaign cannot be started.
40
+ * @throws {CampaignTimeoutError} if the action does not complete within the timeout.
41
+ */
42
+ async execute(actionType, target, actionSettings, options) {
43
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
44
+ const pollInterval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;
45
+ const keepCampaign = options?.keepCampaign ?? false;
46
+ // Step 1: Resolve target to LinkedIn URL and person ID (if known)
47
+ const resolved = this.resolveTarget(target);
48
+ // Step 2: Create temporary campaign
49
+ const campaign = await this.campaignService.create({
50
+ name: `${CAMPAIGN_NAME_PREFIX} ${actionType} ${new Date().toISOString()}`,
51
+ actions: [
52
+ {
53
+ name: actionType,
54
+ actionType,
55
+ ...(actionSettings !== undefined && { actionSettings }),
56
+ coolDown: 0,
57
+ maxActionResultsPerIteration: 1,
58
+ },
59
+ ],
60
+ });
61
+ try {
62
+ // Step 3: Import target person
63
+ const importResult = await this.campaignService.importPeopleFromUrls(campaign.id, [resolved.linkedInUrl]);
64
+ if (importResult.successful === 0 && importResult.alreadyInQueue === 0 && importResult.alreadyProcessed === 0) {
65
+ throw new CampaignExecutionError(`Failed to import target into campaign: ${resolved.linkedInUrl}`, campaign.id);
66
+ }
67
+ // Resolve person ID if not yet known (URL-based target)
68
+ const personId = resolved.personId ?? this.resolvePersonIdFromUrl(resolved.linkedInUrl);
69
+ // Step 4: Start campaign
70
+ await this.campaignService.start(campaign.id, []);
71
+ // Step 5: Poll for completion
72
+ await this.pollForCompletion(campaign.id, timeout, pollInterval);
73
+ // Step 6: Get results
74
+ const runResult = await this.campaignService.getResults(campaign.id);
75
+ const results = runResult.results;
76
+ const success = results.some((r) => r.result > 0);
77
+ // Step 7: Cleanup
78
+ await this.cleanup(campaign.id, keepCampaign);
79
+ return {
80
+ success,
81
+ personId,
82
+ results,
83
+ ...(keepCampaign && { campaignId: campaign.id }),
84
+ };
85
+ }
86
+ catch (error) {
87
+ await this.cleanup(campaign.id, keepCampaign);
88
+ throw error;
89
+ }
90
+ }
91
+ /**
92
+ * Resolve a target (person ID or URL) to a LinkedIn URL and optional person ID.
93
+ */
94
+ resolveTarget(target) {
95
+ if (typeof target === "number") {
96
+ // Person ID → look up LinkedIn URL from profile
97
+ const profiles = this.profileRepo.findByIds([target]);
98
+ const profile = profiles[0];
99
+ if (!profile) {
100
+ throw new CampaignExecutionError(`Person ${String(target)} not found in database`);
101
+ }
102
+ const publicId = profile.externalIds.find((e) => e.typeGroup === "public");
103
+ if (!publicId) {
104
+ throw new CampaignExecutionError(`Person ${String(target)} has no LinkedIn public ID`);
105
+ }
106
+ return {
107
+ linkedInUrl: `https://www.linkedin.com/in/${publicId.externalId}`,
108
+ personId: target,
109
+ };
110
+ }
111
+ // String target → validate and normalize LinkedIn URL; person ID resolved after import
112
+ const slug = this.extractSlug(target);
113
+ return {
114
+ linkedInUrl: `https://www.linkedin.com/in/${slug}`,
115
+ personId: undefined,
116
+ };
117
+ }
118
+ /**
119
+ * Resolve person ID from a LinkedIn profile URL after import.
120
+ */
121
+ resolvePersonIdFromUrl(linkedInUrl) {
122
+ const slug = this.extractSlug(linkedInUrl);
123
+ try {
124
+ const profile = this.profileRepo.findByPublicId(slug);
125
+ return profile.id;
126
+ }
127
+ catch (cause) {
128
+ throw new CampaignExecutionError(`Could not resolve person ID for ${linkedInUrl} after import`, undefined, { cause: cause instanceof Error ? cause : undefined });
129
+ }
130
+ }
131
+ /**
132
+ * Extract the LinkedIn public ID (slug) from a profile URL.
133
+ */
134
+ extractSlug(url) {
135
+ const match = /linkedin\.com\/in\/([^/?#]+)/.exec(url);
136
+ if (!match?.[1]) {
137
+ throw new CampaignExecutionError(`Invalid LinkedIn profile URL: ${url}`);
138
+ }
139
+ return match[1];
140
+ }
141
+ /**
142
+ * Poll campaign status until the target person's action completes or times out.
143
+ */
144
+ async pollForCompletion(campaignId, timeout, pollInterval) {
145
+ const deadline = Date.now() + timeout;
146
+ while (Date.now() < deadline) {
147
+ const status = await this.campaignService.getStatus(campaignId);
148
+ const counts = status.actionCounts[0];
149
+ // Action complete when at least one person is successful or failed
150
+ if (counts && (counts.successful > 0 || counts.failed > 0)) {
151
+ return;
152
+ }
153
+ // Runner returned to idle with no queued people — action is done
154
+ if (status.runnerState === "idle" && counts && counts.queued === 0) {
155
+ return;
156
+ }
157
+ const remaining = deadline - Date.now();
158
+ if (remaining <= 0)
159
+ break;
160
+ await delay(Math.min(pollInterval, remaining));
161
+ }
162
+ throw new CampaignTimeoutError(`Ephemeral action did not complete within ${String(timeout)}ms`, campaignId);
163
+ }
164
+ /**
165
+ * Stop and remove/archive an ephemeral campaign (best-effort).
166
+ */
167
+ async cleanup(campaignId, keepCampaign) {
168
+ await this.safeStop(campaignId);
169
+ if (keepCampaign) {
170
+ await this.safeArchive(campaignId);
171
+ }
172
+ else {
173
+ this.safeHardDelete(campaignId);
174
+ }
175
+ }
176
+ /**
177
+ * Stop campaign without throwing on failure.
178
+ */
179
+ async safeStop(campaignId) {
180
+ try {
181
+ await this.campaignService.stop(campaignId);
182
+ }
183
+ catch {
184
+ // Best-effort cleanup — campaign may already be stopped
185
+ }
186
+ }
187
+ /**
188
+ * Archive campaign without throwing on failure.
189
+ */
190
+ async safeArchive(campaignId) {
191
+ try {
192
+ await this.campaignService.delete(campaignId);
193
+ }
194
+ catch {
195
+ // Best-effort cleanup
196
+ }
197
+ }
198
+ /**
199
+ * Hard-delete campaign without throwing on failure.
200
+ */
201
+ safeHardDelete(campaignId) {
202
+ try {
203
+ this.campaignService.hardDelete(campaignId);
204
+ }
205
+ catch {
206
+ // Best-effort cleanup
207
+ }
208
+ }
209
+ }
210
+ //# sourceMappingURL=ephemeral-campaign.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ephemeral-campaign.js","sourceRoot":"","sources":["../../src/services/ephemeral-campaign.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ephemeral-campaign.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ephemeral-campaign.test.d.ts","sourceRoot":"","sources":["../../src/services/ephemeral-campaign.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,333 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Oleksii PELYKH
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { CampaignExecutionError, CampaignTimeoutError, } from "./errors.js";
5
+ import { EphemeralCampaignService } from "./ephemeral-campaign.js";
6
+ // Mock InstanceService
7
+ const mockEvaluateUI = vi.fn();
8
+ vi.mock("./instance.js", () => ({
9
+ InstanceService: vi.fn().mockImplementation(function () {
10
+ this.evaluateUI = mockEvaluateUI;
11
+ }),
12
+ }));
13
+ // Mock CampaignRepository (via db/index.js)
14
+ const mockListCampaigns = vi.fn();
15
+ const mockGetCampaign = vi.fn();
16
+ const mockGetCampaignActions = vi.fn();
17
+ const mockGetResults = vi.fn();
18
+ const mockFixIsValid = vi.fn();
19
+ const mockCreateActionExcludeLists = vi.fn();
20
+ const mockDeleteCampaign = vi.fn();
21
+ // Mock CampaignStatisticsRepository
22
+ const mockResetForRerun = vi.fn();
23
+ // Mock ProfileRepository
24
+ const mockFindByIds = vi.fn();
25
+ const mockFindByPublicId = vi.fn();
26
+ vi.mock("../db/index.js", async (importOriginal) => {
27
+ const original = await importOriginal();
28
+ return {
29
+ CampaignRepository: vi.fn().mockImplementation(function () {
30
+ this.listCampaigns = mockListCampaigns;
31
+ this.getCampaign = mockGetCampaign;
32
+ this.getCampaignActions = mockGetCampaignActions;
33
+ this.getResults = mockGetResults;
34
+ this.fixIsValid = mockFixIsValid;
35
+ this.createActionExcludeLists = mockCreateActionExcludeLists;
36
+ this.deleteCampaign = mockDeleteCampaign;
37
+ }),
38
+ CampaignStatisticsRepository: vi.fn().mockImplementation(function () {
39
+ this.resetForRerun = mockResetForRerun;
40
+ }),
41
+ ProfileRepository: vi.fn().mockImplementation(function () {
42
+ this.findByIds = mockFindByIds;
43
+ this.findByPublicId = mockFindByPublicId;
44
+ }),
45
+ CampaignNotFoundError: original.CampaignNotFoundError,
46
+ ActionNotFoundError: original.ActionNotFoundError,
47
+ ProfileNotFoundError: original.ProfileNotFoundError,
48
+ };
49
+ });
50
+ import { InstanceService } from "./instance.js";
51
+ const MOCK_CAMPAIGN = {
52
+ id: 99,
53
+ name: "[ephemeral] MessageToPerson",
54
+ description: null,
55
+ state: "paused",
56
+ liAccountId: 1,
57
+ isPaused: true,
58
+ isArchived: false,
59
+ isValid: true,
60
+ createdAt: "2025-01-15T00:00:00Z",
61
+ };
62
+ const MOCK_ACTIONS = [
63
+ {
64
+ id: 10,
65
+ campaignId: 99,
66
+ name: "MessageToPerson",
67
+ description: null,
68
+ config: {
69
+ id: 100,
70
+ actionType: "MessageToPerson",
71
+ actionSettings: { message: "Hello" },
72
+ coolDown: 0,
73
+ maxActionResultsPerIteration: 1,
74
+ isDraft: false,
75
+ },
76
+ versionId: 1000,
77
+ },
78
+ ];
79
+ const MOCK_PROFILE = {
80
+ id: 42,
81
+ miniProfile: { firstName: "Ada", lastName: "Lovelace", headline: "Engineer", avatar: null },
82
+ externalIds: [
83
+ { externalId: "ada-lovelace", typeGroup: "public", isMemberId: false },
84
+ { externalId: "123456789", typeGroup: "member", isMemberId: true },
85
+ ],
86
+ currentPosition: null,
87
+ education: [],
88
+ skills: [],
89
+ emails: [],
90
+ };
91
+ const MOCK_RESULT = {
92
+ id: 1,
93
+ actionVersionId: 1000,
94
+ personId: 42,
95
+ result: 1,
96
+ platform: "LINKEDIN",
97
+ createdAt: "2025-01-15T12:00:00Z",
98
+ profile: { firstName: "Ada", lastName: "Lovelace", headline: "Engineer", company: null, title: null },
99
+ };
100
+ afterEach(() => {
101
+ vi.restoreAllMocks();
102
+ });
103
+ describe("EphemeralCampaignService", () => {
104
+ let service;
105
+ beforeEach(() => {
106
+ vi.useFakeTimers();
107
+ vi.clearAllMocks();
108
+ mockEvaluateUI.mockResolvedValue(undefined);
109
+ const instance = new InstanceService(9223);
110
+ service = new EphemeralCampaignService(instance, {});
111
+ });
112
+ afterEach(() => {
113
+ vi.useRealTimers();
114
+ });
115
+ /**
116
+ * Set up mocks for a successful execute() flow:
117
+ * create → import → start (idle + unpause + runner) → poll (status) → results → stop → cleanup
118
+ */
119
+ function setupSuccessFlow() {
120
+ // create: CDP createCampaign returns campaign ID
121
+ mockEvaluateUI.mockResolvedValueOnce({ id: 99 });
122
+ mockGetCampaign.mockReturnValue(MOCK_CAMPAIGN);
123
+ mockGetCampaignActions.mockReturnValue(MOCK_ACTIONS);
124
+ // import: CDP importPeopleFromUrls
125
+ mockEvaluateUI.mockResolvedValueOnce({
126
+ total: { addToTarget: { successful: 1, alreadyInQueue: 0, alreadyProcessed: 0, failed: 0 } },
127
+ });
128
+ // start: waitForIdle (idle), unpause, start runner
129
+ mockEvaluateUI
130
+ .mockResolvedValueOnce("idle") // getRunnerState
131
+ .mockResolvedValueOnce(undefined) // unpause
132
+ .mockResolvedValueOnce(true); // start
133
+ // poll: getStatus (campaign + actions + runnerState + isPaused + actionCounts)
134
+ mockEvaluateUI
135
+ .mockResolvedValueOnce("idle") // runnerState
136
+ .mockResolvedValueOnce(false) // isPaused
137
+ .mockResolvedValueOnce({ queued: 0, processed: 0, successful: 1, failed: 0 }); // actionCounts
138
+ // results
139
+ mockGetResults.mockReturnValue([MOCK_RESULT]);
140
+ // stop: pause + stop runner
141
+ mockEvaluateUI
142
+ .mockResolvedValueOnce(undefined) // pause
143
+ .mockResolvedValueOnce(undefined); // stop runner
144
+ // hardDelete is sync (db only)
145
+ }
146
+ describe("execute with person ID target", () => {
147
+ it("executes full lifecycle: create → import → start → poll → results → cleanup", async () => {
148
+ mockFindByIds.mockReturnValue([MOCK_PROFILE]);
149
+ setupSuccessFlow();
150
+ const promise = service.execute("MessageToPerson", 42, { message: "Hello" });
151
+ await vi.advanceTimersByTimeAsync(10_000);
152
+ const result = await promise;
153
+ expect(result.success).toBe(true);
154
+ expect(result.personId).toBe(42);
155
+ expect(result.results).toEqual([MOCK_RESULT]);
156
+ expect(result.campaignId).toBeUndefined();
157
+ });
158
+ it("resolves LinkedIn URL from person profile", async () => {
159
+ mockFindByIds.mockReturnValue([MOCK_PROFILE]);
160
+ setupSuccessFlow();
161
+ const promise = service.execute("MessageToPerson", 42);
162
+ await vi.advanceTimersByTimeAsync(10_000);
163
+ await promise;
164
+ // import call should include the resolved LinkedIn URL
165
+ const importExpr = mockEvaluateUI.mock.calls[1]?.[0];
166
+ expect(importExpr).toContain("ada-lovelace");
167
+ });
168
+ it("throws CampaignExecutionError when person ID not found", async () => {
169
+ mockFindByIds.mockReturnValue([null]);
170
+ await expect(service.execute("MessageToPerson", 999)).rejects.toThrow(CampaignExecutionError);
171
+ });
172
+ it("throws CampaignExecutionError when person has no public ID", async () => {
173
+ mockFindByIds.mockReturnValue([{
174
+ ...MOCK_PROFILE,
175
+ externalIds: [{ externalId: "123", typeGroup: "member", isMemberId: true }],
176
+ }]);
177
+ await expect(service.execute("MessageToPerson", 42)).rejects.toThrow(CampaignExecutionError);
178
+ });
179
+ });
180
+ describe("execute with LinkedIn URL target", () => {
181
+ it("imports from URL and resolves person ID after import", async () => {
182
+ mockFindByPublicId.mockReturnValue(MOCK_PROFILE);
183
+ setupSuccessFlow();
184
+ const promise = service.execute("InvitePerson", "https://www.linkedin.com/in/ada-lovelace");
185
+ await vi.advanceTimersByTimeAsync(10_000);
186
+ const result = await promise;
187
+ expect(result.success).toBe(true);
188
+ expect(result.personId).toBe(42);
189
+ expect(mockFindByPublicId).toHaveBeenCalledWith("ada-lovelace");
190
+ });
191
+ it("throws CampaignExecutionError for invalid LinkedIn URL before campaign creation", async () => {
192
+ await expect(service.execute("InvitePerson", "https://example.com/not-linkedin")).rejects.toThrow(CampaignExecutionError);
193
+ // No CDP calls should have been made (validation fails before create)
194
+ expect(mockEvaluateUI).not.toHaveBeenCalled();
195
+ });
196
+ });
197
+ describe("import failure", () => {
198
+ it("throws CampaignExecutionError when import fails and cleans up", async () => {
199
+ mockFindByIds.mockReturnValue([MOCK_PROFILE]);
200
+ // create succeeds
201
+ mockEvaluateUI.mockResolvedValueOnce({ id: 99 });
202
+ mockGetCampaign.mockReturnValue(MOCK_CAMPAIGN);
203
+ mockGetCampaignActions.mockReturnValue(MOCK_ACTIONS);
204
+ // import returns zero successful
205
+ mockEvaluateUI.mockResolvedValueOnce({
206
+ total: { addToTarget: { successful: 0, alreadyInQueue: 0, alreadyProcessed: 0, failed: 1 } },
207
+ });
208
+ await expect(service.execute("MessageToPerson", 42)).rejects.toThrow(CampaignExecutionError);
209
+ // Verify cleanup happened (hardDelete is sync)
210
+ expect(mockDeleteCampaign).toHaveBeenCalledWith(99);
211
+ });
212
+ });
213
+ describe("timeout handling", () => {
214
+ it("throws CampaignTimeoutError when action does not complete", async () => {
215
+ mockFindByIds.mockReturnValue([MOCK_PROFILE]);
216
+ // create
217
+ mockEvaluateUI.mockResolvedValueOnce({ id: 99 });
218
+ mockGetCampaign.mockReturnValue(MOCK_CAMPAIGN);
219
+ mockGetCampaignActions.mockReturnValue(MOCK_ACTIONS);
220
+ // import
221
+ mockEvaluateUI.mockResolvedValueOnce({
222
+ total: { addToTarget: { successful: 1, alreadyInQueue: 0, alreadyProcessed: 0, failed: 0 } },
223
+ });
224
+ // start
225
+ mockEvaluateUI
226
+ .mockResolvedValueOnce("idle")
227
+ .mockResolvedValueOnce(undefined)
228
+ .mockResolvedValueOnce(true);
229
+ // poll: return type-correct shapes that keep the poll going (never completes)
230
+ // getStatus calls 3 CDP methods: runnerState, isPaused, actionCounts
231
+ let pollCall = 0;
232
+ mockEvaluateUI.mockImplementation(() => {
233
+ const phase = pollCall % 3;
234
+ pollCall++;
235
+ if (phase === 0)
236
+ return Promise.resolve("campaigns");
237
+ if (phase === 1)
238
+ return Promise.resolve(false);
239
+ return Promise.resolve({ queued: 1, processed: 0, successful: 0, failed: 0 });
240
+ });
241
+ const promise = service.execute("MessageToPerson", 42, undefined, { timeout: 5_000 });
242
+ const caughtPromise = promise.catch((e) => e);
243
+ await vi.advanceTimersByTimeAsync(10_000);
244
+ const error = await caughtPromise;
245
+ expect(error).toBeInstanceOf(CampaignTimeoutError);
246
+ });
247
+ });
248
+ describe("keepCampaign option", () => {
249
+ it("archives instead of hard-deleting when keepCampaign is true", async () => {
250
+ mockFindByIds.mockReturnValue([MOCK_PROFILE]);
251
+ setupSuccessFlow();
252
+ const promise = service.execute("MessageToPerson", 42, undefined, { keepCampaign: true });
253
+ await vi.advanceTimersByTimeAsync(10_000);
254
+ const result = await promise;
255
+ expect(result.campaignId).toBe(99);
256
+ // Should NOT hard-delete
257
+ expect(mockDeleteCampaign).not.toHaveBeenCalled();
258
+ // Archive is done via CDP (setCampaignArchivedStatus) — verify the call
259
+ const archiveCall = mockEvaluateUI.mock.calls.find((call) => call[0].includes("setCampaignArchivedStatus"));
260
+ expect(archiveCall).toBeDefined();
261
+ });
262
+ });
263
+ describe("failed action result", () => {
264
+ it("returns success=false when action result is negative", async () => {
265
+ mockFindByIds.mockReturnValue([MOCK_PROFILE]);
266
+ // create
267
+ mockEvaluateUI.mockResolvedValueOnce({ id: 99 });
268
+ mockGetCampaign.mockReturnValue(MOCK_CAMPAIGN);
269
+ mockGetCampaignActions.mockReturnValue(MOCK_ACTIONS);
270
+ // import
271
+ mockEvaluateUI.mockResolvedValueOnce({
272
+ total: { addToTarget: { successful: 1, alreadyInQueue: 0, alreadyProcessed: 0, failed: 0 } },
273
+ });
274
+ // start
275
+ mockEvaluateUI
276
+ .mockResolvedValueOnce("idle")
277
+ .mockResolvedValueOnce(undefined)
278
+ .mockResolvedValueOnce(true);
279
+ // poll: action failed
280
+ mockEvaluateUI
281
+ .mockResolvedValueOnce("idle")
282
+ .mockResolvedValueOnce(false)
283
+ .mockResolvedValueOnce({ queued: 0, processed: 0, successful: 0, failed: 1 });
284
+ // results
285
+ const failedResult = { ...MOCK_RESULT, result: -1 };
286
+ mockGetResults.mockReturnValue([failedResult]);
287
+ // stop
288
+ mockEvaluateUI
289
+ .mockResolvedValueOnce(undefined)
290
+ .mockResolvedValueOnce(undefined);
291
+ const promise = service.execute("MessageToPerson", 42);
292
+ await vi.advanceTimersByTimeAsync(10_000);
293
+ const result = await promise;
294
+ expect(result.success).toBe(false);
295
+ expect(result.results[0]?.result).toBe(-1);
296
+ });
297
+ });
298
+ describe("runner idle with no queued", () => {
299
+ it("completes when runner returns to idle with zero queued", async () => {
300
+ mockFindByIds.mockReturnValue([MOCK_PROFILE]);
301
+ // create
302
+ mockEvaluateUI.mockResolvedValueOnce({ id: 99 });
303
+ mockGetCampaign.mockReturnValue(MOCK_CAMPAIGN);
304
+ mockGetCampaignActions.mockReturnValue(MOCK_ACTIONS);
305
+ // import
306
+ mockEvaluateUI.mockResolvedValueOnce({
307
+ total: { addToTarget: { successful: 1, alreadyInQueue: 0, alreadyProcessed: 0, failed: 0 } },
308
+ });
309
+ // start
310
+ mockEvaluateUI
311
+ .mockResolvedValueOnce("idle")
312
+ .mockResolvedValueOnce(undefined)
313
+ .mockResolvedValueOnce(true);
314
+ // poll: idle with zero queued but zero successful/failed (edge case)
315
+ mockEvaluateUI
316
+ .mockResolvedValueOnce("idle")
317
+ .mockResolvedValueOnce(false)
318
+ .mockResolvedValueOnce({ queued: 0, processed: 0, successful: 0, failed: 0 });
319
+ // results (empty — person was processed but no result recorded)
320
+ mockGetResults.mockReturnValue([]);
321
+ // stop
322
+ mockEvaluateUI
323
+ .mockResolvedValueOnce(undefined)
324
+ .mockResolvedValueOnce(undefined);
325
+ const promise = service.execute("MessageToPerson", 42);
326
+ await vi.advanceTimersByTimeAsync(10_000);
327
+ const result = await promise;
328
+ expect(result.success).toBe(false);
329
+ expect(result.results).toEqual([]);
330
+ });
331
+ });
332
+ });
333
+ //# sourceMappingURL=ephemeral-campaign.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ephemeral-campaign.test.js","sourceRoot":"","sources":["../../src/services/ephemeral-campaign.test.ts"],"names":[],"mappings":"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"}
@@ -121,6 +121,16 @@ export declare class CampaignTimeoutError extends ServiceError {
121
121
  readonly campaignId: number | undefined;
122
122
  constructor(message: string, campaignId?: number, options?: ErrorOptions);
123
123
  }
124
+ /**
125
+ * Thrown when an action cannot proceed because its daily budget
126
+ * has been exhausted.
127
+ */
128
+ export declare class BudgetExceededError extends ServiceError {
129
+ readonly limitType: string;
130
+ readonly dailyLimit: number;
131
+ readonly totalUsed: number;
132
+ constructor(limitType: string, dailyLimit: number, totalUsed: number);
133
+ }
124
134
  /**
125
135
  * Thrown when the LinkedHelper UI is in a blocked state due to
126
136
  * a dialog, critical error, or blocking popup.
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/services/errors.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;gBACpC,OAAO,CAAC,EAAE,MAAM;CAM7B;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,YAAY;gBAClC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD;AAED;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,YAAY;gBAC/C,IAAI,EAAE,MAAM;CAMzB;AAED;;;;;;;GAOG;AACH,qBAAa,4BAA6B,SAAQ,YAAY;IAC5D,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,CAAC;gBAExB,SAAS,EAAE,aAAa,EAAE;CASvC;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;gBACtC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM/C;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,YAAY;gBAC3C,OAAO,CAAC,EAAE,MAAM;CAI7B;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,YAAY;gBAClC,IAAI,EAAE,MAAM;CAOzB;AAED;;;;;;;GAOG;AACH,qBAAa,+BAAgC,SAAQ,YAAY;;CAQhE;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;IACpD,oEAAoE;IACpE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAQzE;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,YAAY;gBAC1C,GAAG,EAAE,MAAM;CAMxB;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,YAAY;gBAC1C,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAMlD;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;gBACnC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,eAAe;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAQxD;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,YAAY;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE5B,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAKzE;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;IACpD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE5B,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAKzE;AAED;;;;;;GAMG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;gBAEpB,MAAM,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,YAAY;CAsB3D"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/services/errors.ts"],"names":[],"mappings":"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"}
@@ -179,6 +179,23 @@ export class CampaignTimeoutError extends ServiceError {
179
179
  this.campaignId = campaignId;
180
180
  }
181
181
  }
182
+ /**
183
+ * Thrown when an action cannot proceed because its daily budget
184
+ * has been exhausted.
185
+ */
186
+ export class BudgetExceededError extends ServiceError {
187
+ limitType;
188
+ dailyLimit;
189
+ totalUsed;
190
+ constructor(limitType, dailyLimit, totalUsed) {
191
+ super(`Action budget exceeded for ${limitType}: ` +
192
+ `${String(totalUsed)}/${String(dailyLimit)} used today`);
193
+ this.name = "BudgetExceededError";
194
+ this.limitType = limitType;
195
+ this.dailyLimit = dailyLimit;
196
+ this.totalUsed = totalUsed;
197
+ }
198
+ }
182
199
  /**
183
200
  * Thrown when the LinkedHelper UI is in a blocked state due to
184
201
  * a dialog, critical error, or blocking popup.
@@ -201,6 +218,9 @@ export class UIBlockedError extends ServiceError {
201
218
  if (health.popup?.blocked) {
202
219
  reasons.push(`Popup: ${health.popup.message ?? "blocking overlay active"}`);
203
220
  }
221
+ for (const instancePopup of health.instancePopups) {
222
+ reasons.push(`Instance popup: ${instancePopup.title}${instancePopup.description ? ` — ${instancePopup.description}` : ""}`);
223
+ }
204
224
  super(`LinkedHelper UI is blocked — ${reasons.join("; ")}`, options);
205
225
  this.name = "UIBlockedError";
206
226
  this.health = health;