@diegoaltoworks/chatter 0.47.0 → 0.48.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 (118) hide show
  1. package/README.md +3 -4
  2. package/dist/auth/apikeys.d.ts.map +1 -1
  3. package/dist/auth/secretStrength.d.ts +8 -0
  4. package/dist/auth/secretStrength.d.ts.map +1 -0
  5. package/dist/bin/create-apikey.js +22 -3
  6. package/dist/bin/wa-pair.js +59 -11
  7. package/dist/channels/gates.d.ts +9 -0
  8. package/dist/channels/gates.d.ts.map +1 -1
  9. package/dist/channels/index.d.ts +1 -0
  10. package/dist/channels/index.d.ts.map +1 -1
  11. package/dist/channels/index.js +252 -8
  12. package/dist/channels/index.mjs +250 -8
  13. package/dist/channels/pipeline.d.ts +106 -0
  14. package/dist/channels/pipeline.d.ts.map +1 -0
  15. package/dist/channels/senders.d.ts +2 -1
  16. package/dist/channels/senders.d.ts.map +1 -1
  17. package/dist/channels/whatsapp/channel.d.ts +4 -0
  18. package/dist/channels/whatsapp/channel.d.ts.map +1 -1
  19. package/dist/channels/whatsapp/crypto.d.ts +17 -3
  20. package/dist/channels/whatsapp/crypto.d.ts.map +1 -1
  21. package/dist/channels/whatsapp/inbound.d.ts +34 -4
  22. package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
  23. package/dist/channels/whatsapp/index.d.ts +2 -1
  24. package/dist/channels/whatsapp/index.d.ts.map +1 -1
  25. package/dist/channels/whatsapp/index.js +395 -159
  26. package/dist/channels/whatsapp/index.mjs +394 -160
  27. package/dist/channels/whatsapp/router.d.ts +107 -0
  28. package/dist/channels/whatsapp/router.d.ts.map +1 -0
  29. package/dist/core/answer.d.ts +5 -2
  30. package/dist/core/answer.d.ts.map +1 -1
  31. package/dist/core/logger.d.ts +23 -0
  32. package/dist/core/logger.d.ts.map +1 -0
  33. package/dist/core/messages.d.ts +8 -0
  34. package/dist/core/messages.d.ts.map +1 -1
  35. package/dist/core/pipeline.d.ts +2 -0
  36. package/dist/core/pipeline.d.ts.map +1 -1
  37. package/dist/core/retrieval.d.ts +4 -0
  38. package/dist/core/retrieval.d.ts.map +1 -1
  39. package/dist/flows/engine.d.ts +3 -0
  40. package/dist/flows/engine.d.ts.map +1 -1
  41. package/dist/flows/index.d.ts +1 -0
  42. package/dist/flows/index.d.ts.map +1 -1
  43. package/dist/flows/index.js +91 -25
  44. package/dist/flows/index.mjs +91 -26
  45. package/dist/flows/intent.d.ts +2 -1
  46. package/dist/flows/intent.d.ts.map +1 -1
  47. package/dist/flows/loader.d.ts +5 -3
  48. package/dist/flows/loader.d.ts.map +1 -1
  49. package/dist/flows/types.d.ts +9 -0
  50. package/dist/flows/types.d.ts.map +1 -1
  51. package/dist/history/index.d.ts +17 -0
  52. package/dist/history/index.d.ts.map +1 -0
  53. package/dist/history/index.js +100 -0
  54. package/dist/history/index.mjs +73 -0
  55. package/dist/history/tursoStore.d.ts +26 -0
  56. package/dist/history/tursoStore.d.ts.map +1 -0
  57. package/dist/history/types.d.ts +18 -0
  58. package/dist/history/types.d.ts.map +1 -0
  59. package/dist/index.d.ts +5 -2
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1146 -32444
  62. package/dist/index.mjs +1082 -32410
  63. package/dist/mcp-server/cost-tracker.d.ts +11 -6
  64. package/dist/mcp-server/cost-tracker.d.ts.map +1 -1
  65. package/dist/mcp-server/logger.d.ts +5 -2
  66. package/dist/mcp-server/logger.d.ts.map +1 -1
  67. package/dist/mcp-server/sdk.d.ts +23 -0
  68. package/dist/mcp-server/sdk.d.ts.map +1 -0
  69. package/dist/mcp-server/tool-input.d.ts +12 -0
  70. package/dist/mcp-server/tool-input.d.ts.map +1 -0
  71. package/dist/mcp-server/types.d.ts +10 -3
  72. package/dist/mcp-server/types.d.ts.map +1 -1
  73. package/dist/mcp-server.d.ts +1 -2
  74. package/dist/mcp-server.d.ts.map +1 -1
  75. package/dist/mcp-server.js +192 -111
  76. package/dist/mcp-server.mjs +179 -98
  77. package/dist/middleware/auth.d.ts.map +1 -1
  78. package/dist/middleware/bodyLimit.d.ts +11 -0
  79. package/dist/middleware/bodyLimit.d.ts.map +1 -0
  80. package/dist/middleware/clientKey.d.ts +15 -0
  81. package/dist/middleware/clientKey.d.ts.map +1 -0
  82. package/dist/middleware/cors.d.ts.map +1 -1
  83. package/dist/middleware/jwt.d.ts +0 -3
  84. package/dist/middleware/jwt.d.ts.map +1 -1
  85. package/dist/middleware/originMatch.d.ts +27 -0
  86. package/dist/middleware/originMatch.d.ts.map +1 -0
  87. package/dist/middleware/ratelimit.d.ts +0 -6
  88. package/dist/middleware/ratelimit.d.ts.map +1 -1
  89. package/dist/middleware/referrer.d.ts +5 -1
  90. package/dist/middleware/referrer.d.ts.map +1 -1
  91. package/dist/middleware/securityHeaders.d.ts +20 -0
  92. package/dist/middleware/securityHeaders.d.ts.map +1 -0
  93. package/dist/middleware/session.d.ts +2 -1
  94. package/dist/middleware/session.d.ts.map +1 -1
  95. package/dist/middleware/windowBucket.d.ts +19 -0
  96. package/dist/middleware/windowBucket.d.ts.map +1 -0
  97. package/dist/personas/index.js +20 -1
  98. package/dist/personas/index.mjs +20 -1
  99. package/dist/personas/resolver.d.ts +3 -0
  100. package/dist/personas/resolver.d.ts.map +1 -1
  101. package/dist/routes/demo.d.ts.map +1 -1
  102. package/dist/routes/openai.d.ts.map +1 -1
  103. package/dist/routes/private.d.ts.map +1 -1
  104. package/dist/routes/public.d.ts.map +1 -1
  105. package/dist/scheduler/compose.d.ts +2 -1
  106. package/dist/scheduler/compose.d.ts.map +1 -1
  107. package/dist/scheduler/index.js +29 -9
  108. package/dist/scheduler/index.mjs +29 -9
  109. package/dist/scheduler/scheduler.d.ts +3 -0
  110. package/dist/scheduler/scheduler.d.ts.map +1 -1
  111. package/dist/scheduler/tick.d.ts +2 -0
  112. package/dist/scheduler/tick.d.ts.map +1 -1
  113. package/dist/server.d.ts.map +1 -1
  114. package/dist/server.js +678 -528
  115. package/dist/server.mjs +678 -528
  116. package/dist/types.d.ts +87 -0
  117. package/dist/types.d.ts.map +1 -1
  118. package/package.json +22 -7
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Compose N transport-specific message detectors behind ONE gate
3
+ * resolution per inbound WhatsApp message.
4
+ *
5
+ * A host running its own interceptors alongside the normal chat reply —
6
+ * a voice-note trigger, a fixed-content reaction, an LLM-extraction reply
7
+ * that replaces the answer — has historically had to re-derive the same
8
+ * boilerplate per interceptor: resolving own-identity jids off the socket,
9
+ * the cross-session loop guard, and the allowlist wiring `decideChannelAction`
10
+ * already owns. Copy-pasted across interceptors, that boilerplate drifts, and
11
+ * nothing stops a handler meant to run *alongside* the real reply
12
+ * (`mode: "parallel"`) from being wired with the *replaces the reply*
13
+ * convention (`mode: "replace"`) instead — silently eating the real answer.
14
+ *
15
+ * `createWhatsAppMessageRouter` resolves identity/gating once via
16
+ * `resolveWaMessage` (the same helper `createWhatsAppInboundHandler` uses),
17
+ * builds one {@link WaDetectorContext} for every detector, then:
18
+ *
19
+ * - fires every matching `"parallel"` detector without awaiting or gating
20
+ * the reply path — a rejection is caught and logged, never surfaces as an
21
+ * unhandled rejection, and never blocks another detector or the fallback.
22
+ * - walks `"replace"` detectors in registration order and stops at the
23
+ * first whose `test` matches, awaiting its `handle` and skipping the
24
+ * fallback entirely; no match runs `fallback` instead.
25
+ *
26
+ * The two modes are distinct at the type level (see {@link MessageDetector}):
27
+ * both `handle` signatures return `Promise<void>`, so there is no
28
+ * boolean-return convention to confuse between them — reaching `handle` at
29
+ * all already means "this detector claims the message" for a `"replace"`
30
+ * detector, or "fire independently" for a `"parallel"` one.
31
+ *
32
+ * `fallback` is deliberately typed the same as `onMessage`/`createWhatsAppInboundHandler`'s
33
+ * return, so an existing single-handler host becomes the router's fallback
34
+ * with zero adaptation — see docs/channels.md. Note that plugging it in this
35
+ * way means `resolveWaMessage` runs a second time inside it (once here for
36
+ * every detector, once more inside the fallback it delegates to) — cheap and
37
+ * side-effect-idempotent (it re-derives the same values and re-writes the
38
+ * same registry entry), but real. "Once per message" below is a guarantee
39
+ * for the detectors, not for whatever the fallback does internally.
40
+ */
41
+ import type { WAMessage, WASocket } from "@whiskeysockets/baileys";
42
+ import { type Logger } from "../../core/logger";
43
+ import { type ChannelAction, type ChannelMessage, type SessionIdentityRegistry } from "../gates";
44
+ import type { WhatsAppMessageEvent } from "./channel";
45
+ /** Everything a detector needs, already resolved — never a raw Baileys shape to re-derive. */
46
+ export interface WaDetectorContext {
47
+ event: WhatsAppMessageEvent;
48
+ sock: WASocket;
49
+ message: WAMessage;
50
+ msg: ChannelMessage;
51
+ /** This session's own identities (phone + LID, normalized). */
52
+ ownIds: string[];
53
+ senderId: string;
54
+ /** The already-extracted, own-mention-stripped text — see `stripOwnMentions`. */
55
+ text: string;
56
+ /**
57
+ * What `decideChannelAction` would do with this message given THIS
58
+ * router's own `allowedChats`/`mutedChats`/`muteRegex`/`unmuteRegex` —
59
+ * informational; detectors decide independently via their own `test`.
60
+ * If `fallback` is `createWhatsAppInboundHandler`, note it keeps its own
61
+ * private mute set — this `action` will not reflect chats muted there
62
+ * unless the same mute inputs are also configured on the router.
63
+ */
64
+ action: ChannelAction;
65
+ }
66
+ interface DetectorBase {
67
+ /** Used only in warning logs when a detector throws. */
68
+ name: string;
69
+ test: (ctx: WaDetectorContext) => boolean | Promise<boolean>;
70
+ }
71
+ /** Fires independently of every other detector and of the fallback — never stops the reply path, never treated as "handled". */
72
+ export interface ParallelDetector extends DetectorBase {
73
+ mode: "parallel";
74
+ handle: (ctx: WaDetectorContext) => Promise<void>;
75
+ }
76
+ /** The first matching `"replace"` detector (in registration order) fully owns the message — its `handle` runs and the fallback is skipped. */
77
+ export interface ReplaceDetector extends DetectorBase {
78
+ mode: "replace";
79
+ handle: (ctx: WaDetectorContext) => Promise<void>;
80
+ }
81
+ export type MessageDetector = ParallelDetector | ReplaceDetector;
82
+ export interface WhatsAppMessageRouterConfig {
83
+ /** Shared across every session and every consumer resolving identity from it — see `resolveWaMessage`. */
84
+ registry: SessionIdentityRegistry;
85
+ /** Same meaning as `WhatsAppInboundConfig.allowedChats`: group chats eligible for routing. Empty (default) = every group; has no effect on DMs. Enforced for every detector and the fallback, independently of message text (a caption-less photo/voice note in a non-allowlisted group is dropped too). */
86
+ allowedChats?: string[];
87
+ /** Only consulted to compute `WaDetectorContext.action`; the router itself doesn't act on mute state. */
88
+ mutedChats?: Set<string>;
89
+ muteRegex?: RegExp;
90
+ unmuteRegex?: RegExp;
91
+ /** Checked in the given order for `"replace"`, independently for `"parallel"`. */
92
+ detectors: MessageDetector[];
93
+ /**
94
+ * Runs when no `"replace"` detector matches. Typed identically to
95
+ * `onMessage`/`createWhatsAppInboundHandler`'s return, so an existing
96
+ * single-handler host plugs in unchanged.
97
+ */
98
+ fallback: (event: WhatsAppMessageEvent) => Promise<void>;
99
+ /** A throwing/rejecting callback here is itself caught and logged at `warn` — it can never produce an unhandled rejection. */
100
+ onDetectorError?: (detectorName: string, error: unknown) => void;
101
+ /** Logger for detector failures. Default: a console logger writing to stderr. */
102
+ logger?: Logger;
103
+ }
104
+ /** Builds the `onMessage` handler for `createWhatsAppChannel` that fans a raw message out to N detectors — see the module docstring. */
105
+ export declare function createWhatsAppMessageRouter(config: WhatsAppMessageRouterConfig): (event: WhatsAppMessageEvent) => Promise<void>;
106
+ export {};
107
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,cAAc,EAEnB,KAAK,uBAAuB,EAC7B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGtD,8FAA8F;AAC9F,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,oBAAoB,CAAC;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,SAAS,CAAC;IACnB,GAAG,EAAE,cAAc,CAAC;IACpB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,UAAU,YAAY;IACpB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED,gIAAgI;AAChI,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,8IAA8I;AAC9I,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAEjE,MAAM,WAAW,2BAA2B;IAC1C,0GAA0G;IAC1G,QAAQ,EAAE,uBAAuB,CAAC;IAClC,4SAA4S;IAC5S,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,yGAAyG;IACzG,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,8HAA8H;IAC9H,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACjE,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAgDD,wIAAwI;AACxI,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,2BAA2B,GAClC,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CA4EhD"}
@@ -30,6 +30,8 @@ export interface AnswerFnInput {
30
30
  mode: PipelineMode;
31
31
  /** Channel-specific sender identity, when the surface knows one */
32
32
  sender?: string;
33
+ /** Stable conversation/thread identifier, when the surface has or generates one */
34
+ conversationId?: string;
33
35
  }
