jinn-cli 0.5.2 → 0.6.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 (147) hide show
  1. package/dist/bin/jimmy.js +0 -0
  2. package/dist/src/cli/status.d.ts.map +1 -1
  3. package/dist/src/cli/status.js +10 -1
  4. package/dist/src/cli/status.js.map +1 -1
  5. package/dist/src/connectors/discord/format.d.ts +3 -0
  6. package/dist/src/connectors/discord/format.d.ts.map +1 -0
  7. package/dist/src/connectors/discord/format.js +33 -0
  8. package/dist/src/connectors/discord/format.js.map +1 -0
  9. package/dist/src/connectors/discord/index.d.ts +41 -0
  10. package/dist/src/connectors/discord/index.d.ts.map +1 -0
  11. package/dist/src/connectors/discord/index.js +302 -0
  12. package/dist/src/connectors/discord/index.js.map +1 -0
  13. package/dist/src/connectors/discord/remote.d.ts +33 -0
  14. package/dist/src/connectors/discord/remote.d.ts.map +1 -0
  15. package/dist/src/connectors/discord/remote.js +89 -0
  16. package/dist/src/connectors/discord/remote.js.map +1 -0
  17. package/dist/src/connectors/discord/threads.d.ts +5 -0
  18. package/dist/src/connectors/discord/threads.d.ts.map +1 -0
  19. package/dist/src/connectors/discord/threads.js +22 -0
  20. package/dist/src/connectors/discord/threads.js.map +1 -0
  21. package/dist/src/connectors/whatsapp/format.d.ts +2 -0
  22. package/dist/src/connectors/whatsapp/format.d.ts.map +1 -0
  23. package/dist/src/connectors/whatsapp/format.js +22 -0
  24. package/dist/src/connectors/whatsapp/format.js.map +1 -0
  25. package/dist/src/connectors/whatsapp/index.d.ts +42 -0
  26. package/dist/src/connectors/whatsapp/index.d.ts.map +1 -0
  27. package/dist/src/connectors/whatsapp/index.js +279 -0
  28. package/dist/src/connectors/whatsapp/index.js.map +1 -0
  29. package/dist/src/engines/claude.d.ts +5 -0
  30. package/dist/src/engines/claude.d.ts.map +1 -1
  31. package/dist/src/engines/claude.js +192 -22
  32. package/dist/src/engines/claude.js.map +1 -1
  33. package/dist/src/gateway/api.d.ts +1 -0
  34. package/dist/src/gateway/api.d.ts.map +1 -1
  35. package/dist/src/gateway/api.js +705 -18
  36. package/dist/src/gateway/api.js.map +1 -1
  37. package/dist/src/gateway/lifecycle.d.ts.map +1 -1
  38. package/dist/src/gateway/lifecycle.js +32 -15
  39. package/dist/src/gateway/lifecycle.js.map +1 -1
  40. package/dist/src/gateway/project-tagger.d.ts +20 -0
  41. package/dist/src/gateway/project-tagger.d.ts.map +1 -0
  42. package/dist/src/gateway/project-tagger.js +55 -0
  43. package/dist/src/gateway/project-tagger.js.map +1 -0
  44. package/dist/src/gateway/project-tagger.test.d.ts +2 -0
  45. package/dist/src/gateway/project-tagger.test.d.ts.map +1 -0
  46. package/dist/src/gateway/project-tagger.test.js +110 -0
  47. package/dist/src/gateway/project-tagger.test.js.map +1 -0
  48. package/dist/src/gateway/projects.d.ts +15 -0
  49. package/dist/src/gateway/projects.d.ts.map +1 -0
  50. package/dist/src/gateway/projects.js +48 -0
  51. package/dist/src/gateway/projects.js.map +1 -0
  52. package/dist/src/gateway/projects.test.d.ts +2 -0
  53. package/dist/src/gateway/projects.test.d.ts.map +1 -0
  54. package/dist/src/gateway/projects.test.js +85 -0
  55. package/dist/src/gateway/projects.test.js.map +1 -0
  56. package/dist/src/gateway/server.d.ts.map +1 -1
  57. package/dist/src/gateway/server.js +90 -14
  58. package/dist/src/gateway/server.js.map +1 -1
  59. package/dist/src/gateway/tasks.d.ts +14 -0
  60. package/dist/src/gateway/tasks.d.ts.map +1 -0
  61. package/dist/src/gateway/tasks.js +51 -0
  62. package/dist/src/gateway/tasks.js.map +1 -0
  63. package/dist/src/gateway/tasks.test.d.ts +2 -0
  64. package/dist/src/gateway/tasks.test.d.ts.map +1 -0
  65. package/dist/src/gateway/tasks.test.js +131 -0
  66. package/dist/src/gateway/tasks.test.js.map +1 -0
  67. package/dist/src/mcp/resolver.d.ts.map +1 -1
  68. package/dist/src/mcp/resolver.js +8 -2
  69. package/dist/src/mcp/resolver.js.map +1 -1
  70. package/dist/src/sessions/callbacks.d.ts +29 -0
  71. package/dist/src/sessions/callbacks.d.ts.map +1 -0
  72. package/dist/src/sessions/callbacks.js +110 -0
  73. package/dist/src/sessions/callbacks.js.map +1 -0
  74. package/dist/src/sessions/context.js +4 -3
  75. package/dist/src/sessions/context.js.map +1 -1
  76. package/dist/src/sessions/engine-override.d.ts +3 -0
  77. package/dist/src/sessions/engine-override.d.ts.map +1 -0
  78. package/dist/src/sessions/engine-override.js +42 -0
  79. package/dist/src/sessions/engine-override.js.map +1 -0
  80. package/dist/src/sessions/manager.d.ts.map +1 -1
  81. package/dist/src/sessions/manager.js +385 -40
  82. package/dist/src/sessions/manager.js.map +1 -1
  83. package/dist/src/sessions/queue.d.ts +19 -2
  84. package/dist/src/sessions/queue.d.ts.map +1 -1
  85. package/dist/src/sessions/queue.js +44 -13
  86. package/dist/src/sessions/queue.js.map +1 -1
  87. package/dist/src/sessions/registry.d.ts +21 -1
  88. package/dist/src/sessions/registry.d.ts.map +1 -1
  89. package/dist/src/sessions/registry.js +62 -0
  90. package/dist/src/sessions/registry.js.map +1 -1
  91. package/dist/src/shared/rateLimit.d.ts +13 -0
  92. package/dist/src/shared/rateLimit.d.ts.map +1 -0
  93. package/dist/src/shared/rateLimit.js +30 -0
  94. package/dist/src/shared/rateLimit.js.map +1 -0
  95. package/dist/src/shared/types.d.ts +60 -5
  96. package/dist/src/shared/types.d.ts.map +1 -1
  97. package/dist/src/shared/usageAwareness.d.ts +10 -0
  98. package/dist/src/shared/usageAwareness.d.ts.map +1 -0
  99. package/dist/src/shared/usageAwareness.js +62 -0
  100. package/dist/src/shared/usageAwareness.js.map +1 -0
  101. package/dist/web/404.html +1 -1
  102. package/dist/web/_next/static/chunks/184-5a617386af9a1dd3.js +1 -0
  103. package/dist/web/_next/static/chunks/700-ad04a2a5b3c8880f.js +1 -0
  104. package/dist/web/_next/static/chunks/app/chat/page-36edb6fc1d1e880b.js +1 -0
  105. package/dist/web/_next/static/chunks/app/costs/{page-7940c2fe7e3dace1.js → page-6c5cd46a6b3cde21.js} +1 -1
  106. package/dist/web/_next/static/chunks/app/cron/{page-f81a986689712af7.js → page-210d9d333a7eed94.js} +1 -1
  107. package/dist/web/_next/static/chunks/app/kanban/page-c32370bcd6a7c841.js +1 -0
  108. package/dist/web/_next/static/chunks/app/layout-3ad6b73a0904c24b.js +1 -0
  109. package/dist/web/_next/static/chunks/app/logs/page-7322a6789e16dca4.js +1 -0
  110. package/dist/web/_next/static/chunks/app/org/{page-3d44d51e94edb85e.js → page-02263c5702e0fd3e.js} +1 -1
  111. package/dist/web/_next/static/chunks/app/{page-7ac43789d477a51f.js → page-b68dcf7b8802c704.js} +1 -1
  112. package/dist/web/_next/static/chunks/app/sessions/page-bf7ad2fac281c7d6.js +1 -0
  113. package/dist/web/_next/static/chunks/app/settings/page-d3b89563c42be2e5.js +1 -0
  114. package/dist/web/_next/static/chunks/app/skills/{page-26b727333df9db45.js → page-194f4e97f29ab581.js} +1 -1
  115. package/dist/web/_next/static/css/b809b6af2d241fc8.css +1 -0
  116. package/dist/web/chat.html +1 -1
  117. package/dist/web/chat.txt +4 -4
  118. package/dist/web/costs.html +2 -2
  119. package/dist/web/costs.txt +4 -4
  120. package/dist/web/cron.html +1 -1
  121. package/dist/web/cron.txt +4 -4
  122. package/dist/web/index.html +1 -1
  123. package/dist/web/index.txt +4 -4
  124. package/dist/web/kanban.html +1 -1
  125. package/dist/web/kanban.txt +4 -4
  126. package/dist/web/logs.html +2 -2
  127. package/dist/web/logs.txt +4 -4
  128. package/dist/web/org.html +1 -1
  129. package/dist/web/org.txt +4 -4
  130. package/dist/web/sessions.html +1 -1
  131. package/dist/web/sessions.txt +14 -17
  132. package/dist/web/settings.html +1 -1
  133. package/dist/web/settings.txt +4 -4
  134. package/dist/web/skills.html +1 -1
  135. package/dist/web/skills.txt +4 -4
  136. package/package.json +6 -1
  137. package/dist/web/_next/static/chunks/282-4e9c26e9a600c58e.js +0 -1
  138. package/dist/web/_next/static/chunks/700-a7cbf54fe1fbf4bc.js +0 -1
  139. package/dist/web/_next/static/chunks/app/chat/page-757fcd211d059cb7.js +0 -1
  140. package/dist/web/_next/static/chunks/app/kanban/page-6ab8586b063ca3ac.js +0 -1
  141. package/dist/web/_next/static/chunks/app/layout-c24e2d25774ff71a.js +0 -1
  142. package/dist/web/_next/static/chunks/app/logs/page-388b787cb847ca97.js +0 -1
  143. package/dist/web/_next/static/chunks/app/sessions/page-18757fcd067b7e9d.js +0 -1
  144. package/dist/web/_next/static/chunks/app/settings/page-f62176848534f90a.js +0 -1
  145. package/dist/web/_next/static/css/bd612b1ca9b40306.css +0 -1
  146. /package/dist/web/_next/static/{J4YFiPdzNcFHieP2FIPPe → QrKxazgwMrykF2yLwDvUM}/_buildManifest.js +0 -0
  147. /package/dist/web/_next/static/{J4YFiPdzNcFHieP2FIPPe → QrKxazgwMrykF2yLwDvUM}/_ssgManifest.js +0 -0
