@diegoaltoworks/chatter 0.34.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +36 -0
  4. package/dist/index.mjs +35 -0
  5. package/dist/scheduler/claimStore.d.ts +27 -0
  6. package/dist/scheduler/claimStore.d.ts.map +1 -0
  7. package/dist/scheduler/claimStore.test.d.ts +2 -0
  8. package/dist/scheduler/claimStore.test.d.ts.map +1 -0
  9. package/dist/scheduler/compose.d.ts +23 -0
  10. package/dist/scheduler/compose.d.ts.map +1 -0
  11. package/dist/scheduler/compose.test.d.ts +2 -0
  12. package/dist/scheduler/compose.test.d.ts.map +1 -0
  13. package/dist/scheduler/deliver.d.ts +10 -0
  14. package/dist/scheduler/deliver.d.ts.map +1 -0
  15. package/dist/scheduler/deliver.test.d.ts +2 -0
  16. package/dist/scheduler/deliver.test.d.ts.map +1 -0
  17. package/dist/scheduler/dueDrops.d.ts +13 -0
  18. package/dist/scheduler/dueDrops.d.ts.map +1 -0
  19. package/dist/scheduler/dueDrops.test.d.ts +2 -0
  20. package/dist/scheduler/dueDrops.test.d.ts.map +1 -0
  21. package/dist/scheduler/index.d.ts +31 -0
  22. package/dist/scheduler/index.d.ts.map +1 -0
  23. package/dist/scheduler/index.js +332 -0
  24. package/dist/scheduler/index.mjs +290 -0
  25. package/dist/scheduler/scheduler.d.ts +44 -0
  26. package/dist/scheduler/scheduler.d.ts.map +1 -0
  27. package/dist/scheduler/scheduler.test.d.ts +2 -0
  28. package/dist/scheduler/scheduler.test.d.ts.map +1 -0
  29. package/dist/scheduler/tick.d.ts +19 -0
  30. package/dist/scheduler/tick.d.ts.map +1 -0
  31. package/dist/scheduler/tick.test.d.ts +2 -0
  32. package/dist/scheduler/tick.test.d.ts.map +1 -0
  33. package/dist/scheduler/types.d.ts +36 -0
  34. package/dist/scheduler/types.d.ts.map +1 -0
  35. package/dist/shortener.d.ts +24 -0
  36. package/dist/shortener.d.ts.map +1 -0
  37. package/dist/shortener.test.d.ts +2 -0
  38. package/dist/shortener.test.d.ts.map +1 -0
  39. package/package.json +8 -2
package/dist/index.d.ts CHANGED
@@ -42,5 +42,7 @@ export { privateRoutes } from "./routes/private";
42
42
  export { publicRoutes } from "./routes/public";
43
43
  export type { ChatterApp } from "./server";
44
44
  export { createServer } from "./server";
45
+ export type { Shortener, ShortenerConfig } from "./shortener";
46
+ export { createShortener } from "./shortener";
45
47
  export type { BotBranding, BotChatConfig, BotIdentity, ChatterConfig, CustomRoutes, EmbeddingChunk, KnowledgeDocument, ServerDependencies, } from "./types";
46
48
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAIvC,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,eAAe,GAChB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EACV,QAAQ,EACR,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,GACZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,YAAY,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAIvC,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,eAAe,GAChB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EACV,QAAQ,EACR,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,GACZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,YAAY,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -7550,6 +7550,7 @@ __export(index_exports, {
7550
7550
  createServer: () => createServer,
7551
7551
  createSession: () => createSession,
7552
7552
  createSessionMiddleware: () => createSessionMiddleware,
7553
+ createShortener: () => createShortener,
7553
7554
  defaultBuckets: () => defaultBuckets,
7554
7555
  demoRoutes: () => demoRoutes,
7555
7556
  detectLeakage: () => detectLeakage,
@@ -33396,6 +33397,40 @@ async function createServer(config2) {
33396
33397
  }
33397
33398
  });
33398
33399
  }
