chatwoot-router 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-10-05
11
+
12
+ ### Fixed
13
+
14
+ - Route a resolved ticket that a customer message reopens. Chatwoot reopens it as pending in a bot inbox and keeps
15
+ its assignee, and the router left every pending ticket with a person assignee alone, so such a ticket stayed
16
+ pending: off people's open view and never handed back. Its turn is now routed with its owner kept: a kind with a
17
+ status still ends it (a thanks-only message can be resolved), and anything else, a greeting included, goes back
18
+ to the owner as open. Only another bot's pending tickets are left alone.
19
+
10
20
  ## [0.4.0] - 2026-10-04
11
21
 
12
22
  ### Changed
@@ -102,7 +112,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
102
112
  `routing_kind`.
103
113
  - The `chatwoot-router-store-config` command and `chatwoot-router/stored-config`, for a configuration in KV.
104
114
 
105
- [Unreleased]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.4.0...HEAD
115
+ [Unreleased]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.5.0...HEAD
116
+ [0.5.0]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.4.0...chatwoot-router@0.5.0
106
117
  [0.4.0]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.3.0...chatwoot-router@0.4.0
107
118
  [0.3.0]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.2.0...chatwoot-router@0.3.0
108
119
  [0.2.0]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.1.0...chatwoot-router@0.2.0
package/README.md CHANGED
@@ -17,7 +17,9 @@ is each routed account's native agent bot, connected to the inboxes it should ro
17
17
  may point to another deployment) for the owner, topic, kind, and whether there is a request yet.
18
18
  3. It applies the topic and kind labels, sends the kind's canned reply if it has one (at most once per
19
19
  conversation), and ends the turn: the kind's resolved or snoozed status, the confident owner, or a handoff to
20
- people (`status=open`). A greeting with no request yet stays pending until the customer says more.
20
+ people (`status=open`). A greeting with no request yet stays pending until the customer says more. A resolved
21
+ ticket that a customer message reopens keeps its owner: its kind may still end it (a thanks is resolved),
22
+ otherwise it goes back to the owner.
21
23
  4. Anything uncertain hands off to people: an unclear owner, a failed or unconfirmed reply, a human replying,
22
24
  three failed attempts. Disconnecting an inbox's bot hands its pending tickets to people.
