peak6-x-intelligence-plugin 0.1.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.
package/dist/worker.js ADDED
@@ -0,0 +1,2060 @@
1
+ // node_modules/.pnpm/@paperclipai+plugin-sdk@file+.paperclip-sdk+paperclipai-plugin-sdk-1.0.0.tgz_react@19.2.4/node_modules/@paperclipai/plugin-sdk/dist/define-plugin.js
2
+ function definePlugin(definition) {
3
+ return Object.freeze({ definition });
4
+ }
5
+
6
+ // node_modules/.pnpm/@paperclipai+plugin-sdk@file+.paperclip-sdk+paperclipai-plugin-sdk-1.0.0.tgz_react@19.2.4/node_modules/@paperclipai/plugin-sdk/dist/worker-rpc-host.js
7
+ import path from "node:path";
8
+ import { createInterface } from "node:readline";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ // node_modules/.pnpm/@paperclipai+plugin-sdk@file+.paperclip-sdk+paperclipai-plugin-sdk-1.0.0.tgz_react@19.2.4/node_modules/@paperclipai/plugin-sdk/dist/protocol.js
12
+ var JSONRPC_VERSION = "2.0";
13
+ var JSONRPC_ERROR_CODES = {
14
+ /** Invalid JSON was received by the server. */
15
+ PARSE_ERROR: -32700,
16
+ /** The JSON sent is not a valid Request object. */
17
+ INVALID_REQUEST: -32600,
18
+ /** The method does not exist or is not available. */
19
+ METHOD_NOT_FOUND: -32601,
20
+ /** Invalid method parameter(s). */
21
+ INVALID_PARAMS: -32602,
22
+ /** Internal JSON-RPC error. */
23
+ INTERNAL_ERROR: -32603
24
+ };
25
+ var PLUGIN_RPC_ERROR_CODES = {
26
+ /** The worker process is not running or not reachable. */
27
+ WORKER_UNAVAILABLE: -32e3,
28
+ /** The plugin does not have the required capability for this operation. */
29
+ CAPABILITY_DENIED: -32001,
30
+ /** The worker reported an unhandled error during method execution. */
31
+ WORKER_ERROR: -32002,
32
+ /** The method call timed out waiting for the worker response. */
33
+ TIMEOUT: -32003,
34
+ /** The worker does not implement the requested optional method. */
35
+ METHOD_NOT_IMPLEMENTED: -32004,
36
+ /** A catch-all for errors that do not fit other categories. */
37
+ UNKNOWN: -32099
38
+ };
39
+ var _nextId = 1;
40
+ var MAX_SAFE_RPC_ID = Number.MAX_SAFE_INTEGER - 1;
41
+ function createRequest(method, params, id) {
42
+ if (_nextId >= MAX_SAFE_RPC_ID) {
43
+ _nextId = 1;
44
+ }
45
+ return {
46
+ jsonrpc: JSONRPC_VERSION,
47
+ id: id ?? _nextId++,
48
+ method,
49
+ params
50
+ };
51
+ }
52
+ function createSuccessResponse(id, result) {
53
+ return {
54
+ jsonrpc: JSONRPC_VERSION,
55
+ id,
56
+ result
57
+ };
58
+ }
59
+ function createErrorResponse(id, code, message, data) {
60
+ const response = {
61
+ jsonrpc: JSONRPC_VERSION,
62
+ id,
63
+ error: data !== void 0 ? { code, message, data } : { code, message }
64
+ };
65
+ return response;
66
+ }
67
+ function createNotification(method, params) {
68
+ return {
69
+ jsonrpc: JSONRPC_VERSION,
70
+ method,
71
+ params
72
+ };
73
+ }
74
+ function isJsonRpcRequest(value) {
75
+ if (typeof value !== "object" || value === null)
76
+ return false;
77
+ const obj = value;
78
+ return obj.jsonrpc === JSONRPC_VERSION && typeof obj.method === "string" && "id" in obj && obj.id !== void 0 && obj.id !== null;
79
+ }
80
+ function isJsonRpcNotification(value) {
81
+ if (typeof value !== "object" || value === null)
82
+ return false;
83
+ const obj = value;
84
+ return obj.jsonrpc === JSONRPC_VERSION && typeof obj.method === "string" && !("id" in obj);
85
+ }
86
+ function isJsonRpcResponse(value) {
87
+ if (typeof value !== "object" || value === null)
88
+ return false;
89
+ const obj = value;
90
+ return obj.jsonrpc === JSONRPC_VERSION && "id" in obj && ("result" in obj || "error" in obj);
91
+ }
92
+ function isJsonRpcSuccessResponse(response) {
93
+ return "result" in response && !("error" in response && response.error !== void 0);
94
+ }
95
+ function isJsonRpcErrorResponse(response) {
96
+ return "error" in response && response.error !== void 0;
97
+ }
98
+ var MESSAGE_DELIMITER = "\n";
99
+ function serializeMessage(message) {
100
+ return JSON.stringify(message) + MESSAGE_DELIMITER;
101
+ }
102
+ function parseMessage(line) {
103
+ const trimmed = line.trim();
104
+ if (trimmed.length === 0) {
105
+ throw new JsonRpcParseError("Empty message");
106
+ }
107
+ let parsed;
108
+ try {
109
+ parsed = JSON.parse(trimmed);
110
+ } catch {
111
+ throw new JsonRpcParseError(`Invalid JSON: ${trimmed.slice(0, 200)}`);
112
+ }
113
+ if (typeof parsed !== "object" || parsed === null) {
114
+ throw new JsonRpcParseError("Message must be a JSON object");
115
+ }
116
+ const obj = parsed;
117
+ if (obj.jsonrpc !== JSONRPC_VERSION) {
118
+ throw new JsonRpcParseError(`Invalid or missing jsonrpc version (expected "${JSONRPC_VERSION}", got ${JSON.stringify(obj.jsonrpc)})`);
119
+ }
120
+ return parsed;
121
+ }
122
+ var JsonRpcParseError = class extends Error {
123
+ name = "JsonRpcParseError";
124
+ constructor(message) {
125
+ super(message);
126
+ }
127
+ };
128
+ var JsonRpcCallError = class extends Error {
129
+ name = "JsonRpcCallError";
130
+ /** The JSON-RPC error code. */
131
+ code;
132
+ /** Optional structured error data from the response. */
133
+ data;
134
+ constructor(error) {
135
+ super(error.message);
136
+ this.code = error.code;
137
+ this.data = error.data;
138
+ }
139
+ };
140
+
141
+ // node_modules/.pnpm/@paperclipai+plugin-sdk@file+.paperclip-sdk+paperclipai-plugin-sdk-1.0.0.tgz_react@19.2.4/node_modules/@paperclipai/plugin-sdk/dist/worker-rpc-host.js
142
+ var DEFAULT_RPC_TIMEOUT_MS = 3e4;
143
+ function runWorker(plugin2, moduleUrl, options) {
144
+ if (options?.stdin != null && options?.stdout != null) {
145
+ return startWorkerRpcHost({
146
+ plugin: plugin2,
147
+ stdin: options.stdin,
148
+ stdout: options.stdout
149
+ });
150
+ }
151
+ const entry = process.argv[1];
152
+ if (typeof entry !== "string")
153
+ return;
154
+ const thisFile = path.resolve(fileURLToPath(moduleUrl));
155
+ const entryPath = path.resolve(entry);
156
+ if (thisFile === entryPath) {
157
+ startWorkerRpcHost({ plugin: plugin2 });
158
+ }
159
+ }
160
+ function startWorkerRpcHost(options) {
161
+ const { plugin: plugin2 } = options;
162
+ const stdinStream = options.stdin ?? process.stdin;
163
+ const stdoutStream = options.stdout ?? process.stdout;
164
+ const rpcTimeoutMs = options.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS;
165
+ let running = true;
166
+ let initialized = false;
167
+ let manifest = null;
168
+ let currentConfig = {};
169
+ const eventHandlers = [];
170
+ const jobHandlers = /* @__PURE__ */ new Map();
171
+ const launcherRegistrations = /* @__PURE__ */ new Map();
172
+ const dataHandlers = /* @__PURE__ */ new Map();
173
+ const actionHandlers = /* @__PURE__ */ new Map();
174
+ const toolHandlers = /* @__PURE__ */ new Map();
175
+ const sessionEventCallbacks = /* @__PURE__ */ new Map();
176
+ const pendingRequests = /* @__PURE__ */ new Map();
177
+ let nextOutboundId = 1;
178
+ const MAX_OUTBOUND_ID = Number.MAX_SAFE_INTEGER - 1;
179
+ function sendMessage(message) {
180
+ if (!running)
181
+ return;
182
+ const serialized = serializeMessage(message);
183
+ stdoutStream.write(serialized);
184
+ }
185
+ function callHost(method, params, timeoutMs) {
186
+ return new Promise((resolve, reject) => {
187
+ if (!running) {
188
+ reject(new Error(`Cannot call "${method}" \u2014 worker RPC host is not running`));
189
+ return;
190
+ }
191
+ if (nextOutboundId >= MAX_OUTBOUND_ID) {
192
+ nextOutboundId = 1;
193
+ }
194
+ const id = nextOutboundId++;
195
+ const timeout = timeoutMs ?? rpcTimeoutMs;
196
+ let settled = false;
197
+ const settle = (fn, value) => {
198
+ if (settled)
199
+ return;
200
+ settled = true;
201
+ clearTimeout(timer);
202
+ pendingRequests.delete(id);
203
+ fn(value);
204
+ };
205
+ const timer = setTimeout(() => {
206
+ settle(reject, new JsonRpcCallError({
207
+ code: PLUGIN_RPC_ERROR_CODES.TIMEOUT,
208
+ message: `Worker\u2192host call "${method}" timed out after ${timeout}ms`
209
+ }));
210
+ }, timeout);
211
+ pendingRequests.set(id, {
212
+ resolve: (response) => {
213
+ if (isJsonRpcSuccessResponse(response)) {
214
+ settle(resolve, response.result);
215
+ } else if (isJsonRpcErrorResponse(response)) {
216
+ settle(reject, new JsonRpcCallError(response.error));
217
+ } else {
218
+ settle(reject, new Error(`Unexpected response format for "${method}"`));
219
+ }
220
+ },
221
+ timer
222
+ });
223
+ try {
224
+ const request = createRequest(method, params, id);
225
+ sendMessage(request);
226
+ } catch (err) {
227
+ settle(reject, err instanceof Error ? err : new Error(String(err)));
228
+ }
229
+ });
230
+ }
231
+ function notifyHost(method, params) {
232
+ try {
233
+ sendMessage(createNotification(method, params));
234
+ } catch {
235
+ }
236
+ }
237
+ function buildContext() {
238
+ return {
239
+ get manifest() {
240
+ if (!manifest)
241
+ throw new Error("Plugin context accessed before initialization");
242
+ return manifest;
243
+ },
244
+ config: {
245
+ async get() {
246
+ return callHost("config.get", {});
247
+ }
248
+ },
249
+ events: {
250
+ on(name, filterOrFn, maybeFn) {
251
+ let registration;
252
+ if (typeof filterOrFn === "function") {
253
+ registration = { name, fn: filterOrFn };
254
+ } else {
255
+ if (!maybeFn)
256
+ throw new Error("Event handler function is required");
257
+ registration = { name, filter: filterOrFn, fn: maybeFn };
258
+ }
259
+ eventHandlers.push(registration);
260
+ void callHost("events.subscribe", { eventPattern: name, filter: registration.filter ?? null }).catch((err) => {
261
+ notifyHost("log", {
262
+ level: "warn",
263
+ message: `Failed to subscribe to event "${name}" on host: ${err instanceof Error ? err.message : String(err)}`
264
+ });
265
+ });
266
+ return () => {
267
+ const idx = eventHandlers.indexOf(registration);
268
+ if (idx !== -1)
269
+ eventHandlers.splice(idx, 1);
270
+ };
271
+ },
272
+ async emit(name, companyId, payload) {
273
+ await callHost("events.emit", { name, companyId, payload });
274
+ }
275
+ },
276
+ jobs: {
277
+ register(key, fn) {
278
+ jobHandlers.set(key, fn);
279
+ }
280
+ },
281
+ launchers: {
282
+ register(launcher) {
283
+ launcherRegistrations.set(launcher.id, launcher);
284
+ }
285
+ },
286
+ http: {
287
+ async fetch(url, init) {
288
+ const serializedInit = {};
289
+ if (init) {
290
+ if (init.method)
291
+ serializedInit.method = init.method;
292
+ if (init.headers) {
293
+ if (init.headers instanceof Headers) {
294
+ const obj = {};
295
+ init.headers.forEach((v, k) => {
296
+ obj[k] = v;
297
+ });
298
+ serializedInit.headers = obj;
299
+ } else if (Array.isArray(init.headers)) {
300
+ const obj = {};
301
+ for (const [k, v] of init.headers)
302
+ obj[k] = v;
303
+ serializedInit.headers = obj;
304
+ } else {
305
+ serializedInit.headers = init.headers;
306
+ }
307
+ }
308
+ if (init.body !== void 0 && init.body !== null) {
309
+ serializedInit.body = typeof init.body === "string" ? init.body : String(init.body);
310
+ }
311
+ }
312
+ const result = await callHost("http.fetch", {
313
+ url,
314
+ init: Object.keys(serializedInit).length > 0 ? serializedInit : void 0
315
+ });
316
+ return new Response(result.body, {
317
+ status: result.status,
318
+ statusText: result.statusText,
319
+ headers: result.headers
320
+ });
321
+ }
322
+ },
323
+ secrets: {
324
+ async resolve(secretRef) {
325
+ return callHost("secrets.resolve", { secretRef });
326
+ }
327
+ },
328
+ activity: {
329
+ async log(entry) {
330
+ await callHost("activity.log", {
331
+ companyId: entry.companyId,
332
+ message: entry.message,
333
+ entityType: entry.entityType,
334
+ entityId: entry.entityId,
335
+ metadata: entry.metadata
336
+ });
337
+ }
338
+ },
339
+ state: {
340
+ async get(input) {
341
+ return callHost("state.get", {
342
+ scopeKind: input.scopeKind,
343
+ scopeId: input.scopeId,
344
+ namespace: input.namespace,
345
+ stateKey: input.stateKey
346
+ });
347
+ },
348
+ async set(input, value) {
349
+ await callHost("state.set", {
350
+ scopeKind: input.scopeKind,
351
+ scopeId: input.scopeId,
352
+ namespace: input.namespace,
353
+ stateKey: input.stateKey,
354
+ value
355
+ });
356
+ },
357
+ async delete(input) {
358
+ await callHost("state.delete", {
359
+ scopeKind: input.scopeKind,
360
+ scopeId: input.scopeId,
361
+ namespace: input.namespace,
362
+ stateKey: input.stateKey
363
+ });
364
+ }
365
+ },
366
+ entities: {
367
+ async upsert(input) {
368
+ return callHost("entities.upsert", {
369
+ entityType: input.entityType,
370
+ scopeKind: input.scopeKind,
371
+ scopeId: input.scopeId,
372
+ externalId: input.externalId,
373
+ title: input.title,
374
+ status: input.status,
375
+ data: input.data
376
+ });
377
+ },
378
+ async list(query) {
379
+ return callHost("entities.list", {
380
+ entityType: query.entityType,
381
+ scopeKind: query.scopeKind,
382
+ scopeId: query.scopeId,
383
+ externalId: query.externalId,
384
+ limit: query.limit,
385
+ offset: query.offset
386
+ });
387
+ }
388
+ },
389
+ projects: {
390
+ async list(input) {
391
+ return callHost("projects.list", {
392
+ companyId: input.companyId,
393
+ limit: input.limit,
394
+ offset: input.offset
395
+ });
396
+ },
397
+ async get(projectId, companyId) {
398
+ return callHost("projects.get", { projectId, companyId });
399
+ },
400
+ async listWorkspaces(projectId, companyId) {
401
+ return callHost("projects.listWorkspaces", { projectId, companyId });
402
+ },
403
+ async getPrimaryWorkspace(projectId, companyId) {
404
+ return callHost("projects.getPrimaryWorkspace", { projectId, companyId });
405
+ },
406
+ async getWorkspaceForIssue(issueId, companyId) {
407
+ return callHost("projects.getWorkspaceForIssue", { issueId, companyId });
408
+ }
409
+ },
410
+ companies: {
411
+ async list(input) {
412
+ return callHost("companies.list", {
413
+ limit: input?.limit,
414
+ offset: input?.offset
415
+ });
416
+ },
417
+ async get(companyId) {
418
+ return callHost("companies.get", { companyId });
419
+ }
420
+ },
421
+ issues: {
422
+ async list(input) {
423
+ return callHost("issues.list", {
424
+ companyId: input.companyId,
425
+ projectId: input.projectId,
426
+ assigneeAgentId: input.assigneeAgentId,
427
+ status: input.status,
428
+ limit: input.limit,
429
+ offset: input.offset
430
+ });
431
+ },
432
+ async get(issueId, companyId) {
433
+ return callHost("issues.get", { issueId, companyId });
434
+ },
435
+ async create(input) {
436
+ return callHost("issues.create", {
437
+ companyId: input.companyId,
438
+ projectId: input.projectId,
439
+ goalId: input.goalId,
440
+ parentId: input.parentId,
441
+ title: input.title,
442
+ description: input.description,
443
+ priority: input.priority,
444
+ assigneeAgentId: input.assigneeAgentId
445
+ });
446
+ },
447
+ async update(issueId, patch, companyId) {
448
+ return callHost("issues.update", {
449
+ issueId,
450
+ patch,
451
+ companyId
452
+ });
453
+ },
454
+ async listComments(issueId, companyId) {
455
+ return callHost("issues.listComments", { issueId, companyId });
456
+ },
457
+ async createComment(issueId, body, companyId) {
458
+ return callHost("issues.createComment", { issueId, body, companyId });
459
+ },
460
+ documents: {
461
+ async list(issueId, companyId) {
462
+ return callHost("issues.documents.list", { issueId, companyId });
463
+ },
464
+ async get(issueId, key, companyId) {
465
+ return callHost("issues.documents.get", { issueId, key, companyId });
466
+ },
467
+ async upsert(input) {
468
+ return callHost("issues.documents.upsert", {
469
+ issueId: input.issueId,
470
+ key: input.key,
471
+ body: input.body,
472
+ companyId: input.companyId,
473
+ title: input.title,
474
+ format: input.format,
475
+ changeSummary: input.changeSummary
476
+ });
477
+ },
478
+ async delete(issueId, key, companyId) {
479
+ return callHost("issues.documents.delete", { issueId, key, companyId });
480
+ }
481
+ }
482
+ },
483
+ agents: {
484
+ async list(input) {
485
+ return callHost("agents.list", {
486
+ companyId: input.companyId,
487
+ status: input.status,
488
+ limit: input.limit,
489
+ offset: input.offset
490
+ });
491
+ },
492
+ async get(agentId, companyId) {
493
+ return callHost("agents.get", { agentId, companyId });
494
+ },
495
+ async pause(agentId, companyId) {
496
+ return callHost("agents.pause", { agentId, companyId });
497
+ },
498
+ async resume(agentId, companyId) {
499
+ return callHost("agents.resume", { agentId, companyId });
500
+ },
501
+ async invoke(agentId, companyId, opts) {
502
+ return callHost("agents.invoke", { agentId, companyId, prompt: opts.prompt, reason: opts.reason });
503
+ },
504
+ sessions: {
505
+ async create(agentId, companyId, opts) {
506
+ return callHost("agents.sessions.create", {
507
+ agentId,
508
+ companyId,
509
+ taskKey: opts?.taskKey,
510
+ reason: opts?.reason
511
+ });
512
+ },
513
+ async list(agentId, companyId) {
514
+ return callHost("agents.sessions.list", { agentId, companyId });
515
+ },
516
+ async sendMessage(sessionId, companyId, opts) {
517
+ if (opts.onEvent) {
518
+ sessionEventCallbacks.set(sessionId, opts.onEvent);
519
+ }
520
+ try {
521
+ return await callHost("agents.sessions.sendMessage", {
522
+ sessionId,
523
+ companyId,
524
+ prompt: opts.prompt,
525
+ reason: opts.reason
526
+ });
527
+ } catch (err) {
528
+ sessionEventCallbacks.delete(sessionId);
529
+ throw err;
530
+ }
531
+ },
532
+ async close(sessionId, companyId) {
533
+ sessionEventCallbacks.delete(sessionId);
534
+ await callHost("agents.sessions.close", { sessionId, companyId });
535
+ }
536
+ }
537
+ },
538
+ goals: {
539
+ async list(input) {
540
+ return callHost("goals.list", {
541
+ companyId: input.companyId,
542
+ level: input.level,
543
+ status: input.status,
544
+ limit: input.limit,
545
+ offset: input.offset
546
+ });
547
+ },
548
+ async get(goalId, companyId) {
549
+ return callHost("goals.get", { goalId, companyId });
550
+ },
551
+ async create(input) {
552
+ return callHost("goals.create", {
553
+ companyId: input.companyId,
554
+ title: input.title,
555
+ description: input.description,
556
+ level: input.level,
557
+ status: input.status,
558
+ parentId: input.parentId,
559
+ ownerAgentId: input.ownerAgentId
560
+ });
561
+ },
562
+ async update(goalId, patch, companyId) {
563
+ return callHost("goals.update", {
564
+ goalId,
565
+ patch,
566
+ companyId
567
+ });
568
+ }
569
+ },
570
+ data: {
571
+ register(key, handler) {
572
+ dataHandlers.set(key, handler);
573
+ }
574
+ },
575
+ actions: {
576
+ register(key, handler) {
577
+ actionHandlers.set(key, handler);
578
+ }
579
+ },
580
+ streams: /* @__PURE__ */ (() => {
581
+ const channelCompanyMap = /* @__PURE__ */ new Map();
582
+ return {
583
+ open(channel, companyId) {
584
+ channelCompanyMap.set(channel, companyId);
585
+ notifyHost("streams.open", { channel, companyId });
586
+ },
587
+ emit(channel, event) {
588
+ const companyId = channelCompanyMap.get(channel) ?? "";
589
+ notifyHost("streams.emit", { channel, companyId, event });
590
+ },
591
+ close(channel) {
592
+ const companyId = channelCompanyMap.get(channel) ?? "";
593
+ channelCompanyMap.delete(channel);
594
+ notifyHost("streams.close", { channel, companyId });
595
+ }
596
+ };
597
+ })(),
598
+ tools: {
599
+ register(name, declaration, fn) {
600
+ toolHandlers.set(name, { declaration, fn });
601
+ }
602
+ },
603
+ metrics: {
604
+ async write(name, value, tags) {
605
+ await callHost("metrics.write", { name, value, tags });
606
+ }
607
+ },
608
+ logger: {
609
+ info(message, meta) {
610
+ notifyHost("log", { level: "info", message, meta });
611
+ },
612
+ warn(message, meta) {
613
+ notifyHost("log", { level: "warn", message, meta });
614
+ },
615
+ error(message, meta) {
616
+ notifyHost("log", { level: "error", message, meta });
617
+ },
618
+ debug(message, meta) {
619
+ notifyHost("log", { level: "debug", message, meta });
620
+ }
621
+ }
622
+ };
623
+ }
624
+ const ctx = buildContext();
625
+ async function handleHostRequest(request) {
626
+ const { id, method, params } = request;
627
+ try {
628
+ const result = await dispatchMethod(method, params);
629
+ sendMessage(createSuccessResponse(id, result ?? null));
630
+ } catch (err) {
631
+ const errorMessage = err instanceof Error ? err.message : String(err);
632
+ const errorCode = typeof err?.code === "number" ? err.code : PLUGIN_RPC_ERROR_CODES.WORKER_ERROR;
633
+ sendMessage(createErrorResponse(id, errorCode, errorMessage));
634
+ }
635
+ }
636
+ async function dispatchMethod(method, params) {
637
+ switch (method) {
638
+ case "initialize":
639
+ return handleInitialize(params);
640
+ case "health":
641
+ return handleHealth();
642
+ case "shutdown":
643
+ return handleShutdown();
644
+ case "validateConfig":
645
+ return handleValidateConfig(params);
646
+ case "configChanged":
647
+ return handleConfigChanged(params);
648
+ case "onEvent":
649
+ return handleOnEvent(params);
650
+ case "runJob":
651
+ return handleRunJob(params);
652
+ case "handleWebhook":
653
+ return handleWebhook(params);
654
+ case "getData":
655
+ return handleGetData(params);
656
+ case "performAction":
657
+ return handlePerformAction(params);
658
+ case "executeTool":
659
+ return handleExecuteTool(params);
660
+ default:
661
+ throw Object.assign(new Error(`Unknown method: ${method}`), { code: JSONRPC_ERROR_CODES.METHOD_NOT_FOUND });
662
+ }
663
+ }
664
+ async function handleInitialize(params) {
665
+ if (initialized) {
666
+ throw new Error("Worker already initialized");
667
+ }
668
+ manifest = params.manifest;
669
+ currentConfig = params.config;
670
+ await plugin2.definition.setup(ctx);
671
+ initialized = true;
672
+ const supportedMethods = [];
673
+ if (plugin2.definition.onValidateConfig)
674
+ supportedMethods.push("validateConfig");
675
+ if (plugin2.definition.onConfigChanged)
676
+ supportedMethods.push("configChanged");
677
+ if (plugin2.definition.onHealth)
678
+ supportedMethods.push("health");
679
+ if (plugin2.definition.onShutdown)
680
+ supportedMethods.push("shutdown");
681
+ return { ok: true, supportedMethods };
682
+ }
683
+ async function handleHealth() {
684
+ if (plugin2.definition.onHealth) {
685
+ return plugin2.definition.onHealth();
686
+ }
687
+ return { status: "ok" };
688
+ }
689
+ async function handleShutdown() {
690
+ if (plugin2.definition.onShutdown) {
691
+ await plugin2.definition.onShutdown();
692
+ }
693
+ setImmediate(() => {
694
+ cleanup();
695
+ if (!options.stdin && !options.stdout) {
696
+ process.exit(0);
697
+ }
698
+ });
699
+ }
700
+ async function handleValidateConfig(params) {
701
+ if (!plugin2.definition.onValidateConfig) {
702
+ throw Object.assign(new Error("validateConfig is not implemented by this plugin"), { code: PLUGIN_RPC_ERROR_CODES.METHOD_NOT_IMPLEMENTED });
703
+ }
704
+ return plugin2.definition.onValidateConfig(params.config);
705
+ }
706
+ async function handleConfigChanged(params) {
707
+ currentConfig = params.config;
708
+ if (plugin2.definition.onConfigChanged) {
709
+ await plugin2.definition.onConfigChanged(params.config);
710
+ }
711
+ }
712
+ async function handleOnEvent(params) {
713
+ const event = params.event;
714
+ for (const registration of eventHandlers) {
715
+ const exactMatch = registration.name === event.eventType;
716
+ const wildcardPluginAll = registration.name === "plugin.*" && event.eventType.startsWith("plugin.");
717
+ const wildcardPluginOne = registration.name.endsWith(".*") && event.eventType.startsWith(registration.name.slice(0, -1));
718
+ if (!exactMatch && !wildcardPluginAll && !wildcardPluginOne)
719
+ continue;
720
+ if (registration.filter && !allowsEvent(registration.filter, event))
721
+ continue;
722
+ try {
723
+ await registration.fn(event);
724
+ } catch (err) {
725
+ notifyHost("log", {
726
+ level: "error",
727
+ message: `Event handler for "${registration.name}" failed: ${err instanceof Error ? err.message : String(err)}`,
728
+ meta: { eventType: event.eventType, stack: err instanceof Error ? err.stack : void 0 }
729
+ });
730
+ }
731
+ }
732
+ }
733
+ async function handleRunJob(params) {
734
+ const handler = jobHandlers.get(params.job.jobKey);
735
+ if (!handler) {
736
+ throw new Error(`No handler registered for job "${params.job.jobKey}"`);
737
+ }
738
+ await handler(params.job);
739
+ }
740
+ async function handleWebhook(params) {
741
+ if (!plugin2.definition.onWebhook) {
742
+ throw Object.assign(new Error("handleWebhook is not implemented by this plugin"), { code: PLUGIN_RPC_ERROR_CODES.METHOD_NOT_IMPLEMENTED });
743
+ }
744
+ await plugin2.definition.onWebhook(params);
745
+ }
746
+ async function handleGetData(params) {
747
+ const handler = dataHandlers.get(params.key);
748
+ if (!handler) {
749
+ throw new Error(`No data handler registered for key "${params.key}"`);
750
+ }
751
+ return handler(params.renderEnvironment === void 0 ? params.params : { ...params.params, renderEnvironment: params.renderEnvironment });
752
+ }
753
+ async function handlePerformAction(params) {
754
+ const handler = actionHandlers.get(params.key);
755
+ if (!handler) {
756
+ throw new Error(`No action handler registered for key "${params.key}"`);
757
+ }
758
+ return handler(params.renderEnvironment === void 0 ? params.params : { ...params.params, renderEnvironment: params.renderEnvironment });
759
+ }
760
+ async function handleExecuteTool(params) {
761
+ const entry = toolHandlers.get(params.toolName);
762
+ if (!entry) {
763
+ throw new Error(`No tool handler registered for "${params.toolName}"`);
764
+ }
765
+ return entry.fn(params.parameters, params.runContext);
766
+ }
767
+ function allowsEvent(filter, event) {
768
+ const payload = event.payload;
769
+ if (filter.companyId !== void 0) {
770
+ const companyId = event.companyId ?? String(payload?.companyId ?? "");
771
+ if (companyId !== filter.companyId)
772
+ return false;
773
+ }
774
+ if (filter.projectId !== void 0) {
775
+ const projectId = event.entityType === "project" ? event.entityId : String(payload?.projectId ?? "");
776
+ if (projectId !== filter.projectId)
777
+ return false;
778
+ }
779
+ if (filter.agentId !== void 0) {
780
+ const agentId = event.entityType === "agent" ? event.entityId : String(payload?.agentId ?? "");
781
+ if (agentId !== filter.agentId)
782
+ return false;
783
+ }
784
+ return true;
785
+ }
786
+ function handleHostResponse(response) {
787
+ const id = response.id;
788
+ if (id === null || id === void 0)
789
+ return;
790
+ const pending = pendingRequests.get(id);
791
+ if (!pending)
792
+ return;
793
+ clearTimeout(pending.timer);
794
+ pendingRequests.delete(id);
795
+ pending.resolve(response);
796
+ }
797
+ function handleLine(line) {
798
+ if (!line.trim())
799
+ return;
800
+ let message;
801
+ try {
802
+ message = parseMessage(line);
803
+ } catch (err) {
804
+ if (err instanceof JsonRpcParseError) {
805
+ sendMessage(createErrorResponse(null, JSONRPC_ERROR_CODES.PARSE_ERROR, `Parse error: ${err.message}`));
806
+ }
807
+ return;
808
+ }
809
+ if (isJsonRpcResponse(message)) {
810
+ handleHostResponse(message);
811
+ } else if (isJsonRpcRequest(message)) {
812
+ handleHostRequest(message).catch((err) => {
813
+ const errorMessage = err instanceof Error ? err.message : String(err);
814
+ const errorCode = err?.code ?? PLUGIN_RPC_ERROR_CODES.WORKER_ERROR;
815
+ try {
816
+ sendMessage(createErrorResponse(message.id, typeof errorCode === "number" ? errorCode : PLUGIN_RPC_ERROR_CODES.WORKER_ERROR, errorMessage));
817
+ } catch {
818
+ }
819
+ });
820
+ } else if (isJsonRpcNotification(message)) {
821
+ const notif = message;
822
+ if (notif.method === "agents.sessions.event" && notif.params) {
823
+ const event = notif.params;
824
+ const cb = sessionEventCallbacks.get(event.sessionId);
825
+ if (cb)
826
+ cb(event);
827
+ } else if (notif.method === "onEvent" && notif.params) {
828
+ handleOnEvent(notif.params).catch((err) => {
829
+ notifyHost("log", {
830
+ level: "error",
831
+ message: `Failed to handle event notification: ${err instanceof Error ? err.message : String(err)}`
832
+ });
833
+ });
834
+ }
835
+ }
836
+ }
837
+ function cleanup() {
838
+ running = false;
839
+ if (readline) {
840
+ readline.close();
841
+ readline = null;
842
+ }
843
+ for (const [id, pending] of pendingRequests) {
844
+ clearTimeout(pending.timer);
845
+ pending.resolve(createErrorResponse(id, PLUGIN_RPC_ERROR_CODES.WORKER_UNAVAILABLE, "Worker RPC host is shutting down"));
846
+ }
847
+ pendingRequests.clear();
848
+ sessionEventCallbacks.clear();
849
+ }
850
+ let readline = createInterface({
851
+ input: stdinStream,
852
+ crlfDelay: Infinity
853
+ });
854
+ readline.on("line", handleLine);
855
+ readline.on("close", () => {
856
+ if (running) {
857
+ cleanup();
858
+ if (!options.stdin && !options.stdout) {
859
+ process.exit(0);
860
+ }
861
+ }
862
+ });
863
+ if (!options.stdin && !options.stdout) {
864
+ process.on("uncaughtException", (err) => {
865
+ notifyHost("log", {
866
+ level: "error",
867
+ message: `Uncaught exception: ${err.message}`,
868
+ meta: { stack: err.stack }
869
+ });
870
+ setTimeout(() => process.exit(1), 100);
871
+ });
872
+ process.on("unhandledRejection", (reason) => {
873
+ const message = reason instanceof Error ? reason.message : String(reason);
874
+ const stack = reason instanceof Error ? reason.stack : void 0;
875
+ notifyHost("log", {
876
+ level: "error",
877
+ message: `Unhandled rejection: ${message}`,
878
+ meta: { stack }
879
+ });
880
+ });
881
+ }
882
+ return {
883
+ get running() {
884
+ return running;
885
+ },
886
+ stop() {
887
+ cleanup();
888
+ }
889
+ };
890
+ }
891
+
892
+ // src/constants.ts
893
+ var JOB_KEYS = {
894
+ discoveryRun: "discovery-run",
895
+ authorityDecay: "authority-decay",
896
+ complianceCheck: "compliance-check",
897
+ corpusRetention: "corpus-retention"
898
+ };
899
+ var TOOL_NAMES = {
900
+ searchCorpus: "search-corpus",
901
+ getToday: "get-today",
902
+ getAuthorities: "get-authorities",
903
+ suggestHandles: "suggest-handles",
904
+ trackHandle: "track-handle"
905
+ };
906
+ var ENTITY_TYPES = {
907
+ tweet: "tweet",
908
+ handle: "handle"
909
+ };
910
+ var STATE_KEYS = {
911
+ corpusPrefix: "corpus:",
912
+ lastDiscoveryRun: "last-discovery-run",
913
+ pipelineStats: "pipeline-stats"
914
+ };
915
+ var EVENT_NAMES = {
916
+ corpusUpdated: "corpus.updated",
917
+ handlePromoted: "handle.promoted",
918
+ gapIdentified: "gap.identified"
919
+ };
920
+ var DEFAULT_CONFIG = {
921
+ company_id: "",
922
+ xai_api_key_ref: "XAI_API_KEY",
923
+ x_api_bearer_ref: "BEARER_TOKEN",
924
+ semantic_topics: [
925
+ "institutional investors discussing market structure reform",
926
+ "fintech companies disrupting traditional trading",
927
+ "SEC regulatory changes affecting options trading",
928
+ "algorithmic trading and quantitative strategies",
929
+ "venture capital investing in financial infrastructure",
930
+ "PEAK6",
931
+ "options market making technology",
932
+ "Chicago trading community news"
933
+ ],
934
+ keyword_searches: [
935
+ '"PEAK6 Investments" OR "PEAK6" trading fintech',
936
+ "fintech and trading technology",
937
+ "market structure and regulation",
938
+ "options trading strategies and platforms"
939
+ ],
940
+ content_pillars: [
941
+ "market structure & regulation",
942
+ "fintech & trading technology",
943
+ "venture capital & investment",
944
+ "Chicago business ecosystem",
945
+ "company culture & talent"
946
+ ],
947
+ scoring_weights: {
948
+ relevance: 0.45,
949
+ recency: 0.25,
950
+ engagement: 0.3
951
+ },
952
+ engagement_sub_weights: {
953
+ likes: 0.55,
954
+ reposts: 0.25,
955
+ replies: 0.15,
956
+ quotes: 0.05
957
+ },
958
+ max_corpus_size: 200,
959
+ dedup_threshold: 0.7,
960
+ authority_boost: 0.15,
961
+ authority_lists: {
962
+ market_structure: {
963
+ description: "Market structure, regulation, SEC reform, options trading",
964
+ handles: [
965
+ "LizAnnSonders",
966
+ "matt_levine",
967
+ "unusual_whales",
968
+ "CMEGroup",
969
+ "CBOE",
970
+ "OptionsHawk",
971
+ "InvestorDenis"
972
+ ],
973
+ last_reviewed: "2026-03-29"
974
+ },
975
+ fintech: {
976
+ description: "Fintech, trading technology, AI in finance",
977
+ handles: [
978
+ "twifintech",
979
+ "IBSIntelligence",
980
+ "privy_io",
981
+ "i_Know_First",
982
+ "fintech_germany",
983
+ "venture_radar"
984
+ ],
985
+ last_reviewed: "2026-03-29"
986
+ },
987
+ venture_capital: {
988
+ description: "Venture capital investing in financial infrastructure",
989
+ handles: [
990
+ "a16z",
991
+ "FundersVC"
992
+ ],
993
+ last_reviewed: "2026-03-29"
994
+ },
995
+ chicago_trading: {
996
+ description: "Chicago trading community, prop firms, PEAK6 ecosystem",
997
+ handles: [
998
+ "CBOE",
999
+ "CMEGroup",
1000
+ "boogeymantradez",
1001
+ "adealafia"
1002
+ ],
1003
+ last_reviewed: "2026-03-29"
1004
+ }
1005
+ },
1006
+ global_excluded: ["elonmusk", "openai", "google", "microsoft"],
1007
+ authority_promotion_policy: {
1008
+ auto_promote_threshold: {
1009
+ min_appearances: 7,
1010
+ min_avg_relevance: 0.8,
1011
+ min_mutual_overlap: 3
1012
+ },
1013
+ candidate_threshold: {
1014
+ min_appearances: 5,
1015
+ min_avg_relevance: 0.7,
1016
+ min_mutual_overlap: 2
1017
+ },
1018
+ tracking_window_days: 14
1019
+ },
1020
+ corpus_retention_days: 90
1021
+ };
1022
+
1023
+ // src/pipeline/xai-client.ts
1024
+ var XAI_RESPONSES_URL = "https://api.x.ai/v1/responses";
1025
+ var XAI_MODEL = "grok-4-fast-reasoning";
1026
+ var TWEET_ID_PATTERN = /x\.com\/(?:i\/status|[^/]+\/status)\/(\d+)/;
1027
+ function extractTweetIds(annotations) {
1028
+ const ids = [];
1029
+ for (const ann of annotations) {
1030
+ if (ann.type !== "url_citation" || !ann.url) continue;
1031
+ const match = TWEET_ID_PATTERN.exec(ann.url);
1032
+ if (match?.[1]) {
1033
+ ids.push(match[1]);
1034
+ }
1035
+ }
1036
+ return ids;
1037
+ }
1038
+ function deduplicateIds(idArrays) {
1039
+ const seen = /* @__PURE__ */ new Set();
1040
+ const result = [];
1041
+ for (const ids of idArrays) {
1042
+ for (const id of ids) {
1043
+ if (!seen.has(id)) {
1044
+ seen.add(id);
1045
+ result.push(id);
1046
+ }
1047
+ }
1048
+ }
1049
+ return result;
1050
+ }
1051
+ async function xaiSearch(http, secrets, logger, apiKeyRef, options) {
1052
+ const apiKey = await secrets.resolve(apiKeyRef);
1053
+ const tool = {
1054
+ type: "x_search",
1055
+ from_date: options.fromDate,
1056
+ to_date: options.toDate
1057
+ };
1058
+ if (options.allowedHandles?.length) {
1059
+ tool.allowed_x_handles = options.allowedHandles.slice(0, 10);
1060
+ }
1061
+ if (options.excludedHandles?.length) {
1062
+ tool.excluded_x_handles = options.excludedHandles.slice(0, 10);
1063
+ }
1064
+ const body = {
1065
+ model: XAI_MODEL,
1066
+ input: [{ role: "user", content: options.query }],
1067
+ tools: [tool],
1068
+ inline_citations: true
1069
+ };
1070
+ logger.debug("xAI search request", { query: options.query, handles: options.allowedHandles });
1071
+ const response = await globalThis.fetch(XAI_RESPONSES_URL, {
1072
+ method: "POST",
1073
+ headers: {
1074
+ "Content-Type": "application/json",
1075
+ Authorization: `Bearer ${apiKey}`
1076
+ },
1077
+ body: JSON.stringify(body)
1078
+ });
1079
+ if (!response.ok) {
1080
+ const errorText = await response.text();
1081
+ throw new Error(`xAI API error ${response.status}: ${errorText}`);
1082
+ }
1083
+ const data = await response.json();
1084
+ let synthesisText = "";
1085
+ const allAnnotations = [];
1086
+ for (const output of data.output) {
1087
+ if (output.type === "message" && output.content) {
1088
+ for (const content of output.content) {
1089
+ if (content.type === "output_text") {
1090
+ if (content.text) synthesisText += content.text + "\n";
1091
+ if (content.annotations) allAnnotations.push(...content.annotations);
1092
+ }
1093
+ }
1094
+ }
1095
+ }
1096
+ const tweetIds = extractTweetIds(allAnnotations);
1097
+ logger.debug("xAI search result", {
1098
+ tweetIdsFound: tweetIds.length,
1099
+ annotationsCount: allAnnotations.length,
1100
+ tokensUsed: data.usage?.total_tokens ?? 0,
1101
+ firstAnnotationUrl: allAnnotations[0]?.url ?? "NONE"
1102
+ });
1103
+ return {
1104
+ tweetIds,
1105
+ synthesisText: synthesisText.trim(),
1106
+ annotations: allAnnotations,
1107
+ tokensUsed: data.usage?.total_tokens ?? 0,
1108
+ costUsdTicks: data.usage?.cost_in_usd_ticks ?? 0
1109
+ };
1110
+ }
1111
+
1112
+ // src/pipeline/x-api-client.ts
1113
+ var X_API_BASE = "https://api.x.com/2";
1114
+ var TWEET_FIELDS = "text,author_id,created_at,public_metrics,entities";
1115
+ var USER_FIELDS = "username,name,verified_type,public_metrics";
1116
+ var EXPANSIONS = "author_id";
1117
+ var BATCH_SIZE = 100;
1118
+ async function batchLookupTweets(http, secrets, logger, bearerRef, tweetIds, source) {
1119
+ if (tweetIds.length === 0) return [];
1120
+ const bearerToken = await secrets.resolve(bearerRef);
1121
+ const results = [];
1122
+ for (let i = 0; i < tweetIds.length; i += BATCH_SIZE) {
1123
+ const batch = tweetIds.slice(i, i + BATCH_SIZE);
1124
+ const url = `${X_API_BASE}/tweets?ids=${batch.join(",")}&tweet.fields=${TWEET_FIELDS}&expansions=${EXPANSIONS}&user.fields=${USER_FIELDS}`;
1125
+ logger.debug("X API v2 batch lookup", { batchSize: batch.length, offset: i });
1126
+ const response = await http.fetch(url, {
1127
+ method: "GET",
1128
+ headers: {
1129
+ Authorization: `Bearer ${bearerToken}`
1130
+ }
1131
+ });
1132
+ if (!response.ok) {
1133
+ const errorText = await response.text();
1134
+ logger.warn("X API v2 error", { status: response.status, error: errorText });
1135
+ continue;
1136
+ }
1137
+ const data = await response.json();
1138
+ if (data.errors?.length) {
1139
+ logger.debug("X API v2 partial errors", {
1140
+ errors: data.errors.map((e) => `${e.value}: ${e.title}`)
1141
+ });
1142
+ }
1143
+ const userMap = /* @__PURE__ */ new Map();
1144
+ if (data.includes?.users) {
1145
+ for (const user of data.includes.users) {
1146
+ userMap.set(user.id, user);
1147
+ }
1148
+ }
1149
+ if (data.data) {
1150
+ for (const tweet of data.data) {
1151
+ const author = userMap.get(tweet.author_id);
1152
+ results.push(mapToEnrichedTweet(tweet, author, source));
1153
+ }
1154
+ }
1155
+ }
1156
+ return results;
1157
+ }
1158
+ function mapToEnrichedTweet(tweet, author, source) {
1159
+ return {
1160
+ tweet_id: tweet.id,
1161
+ text: tweet.text,
1162
+ author_id: tweet.author_id,
1163
+ author_username: author?.username ?? "unknown",
1164
+ author_name: author?.name ?? "Unknown",
1165
+ author_verified_type: author?.verified_type,
1166
+ author_followers_count: author?.public_metrics?.followers_count ?? 0,
1167
+ created_at: tweet.created_at,
1168
+ metrics: tweet.public_metrics,
1169
+ entities: tweet.entities,
1170
+ source
1171
+ };
1172
+ }
1173
+ async function verifyTweetExists(http, secrets, bearerRef, tweetId) {
1174
+ const bearerToken = await secrets.resolve(bearerRef);
1175
+ const url = `${X_API_BASE}/tweets?ids=${tweetId}&tweet.fields=id`;
1176
+ const response = await http.fetch(url, {
1177
+ method: "GET",
1178
+ headers: { Authorization: `Bearer ${bearerToken}` }
1179
+ });
1180
+ if (!response.ok) return false;
1181
+ const data = await response.json();
1182
+ return (data.data?.length ?? 0) > 0;
1183
+ }
1184
+
1185
+ // src/pipeline/scoring.ts
1186
+ function trigrams(text) {
1187
+ const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
1188
+ const set = /* @__PURE__ */ new Set();
1189
+ for (let i = 0; i <= normalized.length - 3; i++) {
1190
+ set.add(normalized.substring(i, i + 3));
1191
+ }
1192
+ return set;
1193
+ }
1194
+ function jaccardSimilarity(a, b) {
1195
+ if (a.size === 0 && b.size === 0) return 1;
1196
+ let intersection = 0;
1197
+ for (const item of a) {
1198
+ if (b.has(item)) intersection++;
1199
+ }
1200
+ const union = a.size + b.size - intersection;
1201
+ return union === 0 ? 0 : intersection / union;
1202
+ }
1203
+ function deduplicateTweets(tweets, threshold) {
1204
+ const sorted = [...tweets].sort((a, b) => b.score - a.score);
1205
+ const kept = [];
1206
+ const keptTrigrams = [];
1207
+ for (const tweet of sorted) {
1208
+ const tweetTri = trigrams(tweet.text);
1209
+ let isDuplicate = false;
1210
+ for (const existingTri of keptTrigrams) {
1211
+ if (jaccardSimilarity(tweetTri, existingTri) >= threshold) {
1212
+ isDuplicate = true;
1213
+ break;
1214
+ }
1215
+ }
1216
+ if (!isDuplicate) {
1217
+ kept.push(tweet);
1218
+ keptTrigrams.push(tweetTri);
1219
+ }
1220
+ }
1221
+ return kept;
1222
+ }
1223
+ function logNormalize(value) {
1224
+ return Math.log1p(value) / Math.log1p(1e6);
1225
+ }
1226
+ function computeEngagementScore(metrics, subWeights) {
1227
+ const likes = logNormalize(metrics.like_count) * subWeights.likes;
1228
+ const reposts = logNormalize(metrics.retweet_count) * subWeights.reposts;
1229
+ const replies = logNormalize(metrics.reply_count) * subWeights.replies;
1230
+ const quotes = logNormalize(metrics.quote_count) * subWeights.quotes;
1231
+ return likes + reposts + replies + quotes;
1232
+ }
1233
+ function computeRecencyScore(createdAt, now) {
1234
+ const created = new Date(createdAt);
1235
+ const ageHours = (now.getTime() - created.getTime()) / (1e3 * 60 * 60);
1236
+ if (ageHours <= 0) return 1;
1237
+ if (ageHours <= 6) return 1;
1238
+ if (ageHours <= 24) return 0.9;
1239
+ if (ageHours <= 48) return 0.7;
1240
+ if (ageHours <= 168) return 0.4;
1241
+ return 0.1;
1242
+ }
1243
+ function computeRelevanceScore(tweet, topics) {
1244
+ const textLower = tweet.text.toLowerCase();
1245
+ let matchCount = 0;
1246
+ for (const topic of topics) {
1247
+ const words = topic.toLowerCase().split(/\s+/);
1248
+ const wordMatches = words.filter((w) => textLower.includes(w)).length;
1249
+ if (wordMatches >= Math.ceil(words.length * 0.5)) {
1250
+ matchCount++;
1251
+ }
1252
+ }
1253
+ const topicBoost = Math.min(matchCount * 0.1, 0.5);
1254
+ return Math.min(0.5 + topicBoost, 1);
1255
+ }
1256
+ function scoreTweets(tweets, weights, subWeights, authorityBoost, authorityHandles, authorityByDomain, topics, now) {
1257
+ const scoreTime = now ?? /* @__PURE__ */ new Date();
1258
+ return tweets.map((tweet) => {
1259
+ const relevance = computeRelevanceScore(tweet, topics);
1260
+ const recency = computeRecencyScore(tweet.created_at, scoreTime);
1261
+ const engagement = computeEngagementScore(tweet.metrics, subWeights);
1262
+ const isAuthority = authorityHandles.has(tweet.author_username.toLowerCase());
1263
+ const authorityDomains = [];
1264
+ for (const [domain, handles] of authorityByDomain) {
1265
+ if (handles.some((h) => h.toLowerCase() === tweet.author_username.toLowerCase())) {
1266
+ authorityDomains.push(domain);
1267
+ }
1268
+ }
1269
+ const score = relevance * weights.relevance + (isAuthority ? authorityBoost : 0) + recency * weights.recency + engagement * weights.engagement;
1270
+ return {
1271
+ ...tweet,
1272
+ score: Math.min(score, 1),
1273
+ relevance_score: relevance,
1274
+ recency_score: recency,
1275
+ engagement_score: engagement,
1276
+ is_authority: isAuthority,
1277
+ authority_domains: authorityDomains
1278
+ };
1279
+ });
1280
+ }
1281
+
1282
+ // src/pipeline/queries.ts
1283
+ function buildOpenQueries(topics, keywords, dateStr) {
1284
+ const queries = [];
1285
+ for (const topic of topics) {
1286
+ queries.push({
1287
+ text: `What are people saying about ${topic} today (${dateStr})?`,
1288
+ topic,
1289
+ phase: "open"
1290
+ });
1291
+ }
1292
+ for (const keyword of keywords) {
1293
+ queries.push({
1294
+ text: `What are people saying about ${keyword} today (${dateStr})?`,
1295
+ topic: keyword,
1296
+ phase: "open"
1297
+ });
1298
+ }
1299
+ return queries;
1300
+ }
1301
+ function buildFocusedQueries(topics, authorityByDomain, activeHandles, dateStr) {
1302
+ const queries = [];
1303
+ const sortedActive = [...activeHandles.entries()].sort(([, a], [, b]) => b.engagement + b.relevance - (a.engagement + a.relevance)).map(([handle]) => handle);
1304
+ for (const topic of topics) {
1305
+ const domainHandles = findBestDomainHandles(topic, authorityByDomain);
1306
+ const authoritySlice = domainHandles.slice(0, 5);
1307
+ const activeSlice = sortedActive.filter((h) => !authoritySlice.includes(h)).slice(0, 5);
1308
+ const combinedHandles = [...authoritySlice, ...activeSlice].slice(0, 10);
1309
+ if (combinedHandles.length === 0) continue;
1310
+ queries.push({
1311
+ text: `What are these accounts saying about ${topic} today (${dateStr})?`,
1312
+ topic,
1313
+ phase: "focused",
1314
+ handles: combinedHandles
1315
+ });
1316
+ }
1317
+ for (const [domain, handles] of authorityByDomain) {
1318
+ if (handles.length <= 5) continue;
1319
+ for (let offset = 5; offset < handles.length; offset += 5) {
1320
+ const rotationSlice = handles.slice(offset, offset + 5);
1321
+ const activeSlice = sortedActive.filter((h) => !rotationSlice.includes(h)).slice(0, 5);
1322
+ const combinedHandles = [...rotationSlice, ...activeSlice].slice(0, 10);
1323
+ if (combinedHandles.length === 0) continue;
1324
+ queries.push({
1325
+ text: `What are these accounts saying about ${domain} today (${dateStr})?`,
1326
+ topic: domain,
1327
+ phase: "focused",
1328
+ handles: combinedHandles
1329
+ });
1330
+ }
1331
+ }
1332
+ return queries;
1333
+ }
1334
+ function findBestDomainHandles(topic, authorityByDomain) {
1335
+ const topicLower = topic.toLowerCase();
1336
+ let bestMatch = "";
1337
+ let bestScore = 0;
1338
+ for (const [domain] of authorityByDomain) {
1339
+ const domainWords = domain.toLowerCase().split(/[_\s]+/);
1340
+ const matchCount = domainWords.filter((w) => topicLower.includes(w)).length;
1341
+ if (matchCount > bestScore) {
1342
+ bestScore = matchCount;
1343
+ bestMatch = domain;
1344
+ }
1345
+ }
1346
+ return bestMatch ? authorityByDomain.get(bestMatch) ?? [] : [];
1347
+ }
1348
+
1349
+ // src/pipeline/handles.ts
1350
+ async function updateHandleTracker(entities, logger, updates, existingAuthority) {
1351
+ let discovered = 0;
1352
+ let updated = 0;
1353
+ for (const update of updates) {
1354
+ const usernameLower = update.username.toLowerCase();
1355
+ if (existingAuthority.has(usernameLower)) continue;
1356
+ const existing = await entities.list({
1357
+ entityType: ENTITY_TYPES.handle,
1358
+ externalId: usernameLower,
1359
+ limit: 1
1360
+ });
1361
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1362
+ if (existing.length > 0) {
1363
+ const record = existing[0];
1364
+ const data = record.data;
1365
+ const newAppearances = data.appearances + 1;
1366
+ const newAvgRelevance = (data.avg_relevance * data.appearances + update.relevance) / newAppearances;
1367
+ const newDomains = [.../* @__PURE__ */ new Set([...data.domains, ...update.domains])];
1368
+ const newSampleIds = update.tweetId ? [.../* @__PURE__ */ new Set([...data.sample_tweet_ids, update.tweetId])].slice(-10) : data.sample_tweet_ids;
1369
+ const newFollowedBy = [.../* @__PURE__ */ new Set([...data.followed_by_existing, ...update.followedByExisting])];
1370
+ await entities.upsert({
1371
+ entityType: ENTITY_TYPES.handle,
1372
+ scopeKind: "instance",
1373
+ externalId: usernameLower,
1374
+ title: usernameLower,
1375
+ status: record.status ?? "discovered",
1376
+ data: {
1377
+ ...data,
1378
+ last_seen: now,
1379
+ appearances: newAppearances,
1380
+ avg_relevance: Math.round(newAvgRelevance * 1e3) / 1e3,
1381
+ domains: newDomains,
1382
+ followed_by_existing: newFollowedBy,
1383
+ sample_tweet_ids: newSampleIds,
1384
+ followers_count: update.followersCount ?? data.followers_count,
1385
+ verified_type: update.verifiedType ?? data.verified_type,
1386
+ x_user_id: update.userId ?? data.x_user_id
1387
+ }
1388
+ });
1389
+ updated++;
1390
+ } else {
1391
+ const data = {
1392
+ first_seen: now,
1393
+ last_seen: now,
1394
+ appearances: 1,
1395
+ avg_relevance: update.relevance,
1396
+ domains: update.domains,
1397
+ followed_by_existing: update.followedByExisting,
1398
+ x_user_id: update.userId,
1399
+ followers_count: update.followersCount ?? 0,
1400
+ verified_type: update.verifiedType,
1401
+ sample_tweet_ids: update.tweetId ? [update.tweetId] : [],
1402
+ promoted_to_list: null,
1403
+ notes: ""
1404
+ };
1405
+ await entities.upsert({
1406
+ entityType: ENTITY_TYPES.handle,
1407
+ scopeKind: "instance",
1408
+ externalId: usernameLower,
1409
+ title: usernameLower,
1410
+ status: "discovered",
1411
+ data
1412
+ });
1413
+ discovered++;
1414
+ }
1415
+ }
1416
+ logger.info("Handle tracker updated", { discovered, updated });
1417
+ return { discovered, updated };
1418
+ }
1419
+ async function evaluatePromotions(entities, logger, policy) {
1420
+ const candidates = [];
1421
+ const handles = await entities.list({
1422
+ entityType: ENTITY_TYPES.handle,
1423
+ limit: 200
1424
+ });
1425
+ for (const handle of handles) {
1426
+ const data = handle.data;
1427
+ const currentStatus = handle.status ?? "discovered";
1428
+ if (currentStatus === "promoted") continue;
1429
+ if (data.appearances >= policy.auto_promote_threshold.min_appearances && data.avg_relevance >= policy.auto_promote_threshold.min_avg_relevance && data.followed_by_existing.length >= policy.auto_promote_threshold.min_mutual_overlap) {
1430
+ await entities.upsert({
1431
+ entityType: ENTITY_TYPES.handle,
1432
+ scopeKind: "instance",
1433
+ externalId: handle.externalId,
1434
+ title: handle.title ?? handle.externalId,
1435
+ status: "promoted",
1436
+ data: handle.data
1437
+ });
1438
+ candidates.push({ username: handle.externalId, status: "promoted", data });
1439
+ logger.info("Handle auto-promoted", { handle: handle.externalId });
1440
+ continue;
1441
+ }
1442
+ if (currentStatus === "discovered" && data.appearances >= policy.candidate_threshold.min_appearances && data.avg_relevance >= policy.candidate_threshold.min_avg_relevance) {
1443
+ await entities.upsert({
1444
+ entityType: ENTITY_TYPES.handle,
1445
+ scopeKind: "instance",
1446
+ externalId: handle.externalId,
1447
+ title: handle.title ?? handle.externalId,
1448
+ status: "candidate",
1449
+ data: handle.data
1450
+ });
1451
+ candidates.push({ username: handle.externalId, status: "candidate", data });
1452
+ logger.info("Handle promoted to candidate", { handle: handle.externalId });
1453
+ }
1454
+ }
1455
+ return candidates;
1456
+ }
1457
+ async function decayHandles(entities, logger, trackingWindowDays) {
1458
+ const handles = await entities.list({
1459
+ entityType: ENTITY_TYPES.handle,
1460
+ limit: 500
1461
+ });
1462
+ const cutoffDate = /* @__PURE__ */ new Date();
1463
+ cutoffDate.setDate(cutoffDate.getDate() - trackingWindowDays);
1464
+ const cutoffStr = cutoffDate.toISOString().split("T")[0];
1465
+ let decayed = 0;
1466
+ for (const handle of handles) {
1467
+ const data = handle.data;
1468
+ const status = handle.status ?? "discovered";
1469
+ if (status === "promoted") continue;
1470
+ if (data.last_seen >= cutoffStr) continue;
1471
+ if (status === "candidate") {
1472
+ await entities.upsert({
1473
+ entityType: ENTITY_TYPES.handle,
1474
+ scopeKind: "instance",
1475
+ externalId: handle.externalId,
1476
+ title: handle.title ?? handle.externalId,
1477
+ status: "discovered",
1478
+ data: handle.data
1479
+ });
1480
+ decayed++;
1481
+ }
1482
+ }
1483
+ logger.info("Handle decay complete", { decayed });
1484
+ return decayed;
1485
+ }
1486
+ function buildHandleUpdatesFromTweets(tweets, topics, authorityByDomain) {
1487
+ const handleMap = /* @__PURE__ */ new Map();
1488
+ const authorityDomains = /* @__PURE__ */ new Map();
1489
+ for (const [domain, handles] of authorityByDomain) {
1490
+ for (const h of handles) {
1491
+ const lower = h.toLowerCase();
1492
+ const existing = authorityDomains.get(lower) ?? [];
1493
+ existing.push(domain);
1494
+ authorityDomains.set(lower, existing);
1495
+ }
1496
+ }
1497
+ const topicToDomainKeys = /* @__PURE__ */ new Map();
1498
+ for (const topic of topics) {
1499
+ const topicLower = topic.toLowerCase();
1500
+ const matched = [];
1501
+ for (const [domainKey] of authorityByDomain) {
1502
+ const keyWords = domainKey.toLowerCase().split(/[_\s]+/);
1503
+ if (keyWords.every((w) => topicLower.includes(w))) {
1504
+ matched.push(domainKey);
1505
+ }
1506
+ }
1507
+ if (matched.length > 0) topicToDomainKeys.set(topic, matched);
1508
+ }
1509
+ const handleDomainKeyMap = /* @__PURE__ */ new Map();
1510
+ for (const tweet of tweets) {
1511
+ const username = tweet.author_username.toLowerCase();
1512
+ const existing = handleMap.get(username);
1513
+ const domains = [];
1514
+ const textLower = tweet.text.toLowerCase();
1515
+ for (const topic of topics) {
1516
+ const words = topic.toLowerCase().split(/\s+/);
1517
+ const wordMatches = words.filter((w) => textLower.includes(w)).length;
1518
+ if (wordMatches >= Math.ceil(words.length * 0.5)) {
1519
+ domains.push(topic);
1520
+ }
1521
+ }
1522
+ const domainKeySet = handleDomainKeyMap.get(username) ?? /* @__PURE__ */ new Set();
1523
+ for (const d of domains) {
1524
+ const keys = topicToDomainKeys.get(d);
1525
+ if (keys) for (const k of keys) domainKeySet.add(k);
1526
+ }
1527
+ handleDomainKeyMap.set(username, domainKeySet);
1528
+ if (existing) {
1529
+ existing.relevance = Math.max(existing.relevance, 0.5);
1530
+ existing.domains = [.../* @__PURE__ */ new Set([...existing.domains, ...domains])];
1531
+ if (tweet.tweet_id) existing.tweetId = tweet.tweet_id;
1532
+ } else {
1533
+ handleMap.set(username, {
1534
+ username,
1535
+ relevance: 0.5,
1536
+ domains,
1537
+ followedByExisting: [],
1538
+ tweetId: tweet.tweet_id,
1539
+ userId: tweet.author_id,
1540
+ followersCount: tweet.author_followers_count,
1541
+ verifiedType: tweet.author_verified_type
1542
+ });
1543
+ }
1544
+ }
1545
+ for (const [username, update] of handleMap) {
1546
+ const myDomainKeys = handleDomainKeyMap.get(username);
1547
+ if (!myDomainKeys || myDomainKeys.size === 0) continue;
1548
+ const coOccurring = /* @__PURE__ */ new Set();
1549
+ for (const [authHandle, authDomainKeys] of authorityDomains) {
1550
+ if (authDomainKeys.some((d) => myDomainKeys.has(d))) {
1551
+ coOccurring.add(authHandle);
1552
+ }
1553
+ }
1554
+ update.followedByExisting = [...coOccurring];
1555
+ }
1556
+ return [...handleMap.values()];
1557
+ }
1558
+
1559
+ // src/worker.ts
1560
+ async function getConfig(ctx) {
1561
+ const raw = await ctx.config.get();
1562
+ return { ...DEFAULT_CONFIG, ...raw };
1563
+ }
1564
+ function buildAuthorityMaps(config) {
1565
+ const allHandles = /* @__PURE__ */ new Set();
1566
+ const byDomain = /* @__PURE__ */ new Map();
1567
+ for (const [domain, list] of Object.entries(config.authority_lists)) {
1568
+ const handles = list.handles.map((h) => h.toLowerCase());
1569
+ byDomain.set(domain, handles);
1570
+ for (const h of handles) allHandles.add(h);
1571
+ }
1572
+ return { allHandles, byDomain };
1573
+ }
1574
+ async function runDiscoveryPipeline(ctx, job) {
1575
+ const config = await getConfig(ctx);
1576
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1577
+ const threeDaysAgo = new Date(Date.now() - 3 * 864e5).toISOString().split("T")[0];
1578
+ const { allHandles, byDomain } = buildAuthorityMaps(config);
1579
+ ctx.logger.info("Discovery pipeline started", { date: today, jobRunId: job.runId });
1580
+ await ctx.metrics.write("pipeline.run.started", 1);
1581
+ const openQueries = buildOpenQueries(config.semantic_topics, config.keyword_searches, today);
1582
+ const openTweetIdArrays = [];
1583
+ let totalXaiCost = 0;
1584
+ for (const query of openQueries) {
1585
+ try {
1586
+ const result = await xaiSearch(
1587
+ ctx.http,
1588
+ ctx.secrets,
1589
+ ctx.logger,
1590
+ config.xai_api_key_ref,
1591
+ { query: query.text, fromDate: threeDaysAgo, toDate: today, excludedHandles: config.global_excluded }
1592
+ );
1593
+ openTweetIdArrays.push(result.tweetIds);
1594
+ totalXaiCost += result.costUsdTicks;
1595
+ } catch (err) {
1596
+ ctx.logger.warn("Open discovery query failed", {
1597
+ query: query.text,
1598
+ error: err instanceof Error ? err.message : String(err)
1599
+ });
1600
+ }
1601
+ }
1602
+ const openTweetIds = deduplicateIds(openTweetIdArrays);
1603
+ ctx.logger.info("Stage 1 complete: open discovery", {
1604
+ queriesRun: openQueries.length,
1605
+ uniqueTweetIds: openTweetIds.length
1606
+ });
1607
+ const openTweets = await batchLookupTweets(
1608
+ ctx.http,
1609
+ ctx.secrets,
1610
+ ctx.logger,
1611
+ config.x_api_bearer_ref,
1612
+ openTweetIds,
1613
+ "open_discovery"
1614
+ );
1615
+ const activeHandles = /* @__PURE__ */ new Map();
1616
+ for (const tweet of openTweets) {
1617
+ const username = tweet.author_username.toLowerCase();
1618
+ if (allHandles.has(username) || config.global_excluded.includes(username)) continue;
1619
+ const engagement = tweet.metrics.like_count + tweet.metrics.retweet_count * 2;
1620
+ const existing = activeHandles.get(username);
1621
+ if (existing) {
1622
+ existing.engagement = Math.max(existing.engagement, engagement);
1623
+ } else {
1624
+ activeHandles.set(username, { engagement, relevance: 0.5, domains: [] });
1625
+ }
1626
+ }
1627
+ const focusedQueries = buildFocusedQueries(
1628
+ config.semantic_topics,
1629
+ byDomain,
1630
+ activeHandles,
1631
+ today
1632
+ );
1633
+ const focusedTweetIdArrays = [];
1634
+ for (const query of focusedQueries) {
1635
+ try {
1636
+ const result = await xaiSearch(
1637
+ ctx.http,
1638
+ ctx.secrets,
1639
+ ctx.logger,
1640
+ config.xai_api_key_ref,
1641
+ {
1642
+ query: query.text,
1643
+ fromDate: threeDaysAgo,
1644
+ toDate: today,
1645
+ allowedHandles: query.handles
1646
+ }
1647
+ );
1648
+ focusedTweetIdArrays.push(result.tweetIds);
1649
+ totalXaiCost += result.costUsdTicks;
1650
+ if (result.tweetIds.length === 0 && query.handles?.length) {
1651
+ ctx.logger.warn("Focused query returned 0 results \u2014 handle set may be inactive", {
1652
+ topic: query.topic,
1653
+ handles: query.handles
1654
+ });
1655
+ }
1656
+ } catch (err) {
1657
+ ctx.logger.warn("Focused discovery query failed", {
1658
+ query: query.text,
1659
+ error: err instanceof Error ? err.message : String(err)
1660
+ });
1661
+ }
1662
+ }
1663
+ const focusedTweetIds = deduplicateIds(focusedTweetIdArrays);
1664
+ const openIdSet = new Set(openTweetIds);
1665
+ const newFocusedIds = focusedTweetIds.filter((id) => !openIdSet.has(id));
1666
+ ctx.logger.info("Stage 2 complete: focused discovery", {
1667
+ queriesRun: focusedQueries.length,
1668
+ newTweetIds: newFocusedIds.length
1669
+ });
1670
+ const focusedTweets = await batchLookupTweets(
1671
+ ctx.http,
1672
+ ctx.secrets,
1673
+ ctx.logger,
1674
+ config.x_api_bearer_ref,
1675
+ newFocusedIds,
1676
+ "focused"
1677
+ );
1678
+ const focusedIdSet = new Set(focusedTweetIds);
1679
+ const allTweets = openTweets.map(
1680
+ (t) => focusedIdSet.has(t.tweet_id) ? { ...t, source: "both" } : t
1681
+ );
1682
+ allTweets.push(...focusedTweets);
1683
+ ctx.logger.info("Stage 3 complete: enrichment", { totalTweets: allTweets.length });
1684
+ const scored = scoreTweets(
1685
+ allTweets,
1686
+ config.scoring_weights,
1687
+ config.engagement_sub_weights,
1688
+ config.authority_boost,
1689
+ allHandles,
1690
+ byDomain,
1691
+ config.semantic_topics
1692
+ );
1693
+ const deduplicated = deduplicateTweets(scored, config.dedup_threshold);
1694
+ const corpus = deduplicated.slice(0, config.max_corpus_size);
1695
+ ctx.logger.info("Stage 4: scoring & dedup", {
1696
+ scored: scored.length,
1697
+ afterDedup: deduplicated.length,
1698
+ finalCorpus: corpus.length
1699
+ });
1700
+ for (const tweet of corpus) {
1701
+ await ctx.entities.upsert({
1702
+ entityType: ENTITY_TYPES.tweet,
1703
+ scopeKind: "instance",
1704
+ externalId: tweet.tweet_id,
1705
+ title: `@${tweet.author_username}: ${tweet.text.slice(0, 80)}`,
1706
+ status: "active",
1707
+ data: tweet
1708
+ });
1709
+ }
1710
+ const handleUpdates = buildHandleUpdatesFromTweets(allTweets, config.semantic_topics, byDomain);
1711
+ const handleStats = await updateHandleTracker(
1712
+ ctx.entities,
1713
+ ctx.logger,
1714
+ handleUpdates,
1715
+ allHandles
1716
+ );
1717
+ const promotions = await evaluatePromotions(
1718
+ ctx.entities,
1719
+ ctx.logger,
1720
+ config.authority_promotion_policy
1721
+ );
1722
+ const summary = {
1723
+ date: today,
1724
+ total_tweets: corpus.length,
1725
+ top_items: corpus.slice(0, 10),
1726
+ by_pillar: {},
1727
+ discovery_stats: {
1728
+ open_queries: openQueries.length,
1729
+ focused_queries: focusedQueries.length,
1730
+ total_tweet_ids_extracted: openTweetIds.length + focusedTweetIds.length,
1731
+ tweets_after_dedup: deduplicated.length,
1732
+ tweets_after_scoring: corpus.length,
1733
+ new_handles_discovered: handleStats.discovered,
1734
+ handles_promoted: promotions.length,
1735
+ xai_cost_estimate: totalXaiCost / 1e9,
1736
+ x_api_cost_estimate: allTweets.length * 0.01
1737
+ },
1738
+ generated_at: (/* @__PURE__ */ new Date()).toISOString()
1739
+ };
1740
+ await ctx.state.set(
1741
+ { scopeKind: "instance", stateKey: `${STATE_KEYS.corpusPrefix}${today}` },
1742
+ summary
1743
+ );
1744
+ await ctx.state.set(
1745
+ { scopeKind: "instance", stateKey: STATE_KEYS.lastDiscoveryRun },
1746
+ { date: today, corpusSize: corpus.length, generatedAt: summary.generated_at }
1747
+ );
1748
+ await ctx.events.emit(EVENT_NAMES.corpusUpdated, config.company_id, {
1749
+ date: today,
1750
+ corpusSize: corpus.length,
1751
+ topScore: corpus[0]?.score ?? 0
1752
+ });
1753
+ for (const promo of promotions) {
1754
+ if (promo.status === "promoted") {
1755
+ await ctx.events.emit(EVENT_NAMES.handlePromoted, config.company_id, {
1756
+ handle: promo.username,
1757
+ status: promo.status,
1758
+ appearances: promo.data.appearances,
1759
+ avgRelevance: promo.data.avg_relevance
1760
+ });
1761
+ }
1762
+ }
1763
+ await ctx.activity.log({
1764
+ companyId: config.company_id,
1765
+ message: `X Intelligence discovery complete: ${corpus.length} tweets scored, ${handleStats.discovered} new handles discovered`,
1766
+ metadata: summary.discovery_stats
1767
+ });
1768
+ await ctx.metrics.write("pipeline.run.completed", 1);
1769
+ await ctx.metrics.write("pipeline.corpus.size", corpus.length);
1770
+ await ctx.metrics.write("pipeline.handles.discovered", handleStats.discovered);
1771
+ await ctx.metrics.write("pipeline.cost.xai", totalXaiCost / 1e9);
1772
+ ctx.logger.info("Discovery pipeline complete", {
1773
+ date: today,
1774
+ corpusSize: corpus.length,
1775
+ totalCostEstimate: `$${(totalXaiCost / 1e9 + allTweets.length * 0.01).toFixed(2)}`
1776
+ });
1777
+ }
1778
+ async function handleAuthorityDecay(ctx, _job) {
1779
+ const config = await getConfig(ctx);
1780
+ const decayed = await decayHandles(
1781
+ ctx.entities,
1782
+ ctx.logger,
1783
+ config.authority_promotion_policy.tracking_window_days
1784
+ );
1785
+ await ctx.metrics.write("handles.decayed", decayed);
1786
+ ctx.logger.info("Authority decay job complete", { decayed });
1787
+ }
1788
+ async function handleComplianceCheck(ctx, _job) {
1789
+ const config = await getConfig(ctx);
1790
+ const tweets = await ctx.entities.list({ entityType: ENTITY_TYPES.tweet, limit: 200 });
1791
+ let removed = 0;
1792
+ for (const tweet of tweets) {
1793
+ const tweetId = tweet.externalId;
1794
+ if (!tweetId) continue;
1795
+ try {
1796
+ const exists = await verifyTweetExists(
1797
+ ctx.http,
1798
+ ctx.secrets,
1799
+ config.x_api_bearer_ref,
1800
+ tweetId
1801
+ );
1802
+ if (!exists) {
1803
+ await ctx.entities.upsert({
1804
+ entityType: ENTITY_TYPES.tweet,
1805
+ scopeKind: "instance",
1806
+ externalId: tweetId,
1807
+ title: tweet.title ?? tweetId,
1808
+ status: "removed",
1809
+ data: { ...tweet.data, removed_reason: "deleted_or_suspended", removed_at: (/* @__PURE__ */ new Date()).toISOString() }
1810
+ });
1811
+ removed++;
1812
+ }
1813
+ } catch {
1814
+ }
1815
+ }
1816
+ await ctx.metrics.write("compliance.tweets_removed", removed);
1817
+ ctx.logger.info("Compliance check complete", { checked: tweets.length, removed });
1818
+ }
1819
+ async function handleCorpusRetention(ctx, _job) {
1820
+ const config = await getConfig(ctx);
1821
+ const cutoff = /* @__PURE__ */ new Date();
1822
+ cutoff.setDate(cutoff.getDate() - config.corpus_retention_days);
1823
+ const cutoffStr = cutoff.toISOString().split("T")[0];
1824
+ const tweets = await ctx.entities.list({ entityType: ENTITY_TYPES.tweet, limit: 500 });
1825
+ let archived = 0;
1826
+ for (const tweet of tweets) {
1827
+ const data = tweet.data;
1828
+ const createdAt = typeof data.created_at === "string" ? data.created_at.split("T")[0] : null;
1829
+ if (createdAt && createdAt < cutoffStr) {
1830
+ await ctx.entities.upsert({
1831
+ entityType: ENTITY_TYPES.tweet,
1832
+ scopeKind: "instance",
1833
+ externalId: tweet.externalId,
1834
+ title: tweet.title ?? tweet.externalId,
1835
+ status: "archived",
1836
+ data: { ...tweet.data, archived_at: (/* @__PURE__ */ new Date()).toISOString() }
1837
+ });
1838
+ archived++;
1839
+ }
1840
+ }
1841
+ await ctx.metrics.write("retention.tweets_archived", archived);
1842
+ ctx.logger.info("Corpus retention complete", { archived });
1843
+ }
1844
+ async function handleSearchCorpus(ctx, params, _runCtx) {
1845
+ const p = params;
1846
+ const query = typeof p.query === "string" ? p.query.toLowerCase() : "";
1847
+ const date = typeof p.date === "string" ? p.date : void 0;
1848
+ const pillar = typeof p.pillar === "string" ? p.pillar.toLowerCase() : void 0;
1849
+ const limit = typeof p.limit === "number" ? p.limit : 20;
1850
+ const tweets = await ctx.entities.list({
1851
+ entityType: ENTITY_TYPES.tweet,
1852
+ limit: 200
1853
+ });
1854
+ const results = [];
1855
+ for (const tweet of tweets) {
1856
+ if (tweet.status === "removed" || tweet.status === "archived") continue;
1857
+ const data = tweet.data;
1858
+ const textMatch = query ? data.text.toLowerCase().includes(query) : true;
1859
+ const dateMatch = date ? data.created_at?.startsWith(date) ?? false : true;
1860
+ const pillarMatch = pillar ? data.authority_domains?.some((d) => d.toLowerCase().includes(pillar)) ?? false : true;
1861
+ if (textMatch && dateMatch && pillarMatch) {
1862
+ results.push(data);
1863
+ }
1864
+ }
1865
+ results.sort((a, b) => b.score - a.score);
1866
+ const limited = results.slice(0, limit);
1867
+ return {
1868
+ content: `Found ${limited.length} tweets matching "${query}"`,
1869
+ data: limited
1870
+ };
1871
+ }
1872
+ async function handleGetToday(ctx, params, _runCtx) {
1873
+ const p = params;
1874
+ const pillar = typeof p.pillar === "string" ? p.pillar : void 0;
1875
+ const limit = typeof p.limit === "number" ? p.limit : 20;
1876
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1877
+ const summary = await ctx.state.get({
1878
+ scopeKind: "instance",
1879
+ stateKey: `${STATE_KEYS.corpusPrefix}${today}`
1880
+ });
1881
+ if (!summary) {
1882
+ return { content: "No corpus available for today. Discovery may not have run yet." };
1883
+ }
1884
+ let items = summary.top_items;
1885
+ if (pillar) {
1886
+ items = items.filter(
1887
+ (t) => t.authority_domains?.some((d) => d.toLowerCase().includes(pillar.toLowerCase())) ?? false
1888
+ );
1889
+ }
1890
+ return {
1891
+ content: `Today's corpus: ${summary.total_tweets} tweets. Showing top ${Math.min(limit, items.length)}.`,
1892
+ data: {
1893
+ date: summary.date,
1894
+ total: summary.total_tweets,
1895
+ items: items.slice(0, limit),
1896
+ stats: summary.discovery_stats
1897
+ }
1898
+ };
1899
+ }
1900
+ async function handleGetAuthorities(ctx, params, _runCtx) {
1901
+ const p = params;
1902
+ const listName = typeof p.list_name === "string" ? p.list_name : void 0;
1903
+ const config = await getConfig(ctx);
1904
+ if (listName) {
1905
+ const list = config.authority_lists[listName];
1906
+ if (!list) {
1907
+ return { error: `Authority list "${listName}" not found. Available: ${Object.keys(config.authority_lists).join(", ")}` };
1908
+ }
1909
+ return {
1910
+ content: `Authority list "${listName}": ${list.handles.length} handles`,
1911
+ data: { name: listName, ...list }
1912
+ };
1913
+ }
1914
+ const summary = Object.entries(config.authority_lists).map(([name, list]) => ({
1915
+ name,
1916
+ description: list.description,
1917
+ handleCount: list.handles.length,
1918
+ handles: list.handles,
1919
+ lastReviewed: list.last_reviewed
1920
+ }));
1921
+ return {
1922
+ content: `${summary.length} authority lists configured`,
1923
+ data: summary
1924
+ };
1925
+ }
1926
+ async function handleSuggestHandles(ctx, _params, _runCtx) {
1927
+ const handles = await ctx.entities.list({
1928
+ entityType: ENTITY_TYPES.handle,
1929
+ limit: 200
1930
+ });
1931
+ const candidates = handles.filter((h) => h.status === "candidate" || h.status === "promoted").map((h) => {
1932
+ const data = h.data;
1933
+ return {
1934
+ username: h.externalId,
1935
+ status: h.status,
1936
+ appearances: data.appearances,
1937
+ avg_relevance: data.avg_relevance,
1938
+ domains: data.domains,
1939
+ followers_count: data.followers_count,
1940
+ first_seen: data.first_seen,
1941
+ last_seen: data.last_seen
1942
+ };
1943
+ });
1944
+ candidates.sort((a, b) => {
1945
+ const scoreA = (a.avg_relevance ?? 0) * (a.appearances ?? 0);
1946
+ const scoreB = (b.avg_relevance ?? 0) * (b.appearances ?? 0);
1947
+ return scoreB - scoreA;
1948
+ });
1949
+ return {
1950
+ content: `${candidates.length} handles are candidates or recently promoted`,
1951
+ data: candidates
1952
+ };
1953
+ }
1954
+ async function handleTrackHandle(ctx, params, _runCtx) {
1955
+ const p = params;
1956
+ const handle = typeof p.handle === "string" ? p.handle.toLowerCase().replace(/^@/, "") : "";
1957
+ const relevance = typeof p.relevance === "number" ? p.relevance : 0.5;
1958
+ const domain = typeof p.domain === "string" ? p.domain : "";
1959
+ if (!handle) return { error: "handle is required" };
1960
+ if (!domain) return { error: "domain is required" };
1961
+ const config = await getConfig(ctx);
1962
+ const { allHandles } = buildAuthorityMaps(config);
1963
+ await updateHandleTracker(ctx.entities, ctx.logger, [
1964
+ { username: handle, relevance, domains: [domain], followedByExisting: [] }
1965
+ ], allHandles);
1966
+ return {
1967
+ content: `Handle @${handle} tracked with relevance ${relevance} in domain "${domain}"`
1968
+ };
1969
+ }
1970
+ var plugin = definePlugin({
1971
+ async setup(ctx) {
1972
+ ctx.jobs.register(JOB_KEYS.discoveryRun, (job) => runDiscoveryPipeline(ctx, job));
1973
+ ctx.jobs.register(JOB_KEYS.authorityDecay, (job) => handleAuthorityDecay(ctx, job));
1974
+ ctx.jobs.register(JOB_KEYS.complianceCheck, (job) => handleComplianceCheck(ctx, job));
1975
+ ctx.jobs.register(JOB_KEYS.corpusRetention, (job) => handleCorpusRetention(ctx, job));
1976
+ ctx.tools.register(
1977
+ TOOL_NAMES.searchCorpus,
1978
+ { displayName: "Search X Corpus", description: "Search the scored X intelligence corpus", parametersSchema: {} },
1979
+ (params, runCtx) => handleSearchCorpus(ctx, params, runCtx)
1980
+ );
1981
+ ctx.tools.register(
1982
+ TOOL_NAMES.getToday,
1983
+ { displayName: "Get Today's Intelligence", description: "Get today's scored corpus", parametersSchema: {} },
1984
+ (params, runCtx) => handleGetToday(ctx, params, runCtx)
1985
+ );
1986
+ ctx.tools.register(
1987
+ TOOL_NAMES.getAuthorities,
1988
+ { displayName: "Get Authority Handles", description: "Get authority handle lists", parametersSchema: {} },
1989
+ (params, runCtx) => handleGetAuthorities(ctx, params, runCtx)
1990
+ );
1991
+ ctx.tools.register(
1992
+ TOOL_NAMES.suggestHandles,
1993
+ { displayName: "Suggest Handle Promotions", description: "Get promotion candidates", parametersSchema: {} },
1994
+ (params, runCtx) => handleSuggestHandles(ctx, params, runCtx)
1995
+ );
1996
+ ctx.tools.register(
1997
+ TOOL_NAMES.trackHandle,
1998
+ { displayName: "Track Handle", description: "Record a handle appearance", parametersSchema: {} },
1999
+ (params, runCtx) => handleTrackHandle(ctx, params, runCtx)
2000
+ );
2001
+ ctx.data.register("dashboard-summary", async () => {
2002
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2003
+ const summary = await ctx.state.get({
2004
+ scopeKind: "instance",
2005
+ stateKey: `${STATE_KEYS.corpusPrefix}${today}`
2006
+ });
2007
+ const lastRun = await ctx.state.get({
2008
+ scopeKind: "instance",
2009
+ stateKey: STATE_KEYS.lastDiscoveryRun
2010
+ });
2011
+ const handleCount = (await ctx.entities.list({
2012
+ entityType: ENTITY_TYPES.handle,
2013
+ limit: 1
2014
+ })).length;
2015
+ return {
2016
+ today: summary ?? null,
2017
+ lastRun,
2018
+ trackedHandles: handleCount
2019
+ };
2020
+ });
2021
+ ctx.data.register("corpus", async (params) => {
2022
+ const date = typeof params.date === "string" ? params.date : (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2023
+ return await ctx.state.get({
2024
+ scopeKind: "instance",
2025
+ stateKey: `${STATE_KEYS.corpusPrefix}${date}`
2026
+ });
2027
+ });
2028
+ ctx.data.register("plugin-config", async () => {
2029
+ return await ctx.config.get();
2030
+ });
2031
+ let discoveryRunning = false;
2032
+ ctx.actions.register("trigger-discovery", async () => {
2033
+ if (discoveryRunning) {
2034
+ return { message: "Discovery pipeline already running." };
2035
+ }
2036
+ ctx.logger.info("Manual discovery trigger requested via UI");
2037
+ discoveryRunning = true;
2038
+ const fakeJob = { runId: `manual-${Date.now()}`, jobKey: JOB_KEYS.discoveryRun };
2039
+ runDiscoveryPipeline(ctx, fakeJob).catch((err) => {
2040
+ ctx.logger.error("Manual discovery failed", { error: err instanceof Error ? err.message : String(err) });
2041
+ }).finally(() => {
2042
+ discoveryRunning = false;
2043
+ });
2044
+ return { message: "Discovery pipeline started. Check logs for progress." };
2045
+ });
2046
+ ctx.events.on("issue.created", async (event) => {
2047
+ ctx.logger.debug("Issue created event received", { issueId: event.entityId });
2048
+ });
2049
+ ctx.logger.info("X Intelligence plugin initialized");
2050
+ },
2051
+ async onHealth() {
2052
+ return { status: "ok", message: "X Intelligence plugin is running" };
2053
+ }
2054
+ });
2055
+ var worker_default = plugin;
2056
+ runWorker(plugin, import.meta.url);
2057
+ export {
2058
+ worker_default as default
2059
+ };
2060
+ //# sourceMappingURL=worker.js.map