@interactive-inc/claude-funnel 0.57.0 → 0.58.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.
- package/dist/bin.js +258 -219
- package/dist/claude.d.ts +5 -5
- package/dist/claude.js +2 -2
- package/dist/{connector-adapter-1PxjN-Uk.d.ts → connector-adapter-DGacCppE.d.ts} +1 -1
- package/dist/connectors/discord.d.ts +3 -20
- package/dist/connectors/discord.js +1 -1
- package/dist/connectors/gh.d.ts +4 -4
- package/dist/connectors/schedule.d.ts +1 -1
- package/dist/connectors/slack.d.ts +2 -2
- package/dist/connectors/slack.js +1 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/{discord-connector-schema-CPgcZkXh.d.ts → discord-connector-schema-CQyfDkLD.d.ts} +18 -1
- package/dist/{discord-listener-C0MoKdQO.js → discord-listener-CKsZGTnH.js} +1 -1
- package/dist/docs.d.ts +1 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/{file-process-guard-DI1742H5.d.ts → file-process-guard-B3IFCj_G.d.ts} +5 -5
- package/dist/{funnel-diagnostics-qWy5tPSq.d.ts → funnel-diagnostics-K-wON25Y.d.ts} +1 -1
- package/dist/{funnel-doctor-BF3Rdgk0.d.ts → funnel-doctor-vxO96TCA.d.ts} +2 -2
- package/dist/funnel-log-sqlite-sink-B_5_4ybn.js +301 -0
- package/dist/{funnel-recovery-BUBsu7WX.d.ts → funnel-recovery-COExL9MD.d.ts} +1 -1
- package/dist/gateway/daemon.js +196 -196
- package/dist/gateway.d.ts +2 -2
- package/dist/gateway.js +1 -1
- package/dist/{index-DEeCwhk2.d.ts → index-B9iyugar.d.ts} +49 -14
- package/dist/index.d.ts +17 -16
- package/dist/index.js +76 -11
- package/dist/{local-config-sync-E_t5_fjw.d.ts → local-config-sync--f739oCJ.d.ts} +8 -8
- package/dist/local-config.d.ts +2 -2
- package/dist/local-config.js +1 -1
- package/dist/logger.d.ts +384 -0
- package/dist/logger.js +281 -0
- package/dist/{memory-diagnostic-log-BbFVqDzz.js → memory-diagnostic-log-5LzwJ_F7.js} +110 -323
- package/dist/{memory-token-prompter-DpCC1_Dn.d.ts → memory-token-prompter-BlFwK9k7.d.ts} +2 -2
- package/dist/{profiles-EHTeCOqB.d.ts → profiles-g2qGVOWv.d.ts} +3 -3
- package/dist/profiles.d.ts +1 -1
- package/dist/recovery.d.ts +1 -1
- package/dist/{schedule-listener-DKh0hnkK.d.ts → schedule-listener-DoMPjHZj.d.ts} +2 -2
- package/dist/{settings-reader-CBrgz01o.d.ts → settings-reader-DPwqOVUm.d.ts} +1 -1
- package/dist/{slack-listener-BDyBqatt.js → slack-listener-C4wlZaOq.js} +18 -5
- package/dist/{slack-listener-DFlAzMc7.d.ts → slack-listener-Dj9NFbAJ.d.ts} +2 -1
- package/dist/{yaml-render-OhUN-qkS.js → yaml-render-C9Hhjk-0.js} +1 -1
- package/package.json +6 -1
- /package/dist/{diagnostic-log-Bxe7Bbvw.d.ts → diagnostic-log-Cb3v8P7p.d.ts} +0 -0
- /package/dist/{file-system-Wub9Nto4.d.ts → file-system-DxpnnUVb.d.ts} +0 -0
- /package/dist/{funnel-docs-dXPokzr5.d.ts → funnel-docs-DYBs1-H_.d.ts} +0 -0
- /package/dist/{gh-connector-schema-CU1ojfIF.d.ts → gh-connector-schema-CZzwzvqY.d.ts} +0 -0
- /package/dist/{memory-token-prompter-vBXxY20-.js → memory-token-prompter-C7vREzCL.js} +0 -0
- /package/dist/{process-runner-D5I_jhYQ.d.ts → process-runner-Cx5O_fTf.d.ts} +0 -0
- /package/dist/{settings-schema-zhnMIa8I.d.ts → settings-schema-1hh11jnN.d.ts} +0 -0
|
@@ -2,11 +2,11 @@ import { n as NodeFunnelProcessRunner } from "./gh-connector-schema-DUcZgN2Q.js"
|
|
|
2
2
|
import { t as NodeFunnelFileSystem } from "./node-file-system-BcrmWN9I.js";
|
|
3
3
|
import { r as FUNNEL_DIR, s as resolveFunnelPort, t as gatewayLoopbackUrl } from "./gateway-base-url-6foMXfFf.js";
|
|
4
4
|
import { t as ConnectorDiagnosticSqlReader } from "./diagnostic-sql-reader-CzYgZpq2.js";
|
|
5
|
+
import { t as FunnelLogSqliteSink } from "./funnel-log-sqlite-sink-B_5_4ybn.js";
|
|
5
6
|
import { dirname, join } from "node:path";
|
|
6
7
|
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
import { homedir, tmpdir } from "node:os";
|
|
9
|
-
import { Database } from "bun:sqlite";
|
|
10
10
|
import { timingSafeEqual } from "node:crypto";
|
|
11
11
|
import { createFactory } from "hono/factory";
|
|
12
12
|
import { HTTPException } from "hono/http-exception";
|
|
@@ -360,298 +360,6 @@ const funnelEventSchema = z.object({
|
|
|
360
360
|
*/
|
|
361
361
|
var FunnelEventLog = class {};
|
|
362
362
|
//#endregion
|
|
363
|
-
//#region lib/logger/leuco-logger-sqlite-sink.ts
|
|
364
|
-
/** Conservative whitelist for column names interpolated into SQL. */
|
|
365
|
-
const COLUMN_NAME_RE = /^[a-z_][a-z0-9_]*$/;
|
|
366
|
-
/** How many inserts between on-disk size checks (see insertsSinceByteCheck). */
|
|
367
|
-
const BYTE_CHECK_INTERVAL = 500;
|
|
368
|
-
const RESERVED_COLUMNS = new Set([
|
|
369
|
-
"seq",
|
|
370
|
-
"ts",
|
|
371
|
-
"type",
|
|
372
|
-
"event"
|
|
373
|
-
]);
|
|
374
|
-
/**
|
|
375
|
-
* Schema versions. Each entry is the list of DDL statements that take the
|
|
376
|
-
* database from version i to version i + 1. Migrations run in a transaction
|
|
377
|
-
* so a partial failure rolls back. Adding a new version is append-only —
|
|
378
|
-
* never edit a published one. Caller-defined index columns are added
|
|
379
|
-
* dynamically on construct (independent of versioned migrations) because
|
|
380
|
-
* they are configuration, not schema evolution.
|
|
381
|
-
*/
|
|
382
|
-
const MIGRATIONS = [[
|
|
383
|
-
"CREATE TABLE IF NOT EXISTS leuco_log (seq INTEGER PRIMARY KEY, ts INTEGER NOT NULL, type TEXT, event TEXT NOT NULL)",
|
|
384
|
-
"CREATE INDEX IF NOT EXISTS idx_leuco_log_ts ON leuco_log (ts)",
|
|
385
|
-
"CREATE INDEX IF NOT EXISTS idx_leuco_log_type ON leuco_log (type)"
|
|
386
|
-
]];
|
|
387
|
-
/**
|
|
388
|
-
* SQLite-backed sink built on `bun:sqlite`. Implements both primary and
|
|
389
|
-
* relay roles so the same instance can own seq generation for one bus and
|
|
390
|
-
* mirror records from another (e.g. cross-process replication, restore
|
|
391
|
-
* from a backup stream).
|
|
392
|
-
*
|
|
393
|
-
* Concurrency model: seq is `INTEGER PRIMARY KEY`, so SQLite assigns it
|
|
394
|
-
* atomically via `lastInsertRowid`. Two `LeucoLogger` instances pointed
|
|
395
|
-
* at the same database file therefore see one monotonically increasing
|
|
396
|
-
* seq stream without any bus-level coordination — the database itself is
|
|
397
|
-
* the synchronization point.
|
|
398
|
-
*
|
|
399
|
-
* Schema is version-managed via `PRAGMA user_version`. Migrations are
|
|
400
|
-
* append-only and run in a transaction on every construct so a partial
|
|
401
|
-
* upgrade rolls back cleanly. Caller-defined `indexes` are layered on top
|
|
402
|
-
* via `ALTER TABLE ADD COLUMN` + `CREATE INDEX IF NOT EXISTS`, so adding
|
|
403
|
-
* a new index to an existing database is a no-downtime operation.
|
|
404
|
-
*
|
|
405
|
-
* Type safety: the second generic parameter `I` is the literal tuple of
|
|
406
|
-
* index column names. `extractIndexes` and `getRecords({ where })` are
|
|
407
|
-
* both type-checked against this tuple, so a typo at the call site is a
|
|
408
|
-
* compile-time error rather than a silent miss at runtime.
|
|
409
|
-
*
|
|
410
|
-
* Retention is bounded by `maxRows` and/or `maxAgeMs`. Both run on every
|
|
411
|
-
* insert as a single indexed DELETE that no-ops below the cap.
|
|
412
|
-
*
|
|
413
|
-
* Bulk inserts use `insertMany`, which wraps the batch in one transaction
|
|
414
|
-
* for ~10–100x throughput at the cost of one fsync per batch instead of
|
|
415
|
-
* one per row.
|
|
416
|
-
*/
|
|
417
|
-
var LeucoLoggerSqliteSink = class {
|
|
418
|
-
db;
|
|
419
|
-
maxRows;
|
|
420
|
-
maxAgeMs;
|
|
421
|
-
maxBytes;
|
|
422
|
-
targetBytes;
|
|
423
|
-
now;
|
|
424
|
-
indexes;
|
|
425
|
-
extractIndexes;
|
|
426
|
-
insertStmt;
|
|
427
|
-
insertWithSeqStmt;
|
|
428
|
-
maxSeqStmt;
|
|
429
|
-
countStmt;
|
|
430
|
-
trimRowsStmt;
|
|
431
|
-
trimAgeStmt;
|
|
432
|
-
trimOldestStmt;
|
|
433
|
-
insertsSinceByteCheck = 0;
|
|
434
|
-
constructor(props) {
|
|
435
|
-
if (props.path !== ":memory:") {
|
|
436
|
-
const dir = dirname(props.path);
|
|
437
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
438
|
-
}
|
|
439
|
-
this.db = new Database(props.path);
|
|
440
|
-
this.db.run("PRAGMA journal_mode = WAL");
|
|
441
|
-
this.migrate();
|
|
442
|
-
this.maxRows = props.maxRows ?? null;
|
|
443
|
-
this.maxAgeMs = props.maxAgeMs ?? null;
|
|
444
|
-
this.maxBytes = props.maxBytes ?? null;
|
|
445
|
-
this.targetBytes = props.targetBytes ?? (props.maxBytes !== void 0 ? Math.floor(props.maxBytes / 4) : null);
|
|
446
|
-
this.now = props.now ?? (() => Date.now());
|
|
447
|
-
this.indexes = props.indexes ?? [];
|
|
448
|
-
if (this.indexes.length > 0) {
|
|
449
|
-
validateIndexNames(this.indexes);
|
|
450
|
-
this.extractIndexes = props.extractIndexes ?? null;
|
|
451
|
-
this.syncIndexColumns();
|
|
452
|
-
} else this.extractIndexes = null;
|
|
453
|
-
const cols = [
|
|
454
|
-
"ts",
|
|
455
|
-
"type",
|
|
456
|
-
"event",
|
|
457
|
-
...this.indexes
|
|
458
|
-
];
|
|
459
|
-
const placeholders = cols.map(() => "?").join(", ");
|
|
460
|
-
this.insertStmt = this.db.prepare(`INSERT INTO leuco_log (${cols.join(", ")}) VALUES (${placeholders})`);
|
|
461
|
-
const colsWithSeq = ["seq", ...cols];
|
|
462
|
-
const placeholdersWithSeq = colsWithSeq.map(() => "?").join(", ");
|
|
463
|
-
this.insertWithSeqStmt = this.db.prepare(`INSERT INTO leuco_log (${colsWithSeq.join(", ")}) VALUES (${placeholdersWithSeq})`);
|
|
464
|
-
this.maxSeqStmt = this.db.prepare("SELECT COALESCE(MAX(seq), 0) AS max FROM leuco_log");
|
|
465
|
-
this.countStmt = this.db.prepare("SELECT COUNT(*) AS n FROM leuco_log");
|
|
466
|
-
this.trimRowsStmt = this.db.prepare("DELETE FROM leuco_log WHERE seq <= (SELECT seq FROM leuco_log ORDER BY seq DESC LIMIT 1 OFFSET ?)");
|
|
467
|
-
this.trimAgeStmt = this.db.prepare("DELETE FROM leuco_log WHERE ts < ?");
|
|
468
|
-
this.trimOldestStmt = this.db.prepare("DELETE FROM leuco_log WHERE seq IN (SELECT seq FROM leuco_log ORDER BY seq ASC LIMIT ?)");
|
|
469
|
-
}
|
|
470
|
-
insert(input) {
|
|
471
|
-
try {
|
|
472
|
-
const params = this.buildInsertParams(input.ts, input.event);
|
|
473
|
-
const result = this.insertStmt.run(...params);
|
|
474
|
-
const seq = Number(result.lastInsertRowid);
|
|
475
|
-
this.trim();
|
|
476
|
-
return {
|
|
477
|
-
seq,
|
|
478
|
-
ts: input.ts,
|
|
479
|
-
event: input.event
|
|
480
|
-
};
|
|
481
|
-
} catch (e) {
|
|
482
|
-
return e instanceof Error ? e : new Error(String(e));
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
insertMany(inputs) {
|
|
486
|
-
if (inputs.length === 0) return [];
|
|
487
|
-
try {
|
|
488
|
-
const records = [];
|
|
489
|
-
this.db.transaction((batch) => {
|
|
490
|
-
for (const input of batch) {
|
|
491
|
-
const params = this.buildInsertParams(input.ts, input.event);
|
|
492
|
-
const result = this.insertStmt.run(...params);
|
|
493
|
-
records.push({
|
|
494
|
-
seq: Number(result.lastInsertRowid),
|
|
495
|
-
ts: input.ts,
|
|
496
|
-
event: input.event
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
})(inputs);
|
|
500
|
-
this.trim();
|
|
501
|
-
return records;
|
|
502
|
-
} catch (e) {
|
|
503
|
-
return e instanceof Error ? e : new Error(String(e));
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
write(record) {
|
|
507
|
-
try {
|
|
508
|
-
const params = [record.seq, ...this.buildInsertParams(record.ts, record.event)];
|
|
509
|
-
this.insertWithSeqStmt.run(...params);
|
|
510
|
-
this.trim();
|
|
511
|
-
} catch (e) {
|
|
512
|
-
return e instanceof Error ? e : new Error(String(e));
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
getMaxSeq() {
|
|
516
|
-
const row = this.maxSeqStmt.get();
|
|
517
|
-
return row ? row.max : 0;
|
|
518
|
-
}
|
|
519
|
-
getRecords(props = {}) {
|
|
520
|
-
const conditions = ["seq > ?"];
|
|
521
|
-
const params = [props.sinceSeq ?? 0];
|
|
522
|
-
if (typeof props.type === "string") {
|
|
523
|
-
conditions.push("type = ?");
|
|
524
|
-
params.push(props.type);
|
|
525
|
-
}
|
|
526
|
-
if (props.where) this.appendWhereConditions(props.where, conditions, params);
|
|
527
|
-
const limit = props.limit ?? 1e3;
|
|
528
|
-
params.push(limit);
|
|
529
|
-
const dir = props.order === "desc" ? "DESC" : "ASC";
|
|
530
|
-
const sql = `SELECT seq, ts, type, event FROM leuco_log WHERE ${conditions.join(" AND ")} ORDER BY seq ${dir} LIMIT ?`;
|
|
531
|
-
const rows = this.db.prepare(sql).all(...params);
|
|
532
|
-
if (dir === "DESC") rows.reverse();
|
|
533
|
-
return rows.map(toRecord);
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* Current schema version. Useful for diagnostics and for tests that want
|
|
537
|
-
* to verify migrations ran. Reads `PRAGMA user_version` once per call.
|
|
538
|
-
*/
|
|
539
|
-
getSchemaVersion() {
|
|
540
|
-
return this.db.prepare("PRAGMA user_version").get()?.user_version ?? 0;
|
|
541
|
-
}
|
|
542
|
-
close() {
|
|
543
|
-
this.db.close();
|
|
544
|
-
}
|
|
545
|
-
buildInsertParams(ts, event) {
|
|
546
|
-
const type = extractType(event);
|
|
547
|
-
const json = JSON.stringify(event);
|
|
548
|
-
if (this.indexes.length === 0) return [
|
|
549
|
-
ts,
|
|
550
|
-
type,
|
|
551
|
-
json
|
|
552
|
-
];
|
|
553
|
-
const values = this.extractIndexes ? this.extractIndexes(event) : null;
|
|
554
|
-
return [
|
|
555
|
-
ts,
|
|
556
|
-
type,
|
|
557
|
-
json,
|
|
558
|
-
...this.indexes.map((col) => values?.[col] ?? null)
|
|
559
|
-
];
|
|
560
|
-
}
|
|
561
|
-
appendWhereConditions(where, conditions, params) {
|
|
562
|
-
const widened = where;
|
|
563
|
-
for (const col of this.indexes) {
|
|
564
|
-
const value = widened[col];
|
|
565
|
-
if (value === void 0) continue;
|
|
566
|
-
if (value === null) conditions.push(`${col} IS NULL`);
|
|
567
|
-
else {
|
|
568
|
-
conditions.push(`${col} = ?`);
|
|
569
|
-
params.push(value);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
trim() {
|
|
574
|
-
if (this.maxRows !== null) {
|
|
575
|
-
const row = this.countStmt.get();
|
|
576
|
-
if (row && row.n > this.maxRows) this.trimRowsStmt.run(this.maxRows);
|
|
577
|
-
}
|
|
578
|
-
if (this.maxAgeMs !== null) this.trimAgeStmt.run(this.now() - this.maxAgeMs);
|
|
579
|
-
this.maybeTrimBytes();
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Throttled byte-size enforcement. Only every BYTE_CHECK_INTERVAL inserts do
|
|
583
|
-
* we measure the file; on overflow we estimate how many of the oldest rows to
|
|
584
|
-
* drop to land near targetBytes (by the byte/row ratio), delete them in one
|
|
585
|
-
* statement, then VACUUM once to return the freed pages to the filesystem (a
|
|
586
|
-
* plain DELETE only frees pages inside the file). One DELETE + one VACUUM per
|
|
587
|
-
* overflow keeps the expensive rewrite rare — the file must refill the whole
|
|
588
|
-
* maxBytes→targetBytes delta before the next overflow can trigger.
|
|
589
|
-
*/
|
|
590
|
-
maybeTrimBytes() {
|
|
591
|
-
if (this.maxBytes === null || this.targetBytes === null) return;
|
|
592
|
-
this.insertsSinceByteCheck += 1;
|
|
593
|
-
if (this.insertsSinceByteCheck < BYTE_CHECK_INTERVAL) return;
|
|
594
|
-
this.insertsSinceByteCheck = 0;
|
|
595
|
-
const bytes = this.byteSize();
|
|
596
|
-
if (bytes <= this.maxBytes) return;
|
|
597
|
-
const rows = this.countStmt.get()?.n ?? 0;
|
|
598
|
-
if (rows === 0) return;
|
|
599
|
-
const bytesToFree = bytes - this.targetBytes;
|
|
600
|
-
const bytesPerRow = bytes / rows;
|
|
601
|
-
const rowsToDrop = Math.min(rows, Math.ceil(bytesToFree / bytesPerRow));
|
|
602
|
-
this.trimOldestStmt.run(rowsToDrop);
|
|
603
|
-
this.db.run("VACUUM");
|
|
604
|
-
}
|
|
605
|
-
byteSize() {
|
|
606
|
-
return (this.db.prepare("PRAGMA page_count").get()?.n ?? 0) * (this.db.prepare("PRAGMA page_size").get()?.n ?? 0);
|
|
607
|
-
}
|
|
608
|
-
/** Drop every row and reclaim the file space. Used by `<log>.clear()`. */
|
|
609
|
-
clear() {
|
|
610
|
-
this.db.run("DELETE FROM leuco_log");
|
|
611
|
-
this.db.run("VACUUM");
|
|
612
|
-
this.insertsSinceByteCheck = 0;
|
|
613
|
-
}
|
|
614
|
-
syncIndexColumns() {
|
|
615
|
-
const existing = new Set(this.db.prepare("PRAGMA table_info(leuco_log)").all().map((r) => r.name));
|
|
616
|
-
for (const col of this.indexes) {
|
|
617
|
-
if (!existing.has(col)) this.db.run(`ALTER TABLE leuco_log ADD COLUMN ${col} TEXT`);
|
|
618
|
-
this.db.run(`CREATE INDEX IF NOT EXISTS idx_leuco_log_${col} ON leuco_log (${col})`);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
migrate() {
|
|
622
|
-
const current = this.db.prepare("PRAGMA user_version").get()?.user_version ?? 0;
|
|
623
|
-
if (current >= MIGRATIONS.length) return;
|
|
624
|
-
const pending = MIGRATIONS.slice(current);
|
|
625
|
-
let version = current;
|
|
626
|
-
for (const stmts of pending) {
|
|
627
|
-
version += 1;
|
|
628
|
-
this.db.transaction(() => {
|
|
629
|
-
for (const stmt of stmts) this.db.run(stmt);
|
|
630
|
-
this.db.run(`PRAGMA user_version = ${version}`);
|
|
631
|
-
})();
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
};
|
|
635
|
-
function validateIndexNames(names) {
|
|
636
|
-
for (const name of names) {
|
|
637
|
-
if (!COLUMN_NAME_RE.test(name)) throw new Error(`invalid index column name: ${name}`);
|
|
638
|
-
if (RESERVED_COLUMNS.has(name)) throw new Error(`reserved index column name: ${name}`);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
function extractType(event) {
|
|
642
|
-
if (typeof event !== "object" || event === null) return null;
|
|
643
|
-
if (!("type" in event)) return null;
|
|
644
|
-
const t = event.type;
|
|
645
|
-
return typeof t === "string" ? t : null;
|
|
646
|
-
}
|
|
647
|
-
function toRecord(row) {
|
|
648
|
-
return {
|
|
649
|
-
seq: row.seq,
|
|
650
|
-
ts: row.ts,
|
|
651
|
-
event: JSON.parse(row.event)
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
//#endregion
|
|
655
363
|
//#region lib/gateway/event-log/sqlite-event-log.ts
|
|
656
364
|
const MAX_CONTENT_CHARS = 2e3;
|
|
657
365
|
/**
|
|
@@ -679,7 +387,7 @@ var SqliteFunnelEventLog = class extends FunnelEventLog {
|
|
|
679
387
|
super();
|
|
680
388
|
this.now = props.now ?? (() => Date.now());
|
|
681
389
|
this.logger = props.logger;
|
|
682
|
-
this.sink = new
|
|
390
|
+
this.sink = new FunnelLogSqliteSink({
|
|
683
391
|
path: props.path,
|
|
684
392
|
indexes: ["channel_id", "connector_id"],
|
|
685
393
|
extractIndexes: (event) => ({
|
|
@@ -722,7 +430,7 @@ var SqliteFunnelEventLog = class extends FunnelEventLog {
|
|
|
722
430
|
* so this returns the full slice and lets the caller filter.
|
|
723
431
|
*/
|
|
724
432
|
loadSince(since) {
|
|
725
|
-
const records = this.sink.
|
|
433
|
+
const records = this.sink.query({ sinceSeq: since });
|
|
726
434
|
const out = [];
|
|
727
435
|
for (const record of records) out.push({
|
|
728
436
|
content: record.event.content,
|
|
@@ -739,7 +447,7 @@ var SqliteFunnelEventLog = class extends FunnelEventLog {
|
|
|
739
447
|
loadForChannel(props) {
|
|
740
448
|
const where = { channel_id: props.channelId };
|
|
741
449
|
if (props.connectorId !== void 0) where.connector_id = props.connectorId;
|
|
742
|
-
const records = this.sink.
|
|
450
|
+
const records = this.sink.query({
|
|
743
451
|
where,
|
|
744
452
|
...props.sinceSeq !== void 0 ? { sinceSeq: props.sinceSeq } : {},
|
|
745
453
|
...props.limit !== void 0 ? { limit: props.limit } : {}
|
|
@@ -771,7 +479,8 @@ function truncate(content) {
|
|
|
771
479
|
const defaultOnError$1 = () => {};
|
|
772
480
|
const DEFAULT_HEALTH_INTERVAL_MS = 3e4;
|
|
773
481
|
const DEFAULT_MAX_BACKOFF_MS = 6e4;
|
|
774
|
-
const
|
|
482
|
+
const DEFAULT_START_TIMEOUT_MS = 3e4;
|
|
483
|
+
const defaultSleep$1 = (ms) => new Promise((r) => {
|
|
775
484
|
setTimeout(r, ms);
|
|
776
485
|
});
|
|
777
486
|
/**
|
|
@@ -796,10 +505,13 @@ var FunnelListenerSupervisor = class FunnelListenerSupervisor {
|
|
|
796
505
|
stats = /* @__PURE__ */ new Map();
|
|
797
506
|
healthCheckIntervalMs;
|
|
798
507
|
maxBackoffMs;
|
|
508
|
+
startTimeoutMs;
|
|
799
509
|
sleep;
|
|
800
510
|
now;
|
|
801
511
|
healthCheckTimer = null;
|
|
802
512
|
healthCheckInFlight = false;
|
|
513
|
+
/** Connectors that failed initial start — retried by the health check. */
|
|
514
|
+
pendingRetry = /* @__PURE__ */ new Map();
|
|
803
515
|
constructor(deps) {
|
|
804
516
|
this.channels = deps.channels;
|
|
805
517
|
this.notify = deps.notify;
|
|
@@ -807,7 +519,8 @@ var FunnelListenerSupervisor = class FunnelListenerSupervisor {
|
|
|
807
519
|
this.onError = deps.onError ?? defaultOnError$1;
|
|
808
520
|
this.healthCheckIntervalMs = deps.healthCheckIntervalMs ?? DEFAULT_HEALTH_INTERVAL_MS;
|
|
809
521
|
this.maxBackoffMs = deps.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
|
|
810
|
-
this.
|
|
522
|
+
this.startTimeoutMs = deps.startTimeoutMs ?? DEFAULT_START_TIMEOUT_MS;
|
|
523
|
+
this.sleep = deps.sleep ?? defaultSleep$1;
|
|
811
524
|
this.now = deps.now ?? (() => Date.now());
|
|
812
525
|
}
|
|
813
526
|
static keyOf(channelName, connectorName) {
|
|
@@ -853,13 +566,16 @@ var FunnelListenerSupervisor = class FunnelListenerSupervisor {
|
|
|
853
566
|
}
|
|
854
567
|
};
|
|
855
568
|
try {
|
|
856
|
-
await created.listener.start(bind)
|
|
569
|
+
await Promise.race([created.listener.start(bind), this.sleep(this.startTimeoutMs).then(() => {
|
|
570
|
+
throw new Error(`listener start timed out after ${this.startTimeoutMs}ms`);
|
|
571
|
+
})]);
|
|
857
572
|
this.running.set(key, {
|
|
858
573
|
config: created.config,
|
|
859
574
|
channelName,
|
|
860
575
|
channelId: created.channelId,
|
|
861
576
|
listener: created.listener
|
|
862
577
|
});
|
|
578
|
+
this.pendingRetry.delete(key);
|
|
863
579
|
this.ensureStats(key);
|
|
864
580
|
this.logger?.info(`${created.config.type} listener started`, {
|
|
865
581
|
channel: channelName,
|
|
@@ -894,8 +610,6 @@ var FunnelListenerSupervisor = class FunnelListenerSupervisor {
|
|
|
894
610
|
};
|
|
895
611
|
try {
|
|
896
612
|
await entry.listener.stop();
|
|
897
|
-
this.running.delete(key);
|
|
898
|
-
this.failureCounts.delete(key);
|
|
899
613
|
this.logger?.info(`${entry.config.type} listener stopped`, {
|
|
900
614
|
channel: channelName,
|
|
901
615
|
connector: connectorName
|
|
@@ -918,6 +632,9 @@ var FunnelListenerSupervisor = class FunnelListenerSupervisor {
|
|
|
918
632
|
ok: false,
|
|
919
633
|
reason: err.message
|
|
920
634
|
};
|
|
635
|
+
} finally {
|
|
636
|
+
this.running.delete(key);
|
|
637
|
+
this.failureCounts.delete(key);
|
|
921
638
|
}
|
|
922
639
|
}
|
|
923
640
|
async restart(channelName, connectorName) {
|
|
@@ -927,11 +644,23 @@ var FunnelListenerSupervisor = class FunnelListenerSupervisor {
|
|
|
927
644
|
}
|
|
928
645
|
async startAll() {
|
|
929
646
|
const all = this.channels.listAllConnectors();
|
|
930
|
-
|
|
647
|
+
const results = await Promise.allSettled(all.map((view) => this.start(view.channelName, view.name)));
|
|
648
|
+
for (let i = 0; i < results.length; i++) {
|
|
649
|
+
const result = results[i];
|
|
650
|
+
const view = all[i];
|
|
651
|
+
if (result.status === "rejected" || result.status === "fulfilled" && !result.value.ok) {
|
|
652
|
+
const key = FunnelListenerSupervisor.keyOf(view.channelName, view.name);
|
|
653
|
+
this.pendingRetry.set(key, {
|
|
654
|
+
channelName: view.channelName,
|
|
655
|
+
connectorName: view.name
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
931
659
|
this.startHealthCheck();
|
|
932
660
|
}
|
|
933
661
|
async stopAll() {
|
|
934
662
|
this.stopHealthCheck();
|
|
663
|
+
this.pendingRetry.clear();
|
|
935
664
|
for (const [, entry] of [...this.running.entries()]) await this.stop(entry.channelName, entry.config.name);
|
|
936
665
|
}
|
|
937
666
|
ensureStats(key) {
|
|
@@ -966,6 +695,10 @@ var FunnelListenerSupervisor = class FunnelListenerSupervisor {
|
|
|
966
695
|
clearInterval(this.healthCheckTimer);
|
|
967
696
|
this.healthCheckTimer = null;
|
|
968
697
|
}
|
|
698
|
+
/** Run one health-check pass synchronously. Test-only seam. */
|
|
699
|
+
async runHealthCheckForTest() {
|
|
700
|
+
await this.runHealthCheck();
|
|
701
|
+
}
|
|
969
702
|
async runHealthCheck() {
|
|
970
703
|
if (this.healthCheckInFlight) return;
|
|
971
704
|
this.healthCheckInFlight = true;
|
|
@@ -977,6 +710,23 @@ var FunnelListenerSupervisor = class FunnelListenerSupervisor {
|
|
|
977
710
|
}
|
|
978
711
|
await this.recoverDead(entry.channelName, entry.config.name, entry.config.type);
|
|
979
712
|
}
|
|
713
|
+
for (const [key, pending] of [...this.pendingRetry.entries()]) {
|
|
714
|
+
if (this.running.has(key)) {
|
|
715
|
+
this.pendingRetry.delete(key);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
this.logger?.info("retrying failed listener", {
|
|
719
|
+
channel: pending.channelName,
|
|
720
|
+
connector: pending.connectorName
|
|
721
|
+
});
|
|
722
|
+
const failureCount = this.failureCounts.get(key) ?? 0;
|
|
723
|
+
const backoffMs = Math.min(1e3 * 2 ** failureCount, this.maxBackoffMs);
|
|
724
|
+
await this.sleep(backoffMs);
|
|
725
|
+
if ((await this.start(pending.channelName, pending.connectorName)).ok) {
|
|
726
|
+
this.pendingRetry.delete(key);
|
|
727
|
+
this.failureCounts.delete(key);
|
|
728
|
+
} else this.failureCounts.set(key, failureCount + 1);
|
|
729
|
+
}
|
|
980
730
|
} finally {
|
|
981
731
|
this.healthCheckInFlight = false;
|
|
982
732
|
}
|
|
@@ -1005,7 +755,27 @@ var FunnelListenerSupervisor = class FunnelListenerSupervisor {
|
|
|
1005
755
|
//#endregion
|
|
1006
756
|
//#region lib/gateway/kill-competing-slack-gateways.ts
|
|
1007
757
|
const defaultProcess = new NodeFunnelProcessRunner();
|
|
758
|
+
const SIGTERM_GRACE_MS = 3e3;
|
|
759
|
+
const POLL_INTERVAL_MS = 100;
|
|
760
|
+
const SIGKILL_GRACE_MS = 200;
|
|
761
|
+
const defaultSleep = (ms) => new Promise((resolve) => {
|
|
762
|
+
setTimeout(resolve, ms);
|
|
763
|
+
});
|
|
1008
764
|
const titleFor = (dir) => `funnel-gateway[${dir}]`;
|
|
765
|
+
const waitForExit = async (props) => {
|
|
766
|
+
const deadline = props.now() + SIGTERM_GRACE_MS;
|
|
767
|
+
while (props.now() < deadline) {
|
|
768
|
+
if (props.pids.every((pid) => !props.runner.isAlive(pid))) return;
|
|
769
|
+
await props.sleep(POLL_INTERVAL_MS);
|
|
770
|
+
}
|
|
771
|
+
for (const pid of props.pids) {
|
|
772
|
+
if (!props.runner.isAlive(pid)) continue;
|
|
773
|
+
try {
|
|
774
|
+
props.runner.kill(pid, "SIGKILL");
|
|
775
|
+
} catch {}
|
|
776
|
+
}
|
|
777
|
+
await props.sleep(SIGKILL_GRACE_MS);
|
|
778
|
+
};
|
|
1009
779
|
/**
|
|
1010
780
|
* Kills other funnel daemon processes that share the SAME funnel home dir,
|
|
1011
781
|
* which is the only situation that causes a real conflict (duplicate Slack
|
|
@@ -1015,6 +785,10 @@ const titleFor = (dir) => `funnel-gateway[${dir}]`;
|
|
|
1015
785
|
* `funnel-gateway[<dir>]` marker appended to argv (also assigned to
|
|
1016
786
|
* `process.title` on POSIX). `FunnelProcessRunner.listProcessesContaining`
|
|
1017
787
|
* absorbs the POSIX/Windows enumeration difference behind the marker match.
|
|
788
|
+
*
|
|
789
|
+
* Waits for the killed daemons to actually exit before returning, so the caller
|
|
790
|
+
* can bind the port and open a fresh Socket Mode connection without overlapping
|
|
791
|
+
* the old one (the overlap is what makes Slack split inbound events).
|
|
1018
792
|
*/
|
|
1019
793
|
const killCompetingSlackGateways = async (props) => {
|
|
1020
794
|
const runner = props.process ?? defaultProcess;
|
|
@@ -1031,6 +805,13 @@ const killCompetingSlackGateways = async (props) => {
|
|
|
1031
805
|
args: snapshot.command.slice(0, 160)
|
|
1032
806
|
});
|
|
1033
807
|
}
|
|
808
|
+
if (killed.length === 0) return killed;
|
|
809
|
+
await waitForExit({
|
|
810
|
+
runner,
|
|
811
|
+
pids: killed,
|
|
812
|
+
sleep: props.sleep ?? defaultSleep,
|
|
813
|
+
now: props.now ?? (() => Date.now())
|
|
814
|
+
});
|
|
1034
815
|
return killed;
|
|
1035
816
|
};
|
|
1036
817
|
//#endregion
|
|
@@ -1290,6 +1071,7 @@ const healthHandler = factory.createHandlers((c) => {
|
|
|
1290
1071
|
return c.json({
|
|
1291
1072
|
ok: true,
|
|
1292
1073
|
pid: deps.selfPid,
|
|
1074
|
+
funnelDir: deps.dir,
|
|
1293
1075
|
clients: deps.broadcaster.getClientCount(),
|
|
1294
1076
|
listeners: deps.supervisor.list()
|
|
1295
1077
|
});
|
|
@@ -1341,6 +1123,7 @@ const statusHandler = factory.createHandlers((c) => {
|
|
|
1341
1123
|
return c.json({
|
|
1342
1124
|
ok: true,
|
|
1343
1125
|
pid: deps.selfPid,
|
|
1126
|
+
funnelDir: deps.dir,
|
|
1344
1127
|
uptimeMs: deps.uptimeMs(),
|
|
1345
1128
|
clients: deps.broadcaster.listChannels(),
|
|
1346
1129
|
listeners: deps.supervisor.list(),
|
|
@@ -1450,6 +1233,7 @@ var FunnelGatewayServer = class {
|
|
|
1450
1233
|
if (this.server) return this.server;
|
|
1451
1234
|
if (!this.token && !LOOPBACK_HOSTS.has(this.hostname) && !this.allowInsecureHost) throw new Error(`refusing to start gateway: hostname "${this.hostname}" is reachable off-box but no token is set. Set a token, bind to loopback (127.0.0.1), or pass allowInsecureHost: true.`);
|
|
1452
1235
|
const app = this.buildApp();
|
|
1236
|
+
await this.killCompetingSlackIfNeeded();
|
|
1453
1237
|
this.startedAt = this.nowMs();
|
|
1454
1238
|
this.server = Bun.serve({
|
|
1455
1239
|
port: this.port,
|
|
@@ -1504,6 +1288,7 @@ var FunnelGatewayServer = class {
|
|
|
1504
1288
|
if (this.token && !this.tokenMatchesUpgrade(request)) return new Response("unauthorized", { status: 401 });
|
|
1505
1289
|
const requestedChannel = url.searchParams.get("channel") ?? "";
|
|
1506
1290
|
const channel = requestedChannel ? this.resolveChannel(requestedChannel) : null;
|
|
1291
|
+
if (requestedChannel && !channel) return new Response(`unknown channel "${requestedChannel}"`, { status: 404 });
|
|
1507
1292
|
const channelId = channel?.id ?? requestedChannel;
|
|
1508
1293
|
const channelName = channel?.name ?? null;
|
|
1509
1294
|
const connectors = channel?.connectors ?? [];
|
|
@@ -1567,6 +1352,7 @@ var FunnelGatewayServer = class {
|
|
|
1567
1352
|
base.use((c, next) => {
|
|
1568
1353
|
c.set("deps", {
|
|
1569
1354
|
selfPid: this.selfPid,
|
|
1355
|
+
dir: this.dir,
|
|
1570
1356
|
broadcaster: this.broadcaster,
|
|
1571
1357
|
supervisor: this.supervisor,
|
|
1572
1358
|
channels: this.channels,
|
|
@@ -1606,21 +1392,22 @@ var FunnelGatewayServer = class {
|
|
|
1606
1392
|
delivery: channel.delivery
|
|
1607
1393
|
};
|
|
1608
1394
|
}
|
|
1395
|
+
async killCompetingSlackIfNeeded() {
|
|
1396
|
+
if (!this.killCompetingSlack) return;
|
|
1397
|
+
if (!this.channels.listAllConnectors().some((c) => c.type === "slack")) return;
|
|
1398
|
+
const killed = await killCompetingSlackGateways({
|
|
1399
|
+
selfPid: this.selfPid,
|
|
1400
|
+
dir: this.dir,
|
|
1401
|
+
process: this.process,
|
|
1402
|
+
logger: this.logger
|
|
1403
|
+
});
|
|
1404
|
+
if (killed.length > 0) this.logger?.info("killed competing Slack gateway processes", {
|
|
1405
|
+
event_type: "system",
|
|
1406
|
+
action: "kill_competing",
|
|
1407
|
+
pids: killed.join(",")
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1609
1410
|
async bootListeners() {
|
|
1610
|
-
const allConnectors = this.channels.listAllConnectors();
|
|
1611
|
-
if (this.killCompetingSlack && allConnectors.some((c) => c.type === "slack")) {
|
|
1612
|
-
const killed = await killCompetingSlackGateways({
|
|
1613
|
-
selfPid: this.selfPid,
|
|
1614
|
-
dir: this.dir,
|
|
1615
|
-
process: this.process,
|
|
1616
|
-
logger: this.logger
|
|
1617
|
-
});
|
|
1618
|
-
if (killed.length > 0) this.logger?.info("killed competing Slack gateway processes", {
|
|
1619
|
-
event_type: "system",
|
|
1620
|
-
action: "kill_competing",
|
|
1621
|
-
pids: killed.join(",")
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
1411
|
await this.supervisor.startAll();
|
|
1625
1412
|
for (const entry of this.supervisor.list()) this.logger?.info(`${entry.type} listener started: ${entry.name}`, {
|
|
1626
1413
|
event_type: "system",
|
|
@@ -1885,7 +1672,7 @@ var ConnectorDiagnosticLog = class {};
|
|
|
1885
1672
|
*/
|
|
1886
1673
|
const RAW_PAYLOAD_CAP = 256 * 1024;
|
|
1887
1674
|
/**
|
|
1888
|
-
* Default `ConnectorDiagnosticLog`: three independent `
|
|
1675
|
+
* Default `ConnectorDiagnosticLog`: three independent `FunnelLogSqliteSink`s, one
|
|
1889
1676
|
* per table (raw / processed / connection), in separate files. Each sink
|
|
1890
1677
|
* indexes the columns its queries filter on — `event_id` / `connector_id` /
|
|
1891
1678
|
* `channel_id` for raw, plus `outcome` for processed and `status` for
|
|
@@ -1920,7 +1707,7 @@ var SqliteConnectorDiagnosticLog = class extends ConnectorDiagnosticLog {
|
|
|
1920
1707
|
...ageCap,
|
|
1921
1708
|
...rawMax !== void 0 ? { maxRows: rawMax } : {}
|
|
1922
1709
|
};
|
|
1923
|
-
this.raw = new
|
|
1710
|
+
this.raw = new FunnelLogSqliteSink({
|
|
1924
1711
|
path: props.rawPath,
|
|
1925
1712
|
indexes: [
|
|
1926
1713
|
"event_id",
|
|
@@ -1934,7 +1721,7 @@ var SqliteConnectorDiagnosticLog = class extends ConnectorDiagnosticLog {
|
|
|
1934
1721
|
}),
|
|
1935
1722
|
...rawCap
|
|
1936
1723
|
});
|
|
1937
|
-
this.processed = new
|
|
1724
|
+
this.processed = new FunnelLogSqliteSink({
|
|
1938
1725
|
path: props.processedPath,
|
|
1939
1726
|
indexes: [
|
|
1940
1727
|
"event_id",
|
|
@@ -1950,7 +1737,7 @@ var SqliteConnectorDiagnosticLog = class extends ConnectorDiagnosticLog {
|
|
|
1950
1737
|
}),
|
|
1951
1738
|
...verdictCap
|
|
1952
1739
|
});
|
|
1953
|
-
this.connection = new
|
|
1740
|
+
this.connection = new FunnelLogSqliteSink({
|
|
1954
1741
|
path: props.connectionPath,
|
|
1955
1742
|
indexes: [
|
|
1956
1743
|
"connector_id",
|
|
@@ -2016,7 +1803,7 @@ var SqliteConnectorDiagnosticLog = class extends ConnectorDiagnosticLog {
|
|
|
2016
1803
|
});
|
|
2017
1804
|
}
|
|
2018
1805
|
queryRaw(query) {
|
|
2019
|
-
return this.raw.
|
|
1806
|
+
return this.raw.query({
|
|
2020
1807
|
...query.type !== void 0 ? { type: query.type } : {},
|
|
2021
1808
|
...query.limit !== void 0 ? { limit: query.limit } : {},
|
|
2022
1809
|
where: buildWhere(query),
|
|
@@ -2034,7 +1821,7 @@ var SqliteConnectorDiagnosticLog = class extends ConnectorDiagnosticLog {
|
|
|
2034
1821
|
queryProcessed(query) {
|
|
2035
1822
|
const where = buildWhere(query);
|
|
2036
1823
|
if (query.outcome !== void 0) where.outcome = query.outcome;
|
|
2037
|
-
return this.processed.
|
|
1824
|
+
return this.processed.query({
|
|
2038
1825
|
...query.type !== void 0 ? { type: query.type } : {},
|
|
2039
1826
|
...query.limit !== void 0 ? { limit: query.limit } : {},
|
|
2040
1827
|
where,
|
|
@@ -2053,7 +1840,7 @@ var SqliteConnectorDiagnosticLog = class extends ConnectorDiagnosticLog {
|
|
|
2053
1840
|
queryConnection(query) {
|
|
2054
1841
|
const where = buildWhere(query);
|
|
2055
1842
|
if (query.status !== void 0) where.status = query.status;
|
|
2056
|
-
return this.connection.
|
|
1843
|
+
return this.connection.query({
|
|
2057
1844
|
...query.type !== void 0 ? { type: query.type } : {},
|
|
2058
1845
|
...query.limit !== void 0 ? { limit: query.limit } : {},
|
|
2059
1846
|
where,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as FunnelFileSystem } from "./file-system-
|
|
2
|
-
import { i as FunnelTokenPrompter } from "./local-config-sync
|
|
1
|
+
import { n as FunnelFileSystem } from "./file-system-DxpnnUVb.js";
|
|
2
|
+
import { i as FunnelTokenPrompter } from "./local-config-sync--f739oCJ.js";
|
|
3
3
|
|
|
4
4
|
//#region lib/services/local-config/local-config-json-schema.d.ts
|
|
5
5
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as ProfileConfig } from "./settings-schema-
|
|
2
|
-
import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-
|
|
3
|
-
import { n as FunnelFileSystem } from "./file-system-
|
|
1
|
+
import { r as ProfileConfig } from "./settings-schema-1hh11jnN.js";
|
|
2
|
+
import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-DPwqOVUm.js";
|
|
3
|
+
import { n as FunnelFileSystem } from "./file-system-DxpnnUVb.js";
|
|
4
4
|
|
|
5
5
|
//#region lib/engine/profiles/profiles.d.ts
|
|
6
6
|
type Deps = {
|
package/dist/profiles.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as FunnelProfiles } from "./profiles-
|
|
1
|
+
import { t as FunnelProfiles } from "./profiles-g2qGVOWv.js";
|
|
2
2
|
export { FunnelProfiles };
|
package/dist/recovery.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as RecoveryListenerControl, i as RecoveryGatewayControl, n as RecoveryAction, o as RecoveryResult, r as RecoveryChannelSource, t as FunnelRecovery } from "./funnel-recovery-
|
|
1
|
+
import { a as RecoveryListenerControl, i as RecoveryGatewayControl, n as RecoveryAction, o as RecoveryResult, r as RecoveryChannelSource, t as FunnelRecovery } from "./funnel-recovery-COExL9MD.js";
|
|
2
2
|
export { FunnelRecovery, RecoveryAction, RecoveryChannelSource, RecoveryGatewayControl, RecoveryListenerControl, RecoveryResult };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "./diagnostic-log-
|
|
2
|
-
import { n as FunnelFileSystem } from "./file-system-
|
|
1
|
+
import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "./diagnostic-log-Cb3v8P7p.js";
|
|
2
|
+
import { n as FunnelFileSystem } from "./file-system-DxpnnUVb.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
//#region lib/engine/connectors/schedule-state-store.d.ts
|