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.
- package/dist/bin/jimmy.js +0 -0
- package/dist/src/cli/status.d.ts.map +1 -1
- package/dist/src/cli/status.js +10 -1
- package/dist/src/cli/status.js.map +1 -1
- package/dist/src/connectors/discord/format.d.ts +3 -0
- package/dist/src/connectors/discord/format.d.ts.map +1 -0
- package/dist/src/connectors/discord/format.js +33 -0
- package/dist/src/connectors/discord/format.js.map +1 -0
- package/dist/src/connectors/discord/index.d.ts +41 -0
- package/dist/src/connectors/discord/index.d.ts.map +1 -0
- package/dist/src/connectors/discord/index.js +302 -0
- package/dist/src/connectors/discord/index.js.map +1 -0
- package/dist/src/connectors/discord/remote.d.ts +33 -0
- package/dist/src/connectors/discord/remote.d.ts.map +1 -0
- package/dist/src/connectors/discord/remote.js +89 -0
- package/dist/src/connectors/discord/remote.js.map +1 -0
- package/dist/src/connectors/discord/threads.d.ts +5 -0
- package/dist/src/connectors/discord/threads.d.ts.map +1 -0
- package/dist/src/connectors/discord/threads.js +22 -0
- package/dist/src/connectors/discord/threads.js.map +1 -0
- package/dist/src/connectors/whatsapp/format.d.ts +2 -0
- package/dist/src/connectors/whatsapp/format.d.ts.map +1 -0
- package/dist/src/connectors/whatsapp/format.js +22 -0
- package/dist/src/connectors/whatsapp/format.js.map +1 -0
- package/dist/src/connectors/whatsapp/index.d.ts +42 -0
- package/dist/src/connectors/whatsapp/index.d.ts.map +1 -0
- package/dist/src/connectors/whatsapp/index.js +279 -0
- package/dist/src/connectors/whatsapp/index.js.map +1 -0
- package/dist/src/engines/claude.d.ts +5 -0
- package/dist/src/engines/claude.d.ts.map +1 -1
- package/dist/src/engines/claude.js +192 -22
- package/dist/src/engines/claude.js.map +1 -1
- package/dist/src/gateway/api.d.ts +1 -0
- package/dist/src/gateway/api.d.ts.map +1 -1
- package/dist/src/gateway/api.js +705 -18
- package/dist/src/gateway/api.js.map +1 -1
- package/dist/src/gateway/lifecycle.d.ts.map +1 -1
- package/dist/src/gateway/lifecycle.js +32 -15
- package/dist/src/gateway/lifecycle.js.map +1 -1
- package/dist/src/gateway/project-tagger.d.ts +20 -0
- package/dist/src/gateway/project-tagger.d.ts.map +1 -0
- package/dist/src/gateway/project-tagger.js +55 -0
- package/dist/src/gateway/project-tagger.js.map +1 -0
- package/dist/src/gateway/project-tagger.test.d.ts +2 -0
- package/dist/src/gateway/project-tagger.test.d.ts.map +1 -0
- package/dist/src/gateway/project-tagger.test.js +110 -0
- package/dist/src/gateway/project-tagger.test.js.map +1 -0
- package/dist/src/gateway/projects.d.ts +15 -0
- package/dist/src/gateway/projects.d.ts.map +1 -0
- package/dist/src/gateway/projects.js +48 -0
- package/dist/src/gateway/projects.js.map +1 -0
- package/dist/src/gateway/projects.test.d.ts +2 -0
- package/dist/src/gateway/projects.test.d.ts.map +1 -0
- package/dist/src/gateway/projects.test.js +85 -0
- package/dist/src/gateway/projects.test.js.map +1 -0
- package/dist/src/gateway/server.d.ts.map +1 -1
- package/dist/src/gateway/server.js +90 -14
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/tasks.d.ts +14 -0
- package/dist/src/gateway/tasks.d.ts.map +1 -0
- package/dist/src/gateway/tasks.js +51 -0
- package/dist/src/gateway/tasks.js.map +1 -0
- package/dist/src/gateway/tasks.test.d.ts +2 -0
- package/dist/src/gateway/tasks.test.d.ts.map +1 -0
- package/dist/src/gateway/tasks.test.js +131 -0
- package/dist/src/gateway/tasks.test.js.map +1 -0
- package/dist/src/mcp/resolver.d.ts.map +1 -1
- package/dist/src/mcp/resolver.js +8 -2
- package/dist/src/mcp/resolver.js.map +1 -1
- package/dist/src/sessions/callbacks.d.ts +29 -0
- package/dist/src/sessions/callbacks.d.ts.map +1 -0
- package/dist/src/sessions/callbacks.js +110 -0
- package/dist/src/sessions/callbacks.js.map +1 -0
- package/dist/src/sessions/context.js +4 -3
- package/dist/src/sessions/context.js.map +1 -1
- package/dist/src/sessions/engine-override.d.ts +3 -0
- package/dist/src/sessions/engine-override.d.ts.map +1 -0
- package/dist/src/sessions/engine-override.js +42 -0
- package/dist/src/sessions/engine-override.js.map +1 -0
- package/dist/src/sessions/manager.d.ts.map +1 -1
- package/dist/src/sessions/manager.js +385 -40
- package/dist/src/sessions/manager.js.map +1 -1
- package/dist/src/sessions/queue.d.ts +19 -2
- package/dist/src/sessions/queue.d.ts.map +1 -1
- package/dist/src/sessions/queue.js +44 -13
- package/dist/src/sessions/queue.js.map +1 -1
- package/dist/src/sessions/registry.d.ts +21 -1
- package/dist/src/sessions/registry.d.ts.map +1 -1
- package/dist/src/sessions/registry.js +62 -0
- package/dist/src/sessions/registry.js.map +1 -1
- package/dist/src/shared/rateLimit.d.ts +13 -0
- package/dist/src/shared/rateLimit.d.ts.map +1 -0
- package/dist/src/shared/rateLimit.js +30 -0
- package/dist/src/shared/rateLimit.js.map +1 -0
- package/dist/src/shared/types.d.ts +60 -5
- package/dist/src/shared/types.d.ts.map +1 -1
- package/dist/src/shared/usageAwareness.d.ts +10 -0
- package/dist/src/shared/usageAwareness.d.ts.map +1 -0
- package/dist/src/shared/usageAwareness.js +62 -0
- package/dist/src/shared/usageAwareness.js.map +1 -0
- package/dist/web/404.html +1 -1
- package/dist/web/_next/static/chunks/184-5a617386af9a1dd3.js +1 -0
- package/dist/web/_next/static/chunks/700-ad04a2a5b3c8880f.js +1 -0
- package/dist/web/_next/static/chunks/app/chat/page-36edb6fc1d1e880b.js +1 -0
- package/dist/web/_next/static/chunks/app/costs/{page-7940c2fe7e3dace1.js → page-6c5cd46a6b3cde21.js} +1 -1
- package/dist/web/_next/static/chunks/app/cron/{page-f81a986689712af7.js → page-210d9d333a7eed94.js} +1 -1
- package/dist/web/_next/static/chunks/app/kanban/page-c32370bcd6a7c841.js +1 -0
- package/dist/web/_next/static/chunks/app/layout-3ad6b73a0904c24b.js +1 -0
- package/dist/web/_next/static/chunks/app/logs/page-7322a6789e16dca4.js +1 -0
- package/dist/web/_next/static/chunks/app/org/{page-3d44d51e94edb85e.js → page-02263c5702e0fd3e.js} +1 -1
- package/dist/web/_next/static/chunks/app/{page-7ac43789d477a51f.js → page-b68dcf7b8802c704.js} +1 -1
- package/dist/web/_next/static/chunks/app/sessions/page-bf7ad2fac281c7d6.js +1 -0
- package/dist/web/_next/static/chunks/app/settings/page-d3b89563c42be2e5.js +1 -0
- package/dist/web/_next/static/chunks/app/skills/{page-26b727333df9db45.js → page-194f4e97f29ab581.js} +1 -1
- package/dist/web/_next/static/css/b809b6af2d241fc8.css +1 -0
- package/dist/web/chat.html +1 -1
- package/dist/web/chat.txt +4 -4
- package/dist/web/costs.html +2 -2
- package/dist/web/costs.txt +4 -4
- package/dist/web/cron.html +1 -1
- package/dist/web/cron.txt +4 -4
- package/dist/web/index.html +1 -1
- package/dist/web/index.txt +4 -4
- package/dist/web/kanban.html +1 -1
- package/dist/web/kanban.txt +4 -4
- package/dist/web/logs.html +2 -2
- package/dist/web/logs.txt +4 -4
- package/dist/web/org.html +1 -1
- package/dist/web/org.txt +4 -4
- package/dist/web/sessions.html +1 -1
- package/dist/web/sessions.txt +14 -17
- package/dist/web/settings.html +1 -1
- package/dist/web/settings.txt +4 -4
- package/dist/web/skills.html +1 -1
- package/dist/web/skills.txt +4 -4
- package/package.json +6 -1
- package/dist/web/_next/static/chunks/282-4e9c26e9a600c58e.js +0 -1
- package/dist/web/_next/static/chunks/700-a7cbf54fe1fbf4bc.js +0 -1
- package/dist/web/_next/static/chunks/app/chat/page-757fcd211d059cb7.js +0 -1
- package/dist/web/_next/static/chunks/app/kanban/page-6ab8586b063ca3ac.js +0 -1
- package/dist/web/_next/static/chunks/app/layout-c24e2d25774ff71a.js +0 -1
- package/dist/web/_next/static/chunks/app/logs/page-388b787cb847ca97.js +0 -1
- package/dist/web/_next/static/chunks/app/sessions/page-18757fcd067b7e9d.js +0 -1
- package/dist/web/_next/static/chunks/app/settings/page-f62176848534f90a.js +0 -1
- package/dist/web/_next/static/css/bd612b1ca9b40306.css +0 -1
- /package/dist/web/_next/static/{J4YFiPdzNcFHieP2FIPPe → QrKxazgwMrykF2yLwDvUM}/_buildManifest.js +0 -0
- /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,
|
|
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
|
-
|
|
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
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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)) {
|