@lobu/connector-worker 6.0.1

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 (62) hide show
  1. package/README.md +39 -0
  2. package/dist/__tests__/redact.test.d.ts +2 -0
  3. package/dist/__tests__/redact.test.d.ts.map +1 -0
  4. package/dist/__tests__/redact.test.js +135 -0
  5. package/dist/__tests__/redact.test.js.map +1 -0
  6. package/dist/bin.d.ts +11 -0
  7. package/dist/bin.d.ts.map +1 -0
  8. package/dist/bin.js +129 -0
  9. package/dist/bin.js.map +1 -0
  10. package/dist/daemon/client.d.ts +256 -0
  11. package/dist/daemon/client.d.ts.map +1 -0
  12. package/dist/daemon/client.js +152 -0
  13. package/dist/daemon/client.js.map +1 -0
  14. package/dist/daemon/executor.d.ts +25 -0
  15. package/dist/daemon/executor.d.ts.map +1 -0
  16. package/dist/daemon/executor.js +492 -0
  17. package/dist/daemon/executor.js.map +1 -0
  18. package/dist/daemon/index.d.ts +12 -0
  19. package/dist/daemon/index.d.ts.map +1 -0
  20. package/dist/daemon/index.js +9 -0
  21. package/dist/daemon/index.js.map +1 -0
  22. package/dist/daemon/worker.d.ts +54 -0
  23. package/dist/daemon/worker.d.ts.map +1 -0
  24. package/dist/daemon/worker.js +144 -0
  25. package/dist/daemon/worker.js.map +1 -0
  26. package/dist/embeddings.d.ts +9 -0
  27. package/dist/embeddings.d.ts.map +1 -0
  28. package/dist/embeddings.js +126 -0
  29. package/dist/embeddings.js.map +1 -0
  30. package/dist/executor/child-runner.d.ts +9 -0
  31. package/dist/executor/child-runner.d.ts.map +1 -0
  32. package/dist/executor/child-runner.js +317 -0
  33. package/dist/executor/child-runner.js.map +1 -0
  34. package/dist/executor/interface.d.ts +45 -0
  35. package/dist/executor/interface.d.ts.map +1 -0
  36. package/dist/executor/interface.js +2 -0
  37. package/dist/executor/interface.js.map +1 -0
  38. package/dist/executor/redact.d.ts +28 -0
  39. package/dist/executor/redact.d.ts.map +1 -0
  40. package/dist/executor/redact.js +108 -0
  41. package/dist/executor/redact.js.map +1 -0
  42. package/dist/executor/runtime.d.ts +45 -0
  43. package/dist/executor/runtime.d.ts.map +1 -0
  44. package/dist/executor/runtime.js +82 -0
  45. package/dist/executor/runtime.js.map +1 -0
  46. package/dist/executor/subprocess.d.ts +46 -0
  47. package/dist/executor/subprocess.d.ts.map +1 -0
  48. package/dist/executor/subprocess.js +378 -0
  49. package/dist/executor/subprocess.js.map +1 -0
  50. package/dist/index.d.ts +13 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +12 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/runtime-deps.d.ts +33 -0
  55. package/dist/runtime-deps.d.ts.map +1 -0
  56. package/dist/runtime-deps.js +48 -0
  57. package/dist/runtime-deps.js.map +1 -0
  58. package/dist/types.d.ts +2 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +2 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +70 -0
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Worker API Client
3
+ *
4
+ * HTTP client for communicating with the backend worker API endpoints.
5
+ * Updated for V1 integration platform: runs-based job model.
6
+ */
7
+ function trimTrailingSlashes(value) {
8
+ let end = value.length;
9
+ while (end > 0 && value.charCodeAt(end - 1) === 47)
10
+ end--;
11
+ return end === value.length ? value : value.slice(0, end);
12
+ }
13
+ /**
14
+ * Worker API Client
15
+ */
16
+ export class WorkerClient {
17
+ apiUrl;
18
+ workerId;
19
+ capabilities;
20
+ authToken;
21
+ version;
22
+ constructor(config) {
23
+ this.apiUrl = trimTrailingSlashes(config.apiUrl);
24
+ this.workerId = config.workerId;
25
+ this.capabilities = config.capabilities;
26
+ this.authToken = config.authToken?.trim() || undefined;
27
+ this.version = config.version ?? '1.0.0';
28
+ }
29
+ authHeaders() {
30
+ if (!this.authToken)
31
+ return {};
32
+ return { Authorization: `Bearer ${this.authToken}` };
33
+ }
34
+ async requestJson(path, body) {
35
+ const response = await fetch(`${this.apiUrl}${path}`, {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ ...this.authHeaders(),
40
+ },
41
+ body: JSON.stringify(body),
42
+ });
43
+ if (!response.ok) {
44
+ const responseText = await response.text();
45
+ throw new Error(`${path} failed: ${response.status} ${response.statusText} ${responseText}`);
46
+ }
47
+ return response.json();
48
+ }
49
+ async requestVoid(path, body) {
50
+ const response = await fetch(`${this.apiUrl}${path}`, {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ ...this.authHeaders(),
55
+ },
56
+ body: JSON.stringify(body),
57
+ });
58
+ if (!response.ok) {
59
+ const responseText = await response.text();
60
+ throw new Error(`${path} failed: ${response.status} ${response.statusText} ${responseText}`);
61
+ }
62
+ }
63
+ /**
64
+ * Poll for available runs
65
+ */
66
+ async poll() {
67
+ return this.requestJson('/api/workers/poll', {
68
+ worker_id: this.workerId,
69
+ capabilities: this.capabilities,
70
+ version: this.version,
71
+ });
72
+ }
73
+ /**
74
+ * Send heartbeat for active run
75
+ */
76
+ async heartbeat(runId, progress) {
77
+ await this.requestVoid('/api/workers/heartbeat', {
78
+ run_id: runId,
79
+ worker_id: this.workerId,
80
+ progress,
81
+ });
82
+ }
83
+ /**
84
+ * Stream content batch to backend
85
+ */
86
+ async stream(batch) {
87
+ await this.requestVoid('/api/workers/stream', batch);
88
+ }
89
+ /**
90
+ * Report sync run completion
91
+ */
92
+ async complete(req) {
93
+ await this.requestVoid('/api/workers/complete', req);
94
+ }
95
+ /**
96
+ * Report action run completion
97
+ */
98
+ async completeAction(req) {
99
+ await this.requestVoid('/api/workers/complete-action', req);
100
+ }
101
+ /**
102
+ * Fetch events needing embeddings
103
+ */
104
+ async fetchEventsForEmbedding(eventIds) {
105
+ const result = await this.requestJson('/api/workers/fetch-events', {
106
+ event_ids: eventIds,
107
+ });
108
+ return result.events;
109
+ }
110
+ /**
111
+ * Submit generated embeddings
112
+ */
113
+ async completeEmbeddings(req) {
114
+ await this.requestVoid('/api/workers/complete-embeddings', req);
115
+ }
116
+ /**
117
+ * Emit an auth artifact (QR, redirect URL, prompt) for the UI to render.
118
+ */
119
+ async emitAuthArtifact(req) {
120
+ await this.requestVoid('/api/workers/emit-auth-artifact', req);
121
+ }
122
+ /**
123
+ * Poll for a signal sent by the UI (OAuth callback, form submit, cancel).
124
+ */
125
+ async pollAuthSignal(req) {
126
+ return this.requestJson('/api/workers/poll-auth-signal', req);
127
+ }
128
+ /**
129
+ * Report auth run completion — writes credentials + metadata to auth_profiles.
130
+ */
131
+ async completeAuth(req) {
132
+ await this.requestVoid('/api/workers/complete-auth', req);
133
+ }
134
+ /**
135
+ * Health check
136
+ */
137
+ async healthCheck() {
138
+ try {
139
+ const response = await fetch(`${this.apiUrl}/api/health`, {
140
+ headers: this.authHeaders(),
141
+ });
142
+ return response.ok;
143
+ }
144
+ catch {
145
+ return false;
146
+ }
147
+ }
148
+ get id() {
149
+ return this.workerId;
150
+ }
151
+ }
152
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/daemon/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Run Executor
3
+ *
4
+ * Executes sync and action runs via subprocess execution with compiled connector code.
5
+ * Generates embeddings and streams results.
6
+ */
7
+ import type { Env } from '../types.js';
8
+ import type { ExecutorClient, PollResponse } from './client.js';
9
+ export interface ExecutorConfig {
10
+ batchSize: number;
11
+ heartbeatIntervalMs: number;
12
+ generateEmbeddings: boolean;
13
+ timeoutMs: number;
14
+ maxOldSpaceSize: number;
15
+ }
16
+ /**
17
+ * Execute a run (sync, action, or watcher).
18
+ *
19
+ * Dispatches to sync, action, or watcher execution based on run_type.
20
+ */
21
+ export declare function executeRun(client: ExecutorClient, job: PollResponse, env: Env, config?: Partial<ExecutorConfig>): Promise<{
22
+ itemsCollected: number;
23
+ error?: string;
24
+ }>;
25
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/daemon/executor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAe,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CACzB;AAUD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,GAAG,EACR,MAAM,GAAE,OAAO,CAAC,cAAc,CAAM,GACnC,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA2BrD"}
@@ -0,0 +1,492 @@
1
+ /**
2
+ * Run Executor
3
+ *
4
+ * Executes sync and action runs via subprocess execution with compiled connector code.
5
+ * Generates embeddings and streams results.
6
+ */
7
+ import { generateEmbedding } from '../embeddings.js';
8
+ import { executeCompiledConnector, getActionOutput, normalizeEventEnvelope, } from '../executor/runtime.js';
9
+ import { SubprocessExecutor } from '../executor/subprocess.js';
10
+ const DEFAULT_CONFIG = {
11
+ batchSize: 10,
12
+ heartbeatIntervalMs: 30000,
13
+ generateEmbeddings: true,
14
+ timeoutMs: 600000,
15
+ maxOldSpaceSize: 1024,
16
+ };
17
+ /**
18
+ * Execute a run (sync, action, or watcher).
19
+ *
20
+ * Dispatches to sync, action, or watcher execution based on run_type.
21
+ */
22
+ export async function executeRun(client, job, env, config = {}) {
23
+ const cfg = { ...DEFAULT_CONFIG, ...config };
24
+ if (job.run_type === 'action') {
25
+ return executeActionRun(client, job, env, cfg);
26
+ }
27
+ if (job.run_type === 'watcher') {
28
+ // Watcher reactions now execute inline in the API process (complete_window).
29
+ // If a legacy pending watcher run is polled, mark it as completed to clean up.
30
+ // Note: stuck runs older than the poll interval will be picked up here automatically.
31
+ console.error(`[executor] Cleaning up legacy watcher run ${job.run_id} — reactions now execute inline`);
32
+ await client.complete({
33
+ run_id: job.run_id,
34
+ worker_id: client.id,
35
+ status: 'success',
36
+ items_collected: 0,
37
+ });
38
+ return { itemsCollected: 0 };
39
+ }
40
+ if (job.run_type === 'embed_backfill') {
41
+ return executeEmbedBackfillRun(client, job, env);
42
+ }
43
+ if (job.run_type === 'auth') {
44
+ return executeAuthRun(client, job, env, cfg);
45
+ }
46
+ return executeSyncRun(client, job, env, cfg);
47
+ }
48
+ /**
49
+ * Execute a sync run (feed data ingestion)
50
+ */
51
+ async function executeSyncRun(client, job, env, cfg) {
52
+ const subprocessExecutor = new SubprocessExecutor({
53
+ timeoutMs: cfg.timeoutMs,
54
+ maxOldSpaceSize: cfg.maxOldSpaceSize,
55
+ });
56
+ const { run_id, connector_key, feed_key, config: feedConfig, checkpoint, credentials, compiled_code, } = job;
57
+ if (!run_id || !connector_key) {
58
+ throw new Error('Invalid run: missing run_id or connector_key');
59
+ }
60
+ if (!compiled_code) {
61
+ throw new Error(`Run ${run_id} (${connector_key}): No compiled code available. ` +
62
+ 'Ensure the connector has a compiled version.');
63
+ }
64
+ console.error(`[executor] Starting sync run ${run_id} (${connector_key}/${feed_key})`);
65
+ // Set up heartbeat interval
66
+ let heartbeatInterval;
67
+ let itemsCollectedSoFar = 0;
68
+ const startHeartbeat = () => {
69
+ heartbeatInterval = setInterval(async () => {
70
+ try {
71
+ await client.heartbeat(run_id, {
72
+ items_collected_so_far: itemsCollectedSoFar,
73
+ });
74
+ }
75
+ catch (err) {
76
+ console.error('[executor] Heartbeat failed:', err);
77
+ }
78
+ }, cfg.heartbeatIntervalMs);
79
+ };
80
+ const stopHeartbeat = () => {
81
+ if (heartbeatInterval) {
82
+ clearInterval(heartbeatInterval);
83
+ heartbeatInterval = undefined;
84
+ }
85
+ };
86
+ startHeartbeat();
87
+ try {
88
+ let batch = [];
89
+ let lastCheckpoint = checkpoint;
90
+ const flushBatch = async () => {
91
+ if (batch.length === 0)
92
+ return;
93
+ try {
94
+ await client.stream({
95
+ type: 'batch',
96
+ run_id,
97
+ items: batch,
98
+ checkpoint: lastCheckpoint ?? undefined,
99
+ });
100
+ }
101
+ catch (streamErr) {
102
+ const batchIds = batch.map((b) => b.id);
103
+ console.error(`[executor] Stream batch failed for run ${run_id} (${batchIds.length} items lost: ${batchIds.join(', ')}):`, streamErr);
104
+ const msg = streamErr instanceof Error ? streamErr.message : String(streamErr);
105
+ throw new Error(`Stream batch failed: ${msg} (lost ${batchIds.length} items: ${batchIds.join(', ')})`);
106
+ }
107
+ batch = [];
108
+ };
109
+ const result = await executeCompiledConnector({
110
+ mode: 'sync',
111
+ compiledCode: compiled_code,
112
+ config: (feedConfig ?? {}),
113
+ checkpoint: checkpoint,
114
+ env,
115
+ connectionCredentials: (job.connection_credentials ??
116
+ null),
117
+ sessionState: (job.session_state ?? null),
118
+ credentials,
119
+ feedKey: feed_key,
120
+ entityIds: job.entity_ids ?? [],
121
+ apiType: 'api',
122
+ executor: subprocessExecutor,
123
+ hooks: {
124
+ collectContents: false,
125
+ onCheckpointUpdate: async (nextCheckpoint) => {
126
+ lastCheckpoint = nextCheckpoint;
127
+ if (!lastCheckpoint)
128
+ return;
129
+ try {
130
+ await client.stream({
131
+ type: 'batch',
132
+ run_id,
133
+ items: [],
134
+ checkpoint: lastCheckpoint,
135
+ });
136
+ }
137
+ catch (err) {
138
+ console.error('[executor] Checkpoint flush failed:', err);
139
+ }
140
+ },
141
+ onContentChunk: async (items) => {
142
+ for (const item of items) {
143
+ const contentItem = await processContent(item, cfg.generateEmbeddings);
144
+ batch.push(contentItem);
145
+ itemsCollectedSoFar++;
146
+ if (batch.length >= cfg.batchSize) {
147
+ await flushBatch();
148
+ }
149
+ }
150
+ },
151
+ },
152
+ });
153
+ lastCheckpoint = result.checkpoint;
154
+ await flushBatch();
155
+ stopHeartbeat();
156
+ await client.complete({
157
+ run_id,
158
+ worker_id: client.id,
159
+ status: 'success',
160
+ items_collected: itemsCollectedSoFar,
161
+ checkpoint: lastCheckpoint ?? undefined,
162
+ auth_update: result.auth_update ?? undefined,
163
+ });
164
+ console.error(`[executor] Sync run ${run_id} completed: ${itemsCollectedSoFar} items`);
165
+ return { itemsCollected: itemsCollectedSoFar };
166
+ }
167
+ catch (error) {
168
+ stopHeartbeat();
169
+ const errorMessage = error instanceof Error ? error.message : String(error);
170
+ console.error(`[executor] Sync run ${run_id} failed:`, errorMessage);
171
+ const diag = extractSubprocessDiagnostics(error);
172
+ await client.complete({
173
+ run_id,
174
+ worker_id: client.id,
175
+ status: 'failed',
176
+ items_collected: itemsCollectedSoFar,
177
+ error_message: errorMessage,
178
+ ...(diag ?? {}),
179
+ });
180
+ return { itemsCollected: itemsCollectedSoFar, error: errorMessage };
181
+ }
182
+ }
183
+ /**
184
+ * Pull diagnostic fields off a SubprocessError-shaped error so the worker
185
+ * can persist them on the failed run row. Returns `undefined` when the
186
+ * thrown value isn't a subprocess failure (e.g. a stream/HTTP error).
187
+ */
188
+ function extractSubprocessDiagnostics(error) {
189
+ if (!error || typeof error !== 'object')
190
+ return undefined;
191
+ const e = error;
192
+ if (!e.exitReason && e.exitCode === undefined && !e.outputTail)
193
+ return undefined;
194
+ return {
195
+ output_tail: e.outputTail || undefined,
196
+ exit_code: e.exitCode ?? null,
197
+ exit_signal: e.exitSignal ?? null,
198
+ exit_reason: e.exitReason,
199
+ };
200
+ }
201
+ /**
202
+ * Execute an action run (async action with approval)
203
+ */
204
+ async function executeActionRun(client, job, env, cfg) {
205
+ const subprocessExecutor = new SubprocessExecutor({
206
+ timeoutMs: cfg.timeoutMs,
207
+ maxOldSpaceSize: cfg.maxOldSpaceSize,
208
+ });
209
+ const { run_id, connector_key, action_key, action_input, credentials, compiled_code } = job;
210
+ if (!run_id || !connector_key || !action_key) {
211
+ throw new Error('Invalid action run: missing run_id, connector_key, or action_key');
212
+ }
213
+ if (!compiled_code) {
214
+ throw new Error(`Action run ${run_id}: No compiled code available.`);
215
+ }
216
+ console.error(`[executor] Starting action run ${run_id} (${connector_key}/${action_key})`);
217
+ try {
218
+ const result = await executeCompiledConnector({
219
+ mode: 'action',
220
+ compiledCode: compiled_code,
221
+ actionKey: action_key,
222
+ actionInput: (action_input ?? {}),
223
+ env,
224
+ connectionCredentials: (job.connection_credentials ??
225
+ null),
226
+ credentials,
227
+ apiType: 'api',
228
+ executor: subprocessExecutor,
229
+ });
230
+ // For actions, the "contents" array may contain a single result envelope
231
+ const actionOutput = getActionOutput(result);
232
+ await client.completeAction({
233
+ run_id,
234
+ worker_id: client.id,
235
+ status: 'success',
236
+ action_output: actionOutput,
237
+ });
238
+ console.error(`[executor] Action run ${run_id} completed`);
239
+ return { itemsCollected: 0 };
240
+ }
241
+ catch (error) {
242
+ const errorMessage = error instanceof Error ? error.message : String(error);
243
+ console.error(`[executor] Action run ${run_id} failed:`, errorMessage);
244
+ await client.completeAction({
245
+ run_id,
246
+ worker_id: client.id,
247
+ status: 'failed',
248
+ error_message: errorMessage,
249
+ });
250
+ return { itemsCollected: 0, error: errorMessage };
251
+ }
252
+ }
253
+ /**
254
+ * Execute an 'auth' run: drive connector.authenticate() and stream artifacts
255
+ * to the UI via the API. On success, credentials land on the auth profile.
256
+ */
257
+ async function executeAuthRun(client, job, env, cfg) {
258
+ // Interactive auth runs wait on human input (QR scans, OTP entry, OAuth
259
+ // redirects) — a fixed subprocess timeout would kill the pairing mid-flow.
260
+ // Terminate via the UI cancel signal instead.
261
+ const subprocessExecutor = new SubprocessExecutor({
262
+ timeoutMs: 0,
263
+ maxOldSpaceSize: cfg.maxOldSpaceSize,
264
+ });
265
+ const { run_id, connector_key, compiled_code, previous_credentials } = job;
266
+ if (!run_id || !connector_key) {
267
+ throw new Error('Invalid auth run: missing run_id or connector_key');
268
+ }
269
+ if (!compiled_code) {
270
+ throw new Error(`Auth run ${run_id}: No compiled code available.`);
271
+ }
272
+ console.error(`[executor] Starting auth run ${run_id} (${connector_key})`);
273
+ // Heartbeat so the API doesn't time us out while the user is scanning.
274
+ const heartbeatInterval = setInterval(async () => {
275
+ try {
276
+ await client.heartbeat(run_id);
277
+ }
278
+ catch (err) {
279
+ console.error('[executor] Auth heartbeat failed:', err);
280
+ }
281
+ }, cfg.heartbeatIntervalMs);
282
+ try {
283
+ const result = await executeCompiledConnector({
284
+ mode: 'authenticate',
285
+ compiledCode: compiled_code,
286
+ previousCredentials: previous_credentials ?? null,
287
+ env,
288
+ executor: subprocessExecutor,
289
+ apiType: 'api',
290
+ hooks: {
291
+ collectContents: false,
292
+ onAuthArtifact: async (artifact) => {
293
+ try {
294
+ await client.emitAuthArtifact({
295
+ run_id,
296
+ worker_id: client.id,
297
+ artifact,
298
+ });
299
+ }
300
+ catch (err) {
301
+ console.error('[executor] emitAuthArtifact failed:', err);
302
+ }
303
+ },
304
+ onAwaitAuthSignal: async (name, opts) => {
305
+ const deadline = opts?.timeoutMs ? Date.now() + opts.timeoutMs : null;
306
+ while (true) {
307
+ if (deadline !== null && Date.now() > deadline) {
308
+ throw new Error(`awaitSignal('${name}') timed out`);
309
+ }
310
+ const resp = await client.pollAuthSignal({
311
+ run_id,
312
+ worker_id: client.id,
313
+ signal_name: name,
314
+ });
315
+ if (resp.signal)
316
+ return resp.signal;
317
+ await delay(1500);
318
+ }
319
+ },
320
+ },
321
+ });
322
+ clearInterval(heartbeatInterval);
323
+ if (!result.auth_result?.credentials) {
324
+ await client.completeAuth({
325
+ run_id,
326
+ worker_id: client.id,
327
+ status: 'failed',
328
+ error_message: 'authenticate() returned no credentials',
329
+ });
330
+ return { itemsCollected: 0, error: 'no credentials' };
331
+ }
332
+ await client.completeAuth({
333
+ run_id,
334
+ worker_id: client.id,
335
+ status: 'success',
336
+ credentials: result.auth_result.credentials,
337
+ metadata: result.auth_result.metadata,
338
+ });
339
+ console.error(`[executor] Auth run ${run_id} completed`);
340
+ return { itemsCollected: 0 };
341
+ }
342
+ catch (error) {
343
+ clearInterval(heartbeatInterval);
344
+ const errorMessage = error instanceof Error ? error.message : String(error);
345
+ console.error(`[executor] Auth run ${run_id} failed:`, errorMessage);
346
+ const diag = extractSubprocessDiagnostics(error);
347
+ try {
348
+ await client.completeAuth({
349
+ run_id,
350
+ worker_id: client.id,
351
+ status: 'failed',
352
+ error_message: errorMessage,
353
+ ...(diag ?? {}),
354
+ });
355
+ }
356
+ catch (completeErr) {
357
+ console.error('[executor] completeAuth after failure errored:', completeErr);
358
+ }
359
+ return { itemsCollected: 0, error: errorMessage };
360
+ }
361
+ }
362
+ function delay(ms) {
363
+ return new Promise((resolve) => setTimeout(resolve, ms));
364
+ }
365
+ /**
366
+ * Execute an embed_backfill run (generate embeddings for events missing them)
367
+ */
368
+ async function executeEmbedBackfillRun(client, job, _env) {
369
+ const { run_id, action_input } = job;
370
+ if (!run_id) {
371
+ throw new Error('Invalid embed_backfill run: missing run_id');
372
+ }
373
+ // Parse event_ids from action_input
374
+ let input;
375
+ if (typeof action_input === 'string') {
376
+ try {
377
+ input = JSON.parse(action_input);
378
+ }
379
+ catch (err) {
380
+ const msg = err instanceof Error ? err.message : String(err);
381
+ console.error(`[executor] Embed backfill run ${run_id}: invalid action_input JSON:`, msg);
382
+ await client.complete({
383
+ run_id,
384
+ worker_id: client.id,
385
+ status: 'failed',
386
+ error_message: `Invalid action_input JSON: ${msg}`,
387
+ });
388
+ return { itemsCollected: 0, error: `Invalid action_input JSON: ${msg}` };
389
+ }
390
+ }
391
+ else {
392
+ input = action_input;
393
+ }
394
+ const eventIds = input?.event_ids ?? [];
395
+ if (eventIds.length === 0) {
396
+ console.error(`[executor] Embed backfill run ${run_id}: no event_ids`);
397
+ await client.complete({
398
+ run_id,
399
+ worker_id: client.id,
400
+ status: 'failed',
401
+ error_message: 'No event_ids in action_input',
402
+ });
403
+ return { itemsCollected: 0, error: 'No event_ids' };
404
+ }
405
+ console.error(`[executor] Starting embed_backfill run ${run_id} for ${eventIds.length} events`);
406
+ try {
407
+ // Fetch event content from the API
408
+ const events = await client.fetchEventsForEmbedding(eventIds);
409
+ if (events.length === 0) {
410
+ console.error(`[executor] Embed backfill run ${run_id}: all events already have embeddings`);
411
+ await client.completeEmbeddings({
412
+ run_id,
413
+ worker_id: client.id,
414
+ embeddings: [],
415
+ error_message: 'All events already have embeddings',
416
+ });
417
+ return { itemsCollected: 0 };
418
+ }
419
+ // Generate embeddings for each event
420
+ const results = [];
421
+ for (const event of events) {
422
+ try {
423
+ const textForEmbedding = [event.title, event.content].filter(Boolean).join(' ').trim();
424
+ if (textForEmbedding) {
425
+ const embedding = await generateEmbedding(textForEmbedding);
426
+ if (embedding) {
427
+ results.push({ event_id: event.id, embedding });
428
+ }
429
+ }
430
+ }
431
+ catch (err) {
432
+ console.error(`[executor] Embedding failed for event ${event.id}:`, err);
433
+ }
434
+ }
435
+ // Submit embeddings back to the API
436
+ await client.completeEmbeddings({
437
+ run_id,
438
+ worker_id: client.id,
439
+ embeddings: results,
440
+ });
441
+ console.error(`[executor] Embed backfill run ${run_id} completed: ${results.length}/${events.length} embeddings`);
442
+ return { itemsCollected: results.length };
443
+ }
444
+ catch (error) {
445
+ const errorMessage = error instanceof Error ? error.message : String(error);
446
+ console.error(`[executor] Embed backfill run ${run_id} failed:`, errorMessage);
447
+ await client.complete({
448
+ run_id,
449
+ worker_id: client.id,
450
+ status: 'failed',
451
+ error_message: errorMessage,
452
+ });
453
+ return { itemsCollected: 0, error: errorMessage };
454
+ }
455
+ }
456
+ /**
457
+ * Process a content item - convert to ContentItem and optionally generate embedding
458
+ */
459
+ async function processContent(item, generateEmbeddings) {
460
+ const normalized = normalizeEventEnvelope(item);
461
+ const contentItem = {
462
+ id: normalized.origin_id,
463
+ title: normalized.title,
464
+ payload_text: normalized.payload_text,
465
+ author_name: normalized.author_name,
466
+ occurred_at: normalized.occurred_at instanceof Date
467
+ ? normalized.occurred_at.toISOString()
468
+ : normalized.occurred_at,
469
+ source_url: normalized.source_url,
470
+ score: normalized.score,
471
+ metadata: normalized.metadata,
472
+ origin_parent_id: normalized.origin_parent_id || undefined,
473
+ origin_type: normalized.origin_type,
474
+ semantic_type: normalized.semantic_type,
475
+ };
476
+ if (generateEmbeddings) {
477
+ try {
478
+ const textForEmbedding = [normalized.title, normalized.payload_text]
479
+ .filter(Boolean)
480
+ .join(' ')
481
+ .trim();
482
+ if (textForEmbedding) {
483
+ contentItem.embedding = await generateEmbedding(textForEmbedding);
484
+ }
485
+ }
486
+ catch (err) {
487
+ console.error(`[executor] Embedding generation failed for ${normalized.origin_id}:`, err);
488
+ }
489
+ }
490
+ return contentItem;
491
+ }
492
+ //# sourceMappingURL=executor.js.map