23
25
  5. A sweep every five minutes lists pending and open conversations, so a missed webhook is caught up and a bot
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
- export { ConfigError } from "../../../shared/config.ts";
3
2
  export declare const configSchema: z.ZodObject<{
4
3
  chatwoot: z.ZodObject<{
5
4
  baseUrl: z.ZodURL;
@@ -39,3 +38,4 @@ export interface Settings {
39
38
  }
40
39
  export declare function parseSettings(rawConfig: unknown, rawSecrets: object): Settings;
41
40
  export declare function buildSettings(config: Config, secrets: Secrets): Settings;
41
+ export {};
@@ -115,6 +115,11 @@ var ChatwootError = class extends Error {
115
115
  this.conversationMissing = conversationMissing;
116
116
  }
117
117
  };
118
+ function snoozedUntil(conversation) {
119
+ const value = conversation.snoozed_until;
120
+ const ms = typeof value === "string" ? Date.parse(value) : Number.NaN;
121
+ return Number.isNaN(ms) ? null : Math.floor(ms / 1e3);
122
+ }
118
123
  var text = z2.string().nullish();
119
124
  var attachmentSchema = z2.object({
120
125
  file_type: text,
@@ -433,6 +438,8 @@ function toRelayConversation(conversationId, conversation) {
433
438
  },
434
439
  assignee: assignee ? { id: assignee.id, name: assignee.name } : null,
435
440
  assigneeType: meta?.assignee_type ?? null,
441
+ waitingSince: conversation.waiting_since || null,
442
+ snoozedUntil: snoozedUntil(conversation),
436
443
  customAttributes: conversation.custom_attributes ?? {}
437
444
  };
438
445
  }
@@ -781,8 +788,7 @@ async function routeConversation(ctx, accountId2, conversationId) {
781
788
  return;
782
789
  }
783
790
  const conversation = toRelayConversation(conversationId, raw);
784
- if (conversation.assignee) return;
785
- if (raw.meta?.assignee && (raw.meta.assignee_type !== "AgentBot" || raw.meta.assignee.id !== botId)) return;
791
+ if (raw.meta?.assignee_type === "AgentBot" && raw.meta.assignee?.id !== botId) return;
786
792
  if ((await chatwoot.inboxBot(accountId2, raw.inbox_id))?.id !== botId) {
787
793
  if (owns(raw)) {
788
794
  const latest = await chatwoot.getConversation(accountId2, conversationId);
@@ -844,7 +850,7 @@ async function routeConversation(ctx, accountId2, conversationId) {
844
850
  let current = await fresh();
845
851
  if (current === "defer" || !current) return current;
846
852
  if (current.handoff) return handoff(current);
847
- if (!kind && decision.noRequest && input.count < MAX_MESSAGES) return;
853
+ if (!kind && decision.noRequest && input.count < MAX_MESSAGES && !current.conversation.assignee) return;
848
854
  const topic = !kind?.status && decision.topic !== null && Object.hasOwn(routing.topics ?? {}, decision.topic) && decision.topicConfidence >= routing.minConfidence && current.conversation.labels.every((label) => Object.hasOwn(kinds, label)) ? decision.topic : null;
849
855
  const labels = [
850
856
  .../* @__PURE__ */ new Set([
@@ -12,11 +12,14 @@ export declare class ChatwootError extends Error {
12
12
  * A conversation as returned by GET conversations/{id} and in the conversation list. The spec's
13
13
  * `status` enum omits `snoozed`, which the API does return, so the relay treats it as a string.
14
14
  */
15
- export type ChatwootConversation = components["schemas"]["conversation_show"] & {
15
+ export type ChatwootConversation = Omit<components["schemas"]["conversation_show"], "snoozed_until"> & {
16
16
  meta?: {
17
17
  assignee_type?: string | null;
18
18
  };
19
+ snoozed_until?: string | number | null;
19
20
  };
21
+ /** Unix seconds when a snoozed conversation reopens; null unless it is snoozed until a time. */
22
+ export declare function snoozedUntil(conversation: ChatwootConversation): number | null;
20
23
  declare const messageSchema: z.ZodObject<{
21
24
  id: z.ZodNumber;
22
25
  conversation_id: z.ZodOptional<z.ZodNumber>;
@@ -51,6 +51,10 @@ export interface RelayConversation {
51
51
  };
52
52
  /** Person only; agent bots are represented as unassigned. */
53
53
  assignee?: RelayAssignee | null;
54
+ /** Unix seconds since the customer has waited for a reply (Chatwoot's `waiting_since`); null when they do not. */
55
+ waitingSince?: number | null;
56
+ /** Unix seconds when a snoozed conversation reopens; null when not snoozed or snoozed until the next reply. */
57
+ snoozedUntil?: number | null;
54
58
  assigneeType?: string | null;
55
59
  customAttributes: Record<string, unknown>;
56
60
  }
@@ -66,8 +70,8 @@ export interface RelayMessage {
66
70
  emailSubject?: string | null;
67
71
  /** An automatic email reply (out of office, for example): relayed without notifications. */
68
72
  autoReply?: boolean;
69
- /** A qualifying public reply follows this message in Chatwoot. */
70
- answered?: boolean;
73
+ /** A public reply that answers the customer (see isAnsweringReply). */
74
+ answers?: boolean;
71
75
  attachments: RelayAttachment[];
72
76
  /** A bot's options to pick, cards, or articles, with a link each when they have one. */
73
77
  items?: RelayItem[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatwoot-router",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Route Chatwoot conversations with TypeSafe Jev on Cloudflare Workers.",
5
5
  "keywords": [
6
6
  "chatwoot",