33400
+
33401
+ // src/shortener.ts
33402
+ var DEFAULT_TIMEOUT_MS = 5e3;
33403
+ function createShortener(config2, fetchImpl = fetch) {
33404
+ const apiUrl = config2.apiUrl.replace(/\/+$/, "");
33405
+ return {
33406
+ async shorten(url2) {
33407
+ const controller = new AbortController();
33408
+ const timeout = setTimeout(() => controller.abort(), config2.timeoutMs ?? DEFAULT_TIMEOUT_MS);
33409
+ try {
33410
+ const response = await fetchImpl(`${apiUrl}/api/v2/links`, {
33411
+ method: "POST",
33412
+ headers: {
33413
+ "Content-Type": "application/json",
33414
+ "X-API-Key": config2.apiKey
33415
+ },
33416
+ body: JSON.stringify({ target: url2, domain: config2.domain }),
33417
+ signal: controller.signal
33418
+ });
33419
+ if (!response.ok) {
33420
+ await response.text().catch(() => "");
33421
+ return url2;
33422
+ }
33423
+ const data = await response.json();
33424
+ if (typeof data.link !== "string") return url2;
33425
+ return data.link.replace(/^http:\/\//, "https://");
33426
+ } catch {
33427
+ return url2;
33428
+ } finally {
33429
+ clearTimeout(timeout);
33430
+ }
33431
+ }
33432
+ };
33433
+ }
33399
33434
  // Annotate the CommonJS export names for ESM import in node:
33400
33435
  0 && (module.exports = {
33401
33436
  ApiKeyManager,
@@ -33418,6 +33453,7 @@ async function createServer(config2) {
33418
33453
  createServer,
33419
33454
  createSession,
33420
33455
  createSessionMiddleware,
33456
+ createShortener,
33421
33457
  defaultBuckets,
33422
33458
  demoRoutes,
33423
33459
  detectLeakage,
package/dist/index.mjs CHANGED
@@ -33358,6 +33358,40 @@ async function createServer(config2) {
33358
33358
  }
33359
33359
  });
33360
33360
  }
33361
+
33362
+ // src/shortener.ts
33363
+ var DEFAULT_TIMEOUT_MS = 5e3;
33364
+ function createShortener(config2, fetchImpl = fetch) {
33365
+ const apiUrl = config2.apiUrl.replace(/\/+$/, "");
33366
+ return {
33367
+ async shorten(url2) {
33368
+ const controller = new AbortController();
33369
+ const timeout = setTimeout(() => controller.abort(), config2.timeoutMs ?? DEFAULT_TIMEOUT_MS);
33370
+ try {
33371
+ const response = await fetchImpl(`${apiUrl}/api/v2/links`, {
33372
+ method: "POST",
33373
+ headers: {
33374
+ "Content-Type": "application/json",
33375
+ "X-API-Key": config2.apiKey
33376
+ },
33377
+ body: JSON.stringify({ target: url2, domain: config2.domain }),
33378
+ signal: controller.signal
33379
+ });
33380
+ if (!response.ok) {
33381
+ await response.text().catch(() => "");
33382
+ return url2;
33383
+ }
33384
+ const data = await response.json();
33385
+ if (typeof data.link !== "string") return url2;
33386
+ return data.link.replace(/^http:\/\//, "https://");
33387
+ } catch {
33388
+ return url2;
33389
+ } finally {
33390
+ clearTimeout(timeout);
33391
+ }
33392
+ }
33393
+ };
33394
+ }
33361
33395
  var export_Chat = import_client3.Chat;
33362
33396
  var export_ChatBot = import_client3.ChatBot;
33363
33397
  var export_ChatButton = import_client3.ChatButton;
@@ -33382,6 +33416,7 @@ export {
33382
33416
  createServer,
33383
33417
  createSession,
33384
33418
  createSessionMiddleware,
33419
+ createShortener,
33385
33420
  defaultBuckets,
33386
33421
  demoRoutes,
33387
33422
  detectLeakage,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A Turso-backed exactly-once claim: an atomic upsert means two instances
3
+ * racing the same entry can never both believe they claimed it — see
4
+ * `canClaim`, which mirrors `canAcquireLease` in
5
+ * `../channels/whatsapp/lease.ts`. A failed send calls `release` to delete
6
+ * the claim row, letting the entry be claimed again by whichever instance
7
+ * ticks next within the grace window. An instance that dies mid-delivery
8
+ * without releasing leaves its claim to go stale instead: after
9
+ * `claimTimeoutMs` with no renewal, another instance may take it over.
10
+ */
11
+ import type { Client } from "@libsql/client";
12
+ import type { ScheduleClaimStore } from "./types";
13
+ export declare const DEFAULT_CLAIM_TABLE = "chatter_schedule_claims";
14
+ /** How long an unreleased claim blocks reclaiming — short relative to a typical grace window, so a crashed claimant frees up in time for a retry to still land within grace. */
15
+ export declare const DEFAULT_CLAIM_TIMEOUT_MS: number;
16
+ export interface ScheduleClaimRow {
17
+ instanceId: string;
18
+ claimedAt: number;
19
+ }
20
+ /**
21
+ * Pure: may `instanceId` take (or keep) the claim on an entry right now,
22
+ * given the current row (null = unclaimed)? Mirrors the SQL WHERE clause in
23
+ * `createTursoScheduleClaimStore` — keep the two in sync.
24
+ */
25
+ export declare function canClaim(existing: ScheduleClaimRow | null, instanceId: string, now: number, claimTimeoutMs: number): boolean;
26
+ export declare function createTursoScheduleClaimStore(client: Client, instanceId: string, tableName?: string, claimTimeoutMs?: number): ScheduleClaimStore;
27
+ //# sourceMappingURL=claimStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claimStore.d.ts","sourceRoot":"","sources":["../../src/scheduler/claimStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAC7D,gLAAgL;AAChL,eAAO,MAAM,wBAAwB,QAAa,CAAC;AAKnD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,GACrB,OAAO,CAIT;AA8BD,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,MAA4B,EACvC,cAAc,GAAE,MAAiC,GAChD,kBAAkB,CA4BpB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=claimStore.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claimStore.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/claimStore.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import type OpenAI from "openai";
2
+ import type { AnswerFn } from "../core/answer";
3
+ import type { PipelineMessage, PipelineMode } from "../core/pipeline";
4
+ import type { ComposeFn, ScheduleEntry } from "./types";
5
+ /**
6
+ * Runs the pluggable compose step and guarantees a message comes back: a
7
+ * missing compose function, a blank result, or a throwing/rejecting compose
8
+ * all fall back to `fallbackMessage` rather than blocking delivery.
9
+ */
10
+ export declare function composeMessage(entry: ScheduleEntry, compose: ComposeFn | undefined, fallbackMessage: string): Promise<string>;
11
+ export interface AnswerComposeOptions {
12
+ client: OpenAI;
13
+ system: string;
14
+ /** Turns a schedule entry's opaque payload into the messages sent to the brain. */
15
+ buildMessages: (entry: ScheduleEntry) => PipelineMessage[];
16
+ answerFn?: AnswerFn;
17
+ mode?: PipelineMode;
18
+ temperature?: number;
19
+ model?: string;
20
+ }
21
+ /** Builds a `ComposeFn` that composes via `answerFn`/`completeOnce`, for callers who want an LLM-written message instead of static text. */
22
+ export declare function createAnswerCompose(options: AnswerComposeOptions): ComposeFn;
23
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/scheduler/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,SAAS,GAAG,SAAS,EAC9B,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,eAAe,EAAE,CAAC;IAC3D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4IAA4I;AAC5I,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAa5E"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=compose.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/compose.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { ChannelSenderRegistry } from "../channels/senders";
2
+ import type { ScheduleEntry } from "./types";
3
+ export interface DeliverOptions {
4
+ senders: ChannelSenderRegistry;
5
+ /** Try voice first when the channel supports it; text is always the guaranteed fallback. */
6
+ voiceAttempted?: boolean;
7
+ }
8
+ /** Delivers a composed message. Text is guaranteed; voice is only ever a best-effort first attempt. */
9
+ export declare function deliver(entry: ScheduleEntry, message: string, options: DeliverOptions): Promise<boolean>;
10
+ //# sourceMappingURL=deliver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/scheduler/deliver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,4FAA4F;IAC5F,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,uGAAuG;AACvG,wBAAsB,OAAO,CAC3B,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,CAAC,CAMlB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=deliver.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliver.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/deliver.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Pure fire-time filtering. A schedule fires once its time has passed, but
3
+ * only within a grace window — a stale entry (the process was down, or the
4
+ * tick was delayed) past grace is skipped rather than delivered late.
5
+ */
6
+ import type { ScheduleEntry } from "./types";
7
+ export declare function isDue(entry: ScheduleEntry, now: number): boolean;
8
+ export declare function isWithinGrace(entry: ScheduleEntry, now: number, graceMs: number): boolean;
9
+ /** Due entries still inside their grace window, in caller-supplied order. */
10
+ export declare function dueDrops(entries: ScheduleEntry[], now: number, graceMs: number): ScheduleEntry[];
11
+ /** Due entries whose grace window has already passed — reported so callers can log them, never delivered. */
12
+ export declare function staleDrops(entries: ScheduleEntry[], now: number, graceMs: number): ScheduleEntry[];
13
+ //# sourceMappingURL=dueDrops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dueDrops.d.ts","sourceRoot":"","sources":["../../src/scheduler/dueDrops.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,wBAAgB,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzF;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAEhG;AAED,6GAA6G;AAC7G,wBAAgB,UAAU,CACxB,OAAO,EAAE,aAAa,EAAE,EACxB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,aAAa,EAAE,CAEjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dueDrops.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dueDrops.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/dueDrops.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Scheduler — exactly-once outbound delivery on a caller-supplied fire time.
3
+ * Published as the `@diegoaltoworks/chatter/scheduler` subpath so the core
4
+ * install stays untouched by it.
5
+ *
6
+ * Content-free: chatter stores no schedule data of its own. A caller
7
+ * supplies candidate {@link ScheduleEntry} objects each tick (its own store,
8
+ * cron, queue, anything); this module adds the parts that are easy to get
9
+ * wrong in a multi-instance deployment — an exactly-once claim in Turso so
10
+ * two instances never both deliver the same entry, a fire-time grace window
11
+ * so a delayed tick doesn't send stale messages, a pluggable compose step
12
+ * (static text or an LLM call via `answerFn`/`completeOnce`) that always
13
+ * falls back rather than blocking delivery, and delivery through the
14
+ * channel sender registry with an optional voice-attempted/text-guaranteed
15
+ * mode.
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ export type { ScheduleClaimRow } from "./claimStore";
20
+ export { canClaim, createTursoScheduleClaimStore, DEFAULT_CLAIM_TABLE, DEFAULT_CLAIM_TIMEOUT_MS, } from "./claimStore";
21
+ export type { AnswerComposeOptions } from "./compose";
22
+ export { composeMessage, createAnswerCompose } from "./compose";
23
+ export type { DeliverOptions } from "./deliver";
24
+ export { deliver } from "./deliver";
25
+ export { dueDrops, isDue, isWithinGrace, staleDrops } from "./dueDrops";
26
+ export type { Scheduler, SchedulerConfig } from "./scheduler";
27
+ export { createScheduler, DEFAULT_FALLBACK_MESSAGE, DEFAULT_GRACE_MS, DEFAULT_INTERVAL_MS, } from "./scheduler";
28
+ export type { TickOptions } from "./tick";
29
+ export { runTick } from "./tick";
30
+ export type { ComposeFn, ScheduleClaimStore, ScheduleEntry, TickResult } from "./types";
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scheduler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,QAAQ,EACR,6BAA6B,EAC7B,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxE,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,332 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/scheduler/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DEFAULT_CLAIM_TABLE: () => DEFAULT_CLAIM_TABLE,
24
+ DEFAULT_CLAIM_TIMEOUT_MS: () => DEFAULT_CLAIM_TIMEOUT_MS,
25
+ DEFAULT_FALLBACK_MESSAGE: () => DEFAULT_FALLBACK_MESSAGE,
26
+ DEFAULT_GRACE_MS: () => DEFAULT_GRACE_MS,
27
+ DEFAULT_INTERVAL_MS: () => DEFAULT_INTERVAL_MS,
28
+ canClaim: () => canClaim,
29
+ composeMessage: () => composeMessage,
30
+ createAnswerCompose: () => createAnswerCompose,
31
+ createScheduler: () => createScheduler,
32
+ createTursoScheduleClaimStore: () => createTursoScheduleClaimStore,
33
+ deliver: () => deliver,
34
+ dueDrops: () => dueDrops,
35
+ isDue: () => isDue,
36
+ isWithinGrace: () => isWithinGrace,
37
+ runTick: () => runTick,
38
+ staleDrops: () => staleDrops
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/scheduler/claimStore.ts
43
+ var DEFAULT_CLAIM_TABLE = "chatter_schedule_claims";
44
+ var DEFAULT_CLAIM_TIMEOUT_MS = 2 * 6e4;
45
+ var VALID_TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
46
+ function canClaim(existing, instanceId, now, claimTimeoutMs) {
47
+ if (!existing) return true;
48
+ if (existing.instanceId === instanceId) return true;
49
+ return now - existing.claimedAt >= claimTimeoutMs;
50
+ }
51
+ var schemaReady = /* @__PURE__ */ new WeakMap();
52
+ function ensureSchema(client, tableName) {
53
+ let perTable = schemaReady.get(client);
54
+ if (!perTable) {
55
+ perTable = /* @__PURE__ */ new Map();
56
+ schemaReady.set(client, perTable);
57
+ }
58
+ let ready = perTable.get(tableName);
59
+ if (!ready) {
60
+ const table = perTable;
61
+ ready = client.execute(
62
+ `CREATE TABLE IF NOT EXISTS ${tableName} (
63
+ schedule_id TEXT PRIMARY KEY,
64
+ instance_id TEXT NOT NULL,
65
+ claimed_at INTEGER NOT NULL
66
+ )`
67
+ ).catch((error) => {
68
+ table.delete(tableName);
69
+ throw error;
70
+ });
71
+ perTable.set(tableName, ready);
72
+ }
73
+ return ready;
74
+ }
75
+ function createTursoScheduleClaimStore(client, instanceId, tableName = DEFAULT_CLAIM_TABLE, claimTimeoutMs = DEFAULT_CLAIM_TIMEOUT_MS) {
76
+ if (!VALID_TABLE_NAME.test(tableName)) {
77
+ throw new Error(`Invalid schedule claim table name: ${tableName}`);
78
+ }
79
+ return {
80
+ async claim(id, now) {
81
+ await ensureSchema(client, tableName);
82
+ const result = await client.execute({
83
+ sql: `INSERT INTO ${tableName} (schedule_id, instance_id, claimed_at) VALUES (?, ?, ?)
84
+ ON CONFLICT(schedule_id) DO UPDATE SET
85
+ instance_id = excluded.instance_id,
86
+ claimed_at = excluded.claimed_at
87
+ WHERE ${tableName}.instance_id = excluded.instance_id
88
+ OR ${tableName}.claimed_at <= excluded.claimed_at - ?
89
+ RETURNING instance_id`,
90
+ args: [id, instanceId, now, claimTimeoutMs]
91
+ });
92
+ const row = result.rows[0];
93
+ return Boolean(row) && row.instance_id === instanceId;
94
+ },
95
+ async release(id) {
96
+ await ensureSchema(client, tableName);
97
+ await client.execute({
98
+ sql: `DELETE FROM ${tableName} WHERE schedule_id = ? AND instance_id = ?`,
99
+ args: [id, instanceId]
100
+ });
101
+ }
102
+ };
103
+ }
104
+
105
+ // src/core/guardrails.ts
106
+ function detectLeakage(text) {
107
+ const flags = [
108
+ /system prompt/i,
109
+ /hidden instruction/i,
110
+ /here (are|is) (my|the) rules/i,
111
+ /BEGIN SYSTEM PROMPT/i,
112
+ /tool instructions/i
113
+ ];
114
+ return flags.some((rx) => rx.test(text));
115
+ }
116
+ function scrubOutput(text) {
117
+ return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
118
+ }
119
+ function guardOutput(text) {
120
+ if (detectLeakage(text)) {
121
+ return "Sorry, I can't share internal instructions. How else can I help?";
122
+ }
123
+ return scrubOutput(text);
124
+ }
125
+
126
+ // src/core/llm.ts
127
+ var DEFAULT_MODEL = "gpt-4o";
128
+ async function completeOnce({
129
+ client,
130
+ system,
131
+ messages,
132
+ temperature = 0.2,
133
+ model = DEFAULT_MODEL
134
+ }) {
135
+ const res = await client.chat.completions.create({
136
+ model,
137
+ temperature,
138
+ messages: [{ role: "system", content: system }, ...messages]
139
+ });
140
+ const text = res.choices[0]?.message?.content ?? "";
141
+ return {
142
+ content: guardOutput(text),
143
+ usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
144
+ };
145
+ }
146
+
147
+ // src/core/answer.ts
148
+ var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
149
+ async function runAnswerFn(answerFn, { system, messages, mode, sender }) {
150
+ const result = await answerFn({ system, messages, mode, sender });
151
+ const raw = typeof result === "string" ? result : result?.content;
152
+ const usage = typeof result === "string" ? void 0 : result?.usage;
153
+ return {
154
+ content: guardOutput(typeof raw === "string" ? raw : ""),
155
+ usage: usage ?? noUsage()
156
+ };
157
+ }
158
+ async function answerOnce({
159
+ answerFn,
160
+ client,
161
+ system,
162
+ messages,
163
+ mode,
164
+ sender,
165
+ temperature,
166
+ model
167
+ }) {
168
+ if (answerFn) {
169
+ return runAnswerFn(answerFn, { system, messages, mode, sender });
170
+ }
171
+ return completeOnce({ client, system, messages, temperature, model });
172
+ }
173
+
174
+ // src/scheduler/compose.ts
175
+ async function composeMessage(entry, compose, fallbackMessage) {
176
+ if (!compose) return fallbackMessage;
177
+ try {
178
+ const result = await compose(entry);
179
+ return result.trim().length > 0 ? result : fallbackMessage;
180
+ } catch (error) {
181
+ console.warn(`Scheduler compose failed for entry "${entry.id}":`, error);
182
+ return fallbackMessage;
183
+ }
184
+ }
185
+ function createAnswerCompose(options) {
186
+ return async (entry) => {
187
+ const { content } = await answerOnce({
188
+ client: options.client,
189
+ system: options.system,
190
+ messages: options.buildMessages(entry),
191
+ mode: options.mode ?? "private",
192
+ answerFn: options.answerFn,
193
+ temperature: options.temperature,
194
+ model: options.model
195
+ });
196
+ return content;
197
+ };
198
+ }
199
+
200
+ // src/scheduler/deliver.ts
201
+ async function deliver(entry, message, options) {
202
+ if (options.voiceAttempted) {
203
+ const voiceSent = await options.senders.sendVoice(entry.channel, entry.chatId, message);
204
+ if (voiceSent) return true;
205
+ }
206
+ return options.senders.sendText(entry.channel, entry.chatId, message);
207
+ }
208
+
209
+ // src/scheduler/dueDrops.ts
210
+ function isDue(entry, now) {
211
+ return entry.fireAt <= now;
212
+ }
213
+ function isWithinGrace(entry, now, graceMs) {
214
+ return now - entry.fireAt <= graceMs;
215
+ }
216
+ function dueDrops(entries, now, graceMs) {
217
+ return entries.filter((entry) => isDue(entry, now) && isWithinGrace(entry, now, graceMs));
218
+ }
219
+ function staleDrops(entries, now, graceMs) {
220
+ return entries.filter((entry) => isDue(entry, now) && !isWithinGrace(entry, now, graceMs));
221
+ }
222
+
223
+ // src/scheduler/tick.ts
224
+ async function runTick(entries, options) {
225
+ const result = { sent: [], notClaimed: [], stale: [], failed: [] };
226
+ for (const entry of staleDrops(entries, options.now, options.graceMs)) {
227
+ result.stale.push(entry.id);
228
+ }
229
+ for (const entry of dueDrops(entries, options.now, options.graceMs)) {
230
+ const claimed = await options.claims.claim(entry.id, options.now);
231
+ if (!claimed) {
232
+ result.notClaimed.push(entry.id);
233
+ continue;
234
+ }
235
+ try {
236
+ const message = await composeMessage(entry, options.compose, options.fallbackMessage);
237
+ const delivered = await deliver(entry, message, {
238
+ senders: options.senders,
239
+ voiceAttempted: options.voiceAttempted
240
+ });
241
+ if (delivered) {
242
+ result.sent.push(entry.id);
243
+ } else {
244
+ await options.claims.release(entry.id);
245
+ result.failed.push(entry.id);
246
+ }
247
+ } catch (error) {
248
+ console.warn(`Scheduler delivery failed for entry "${entry.id}":`, error);
249
+ await options.claims.release(entry.id);
250
+ result.failed.push(entry.id);
251
+ }
252
+ }
253
+ return result;
254
+ }
255
+
256
+ // src/scheduler/scheduler.ts
257
+ var DEFAULT_GRACE_MS = 5 * 6e4;
258
+ var DEFAULT_INTERVAL_MS = 6e4;
259
+ var DEFAULT_FALLBACK_MESSAGE = "You have a scheduled message.";
260
+ var defaultSchedule = (fn, ms) => {
261
+ const timer = setInterval(fn, ms);
262
+ if (typeof timer === "object" && "unref" in timer) timer.unref();
263
+ return { stop: () => clearInterval(timer) };
264
+ };
265
+ function createScheduler(config) {
266
+ const claims = createTursoScheduleClaimStore(
267
+ config.db,
268
+ config.instanceId ?? crypto.randomUUID(),
269
+ config.tableName ?? DEFAULT_CLAIM_TABLE,
270
+ config.claimTimeoutMs ?? DEFAULT_CLAIM_TIMEOUT_MS
271
+ );
272
+ const graceMs = config.graceMs ?? DEFAULT_GRACE_MS;
273
+ const fallbackMessage = config.fallbackMessage ?? DEFAULT_FALLBACK_MESSAGE;
274
+ const now = config.now ?? Date.now;
275
+ const schedule = config.schedule ?? defaultSchedule;
276
+ let running;
277
+ let ticking = false;
278
+ async function tickOnce() {
279
+ const entries = await config.fetchPending();
280
+ const result = await runTick(entries, {
281
+ claims,
282
+ senders: config.senders,
283
+ now: now(),
284
+ graceMs,
285
+ fallbackMessage,
286
+ compose: config.compose,
287
+ voiceAttempted: config.voiceAttempted
288
+ });
289
+ config.onTick?.(result);
290
+ return result;
291
+ }
292
+ return {
293
+ start() {
294
+ if (running) return;
295
+ running = schedule(() => {
296
+ if (ticking) return;
297
+ ticking = true;
298
+ tickOnce().catch((error) => {
299
+ if (config.onError) config.onError(error);
300
+ else console.warn("Scheduler tick failed:", error);
301
+ }).finally(() => {
302
+ ticking = false;
303
+ });
304
+ }, config.intervalMs ?? DEFAULT_INTERVAL_MS);
305
+ },
306
+ stop() {
307
+ if (!running) return;
308
+ running.stop();
309
+ running = void 0;
310
+ },
311
+ tickOnce
312
+ };
313
+ }
314
+ // Annotate the CommonJS export names for ESM import in node:
315
+ 0 && (module.exports = {
316
+ DEFAULT_CLAIM_TABLE,
317
+ DEFAULT_CLAIM_TIMEOUT_MS,
318
+ DEFAULT_FALLBACK_MESSAGE,
319
+ DEFAULT_GRACE_MS,
320
+ DEFAULT_INTERVAL_MS,
321
+ canClaim,
322
+ composeMessage,
323
+ createAnswerCompose,
324
+ createScheduler,
325
+ createTursoScheduleClaimStore,
326
+ deliver,
327
+ dueDrops,
328
+ isDue,
329
+ isWithinGrace,
330
+ runTick,
331
+ staleDrops
332
+ });
@@ -0,0 +1,290 @@
1
+ // src/scheduler/claimStore.ts
2
+ var DEFAULT_CLAIM_TABLE = "chatter_schedule_claims";
3
+ var DEFAULT_CLAIM_TIMEOUT_MS = 2 * 6e4;
4
+ var VALID_TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
5
+ function canClaim(existing, instanceId, now, claimTimeoutMs) {
6
+ if (!existing) return true;
7
+ if (existing.instanceId === instanceId) return true;
8
+ return now - existing.claimedAt >= claimTimeoutMs;
9
+ }
10
+ var schemaReady = /* @__PURE__ */ new WeakMap();
11
+ function ensureSchema(client, tableName) {
12
+ let perTable = schemaReady.get(client);
13
+ if (!perTable) {
14
+ perTable = /* @__PURE__ */ new Map();
15
+ schemaReady.set(client, perTable);
16
+ }
17
+ let ready = perTable.get(tableName);
18
+ if (!ready) {
19
+ const table = perTable;
20
+ ready = client.execute(
21
+ `CREATE TABLE IF NOT EXISTS ${tableName} (
22
+ schedule_id TEXT PRIMARY KEY,
23
+ instance_id TEXT NOT NULL,
24
+ claimed_at INTEGER NOT NULL
25
+ )`
26
+ ).catch((error) => {
27
+ table.delete(tableName);
28
+ throw error;
29
+ });
30
+ perTable.set(tableName, ready);
31
+ }
32
+ return ready;
33
+ }
34
+ function createTursoScheduleClaimStore(client, instanceId, tableName = DEFAULT_CLAIM_TABLE, claimTimeoutMs = DEFAULT_CLAIM_TIMEOUT_MS) {
35
+ if (!VALID_TABLE_NAME.test(tableName)) {
36
+ throw new Error(`Invalid schedule claim table name: ${tableName}`);
37
+ }
38
+ return {
39
+ async claim(id, now) {
40
+ await ensureSchema(client, tableName);
41
+ const result = await client.execute({
42
+ sql: `INSERT INTO ${tableName} (schedule_id, instance_id, claimed_at) VALUES (?, ?, ?)
43
+ ON CONFLICT(schedule_id) DO UPDATE SET
44
+ instance_id = excluded.instance_id,
45
+ claimed_at = excluded.claimed_at
46
+ WHERE ${tableName}.instance_id = excluded.instance_id
47
+ OR ${tableName}.claimed_at <= excluded.claimed_at - ?
48
+ RETURNING instance_id`,
49
+ args: [id, instanceId, now, claimTimeoutMs]
50
+ });
51
+ const row = result.rows[0];
52
+ return Boolean(row) && row.instance_id === instanceId;
53
+ },
54
+ async release(id) {
55
+ await ensureSchema(client, tableName);
56
+ await client.execute({
57
+ sql: `DELETE FROM ${tableName} WHERE schedule_id = ? AND instance_id = ?`,
58
+ args: [id, instanceId]
59
+ });
60
+ }
61
+ };
62
+ }
63
+
64
+ // src/core/guardrails.ts
65
+ function detectLeakage(text) {
66
+ const flags = [
67
+ /system prompt/i,
68
+ /hidden instruction/i,
69
+ /here (are|is) (my|the) rules/i,
70
+ /BEGIN SYSTEM PROMPT/i,
71
+ /tool instructions/i
72
+ ];
73
+ return flags.some((rx) => rx.test(text));
74
+ }
75
+ function scrubOutput(text) {
76
+ return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
77
+ }
78
+ function guardOutput(text) {
79
+ if (detectLeakage(text)) {
80
+ return "Sorry, I can't share internal instructions. How else can I help?";
81
+ }
82
+ return scrubOutput(text);
83
+ }
84
+
85
+ // src/core/llm.ts
86
+ var DEFAULT_MODEL = "gpt-4o";
87
+ async function completeOnce({
88
+ client,
89
+ system,
90
+ messages,
91
+ temperature = 0.2,
92
+ model = DEFAULT_MODEL
93
+ }) {
94
+ const res = await client.chat.completions.create({
95
+ model,
96
+ temperature,
97
+ messages: [{ role: "system", content: system }, ...messages]
98
+ });
99
+ const text = res.choices[0]?.message?.content ?? "";
100
+ return {
101
+ content: guardOutput(text),
102
+ usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
103
+ };
104
+ }
105
+
106
+ // src/core/answer.ts
107
+ var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
108
+ async function runAnswerFn(answerFn, { system, messages, mode, sender }) {
109
+ const result = await answerFn({ system, messages, mode, sender });
110
+ const raw = typeof result === "string" ? result : result?.content;
111
+ const usage = typeof result === "string" ? void 0 : result?.usage;
112
+ return {
113
+ content: guardOutput(typeof raw === "string" ? raw : ""),
114
+ usage: usage ?? noUsage()
115
+ };
116
+ }
117
+ async function answerOnce({
118
+ answerFn,
119
+ client,
120
+ system,
121
+ messages,
122
+ mode,
123
+ sender,
124
+ temperature,
125
+ model
126
+ }) {
127
+ if (answerFn) {
128
+ return runAnswerFn(answerFn, { system, messages, mode, sender });
129
+ }
130
+ return completeOnce({ client, system, messages, temperature, model });
131
+ }
132
+
133
+ // src/scheduler/compose.ts
134
+ async function composeMessage(entry, compose, fallbackMessage) {
135
+ if (!compose) return fallbackMessage;
136
+ try {
137
+ const result = await compose(entry);
138
+ return result.trim().length > 0 ? result : fallbackMessage;
139
+ } catch (error) {
140
+ console.warn(`Scheduler compose failed for entry "${entry.id}":`, error);
141
+ return fallbackMessage;
142
+ }
143
+ }
144
+ function createAnswerCompose(options) {
145
+ return async (entry) => {
146
+ const { content } = await answerOnce({
147
+ client: options.client,
148
+ system: options.system,
149
+ messages: options.buildMessages(entry),
150
+ mode: options.mode ?? "private",
151
+ answerFn: options.answerFn,
152
+ temperature: options.temperature,
153
+ model: options.model
154
+ });
155
+ return content;
156
+ };
157
+ }
158
+
159
+ // src/scheduler/deliver.ts
160
+ async function deliver(entry, message, options) {
161
+ if (options.voiceAttempted) {
162
+ const voiceSent = await options.senders.sendVoice(entry.channel, entry.chatId, message);
163
+ if (voiceSent) return true;
164
+ }
165
+ return options.senders.sendText(entry.channel, entry.chatId, message);
166
+ }
167
+
168
+ // src/scheduler/dueDrops.ts
169
+ function isDue(entry, now) {
170
+ return entry.fireAt <= now;
171
+ }
172
+ function isWithinGrace(entry, now, graceMs) {
173
+ return now - entry.fireAt <= graceMs;
174
+ }
175
+ function dueDrops(entries, now, graceMs) {
176
+ return entries.filter((entry) => isDue(entry, now) && isWithinGrace(entry, now, graceMs));
177
+ }
178
+ function staleDrops(entries, now, graceMs) {
179
+ return entries.filter((entry) => isDue(entry, now) && !isWithinGrace(entry, now, graceMs));
180
+ }
181
+
182
+ // src/scheduler/tick.ts
183
+ async function runTick(entries, options) {
184
+ const result = { sent: [], notClaimed: [], stale: [], failed: [] };
185
+ for (const entry of staleDrops(entries, options.now, options.graceMs)) {
186
+ result.stale.push(entry.id);
187
+ }
188
+ for (const entry of dueDrops(entries, options.now, options.graceMs)) {
189
+ const claimed = await options.claims.claim(entry.id, options.now);
190
+ if (!claimed) {
191
+ result.notClaimed.push(entry.id);
192
+ continue;
193
+ }
194
+ try {
195
+ const message = await composeMessage(entry, options.compose, options.fallbackMessage);
196
+ const delivered = await deliver(entry, message, {
197
+ senders: options.senders,
198
+ voiceAttempted: options.voiceAttempted
199
+ });
200
+ if (delivered) {
201
+ result.sent.push(entry.id);
202
+ } else {
203
+ await options.claims.release(entry.id);
204
+ result.failed.push(entry.id);
205
+ }
206
+ } catch (error) {
207
+ console.warn(`Scheduler delivery failed for entry "${entry.id}":`, error);
208
+ await options.claims.release(entry.id);
209
+ result.failed.push(entry.id);
210
+ }
211
+ }
212
+ return result;
213
+ }
214
+
215
+ // src/scheduler/scheduler.ts
216
+ var DEFAULT_GRACE_MS = 5 * 6e4;
217
+ var DEFAULT_INTERVAL_MS = 6e4;
218
+ var DEFAULT_FALLBACK_MESSAGE = "You have a scheduled message.";
219
+ var defaultSchedule = (fn, ms) => {
220
+ const timer = setInterval(fn, ms);
221
+ if (typeof timer === "object" && "unref" in timer) timer.unref();
222
+ return { stop: () => clearInterval(timer) };
223
+ };
224
+ function createScheduler(config) {
225
+ const claims = createTursoScheduleClaimStore(
226
+ config.db,
227
+ config.instanceId ?? crypto.randomUUID(),
228
+ config.tableName ?? DEFAULT_CLAIM_TABLE,
229
+ config.claimTimeoutMs ?? DEFAULT_CLAIM_TIMEOUT_MS
230
+ );
231
+ const graceMs = config.graceMs ?? DEFAULT_GRACE_MS;
232
+ const fallbackMessage = config.fallbackMessage ?? DEFAULT_FALLBACK_MESSAGE;
233
+ const now = config.now ?? Date.now;
234
+ const schedule = config.schedule ?? defaultSchedule;
235
+ let running;
236
+ let ticking = false;
237
+ async function tickOnce() {
238
+ const entries = await config.fetchPending();
239
+ const result = await runTick(entries, {
240
+ claims,
241
+ senders: config.senders,
242
+ now: now(),
243
+ graceMs,
244
+ fallbackMessage,
245
+ compose: config.compose,
246
+ voiceAttempted: config.voiceAttempted
247
+ });
248
+ config.onTick?.(result);
249
+ return result;
250
+ }
251
+ return {
252
+ start() {
253
+ if (running) return;
254
+ running = schedule(() => {
255
+ if (ticking) return;
256
+ ticking = true;
257
+ tickOnce().catch((error) => {
258
+ if (config.onError) config.onError(error);
259
+ else console.warn("Scheduler tick failed:", error);
260
+ }).finally(() => {
261
+ ticking = false;
262
+ });
263
+ }, config.intervalMs ?? DEFAULT_INTERVAL_MS);
264
+ },
265
+ stop() {
266
+ if (!running) return;
267
+ running.stop();
268
+ running = void 0;
269
+ },
270
+ tickOnce
271
+ };
272
+ }
273
+ export {
274
+ DEFAULT_CLAIM_TABLE,
275
+ DEFAULT_CLAIM_TIMEOUT_MS,
276
+ DEFAULT_FALLBACK_MESSAGE,
277
+ DEFAULT_GRACE_MS,
278
+ DEFAULT_INTERVAL_MS,
279
+ canClaim,
280
+ composeMessage,
281
+ createAnswerCompose,
282
+ createScheduler,
283
+ createTursoScheduleClaimStore,
284
+ deliver,
285
+ dueDrops,
286
+ isDue,
287
+ isWithinGrace,
288
+ runTick,
289
+ staleDrops
290
+ };
@@ -0,0 +1,44 @@
1
+ import type { Client } from "@libsql/client";
2
+ import type { ChannelSenderRegistry } from "../channels/senders";
3
+ import type { ComposeFn, ScheduleEntry, TickResult } from "./types";
4
+ export declare const DEFAULT_GRACE_MS: number;
5
+ export declare const DEFAULT_INTERVAL_MS = 60000;
6
+ export declare const DEFAULT_FALLBACK_MESSAGE = "You have a scheduled message.";
7
+ export interface SchedulerConfig {
8
+ db: Client;
9
+ senders: ChannelSenderRegistry;
10
+ /** Returns the caller's own pending candidates for this tick — chatter stores no schedule content. */
11
+ fetchPending: () => Promise<ScheduleEntry[]> | ScheduleEntry[];
12
+ tableName?: string;
13
+ graceMs?: number;
14
+ intervalMs?: number;
15
+ claimTimeoutMs?: number;
16
+ instanceId?: string;
17
+ compose?: ComposeFn;
18
+ fallbackMessage?: string;
19
+ voiceAttempted?: boolean;
20
+ onTick?: (result: TickResult) => void;
21
+ onError?: (error: unknown) => void;
22
+ /** Injectable for tests; defaults to `setInterval`/`clearInterval`. */
23
+ schedule?: (fn: () => void, ms: number) => {
24
+ stop: () => void;
25
+ };
26
+ /** Injectable for tests; defaults to `Date.now`. */
27
+ now?: () => number;
28
+ }
29
+ export interface Scheduler {
30
+ /** Starts ticking on `intervalMs`. No-op if already started. */
31
+ start(): void;
32
+ /** Stops ticking. No-op if not started. */
33
+ stop(): void;
34
+ /** Runs one tick immediately, outside the interval — useful for tests and manual triggers. */
35
+ tickOnce(): Promise<TickResult>;
36
+ }
37
+ /**
38
+ * The ticker: on each interval, pulls pending candidates from the caller,
39
+ * claims each due one exactly once across every running instance, composes
40
+ * and delivers it, and releases the claim on a failed send so it retries
41
+ * within the grace window instead of being lost.
42
+ */
43
+ export declare function createScheduler(config: SchedulerConfig): Scheduler;
44
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/scheduler/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAOjE,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEpE,eAAO,MAAM,gBAAgB,QAAa,CAAC;AAC3C,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAC1C,eAAO,MAAM,wBAAwB,kCAAkC,CAAC;AAExE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,qBAAqB,CAAC;IAC/B,sGAAsG;IACtG,YAAY,EAAE,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC;IAChE,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,gEAAgE;IAChE,KAAK,IAAI,IAAI,CAAC;IACd,2CAA2C;IAC3C,IAAI,IAAI,IAAI,CAAC;IACb,8FAA8F;IAC9F,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CACjC;AAQD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAsDlE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scheduler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/scheduler.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ import type { ChannelSenderRegistry } from "../channels/senders";
2
+ import type { ComposeFn, ScheduleClaimStore, ScheduleEntry, TickResult } from "./types";
3
+ export interface TickOptions {
4
+ claims: ScheduleClaimStore;
5
+ senders: ChannelSenderRegistry;
6
+ now: number;
7
+ graceMs: number;
8
+ fallbackMessage: string;
9
+ compose?: ComposeFn;
10
+ voiceAttempted?: boolean;
11
+ }
12
+ /**
13
+ * One scheduler tick over caller-supplied candidate entries. Chatter stores
14
+ * no schedule content of its own — `entries` is whatever the caller's own
15
+ * store considers pending; this adds the exactly-once claim, the grace
16
+ * window, and delivery on top.
17
+ */
18
+ export declare function runTick(entries: ScheduleEntry[], options: TickOptions): Promise<TickResult>;
19
+ //# sourceMappingURL=tick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tick.d.ts","sourceRoot":"","sources":["../../src/scheduler/tick.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAIjE,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAExF,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAsCjG"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tick.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tick.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/tick.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Content-free by design: chatter stores no schedule data of its own.
3
+ * `id`/`fireAt`/`channel`/`chatId` are the minimum the claim/grace/delivery
4
+ * machinery needs; `payload` is opaque caller data for the compose step.
5
+ */
6
+ export interface ScheduleEntry {
7
+ /** Stable, caller-assigned id — the claim table's primary key. */
8
+ id: string;
9
+ /** Epoch ms the entry is due to fire. */
10
+ fireAt: number;
11
+ /** Sender-registry channel name to deliver through. */
12
+ channel: string;
13
+ /** Channel-specific recipient id. */
14
+ chatId: string;
15
+ /** Opaque caller data passed to the compose step; chatter never reads it. */
16
+ payload?: unknown;
17
+ }
18
+ /** Turns an entry into outbound text. Chatter never inspects the result beyond checking it is non-blank. */
19
+ export type ComposeFn = (entry: ScheduleEntry) => Promise<string> | string;
20
+ export interface ScheduleClaimStore {
21
+ /** Atomically claims `id` for this instance; resolves false if another instance already holds it. */
22
+ claim(id: string, now: number): Promise<boolean>;
23
+ /** Releases this instance's claim on `id`, e.g. after a failed send, so it can be retried within grace. */
24
+ release(id: string): Promise<void>;
25
+ }
26
+ export interface TickResult {
27
+ /** Ids delivered this tick. */
28
+ sent: string[];
29
+ /** Ids due within grace but already claimed by another instance. */
30
+ notClaimed: string[];
31
+ /** Ids due but past their grace window — skipped, never claimed or delivered. */
32
+ stale: string[];
33
+ /** Ids claimed this tick whose delivery failed; the claim was released so a later tick can retry. */
34
+ failed: string[];
35
+ }
36
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scheduler/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,4GAA4G;AAC5G,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IACjC,qGAAqG;IACrG,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,2GAA2G;IAC3G,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,oEAAoE;IACpE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,iFAAiF;IACjF,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,qGAAqG;IACrG,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * URL shortening against a Kutt-compatible API, config-injected (no
3
+ * `process.env` reads) so hosts can wire credentials however they like.
4
+ *
5
+ * The hard invariant: {@link Shortener.shorten} never throws. Any failure —
6
+ * network error, an aborted request past `timeoutMs`, or a non-2xx/malformed
7
+ * response — resolves to the original URL, since a shortener outage must
8
+ * never break something user-facing that only wanted a shorter link.
9
+ */
10
+ export interface ShortenerConfig {
11
+ /** Base URL of the Kutt (or Kutt-compatible) instance, e.g. "https://kutt.it". */
12
+ apiUrl: string;
13
+ apiKey: string;
14
+ /** Custom short domain configured on the Kutt instance, if any. */
15
+ domain?: string;
16
+ /** Abort the request after this many milliseconds. Defaults to 5000. */
17
+ timeoutMs?: number;
18
+ }
19
+ export interface Shortener {
20
+ /** Resolves to the shortened URL, or the input `url` unchanged on any failure. */
21
+ shorten(url: string): Promise<string>;
22
+ }
23
+ export declare function createShortener(config: ShortenerConfig, fetchImpl?: typeof fetch): Shortener;
24
+ //# sourceMappingURL=shortener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shortener.d.ts","sourceRoot":"","sources":["../src/shortener.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACvC;AAQD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,eAAe,EACvB,SAAS,GAAE,OAAO,KAAa,GAC9B,SAAS,CAqCX"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=shortener.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shortener.test.d.ts","sourceRoot":"","sources":["../src/shortener.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.34.0",
3
+ "version": "0.36.0",
4
4
  "description": "Embeddable AI chatbot framework with RAG, authentication, and customizable widgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -59,6 +59,11 @@
59
59
  "types": "./dist/images/index.d.ts",
60
60
  "import": "./dist/images/index.mjs",
61
61
  "require": "./dist/images/index.js"
62
+ },
63
+ "./scheduler": {
64
+ "types": "./dist/scheduler/index.d.ts",
65
+ "import": "./dist/scheduler/index.mjs",
66
+ "require": "./dist/scheduler/index.js"
62
67
  }
63
68
  },
64
69
  "files": [
@@ -68,7 +73,7 @@
68
73
  ],
69
74
  "scripts": {
70
75
  "dev": "bun --watch src/index.ts",
71
- "build": "bun run build:clean && bun run build:types && bun run build:esm && bun run build:cjs && bun run build:mcp && bun run build:usage && bun run build:personas && bun run build:channels && bun run build:whatsapp && bun run build:flows && bun run build:images && bun run build:bin && bun run build:client && bun run build:copy-client",
76
+ "build": "bun run build:clean && bun run build:types && bun run build:esm && bun run build:cjs && bun run build:mcp && bun run build:usage && bun run build:personas && bun run build:channels && bun run build:whatsapp && bun run build:flows && bun run build:images && bun run build:scheduler && bun run build:bin && bun run build:client && bun run build:copy-client",
72
77
  "build:clean": "rm -rf dist",
73
78
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
74
79
  "build:esm": "esbuild src/index.ts --bundle --format=esm --outfile=dist/index.mjs --external:hono --external:openai --external:@libsql/client --external:jose --platform=node --target=node24",
@@ -80,6 +85,7 @@
80
85
  "build:whatsapp": "esbuild src/channels/whatsapp/index.ts --bundle --format=esm --outfile=dist/channels/whatsapp/index.mjs --external:@libsql/client --external:@whiskeysockets/baileys --platform=node --target=node24 && esbuild src/channels/whatsapp/index.ts --bundle --format=cjs --outfile=dist/channels/whatsapp/index.js --external:@libsql/client --external:@whiskeysockets/baileys --platform=node --target=node24",
81
86
  "build:flows": "esbuild src/flows/index.ts --bundle --format=esm --outfile=dist/flows/index.mjs --external:@libsql/client --external:openai --platform=node --target=node24 && esbuild src/flows/index.ts --bundle --format=cjs --outfile=dist/flows/index.js --external:@libsql/client --external:openai --platform=node --target=node24",
82
87
  "build:images": "esbuild src/images/index.ts --bundle --format=esm --outfile=dist/images/index.mjs --external:@libsql/client --external:openai --platform=node --target=node24 && esbuild src/images/index.ts --bundle --format=cjs --outfile=dist/images/index.js --external:@libsql/client --external:openai --platform=node --target=node24",
88
+ "build:scheduler": "esbuild src/scheduler/index.ts --bundle --format=esm --outfile=dist/scheduler/index.mjs --external:@libsql/client --external:openai --platform=node --target=node24 && esbuild src/scheduler/index.ts --bundle --format=cjs --outfile=dist/scheduler/index.js --external:@libsql/client --external:openai --platform=node --target=node24",
83
89
  "build:bin": "esbuild bin/create-apikey.ts --bundle --format=cjs --outfile=dist/bin/create-apikey.js --external:jose --platform=node --target=node18 --banner:js='#!/usr/bin/env node' && esbuild bin/wa-pair.ts --bundle --format=cjs --outfile=dist/bin/wa-pair.js --external:@libsql/client --external:@whiskeysockets/baileys --external:qrcode-terminal --platform=node --target=node18 --banner:js='#!/usr/bin/env node'",
84
90
  "build:client": "cd src/client && bun run build",
85
91
  "build:copy-client": "mkdir -p dist/static && cp src/client/dist/chatter.js src/client/dist/chatter.css dist/static/",