34
36
  /**
35
37
  * What a brain returns: the answer text, optionally with token usage for
@@ -53,13 +55,14 @@ export interface AnswerOptions {
53
55
  messages: PipelineMessage[];
54
56
  mode: PipelineMode;
55
57
  sender?: string;
58
+ conversationId?: string;
56
59
  temperature?: number;
57
60
  model?: string;
58
61
  }
59
62
  /**
60
63
  * Produce one complete answer, from `answerFn` when configured.
61
64
  */
62
- export declare function answerOnce({ answerFn, client, system, messages, mode, sender, temperature, model, }: AnswerOptions): Promise<{
65
+ export declare function answerOnce({ answerFn, client, system, messages, mode, sender, conversationId, temperature, model, }: AnswerOptions): Promise<{
63
66
  content: string;
64
67
  usage: AnswerUsage;
65
68
  }>;
@@ -72,5 +75,5 @@ export declare function answerOnce({ answerFn, client, system, messages, mode, s
72
75
  * built-in stream only scrubs each delta, since leakage detection needs text
73
76
  * no streamed chunk has yet.
74
77
  */
75
- export declare function answerStream({ answerFn, client, system, messages, mode, sender, temperature, model, }: AnswerOptions): AsyncGenerator<string>;
78
+ export declare function answerStream({ answerFn, client, system, messages, mode, sender, conversationId, temperature, model, }: AnswerOptions): AsyncGenerator<string>;
76
79
  //# sourceMappingURL=answer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/core/answer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhE,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAKD,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,uCAAuC;IACvC,IAAI,EAAE,YAAY,CAAC;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBD;;GAEG;AACH,wBAAsB,UAAU,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC,CAKlE;AAED;;;;;;;;GAQG;AACH,wBAAuB,YAAY,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,CAOxC"}
1
+ {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/core/answer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhE,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAKD,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,uCAAuC;IACvC,IAAI,EAAE,YAAY,CAAC;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBD;;GAEG;AACH,wBAAsB,UAAU,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC,CAKlE;AAED;;;;;;;;GAQG;AACH,wBAAuB,YAAY,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,CAaxC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Leveled logging seam. The default implementation never writes to stdout —
3
+ * only stderr — so a host running the stdio MCP transport (which reserves
4
+ * stdout for the JSON-RPC stream) gets a clean channel by default, and so do
5
+ * hosts that simply want their own program output on stdout to stay
6
+ * unpolluted by library logging.
7
+ */
8
+ export type LogLevel = "debug" | "info" | "warn" | "error";
9
+ export interface Logger {
10
+ debug(...args: unknown[]): void;
11
+ info(...args: unknown[]): void;
12
+ warn(...args: unknown[]): void;
13
+ error(...args: unknown[]): void;
14
+ }
15
+ /**
16
+ * Console-backed logger. Every level writes via `console.error` (stderr),
17
+ * gated by `level` (default `"info"`, so `debug` is silent unless a caller
18
+ * opts in) — see the module docstring for why stdout is never touched.
19
+ */
20
+ export declare function createConsoleLogger(level?: LogLevel): Logger;
21
+ /** Resolves a caller-supplied logger, falling back to the default console logger. */
22
+ export declare function resolveLogger(logger?: Logger, level?: LogLevel): Logger;
23
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CACjC;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,QAAiB,GAAG,MAAM,CAcpE;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAEvE"}
@@ -12,6 +12,14 @@
12
12
  * parts, tool calls, objects, numbers) carries no usable text and is skipped.
13
13
  */
14
14
  import type { PipelineMessage } from "./pipeline";
15
+ /**
16
+ * Find the most recent user turn in a conversation. Every chat surface needs
17
+ * this (retrieval query, RAG logging, ...); a shared helper keeps the
18
+ * "search from the end" behaviour from drifting between copies.
19
+ */
20
+ export declare function lastUserMessage<T extends {
21
+ role: string;
22
+ }>(messages: T[]): T | undefined;
15
23
  /**
16
24
  * Normalize a raw `messages` array to the pipeline format.
17
25
  *
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/core/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAWlD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,EAAE,GAAG,IAAI,CA8BxE;AAED,yDAAyD;AACzD,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,eAAe,EAAE,CAAA;CAAE,GACzC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,CA2BnE"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/core/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAWlD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAExF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,EAAE,GAAG,IAAI,CA8BxE;AAED,yDAAyD;AACzD,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,eAAe,EAAE,CAAA;CAAE,GACzC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,CA2BnE"}
@@ -20,6 +20,8 @@ export interface PreparedChat {
20
20
  system: string;
21
21
  /** Conversation messages, unchanged */
22
22
  messages: PipelineMessage[];
23
+ /** Raw retrieved chunks, before being folded into `system` — for callers that log or display RAG context alongside the answer */
24
+ context: string[];
23
25
  }
24
26
  /**
25
27
  * Run retrieval for the latest user message and assemble the system prompt.
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAAC,EAChC,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,GAAG,OAAO,CAAC,YAAY,CAAC,CAmBxB"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,iIAAiI;IACjI,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAAC,EAChC,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,GAAG,OAAO,CAAC,YAAY,CAAC,CAmBxB"}
@@ -1,5 +1,6 @@
1
1
  import { type Client as LibsqlClient } from "@libsql/client";
2
2
  import type OpenAI from "openai";
3
+ import { type Logger } from "./logger";
3
4
  /**
4
5
  * How a {@link VectorStore} obtains its database connection: either credentials
5
6
  * to open its own, or an existing libsql client to reuse.
@@ -11,6 +12,8 @@ import type OpenAI from "openai";
11
12
  export type VectorStoreOptions = {
12
13
  /** Directory of markdown knowledge files. Default: `./config/knowledge` */
13
14
  knowledgeDir?: string;
15
+ /** Logger for build progress. Default: a console logger writing to stderr. */
16
+ logger?: Logger;
14
17
  } & ({
15
18
  /** Turso database URL */
16
19
  databaseUrl: string;
@@ -32,6 +35,7 @@ export declare class VectorStore {
32
35
  */
33
36
  readonly db: LibsqlClient;
34
37
  private knowledgeDir;
38
+ private logger;
35
39
  constructor(client: OpenAI, options: VectorStoreOptions);
36
40
  build(): Promise<void>;
37
41
  private cleanupStaleChunks;
@@ -1 +1 @@
1
- {"version":3,"file":"retrieval.d.ts","sourceRoot":"","sources":["../../src/core/retrieval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AA0BjC;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,CACA;IACE,yBAAyB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,KAAK,CAAC;CACxB,GACD;IACE,gFAAgF;IAChF,cAAc,EAAE,YAAY,CAAC;IAC7B,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,iBAAiB,CAAC,EAAE,KAAK,CAAC;CAC3B,CACJ,CAAC;AAEF,qBAAa,WAAW;IAUpB,OAAO,CAAC,MAAM;IAThB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,OAAO,CAAC,YAAY,CAAS;IAE7B,YACU,MAAM,EAAE,MAAM,EACtB,OAAO,EAAE,kBAAkB,EAS5B;IAGK,KAAK,kBA0FV;YAGa,kBAAkB;IA8BhC,OAAO,CAAC,MAAM,CAAC,MAAM;IAYrB;;;;;;;;;;OAUG;IACG,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,SAAI,EAAE,OAAO,GAAE,MAAM,EAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwB7E;CACF"}
1
+ {"version":3,"file":"retrieval.d.ts","sourceRoot":"","sources":["../../src/core/retrieval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAyB5D;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,CACA;IACE,yBAAyB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,KAAK,CAAC;CACxB,GACD;IACE,gFAAgF;IAChF,cAAc,EAAE,YAAY,CAAC;IAC7B,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,iBAAiB,CAAC,EAAE,KAAK,CAAC;CAC3B,CACJ,CAAC;AAEF,qBAAa,WAAW;IAWpB,OAAO,CAAC,MAAM;IAVhB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAS;IAEvB,YACU,MAAM,EAAE,MAAM,EACtB,OAAO,EAAE,kBAAkB,EAU5B;IAGK,KAAK,kBA0FV;YAGa,kBAAkB;IA8BhC,OAAO,CAAC,MAAM,CAAC,MAAM;IAYrB;;;;;;;;;;OAUG;IACG,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,SAAI,EAAE,OAAO,GAAE,MAAM,EAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwB7E;CACF"}
@@ -5,6 +5,7 @@
5
5
  * channel-specific presentation (see {@link FlowHandlerResult}).
6
6
  */
7
7
  import type OpenAI from "openai";
8
+ import { type Logger } from "../core/logger";
8
9
  import type { FlowResult, FlowSessionStore, LoadedFlow } from "./types";
9
10
  export interface FlowEngineOptions {
10
11
  /** OpenAI client used for intent detection and parameter extraction. */
@@ -36,6 +37,8 @@ export interface FlowEngineOptions {
36
37
  sessionTtlMs?: number;
37
38
  /** Clock, injectable for deterministic tests. */
38
39
  now?: () => number;
40
+ /** Logger for load/intent-detection failures. Default: a console logger writing to stderr. */
41
+ logger?: Logger;
39
42
  }
40
43
  export interface FlowEngine {
41
44
  /** Loads (or reloads) every flow directory under `flowsDir`. */
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/flows/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAKjC,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGxE,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,YAAY,EAAE,gBAAgB,CAAC;IAC/B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,gEAAgE;IAChE,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC5C,WAAW,IAAI,UAAU,EAAE,CAAC;IAC5B,wFAAwF;IACxF,OAAO,CACL,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,+EAA+E;QAC/E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC1C,GACA,OAAO,CAAC,UAAU,CAAC,CAAC;CACxB;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAkKvE"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/flows/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAKlE,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGxE,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,YAAY,EAAE,gBAAgB,CAAC;IAC/B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,gEAAgE;IAChE,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC5C,WAAW,IAAI,UAAU,EAAE,CAAC;IAC5B,wFAAwF;IACxF,OAAO,CACL,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,+EAA+E;QAC/E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC1C,GACA,OAAO,CAAC,UAAU,CAAC,CAAC;CACxB;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA0KvE"}
@@ -21,5 +21,6 @@ export type { FlowRegistry } from "./registry";
21
21
  export { createFlowRegistry } from "./registry";
22
22
  export { createTursoFlowSessionStore } from "./session";
23
23
  export type { FlowDefinition, FlowExtractionResult, FlowHandler, FlowHandlerContext, FlowHandlerResult, FlowPrefill, FlowResult, FlowSchema, FlowSchemaProperty, FlowSessionState, FlowSessionStore, IntentDetection, LoadedFlow, } from "./types";
24
+ export { CURRENT_FLOW_CONTRACT_VERSION } from "./types";
24
25
  export { shouldExitFlow } from "./utils";
25
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/flows/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AACxD,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/flows/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AACxD,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,6BAA6B,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC"}
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/flows/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ CURRENT_FLOW_CONTRACT_VERSION: () => CURRENT_FLOW_CONTRACT_VERSION,
23
24
  createFlowEngine: () => createFlowEngine,
24
25
  createFlowRegistry: () => createFlowRegistry,
25
26
  createTursoFlowSessionStore: () => createTursoFlowSessionStore,
@@ -30,8 +31,24 @@ __export(index_exports, {
30
31
  });
31
32
  module.exports = __toCommonJS(index_exports);
32
33
 
34
+ // src/core/logger.ts
35
+ var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
36
+ function createConsoleLogger(level = "info") {
37
+ const threshold = LEVEL_ORDER[level];
38
+ const write = (at) => (...args) => {
39
+ if (LEVEL_ORDER[at] < threshold) return;
40
+ console.error(`[${at}]`, ...args);
41
+ };
42
+ return {
43
+ debug: write("debug"),
44
+ info: write("info"),
45
+ warn: write("warn"),
46
+ error: write("error")
47
+ };
48
+ }
49
+
33
50
  // src/flows/intent.ts
34
- async function detectIntent(client, model, message, flows, conversationContext) {
51
+ async function detectIntent(client, model, message, flows, conversationContext, logger = createConsoleLogger()) {
35
52
  const contextStr = conversationContext && conversationContext.length > 0 ? `
36
53
 
37
54
  Recent conversation:
@@ -74,7 +91,7 @@ Rules:
74
91
  const confidence = typeof raw.confidence === "number" ? raw.confidence : 0;
75
92
  return { intent, confidence, reasoning: raw.reasoning };
76
93
  } catch (error) {
77
- console.warn(
94
+ logger.warn(
78
95
  `[flows] intent detection failed: ${error instanceof Error ? error.message : String(error)}`
79
96
  );
80
97
  return { intent: "chatbot", confidence: 0, reasoning: "exception, defaulting to chatbot" };
@@ -84,68 +101,108 @@ Rules:
84
101
  // src/flows/loader.ts
85
102
  var import_node_fs = require("node:fs");
86
103
  var import_node_path = require("node:path");
104
+
105
+ // src/flows/types.ts
106
+ var CURRENT_FLOW_CONTRACT_VERSION = 1;
107
+
108
+ // src/flows/loader.ts
87
109
  var SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set(["lib", "tests", "registry"]);
88
- async function loadFlowsFromDirectory(flowsDir) {
110
+ var MODULE_EXTENSIONS = ["ts", "js", "mjs"];
111
+ async function loadFlowsFromDirectory(flowsDir, logger = createConsoleLogger()) {
89
112
  const flows = /* @__PURE__ */ new Map();
90
113
  if (!(0, import_node_fs.existsSync)(flowsDir)) {
91
- console.warn(`[flows] directory does not exist: ${flowsDir}`);
114
+ logger.warn(`[flows] directory does not exist: ${flowsDir}`);
92
115
  return flows;
93
116
  }
94
117
  const flowDirs = (0, import_node_fs.readdirSync)(flowsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).filter((dirent) => !SKIPPED_DIR_NAMES.has(dirent.name)).map((dirent) => dirent.name);
95
118
  for (const flowName of flowDirs) {
96
119
  try {
97
- flows.set(flowName, await loadFlow(flowsDir, flowName));
120
+ flows.set(flowName, await loadFlow(flowsDir, flowName, logger));
98
121
  } catch (error) {
99
- console.warn(
122
+ logger.warn(
100
123
  `[flows] failed to load "${flowName}": ${error instanceof Error ? error.message : String(error)}`
101
124
  );
102
125
  }
103
126
  }
104
127
  return flows;
105
128
  }
106
- async function loadFlow(flowsDir, flowName) {
129
+ function validateFlowDefinition(raw, flowName) {
130
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
131
+ throw new Error(`flow.json for "${flowName}" must be a JSON object`);
132
+ }
133
+ const definition = raw;
134
+ if (typeof definition.id !== "string" || definition.id.length === 0) {
135
+ throw new Error(`flow.json for "${flowName}": "id" must be a non-empty string`);
136
+ }
137
+ if (!Array.isArray(definition.triggerKeywords) || definition.triggerKeywords.length === 0) {
138
+ throw new Error(`flow.json for "${flowName}": "triggerKeywords" must be a non-empty array`);
139
+ }
140
+ const properties = definition.schema?.properties;
141
+ if (!properties || typeof properties !== "object" || Array.isArray(properties) || Object.keys(properties).length === 0) {
142
+ throw new Error(`flow.json for "${flowName}": "schema.properties" must be a non-empty object`);
143
+ }
144
+ if (definition.contractVersion !== void 0) {
145
+ const version = definition.contractVersion;
146
+ if (!Number.isInteger(version) || version < 1 || version > CURRENT_FLOW_CONTRACT_VERSION) {
147
+ throw new Error(
148
+ `flow.json for "${flowName}": "contractVersion" ${JSON.stringify(version)} is not supported (this loader understands versions 1 through ${CURRENT_FLOW_CONTRACT_VERSION})`
149
+ );
150
+ }
151
+ }
152
+ }
153
+ async function loadFlow(flowsDir, flowName, logger) {
107
154
  const flowPath = (0, import_node_path.resolve)(flowsDir, flowName);
108
155
  const definitionPath = (0, import_node_path.join)(flowPath, "flow.json");
109
156
  const instructionsPath = (0, import_node_path.join)(flowPath, "instructions.md");
110
- const handlerPath = (0, import_node_path.join)(flowPath, "handler.ts");
111
157
  if (!(0, import_node_fs.existsSync)(definitionPath)) {
112
158
  throw new Error(`flow.json not found for ${flowName}`);
113
159
  }
114
160
  if (!(0, import_node_fs.existsSync)(instructionsPath)) {
115
161
  throw new Error(`instructions.md not found for ${flowName}`);
116
162
  }
117
- if (!(0, import_node_fs.existsSync)(handlerPath)) {
118
- throw new Error(`handler.ts not found for ${flowName}`);
163
+ const handlerPath = findModulePath(flowPath, "handler");
164
+ if (!handlerPath) {
165
+ throw new Error(
166
+ `no handler found for ${flowName} (looked for ${MODULE_EXTENSIONS.map((ext) => `handler.${ext}`).join(", ")})`
167
+ );
119
168
  }
120
- const definition = JSON.parse((0, import_node_fs.readFileSync)(definitionPath, "utf-8"));
169
+ const rawDefinition = JSON.parse((0, import_node_fs.readFileSync)(definitionPath, "utf-8"));
170
+ validateFlowDefinition(rawDefinition, flowName);
171
+ const definition = {
172
+ ...rawDefinition,
173
+ contractVersion: rawDefinition.contractVersion ?? CURRENT_FLOW_CONTRACT_VERSION
174
+ };
121
175
  if (definition.id !== flowName) {
122
176
  throw new Error(`Flow id mismatch: ${definition.id} !== ${flowName}`);
123
177
  }
124
- if (!definition.triggerKeywords || definition.triggerKeywords.length === 0) {
125
- throw new Error(`Flow ${flowName} has no trigger keywords`);
126
- }
127
- if (!definition.schema || Object.keys(definition.schema.properties ?? {}).length === 0) {
128
- throw new Error(`Flow ${flowName} has no schema`);
129
- }
130
178
  const handlerModule = await import(handlerPath);
131
179
  const handler = handlerModule.execute;
132
180
  if (typeof handler !== "function") {
133
- throw new Error(`Flow ${flowName} handler.ts must export an 'execute' function`);
181
+ throw new Error(`Flow ${flowName} ${(0, import_node_path.basename)(handlerPath)} must export an 'execute' function`);
134
182
  }
135
- const prefillPath = (0, import_node_path.join)(flowPath, "prefill.ts");
183
+ const prefillPath = findModulePath(flowPath, "prefill");
136
184
  let prefill;
137
- if ((0, import_node_fs.existsSync)(prefillPath)) {
185
+ if (prefillPath) {
138
186
  const prefillModule = await import(prefillPath);
139
187
  if (typeof prefillModule.prefillFromContext === "function") {
140
188
  prefill = prefillModule.prefillFromContext;
141
189
  } else {
142
- console.warn(
143
- `[flows] "${flowName}" prefill.ts does not export a prefillFromContext function`
190
+ logger.warn(
191
+ `[flows] "${flowName}" ${(0, import_node_path.basename)(prefillPath)} does not export a prefillFromContext function`
144
192
  );
145
193
  }
146
194
  }
147
195
  return { definition, handler, instructionsPath, prefill };
148
196
  }
197
+ function findModulePath(flowPath, base) {
198
+ for (const ext of MODULE_EXTENSIONS) {
199
+ const candidate = (0, import_node_path.join)(flowPath, `${base}.${ext}`);
200
+ if ((0, import_node_fs.existsSync)(candidate)) {
201
+ return candidate;
202
+ }
203
+ }
204
+ return void 0;
205
+ }
149
206
 
150
207
  // src/flows/params.ts
151
208
  var import_node_fs2 = require("node:fs");
@@ -288,7 +345,8 @@ function createFlowEngine(options) {
288
345
  cancelMessage = DEFAULT_CANCEL_MESSAGE,
289
346
  errorMessage = DEFAULT_ERROR_MESSAGE,
290
347
  sessionTtlMs,
291
- now = Date.now
348
+ now = Date.now,
349
+ logger = createConsoleLogger()
292
350
  } = options;
293
351
  let registry = createFlowRegistry(/* @__PURE__ */ new Map());
294
352
  async function matchFlow(message, conversationContext) {
@@ -297,7 +355,14 @@ function createFlowEngine(options) {
297
355
  const allFlows = registry.getAllFlows();
298
356
  if (allFlows.length === 0) return void 0;
299
357
  const flowMap = new Map(allFlows.map((flow) => [flow.definition.id, flow]));
300
- const detection = await detectIntent(client, model, message, flowMap, conversationContext);
358
+ const detection = await detectIntent(
359
+ client,
360
+ model,
361
+ message,
362
+ flowMap,
363
+ conversationContext,
364
+ logger
365
+ );
301
366
  if (detection.confidence >= minConfidence) {
302
367
  return registry.getFlow(detection.intent);
303
368
  }
@@ -331,7 +396,7 @@ function createFlowEngine(options) {
331
396
  }
332
397
  return {
333
398
  async loadFlows() {
334
- const flows = await loadFlowsFromDirectory(flowsDir);
399
+ const flows = await loadFlowsFromDirectory(flowsDir, logger);
335
400
  registry = createFlowRegistry(flows, criticalKeywords);
336
401
  },
337
402
  getFlow(id) {
@@ -471,6 +536,7 @@ function createTursoFlowSessionStore(client, tableName = "flow_sessions") {
471
536
  }
472
537
  // Annotate the CommonJS export names for ESM import in node:
473
538
  0 && (module.exports = {
539
+ CURRENT_FLOW_CONTRACT_VERSION,
474
540
  createFlowEngine,
475
541
  createFlowRegistry,
476
542
  createTursoFlowSessionStore,