@@ -0,0 +1,22 @@
1
+ const WA_MAX_LENGTH = 4000;
2
+ export function formatResponse(text) {
3
+ if (text.length <= WA_MAX_LENGTH)
4
+ return [text];
5
+ const chunks = [];
6
+ let remaining = text;
7
+ while (remaining.length > 0) {
8
+ if (remaining.length <= WA_MAX_LENGTH) {
9
+ chunks.push(remaining);
10
+ break;
11
+ }
12
+ let cutAt = remaining.lastIndexOf("\n", WA_MAX_LENGTH);
13
+ if (cutAt <= 0)
14
+ cutAt = remaining.lastIndexOf(" ", WA_MAX_LENGTH);
15
+ if (cutAt <= 0)
16
+ cutAt = WA_MAX_LENGTH;
17
+ chunks.push(remaining.slice(0, cutAt));
18
+ remaining = remaining.slice(cutAt).trimStart();
19
+ }
20
+ return chunks;
21
+ }
22
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../../../src/connectors/whatsapp/format.ts"],"names":[],"mappings":"AAAA,MAAM,aAAa,GAAG,IAAI,CAAC;AAE3B,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,IAAI,CAAC,MAAM,IAAI,aAAa;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,SAAS,GAAG,IAAI,CAAC;IACrB,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,IAAI,SAAS,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAAC,MAAM;QAAC,CAAC;QACzE,IAAI,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QACvD,IAAI,KAAK,IAAI,CAAC;YAAE,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QAClE,IAAI,KAAK,IAAI,CAAC;YAAE,KAAK,GAAG,aAAa,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QACvC,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC;IACjD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { Connector, ConnectorCapabilities, ConnectorHealth, IncomingMessage, Target } from "../../shared/types.js";
2
+ export interface WhatsAppConnectorConfig {
3
+ /** Where to store session credentials (default: JINN_HOME/.whatsapp-auth) */
4
+ authDir?: string;
5
+ /** Allowed phone numbers in JID format (e.g. "447700900000@s.whatsapp.net") — empty = allow all */
6
+ allowFrom?: string[];
7
+ ignoreOldMessagesOnBoot?: boolean;
8
+ }
9
+ export declare class WhatsAppConnector implements Connector {
10
+ name: string;
11
+ private sock;
12
+ private config;
13
+ private handler;
14
+ private bootTimeMs;
15
+ private allowedJids;
16
+ private connectionStatus;
17
+ private lastError;
18
+ private authDir;
19
+ private latestQr;
20
+ private reconnectAttempts;
21
+ private reconnectTimer;
22
+ private typingIntervals;
23
+ private readonly capabilities;
24
+ constructor(config: WhatsAppConnectorConfig);
25
+ onMessage(handler: (msg: IncomingMessage) => void): void;
26
+ start(): Promise<void>;
27
+ private scheduleReconnect;
28
+ private connect;
29
+ stop(): Promise<void>;
30
+ getCapabilities(): ConnectorCapabilities;
31
+ getQrCode(): string | null;
32
+ getHealth(): ConnectorHealth;
33
+ reconstructTarget(replyContext: Record<string, unknown> | null | undefined): Target;
34
+ sendMessage(target: Target, text: string): Promise<string | undefined>;
35
+ replyMessage(target: Target, text: string): Promise<string | undefined>;
36
+ editMessage(_target: Target, _text: string): Promise<void>;
37
+ addReaction(_target: Target, _emoji: string): Promise<void>;
38
+ removeReaction(_target: Target, _emoji: string): Promise<void>;
39
+ setTypingStatus(channelId: string, _threadTs: string | undefined, status: string): Promise<void>;
40
+ private handleMessage;
41
+ }
42
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/connectors/whatsapp/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,SAAS,EACT,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,MAAM,EACP,MAAM,uBAAuB,CAAC;AAO/B,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAaD,qBAAa,iBAAkB,YAAW,SAAS;IACjD,IAAI,SAAc;IAClB,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,OAAO,CAAiD;IAChE,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,gBAAgB,CAA2E;IACnG,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,eAAe,CAAqD;IAE5E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAK3B;gBAEU,MAAM,EAAE,uBAAuB;IAO3C,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IAIlD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,CAAC,iBAAiB;YASX,OAAO;IAqDf,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,eAAe,IAAI,qBAAqB;IAIxC,SAAS,IAAI,MAAM,GAAG,IAAI;IAI1B,SAAS,IAAI,eAAe;IAa5B,iBAAiB,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM;IAS7E,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAItE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAavE,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAmBxF,aAAa;CA0F5B"}
@@ -0,0 +1,279 @@
1
+ import makeWASocketImport, { Browsers, DisconnectReason, fetchLatestWaWebVersion, useMultiFileAuthState, downloadMediaMessage, } from "@whiskeysockets/baileys";
2
+ // Handle ESM/CJS interop — Baileys may export as .default in some environments
3
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
+ const makeWASocket = (makeWASocketImport.default ?? makeWASocketImport);
5
+ import { logger } from "../../shared/logger.js";
6
+ import { JINN_HOME } from "../../shared/paths.js";
7
+ import { formatResponse } from "./format.js";
8
+ import path from "node:path";
9
+ import fs from "node:fs";
10
+ // Minimal ILogger implementation that routes Baileys noise to silence
11
+ const silentLogger = {
12
+ level: "silent",
13
+ child: () => silentLogger,
14
+ trace: () => { },
15
+ debug: () => { },
16
+ info: () => { },
17
+ warn: () => { },
18
+ error: () => { },
19
+ };
20
+ export class WhatsAppConnector {
21
+ name = "whatsapp";
22
+ sock = null;
23
+ config;
24
+ handler = null;
25
+ bootTimeMs = Date.now();
26
+ allowedJids;
27
+ connectionStatus = "starting";
28
+ lastError = null;
29
+ authDir;
30
+ latestQr = null;
31
+ reconnectAttempts = 0;
32
+ reconnectTimer = null;
33
+ typingIntervals = new Map();
34
+ capabilities = {
35
+ threading: false,
36
+ messageEdits: false,
37
+ reactions: false,
38
+ attachments: true,
39
+ };
40
+ constructor(config) {
41
+ this.config = config;
42
+ this.authDir = config.authDir ?? path.join(JINN_HOME, ".whatsapp-auth");
43
+ this.allowedJids = new Set(config.allowFrom ?? []);
44
+ fs.mkdirSync(this.authDir, { recursive: true });
45
+ }
46
+ onMessage(handler) {
47
+ this.handler = handler;
48
+ }
49
+ async start() {
50
+ await this.connect();
51
+ }
52
+ scheduleReconnect() {
53
+ if (this.connectionStatus === "stopped")
54
+ return;
55
+ // Exponential backoff: 5s, 10s, 20s, 40s, 60s max
56
+ const delay = Math.min(5000 * Math.pow(2, this.reconnectAttempts), 60000);
57
+ this.reconnectAttempts++;
58
+ logger.info(`WhatsApp reconnecting in ${delay / 1000}s (attempt ${this.reconnectAttempts})`);
59
+ this.reconnectTimer = setTimeout(() => this.connect(), delay);
60
+ }
61
+ async connect() {
62
+ const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
63
+ // Fetch latest WA Web version to avoid 405 rejections from outdated version
64
+ const { version } = await fetchLatestWaWebVersion().catch(() => ({ version: undefined }));
65
+ this.sock = makeWASocket({
66
+ auth: state,
67
+ printQRInTerminal: false,
68
+ logger: silentLogger,
69
+ browser: Browsers.macOS("Chrome"),
70
+ ...(version ? { version } : {}),
71
+ });
72
+ this.sock.ev.on("creds.update", saveCreds);
73
+ this.sock.ev.on("connection.update", ({ connection, lastDisconnect, qr }) => {
74
+ if (qr) {
75
+ this.latestQr = qr;
76
+ this.connectionStatus = "qr_pending";
77
+ logger.info("WhatsApp QR code generated — scan with your WhatsApp app to connect");
78
+ }
79
+ if (connection === "open") {
80
+ this.latestQr = null;
81
+ this.connectionStatus = "running";
82
+ this.lastError = null;
83
+ this.reconnectAttempts = 0;
84
+ logger.info("WhatsApp connector connected");
85
+ }
86
+ if (connection === "close") {
87
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
88
+ const isLoggedOut = statusCode === DisconnectReason.loggedOut;
89
+ logger.info(`WhatsApp connection closed (${statusCode}), reconnecting: ${!isLoggedOut}`);
90
+ if (!isLoggedOut && this.connectionStatus !== "stopped") {
91
+ this.scheduleReconnect();
92
+ }
93
+ else {
94
+ this.connectionStatus = "stopped";
95
+ }
96
+ }
97
+ });
98
+ this.sock.ev.on("messages.upsert", async ({ messages, type }) => {
99
+ if (type !== "notify")
100
+ return;
101
+ for (const message of messages) {
102
+ try {
103
+ await this.handleMessage(message);
104
+ }
105
+ catch (err) {
106
+ logger.error(`WhatsApp message handler error: ${err instanceof Error ? err.message : err}`);
107
+ }
108
+ }
109
+ });
110
+ }
111
+ async stop() {
112
+ this.connectionStatus = "stopped";
113
+ if (this.reconnectTimer) {
114
+ clearTimeout(this.reconnectTimer);
115
+ this.reconnectTimer = null;
116
+ }
117
+ await this.sock?.end(undefined);
118
+ logger.info("WhatsApp connector stopped");
119
+ }
120
+ getCapabilities() {
121
+ return this.capabilities;
122
+ }
123
+ getQrCode() {
124
+ return this.latestQr;
125
+ }
126
+ getHealth() {
127
+ let status = "stopped";
128
+ if (this.connectionStatus === "running")
129
+ status = "running";
130
+ else if (this.connectionStatus === "qr_pending")
131
+ status = "qr_pending";
132
+ return {
133
+ status,
134
+ detail: this.connectionStatus === "qr_pending"
135
+ ? "Scan QR code in settings to connect"
136
+ : (this.lastError ?? undefined),
137
+ capabilities: this.capabilities,
138
+ };
139
+ }
140
+ reconstructTarget(replyContext) {
141
+ const ctx = (replyContext ?? {});
142
+ return {
143
+ channel: (typeof ctx.channel === "string" ? ctx.channel : "") ?? "",
144
+ thread: undefined,
145
+ messageTs: typeof ctx.messageTs === "string" ? ctx.messageTs : undefined,
146
+ };
147
+ }
148
+ async sendMessage(target, text) {
149
+ return this.replyMessage(target, text);
150
+ }
151
+ async replyMessage(target, text) {
152
+ if (!this.sock || this.connectionStatus !== "running")
153
+ return;
154
+ try {
155
+ const chunks = formatResponse(text);
156
+ for (const chunk of chunks) {
157
+ await this.sock.sendMessage(target.channel, { text: chunk });
158
+ }
159
+ }
160
+ catch (err) {
161
+ logger.error(`WhatsApp replyMessage error: ${err instanceof Error ? err.message : err}`);
162
+ }
163
+ return undefined;
164
+ }
165
+ async editMessage(_target, _text) {
166
+ // WhatsApp doesn't support editing via Baileys reliably — no-op
167
+ }
168
+ async addReaction(_target, _emoji) {
169
+ // No-op: reactions are supported in WA but complex to map from Slack emoji names
170
+ }
171
+ async removeReaction(_target, _emoji) {
172
+ // No-op
173
+ }
174
+ async setTypingStatus(channelId, _threadTs, status) {
175
+ if (!this.sock || this.connectionStatus !== "running")
176
+ return;
177
+ const existing = this.typingIntervals.get(channelId);
178
+ if (existing) {
179
+ clearInterval(existing);
180
+ this.typingIntervals.delete(channelId);
181
+ }
182
+ if (status) {
183
+ // Show "typing..." and refresh every 20s (WhatsApp composing expires ~25s)
184
+ await this.sock.sendPresenceUpdate("composing", channelId).catch(() => { });
185
+ const interval = setInterval(() => {
186
+ this.sock?.sendPresenceUpdate("composing", channelId).catch(() => { });
187
+ }, 20_000);
188
+ this.typingIntervals.set(channelId, interval);
189
+ }
190
+ else {
191
+ await this.sock.sendPresenceUpdate("paused", channelId).catch(() => { });
192
+ }
193
+ }
194
+ async handleMessage(message) {
195
+ const jid = message.key.remoteJid;
196
+ const ownJid = this.sock?.user?.id ?? "";
197
+ const ownJidBare = ownJid.split(":")[0] + "@s.whatsapp.net";
198
+ // WhatsApp now uses LID (Linked ID) format — extract from sock.user.lid
199
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
200
+ const ownLid = this.sock?.user?.lid ?? "";
201
+ const ownLidBare = ownLid ? ownLid.split(":")[0] + "@lid" : "";
202
+ if (!jid)
203
+ return;
204
+ if (jid.endsWith("@g.us"))
205
+ return;
206
+ if (jid.endsWith("@newsletter"))
207
+ return;
208
+ if (jid.endsWith("@broadcast"))
209
+ return;
210
+ // Allow "note to self" — matches own phone JID or own LID
211
+ const isSelfChat = message.key.fromMe && (jid === ownJidBare || jid === ownJid ||
212
+ jid === ownLidBare || jid === ownLid);
213
+ if (message.key.fromMe && !isSelfChat)
214
+ return;
215
+ const msgTimestampMs = Number(message.messageTimestamp ?? 0) * 1000;
216
+ if (this.config.ignoreOldMessagesOnBoot !== false &&
217
+ msgTimestampMs < this.bootTimeMs)
218
+ return;
219
+ // If no allowFrom configured, only self-chat is allowed.
220
+ // If allowFrom is set, also allow those specific JIDs.
221
+ if (!isSelfChat && (this.allowedJids.size === 0 || !this.allowedJids.has(jid)))
222
+ return;
223
+ if (!this.handler)
224
+ return;
225
+ const text = message.message?.conversation ||
226
+ message.message?.extendedTextMessage?.text ||
227
+ message.message?.imageMessage?.caption ||
228
+ message.message?.documentMessage?.caption ||
229
+ "";
230
+ if (!text.trim())
231
+ return;
232
+ logger.info(`WhatsApp message received from ${jid}`);
233
+ // Download media attachment if present
234
+ const attachments = [];
235
+ const hasMedia = message.message?.imageMessage || message.message?.documentMessage || message.message?.audioMessage;
236
+ if (hasMedia && this.sock) {
237
+ try {
238
+ const buffer = await downloadMediaMessage(message, "buffer", {}, {
239
+ logger: silentLogger,
240
+ reuploadRequest: this.sock.updateMediaMessage,
241
+ });
242
+ const ext = message.message?.imageMessage ? "jpg"
243
+ : message.message?.audioMessage ? "ogg"
244
+ : "bin";
245
+ const filename = `wa-attachment-${message.key.id}.${ext}`;
246
+ const tmpDir = path.join(JINN_HOME, "tmp");
247
+ const localPath = path.join(tmpDir, filename);
248
+ fs.mkdirSync(tmpDir, { recursive: true });
249
+ fs.writeFileSync(localPath, buffer);
250
+ const mimeType = ext === "jpg" ? "image/jpeg"
251
+ : ext === "ogg" ? "audio/ogg"
252
+ : "application/octet-stream";
253
+ attachments.push({ name: filename, localPath, mimeType, url: localPath });
254
+ }
255
+ catch {
256
+ // Non-fatal: continue without attachment
257
+ }
258
+ }
259
+ const sessionKey = `whatsapp:${jid}`;
260
+ const replyContext = { channel: jid, thread: null, messageTs: message.key.id ?? null };
261
+ const incomingMessage = {
262
+ connector: "whatsapp",
263
+ source: "whatsapp",
264
+ sessionKey,
265
+ channel: jid,
266
+ thread: undefined,
267
+ user: jid.replace("@s.whatsapp.net", ""),
268
+ userId: jid,
269
+ text,
270
+ attachments,
271
+ replyContext,
272
+ messageId: message.key.id ?? undefined,
273
+ transportMeta: { jid },
274
+ raw: message,
275
+ };
276
+ this.handler(incomingMessage);
277
+ }
278
+ }
279
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/connectors/whatsapp/index.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,12 @@ export declare class ClaudeEngine implements InterruptibleEngine {
8
8
  run(opts: EngineRunOpts): Promise<EngineResult>;
9
9
  private runOnce;
10
10
  private processStreamLine;
11
+ private formatClaudeError;
12
+ private buildEngineResultFromResultEvent;
13
+ private parseRateLimitInfo;
14
+ private parseClaudeJsonOutput;
11
15
  private extractResult;
16
+ private normalizeRateLimitInfo;
12
17
  private buildCleanEnv;
13
18
  private signalProcess;
14
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../../src/engines/claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,YAAY,EAAe,MAAM,oBAAoB,CAAC;AA6BxG,qBAAa,YAAa,YAAW,mBAAmB;IACtD,IAAI,EAAG,QAAQ,CAAU;IACzB,OAAO,CAAC,aAAa,CAAkC;IAEvD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAgB,GAAG,IAAI;IAcrD,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAK7B,GAAG,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;YAoCvC,OAAO;IAkMrB,OAAO,CAAC,iBAAiB;IA+DzB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,aAAa;CAYtB"}
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../../src/engines/claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAuB,mBAAmB,EAAE,aAAa,EAAE,YAAY,EAAe,MAAM,oBAAoB,CAAC;AA6B7H,qBAAa,YAAa,YAAW,mBAAmB;IACtD,IAAI,EAAG,QAAQ,CAAU;IACzB,OAAO,CAAC,aAAa,CAAkC;IAEvD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAgB,GAAG,IAAI;IAcrD,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAK7B,GAAG,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;YAoCvC,OAAO;IAmPrB,OAAO,CAAC,iBAAiB;IAwFzB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,gCAAgC;IAkBxC,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,qBAAqB;IAqD7B,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,sBAAsB;IAc9B,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,aAAa;CAYtB"}
@@ -116,6 +116,7 @@ export class ClaudeEngine {
116
116
  let stderr = "";
117
117
  let settled = false;
118
118
  let lastResultMsg = null;
119
+ let rateLimitInfo;
119
120
  let lineCount = 0;
120
121
  let inTool = false;
121
122
  const STDERR_MAX = 10 * 1024; // 10KB rolling window for error reporting
@@ -137,6 +138,10 @@ export class ClaudeEngine {
137
138
  lastResultMsg = parsed.msg;
138
139
  continue;
139
140
  }
141
+ if (parsed.type === "__rate_limit") {
142
+ rateLimitInfo = parsed.info;
143
+ continue;
144
+ }
140
145
  if (parsed.type === "__tool_start") {
141
146
  inTool = true;
142
147
  onStream(parsed.delta);
@@ -188,29 +193,16 @@ export class ClaudeEngine {
188
193
  }
189
194
  if (code === 0) {
190
195
  if (streaming && lastResultMsg) {
191
- resolve(this.extractResult(lastResultMsg, opts.resumeSessionId));
196
+ const extracted = this.buildEngineResultFromResultEvent(lastResultMsg, String(lastResultMsg.result || ""), opts.resumeSessionId, rateLimitInfo);
197
+ if (extracted.error)
198
+ opts.onStream?.({ type: "error", content: extracted.error });
199
+ resolve(extracted);
192
200
  return;
193
201
  }
194
202
  try {
195
- const parsed = JSON.parse(stdout);
196
- // Claude --output-format json returns an array of events.
197
- // The last element with type "result" has the final output.
198
- let result;
199
- if (Array.isArray(parsed)) {
200
- const resultEvent = [...parsed].reverse().find((e) => e.type === "result");
201
- result = resultEvent || parsed[parsed.length - 1] || {};
202
- }
203
- else {
204
- result = parsed;
205
- }
206
- logger.info(`Claude result: session_id=${result.session_id || "none"}, result_length=${(result.result || "").length}, cost=$${result.total_cost_usd || 0}`);
207
- resolve({
208
- sessionId: result.session_id,
209
- result: result.result,
210
- cost: result.total_cost_usd,
211
- durationMs: result.duration_ms,
212
- numTurns: result.num_turns,
213
- });
203
+ const parsedResult = this.parseClaudeJsonOutput(stdout, opts.resumeSessionId);
204
+ logger.info(`Claude result: session_id=${parsedResult.sessionId || "none"}, result_length=${parsedResult.result.length}, cost=$${parsedResult.cost || 0}`);
205
+ resolve(parsedResult);
214
206
  }
215
207
  catch (err) {
216
208
  logger.error(`Failed to parse Claude output: ${err}\nstdout: ${stdout.slice(0, 500)}`);
@@ -222,10 +214,57 @@ export class ClaudeEngine {
222
214
  }
223
215
  return;
224
216
  }
217
+ // Non-zero exit code — if we still got a structured "result" message, use it.
218
+ if (streaming && lastResultMsg) {
219
+ resolve(this.extractResult(lastResultMsg, opts.resumeSessionId, rateLimitInfo));
220
+ return;
221
+ }
222
+ if (!streaming && stdout.trim()) {
223
+ try {
224
+ const parsed = JSON.parse(stdout);
225
+ if (Array.isArray(parsed)) {
226
+ const resultEvent = [...parsed].reverse().find((e) => e.type === "result");
227
+ const rlEvent = [...parsed].reverse().find((e) => e.type === "rate_limit_event");
228
+ const rl = rlEvent ? this.normalizeRateLimitInfo(rlEvent.rate_limit_info) : rateLimitInfo;
229
+ if (resultEvent) {
230
+ resolve(this.extractResult(resultEvent, opts.resumeSessionId, rl));
231
+ return;
232
+ }
233
+ }
234
+ else if (parsed && typeof parsed === "object" && parsed.type === "result") {
235
+ resolve(this.extractResult(parsed, opts.resumeSessionId, rateLimitInfo));
236
+ return;
237
+ }
238
+ }
239
+ catch {
240
+ // ignore parse errors, fall through to generic stderr/code error
241
+ }
242
+ }
225
243
  // Non-zero exit code — log full stderr for debugging
226
244
  if (stderr.trim()) {
227
245
  logger.error(`Claude stderr (exit code ${code}):\n${stderr}`);
228
246
  }
247
+ // Prefer structured output when available (stream-json)
248
+ if (streaming && lastResultMsg) {
249
+ const extracted = this.buildEngineResultFromResultEvent(lastResultMsg, String(lastResultMsg.result || ""), opts.resumeSessionId, rateLimitInfo);
250
+ if (extracted.error)
251
+ opts.onStream?.({ type: "error", content: extracted.error });
252
+ resolve(extracted);
253
+ return;
254
+ }
255
+ // If stdout contains JSON output, try to parse it even on non-zero exit.
256
+ if (!streaming && stdout.trim()) {
257
+ try {
258
+ const parsedResult = this.parseClaudeJsonOutput(stdout, opts.resumeSessionId);
259
+ if (parsedResult.error)
260
+ opts.onStream?.({ type: "error", content: parsedResult.error });
261
+ resolve(parsedResult);
262
+ return;
263
+ }
264
+ catch {
265
+ // Fall through to generic error below
266
+ }
267
+ }
229
268
  const errMsg = `Claude exited with code ${code}${stderr.trim() ? `: ${stderr.slice(0, 500)}` : " (no stderr output)"}`;
230
269
  logger.error(errMsg);
231
270
  // Emit error delta so WebSocket clients see the failure immediately
@@ -269,6 +308,29 @@ export class ClaudeEngine {
269
308
  if (msgType === "result") {
270
309
  return { type: "__result", msg };
271
310
  }
311
+ if (msgType === "rate_limit_event") {
312
+ const info = this.parseRateLimitInfo(msg.rate_limit_info);
313
+ if (!info)
314
+ return null;
315
+ return { type: "__rate_limit", info };
316
+ }
317
+ // Partial assistant messages contain the full accumulated text so far.
318
+ // Use these as snapshots to correct any dropped text_delta events.
319
+ if (msgType === "assistant") {
320
+ const message = msg.message;
321
+ if (message) {
322
+ const content = message.content;
323
+ if (Array.isArray(content)) {
324
+ const textParts = content
325
+ .filter((b) => b.type === "text" && typeof b.text === "string")
326
+ .map((b) => b.text);
327
+ if (textParts.length > 0) {
328
+ return { type: "delta", delta: { type: "text_snapshot", content: textParts.join("") } };
329
+ }
330
+ }
331
+ }
332
+ return null;
333
+ }
272
334
  if (msgType === "stream_event") {
273
335
  const event = msg.event;
274
336
  if (!event)
@@ -303,15 +365,123 @@ export class ClaudeEngine {
303
365
  }
304
366
  return null;
305
367
  }
306
- extractResult(result, fallbackSessionId) {
368
+ formatClaudeError(message, rateLimit) {
369
+ const cleaned = message.trim() || "Claude error";
370
+ if (rateLimit?.status === "rejected") {
371
+ return `Claude usage limit reached: ${cleaned}`;
372
+ }
373
+ return cleaned;
374
+ }
375
+ buildEngineResultFromResultEvent(resultEvent, finalText, fallbackSessionId, rateLimit) {
376
+ const isError = resultEvent.is_error === true || rateLimit?.status === "rejected";
377
+ return {
378
+ sessionId: String(resultEvent.session_id || fallbackSessionId || ""),
379
+ result: isError ? "" : finalText,
380
+ error: isError ? this.formatClaudeError(finalText, rateLimit) : undefined,
381
+ cost: typeof resultEvent.total_cost_usd === "number" ? resultEvent.total_cost_usd : undefined,
382
+ durationMs: typeof resultEvent.duration_ms === "number" ? resultEvent.duration_ms : undefined,
383
+ numTurns: typeof resultEvent.num_turns === "number" ? resultEvent.num_turns : undefined,
384
+ ...(rateLimit ? { rateLimit } : {}),
385
+ };
386
+ }
387
+ parseRateLimitInfo(value) {
388
+ if (!value || typeof value !== "object")
389
+ return undefined;
390
+ const obj = value;
391
+ const info = {};
392
+ if (typeof obj.status === "string")
393
+ info.status = obj.status;
394
+ const resetsAt = obj.resetsAt;
395
+ if (typeof resetsAt === "number" && Number.isFinite(resetsAt))
396
+ info.resetsAt = resetsAt;
397
+ if (typeof resetsAt === "string" && resetsAt.trim()) {
398
+ const parsed = Number(resetsAt);
399
+ if (Number.isFinite(parsed))
400
+ info.resetsAt = parsed;
401
+ }
402
+ if (typeof obj.rateLimitType === "string")
403
+ info.rateLimitType = obj.rateLimitType;
404
+ if (typeof obj.overageStatus === "string")
405
+ info.overageStatus = obj.overageStatus;
406
+ if (typeof obj.overageDisabledReason === "string")
407
+ info.overageDisabledReason = obj.overageDisabledReason;
408
+ if (typeof obj.isUsingOverage === "boolean")
409
+ info.isUsingOverage = obj.isUsingOverage;
410
+ return Object.keys(info).length > 0 ? info : undefined;
411
+ }
412
+ parseClaudeJsonOutput(stdout, fallbackSessionId) {
413
+ const parsed = JSON.parse(stdout);
414
+ let rateLimitInfo;
415
+ let resultEvent = {};
416
+ if (Array.isArray(parsed)) {
417
+ const foundResult = [...parsed].reverse().find((e) => !!e && typeof e === "object" && e.type === "result");
418
+ resultEvent = foundResult || parsed[parsed.length - 1] || {};
419
+ const foundRate = [...parsed].reverse().find((e) => !!e && typeof e === "object" && e.type === "rate_limit_event");
420
+ if (foundRate) {
421
+ rateLimitInfo = this.parseRateLimitInfo(foundRate.rate_limit_info);
422
+ }
423
+ }
424
+ else if (parsed && typeof parsed === "object") {
425
+ resultEvent = parsed;
426
+ }
427
+ let finalText = resultEvent.result || "";
428
+ // If result text is empty, extract last assistant text from conversation
429
+ if (!finalText.trim() && Array.isArray(parsed)) {
430
+ for (let i = parsed.length - 1; i >= 0; i--) {
431
+ const evt = parsed[i];
432
+ if (evt.type === "assistant" && Array.isArray(evt.content)) {
433
+ const textBlocks = evt.content
434
+ .filter((b) => b.type === "text" && typeof b.text === "string")
435
+ .map((b) => b.text);
436
+ if (textBlocks.length > 0) {
437
+ finalText = textBlocks.join("\n");
438
+ break;
439
+ }
440
+ }
441
+ // Also check for message format with role field
442
+ if (evt.role === "assistant" && Array.isArray(evt.content)) {
443
+ const textBlocks = evt.content
444
+ .filter((b) => b.type === "text" && typeof b.text === "string")
445
+ .map((b) => b.text);
446
+ if (textBlocks.length > 0) {
447
+ finalText = textBlocks.join("\n");
448
+ break;
449
+ }
450
+ }
451
+ }
452
+ }
453
+ return this.buildEngineResultFromResultEvent(resultEvent, finalText, fallbackSessionId, rateLimitInfo);
454
+ }
455
+ extractResult(result, fallbackSessionId, rateLimitInfo) {
456
+ const isError = result.is_error === true;
457
+ const msg = String(result.result || "");
458
+ const error = isError
459
+ ? (rateLimitInfo?.status === "rejected" ? `Claude usage limit reached: ${msg || "Rate limited"}` : (msg || "Claude error"))
460
+ : undefined;
307
461
  return {
308
462
  sessionId: String(result.session_id || fallbackSessionId || ""),
309
- result: String(result.result || ""),
463
+ result: isError ? "" : String(result.result || ""),
464
+ error,
465
+ rateLimit: rateLimitInfo,
310
466
  cost: typeof result.total_cost_usd === "number" ? result.total_cost_usd : undefined,
311
467
  durationMs: typeof result.duration_ms === "number" ? result.duration_ms : undefined,
312
468
  numTurns: typeof result.num_turns === "number" ? result.num_turns : undefined,
313
469
  };
314
470
  }
471
+ normalizeRateLimitInfo(raw) {
472
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
473
+ return undefined;
474
+ const o = raw;
475
+ const resetsAt = typeof o.resetsAt === "number" ? o.resetsAt : undefined;
476
+ return {
477
+ status: typeof o.status === "string" ? o.status : undefined,
478
+ resetsAt,
479
+ rateLimitType: typeof o.rateLimitType === "string" ? o.rateLimitType : undefined,
480
+ overageStatus: typeof o.overageStatus === "string" ? o.overageStatus : undefined,
481
+ overageDisabledReason: typeof o.overageDisabledReason === "string" ? o.overageDisabledReason : undefined,
482
+ isUsingOverage: typeof o.isUsingOverage === "boolean" ? o.isUsingOverage : undefined,
483
+ };
484
+ }
315
485
  buildCleanEnv() {
316
486
  const cleanEnv = {};
317
487
  for (const [k, v] of Object.entries(process.env)) {