crewly 1.20.121 → 1.20.123
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/config/skills/agent/core/whatsapp-draft/SKILL.md +80 -0
- package/config/skills/agent/core/whatsapp-draft/execute.sh +84 -0
- package/config/skills/agent/core/whatsapp-draft/execute.test.sh +70 -0
- package/config/skills/agent/core/whatsapp-inbox/SKILL.md +75 -0
- package/config/skills/agent/core/whatsapp-inbox/execute.sh +87 -0
- package/config/skills/agent/core/whatsapp-inbox/execute.test.sh +68 -0
- package/config/skills/agent/core/whatsapp-read/SKILL.md +80 -0
- package/config/skills/agent/core/whatsapp-read/execute.sh +116 -0
- package/config/skills/agent/core/whatsapp-read/execute.test.sh +84 -0
- package/config/skills/agent/core/whatsapp-send/SKILL.md +70 -0
- package/config/skills/agent/core/whatsapp-send/execute.sh +84 -0
- package/config/skills/agent/core/whatsapp-send/execute.test.sh +79 -0
- package/dist/backend/backend/src/constants.d.ts +107 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +105 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.d.ts +73 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.js +310 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js +53 -4
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +5 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.js +13 -0
- package/dist/backend/backend/src/services/browser/browser-session.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +29 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/index.d.ts +5 -1
- package/dist/backend/backend/src/services/whatsapp/index.d.ts.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/index.js +5 -1
- package/dist/backend/backend/src/services/whatsapp/index.js.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.d.ts +59 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.js +108 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.d.ts +74 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.js +82 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.d.ts +184 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.js +420 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.d.ts +243 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.js +538 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts +32 -2
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js +65 -9
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js +11 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts +59 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js +159 -9
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js.map +1 -1
- package/dist/backend/backend/src/types/whatsapp.types.d.ts +108 -0
- package/dist/backend/backend/src/types/whatsapp.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/whatsapp.types.js +9 -0
- package/dist/backend/backend/src/types/whatsapp.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +107 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +105 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +5 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/frontend/dist/assets/{index-07e338a9.js → index-daf43f1a.js} +268 -268
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsApp Connection Config Persistence
|
|
3
|
+
*
|
|
4
|
+
* Stores how the owner last connected WhatsApp (mode, allowed contacts,
|
|
5
|
+
* whether to reconnect on boot) at `~/.crewly/whatsapp/connection.json`, so
|
|
6
|
+
* a restart keeps inbox mode instead of silently falling back to the
|
|
7
|
+
* auto-replying assistant.
|
|
8
|
+
*
|
|
9
|
+
* @module services/whatsapp/whatsapp-connection-config
|
|
10
|
+
*/
|
|
11
|
+
import { promises as fs, existsSync } from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { WHATSAPP_CONSTANTS } from '../../constants.js';
|
|
14
|
+
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
15
|
+
import { isWhatsAppMode } from '../../types/whatsapp.types.js';
|
|
16
|
+
/** Owner-only file mode for the config file. */
|
|
17
|
+
const PRIVATE_FILE_MODE = 0o600;
|
|
18
|
+
const PRIVATE_DIR_MODE = 0o700;
|
|
19
|
+
/**
|
|
20
|
+
* Path of the persisted connection config.
|
|
21
|
+
*
|
|
22
|
+
* @returns `<crewly home>/whatsapp/connection.json`
|
|
23
|
+
*/
|
|
24
|
+
export function getConnectionConfigPath() {
|
|
25
|
+
return path.join(getCrewlyHomePath(), WHATSAPP_CONSTANTS.DATA_DIR, WHATSAPP_CONSTANTS.CONNECTION_CONFIG_FILE);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Read the persisted connection config.
|
|
29
|
+
*
|
|
30
|
+
* @returns The config, or null when absent, unreadable or malformed
|
|
31
|
+
*/
|
|
32
|
+
export async function loadWhatsAppConnection() {
|
|
33
|
+
const file = getConnectionConfigPath();
|
|
34
|
+
try {
|
|
35
|
+
if (!existsSync(file))
|
|
36
|
+
return null;
|
|
37
|
+
const parsed = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
38
|
+
if (!parsed || typeof parsed !== 'object')
|
|
39
|
+
return null;
|
|
40
|
+
const p = parsed;
|
|
41
|
+
if (!isWhatsAppMode(p.mode))
|
|
42
|
+
return null;
|
|
43
|
+
return {
|
|
44
|
+
mode: p.mode,
|
|
45
|
+
autoConnect: p.autoConnect === true,
|
|
46
|
+
allowedContacts: Array.isArray(p.allowedContacts)
|
|
47
|
+
? p.allowedContacts.filter((c) => typeof c === 'string')
|
|
48
|
+
: undefined,
|
|
49
|
+
phoneNumber: typeof p.phoneNumber === 'string' ? p.phoneNumber : undefined,
|
|
50
|
+
authStatePath: typeof p.authStatePath === 'string' ? p.authStatePath : undefined,
|
|
51
|
+
updatedAt: typeof p.updatedAt === 'number' ? p.updatedAt : 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Persist the config a connection was started with (auto-connect on).
|
|
60
|
+
*
|
|
61
|
+
* @param config - The config passed to `WhatsAppService.initialize`
|
|
62
|
+
* @param mode - The resolved mode
|
|
63
|
+
*/
|
|
64
|
+
export async function saveWhatsAppConnection(config, mode) {
|
|
65
|
+
const record = {
|
|
66
|
+
mode,
|
|
67
|
+
autoConnect: true,
|
|
68
|
+
allowedContacts: config.allowedContacts,
|
|
69
|
+
phoneNumber: config.phoneNumber,
|
|
70
|
+
authStatePath: config.authStatePath,
|
|
71
|
+
updatedAt: Date.now(),
|
|
72
|
+
};
|
|
73
|
+
await writeConnection(record);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Turn auto-connect off after an explicit disconnect, keeping the mode.
|
|
77
|
+
*/
|
|
78
|
+
export async function markWhatsAppDisconnected() {
|
|
79
|
+
const current = await loadWhatsAppConnection();
|
|
80
|
+
if (!current)
|
|
81
|
+
return;
|
|
82
|
+
await writeConnection({ ...current, autoConnect: false, updatedAt: Date.now() });
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Convert a persisted record into a service config.
|
|
86
|
+
*
|
|
87
|
+
* @param persisted - Stored record
|
|
88
|
+
* @returns Config for `WhatsAppService.initialize`
|
|
89
|
+
*/
|
|
90
|
+
export function toWhatsAppConfig(persisted) {
|
|
91
|
+
return {
|
|
92
|
+
mode: persisted.mode,
|
|
93
|
+
allowedContacts: persisted.allowedContacts,
|
|
94
|
+
phoneNumber: persisted.phoneNumber,
|
|
95
|
+
authStatePath: persisted.authStatePath,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Write the record owner-only.
|
|
100
|
+
*
|
|
101
|
+
* @param record - Record to write
|
|
102
|
+
*/
|
|
103
|
+
async function writeConnection(record) {
|
|
104
|
+
const file = getConnectionConfigPath();
|
|
105
|
+
await fs.mkdir(path.dirname(file), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
106
|
+
await fs.writeFile(file, JSON.stringify(record, null, 2), { mode: PRIVATE_FILE_MODE });
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=whatsapp-connection-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-connection-config.js","sourceRoot":"","sources":["../../../../../../backend/src/services/whatsapp/whatsapp-connection-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsApp Draft Send Gate
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a reply draft may be sent. Owner decision (2026-09-24):
|
|
5
|
+
* nothing is ever sent to WhatsApp without the owner's explicit
|
|
6
|
+
* confirmation, one message at a time.
|
|
7
|
+
*
|
|
8
|
+
* - A request with no `X-Agent-Session` header is the owner (dashboard,
|
|
9
|
+
* mobile, portal) — the click is the confirmation.
|
|
10
|
+
* - An agent may send only when a genuine owner chat message (from
|
|
11
|
+
* `ChatV2Service.getRecentOwnerMessageContents`, which agents cannot
|
|
12
|
+
* write) created after the draft says 「发 W12」 for this draft's code, and
|
|
13
|
+
* the draft is at most `DRAFT_CONFIRM_WINDOW_MS` old. Because the owner
|
|
14
|
+
* message must post-date the draft, bounding the draft's age also bounds
|
|
15
|
+
* the confirmation to that window.
|
|
16
|
+
*
|
|
17
|
+
* @module services/whatsapp/whatsapp-draft-gate
|
|
18
|
+
*/
|
|
19
|
+
import type { WhatsAppDraft } from '../../types/whatsapp.types.js';
|
|
20
|
+
/** Why an agent's send was refused. */
|
|
21
|
+
export type DraftSendRefusal = 'no_confirmation' | 'window_expired';
|
|
22
|
+
/** Outcome of {@link decideDraftSend}. */
|
|
23
|
+
export type DraftSendDecision = {
|
|
24
|
+
allowed: true;
|
|
25
|
+
via: 'owner' | 'owner_confirmation';
|
|
26
|
+
confirmation?: string;
|
|
27
|
+
} | {
|
|
28
|
+
allowed: false;
|
|
29
|
+
reason: DraftSendRefusal;
|
|
30
|
+
};
|
|
31
|
+
/** Inputs for {@link decideDraftSend}. */
|
|
32
|
+
export interface DraftSendContext {
|
|
33
|
+
/** The draft (must be pending — checked by the caller) */
|
|
34
|
+
draft: WhatsAppDraft;
|
|
35
|
+
/** `X-Agent-Session` of the caller; undefined = owner */
|
|
36
|
+
agentSession: string | undefined;
|
|
37
|
+
/** Owner message texts created at/after `sinceMs`, newest first */
|
|
38
|
+
ownerMessagesSince: (sinceMs: number) => string[];
|
|
39
|
+
/** Current epoch ms */
|
|
40
|
+
now: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether one owner message confirms sending a given draft.
|
|
44
|
+
*
|
|
45
|
+
* @param text - Owner message text
|
|
46
|
+
* @param draft - The draft
|
|
47
|
+
* @returns True for 「发 W12」 / 「发送 12」 / "send #W12" / 「确认发送 W12」 naming this draft
|
|
48
|
+
*/
|
|
49
|
+
export declare function isConfirmationFor(text: string, draft: Pick<WhatsAppDraft, 'seq'>): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Decide whether the caller may send this draft now.
|
|
52
|
+
*
|
|
53
|
+
* @param ctx - Draft, caller, owner-message source, clock
|
|
54
|
+
* @returns Allowed (and why), or refused (and why)
|
|
55
|
+
*/
|
|
56
|
+
export declare function decideDraftSend(ctx: DraftSendContext): DraftSendDecision;
|
|
57
|
+
/**
|
|
58
|
+
* The instruction an agent must follow after drafting: show the owner the
|
|
59
|
+
* draft and ask for 「发 <code>」.
|
|
60
|
+
*
|
|
61
|
+
* @param draft - The draft
|
|
62
|
+
* @param recipient - Human label for the recipient (chat name or JID)
|
|
63
|
+
* @returns Instruction text
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildOwnerConfirmPrompt(draft: Pick<WhatsAppDraft, 'code' | 'text'>, recipient: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Human message for a refused agent send.
|
|
68
|
+
*
|
|
69
|
+
* @param draft - The draft
|
|
70
|
+
* @param reason - Refusal reason
|
|
71
|
+
* @returns Message for the 403 body
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildRefusalMessage(draft: Pick<WhatsAppDraft, 'code'>, reason: DraftSendRefusal): string;
|
|
74
|
+
//# sourceMappingURL=whatsapp-draft-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-draft-gate.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/whatsapp/whatsapp-draft-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAMnE,uCAAuC;AACvC,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAEpE,0CAA0C;AAC1C,MAAM,MAAM,iBAAiB,GACzB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAEjD,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,KAAK,EAAE,aAAa,CAAC;IACrB,yDAAyD;IACzD,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,mEAAmE;IACnE,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IAClD,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,GAAG,OAAO,CAI1F;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,gBAAgB,GAAG,iBAAiB,CASxE;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAM9G;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAYxG"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsApp Draft Send Gate
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a reply draft may be sent. Owner decision (2026-09-24):
|
|
5
|
+
* nothing is ever sent to WhatsApp without the owner's explicit
|
|
6
|
+
* confirmation, one message at a time.
|
|
7
|
+
*
|
|
8
|
+
* - A request with no `X-Agent-Session` header is the owner (dashboard,
|
|
9
|
+
* mobile, portal) — the click is the confirmation.
|
|
10
|
+
* - An agent may send only when a genuine owner chat message (from
|
|
11
|
+
* `ChatV2Service.getRecentOwnerMessageContents`, which agents cannot
|
|
12
|
+
* write) created after the draft says 「发 W12」 for this draft's code, and
|
|
13
|
+
* the draft is at most `DRAFT_CONFIRM_WINDOW_MS` old. Because the owner
|
|
14
|
+
* message must post-date the draft, bounding the draft's age also bounds
|
|
15
|
+
* the confirmation to that window.
|
|
16
|
+
*
|
|
17
|
+
* @module services/whatsapp/whatsapp-draft-gate
|
|
18
|
+
*/
|
|
19
|
+
import { WHATSAPP_CONSTANTS } from '../../constants.js';
|
|
20
|
+
import { parseDraftCode } from './whatsapp-inbox.store.js';
|
|
21
|
+
/** Milliseconds per minute (for human-readable window text). */
|
|
22
|
+
const MS_PER_MINUTE = 60 * 1000;
|
|
23
|
+
/**
|
|
24
|
+
* Whether one owner message confirms sending a given draft.
|
|
25
|
+
*
|
|
26
|
+
* @param text - Owner message text
|
|
27
|
+
* @param draft - The draft
|
|
28
|
+
* @returns True for 「发 W12」 / 「发送 12」 / "send #W12" / 「确认发送 W12」 naming this draft
|
|
29
|
+
*/
|
|
30
|
+
export function isConfirmationFor(text, draft) {
|
|
31
|
+
const m = WHATSAPP_CONSTANTS.DRAFT_CONFIRM_PATTERN.exec(text.trim());
|
|
32
|
+
if (!m)
|
|
33
|
+
return false;
|
|
34
|
+
return parseDraftCode(m[2]) === draft.seq;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Decide whether the caller may send this draft now.
|
|
38
|
+
*
|
|
39
|
+
* @param ctx - Draft, caller, owner-message source, clock
|
|
40
|
+
* @returns Allowed (and why), or refused (and why)
|
|
41
|
+
*/
|
|
42
|
+
export function decideDraftSend(ctx) {
|
|
43
|
+
if (!ctx.agentSession)
|
|
44
|
+
return { allowed: true, via: 'owner' };
|
|
45
|
+
if (ctx.now - ctx.draft.createdAt > WHATSAPP_CONSTANTS.DRAFT_CONFIRM_WINDOW_MS) {
|
|
46
|
+
return { allowed: false, reason: 'window_expired' };
|
|
47
|
+
}
|
|
48
|
+
const confirmation = ctx.ownerMessagesSince(ctx.draft.createdAt).find((t) => isConfirmationFor(t, ctx.draft));
|
|
49
|
+
return confirmation !== undefined
|
|
50
|
+
? { allowed: true, via: 'owner_confirmation', confirmation }
|
|
51
|
+
: { allowed: false, reason: 'no_confirmation' };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The instruction an agent must follow after drafting: show the owner the
|
|
55
|
+
* draft and ask for 「发 <code>」.
|
|
56
|
+
*
|
|
57
|
+
* @param draft - The draft
|
|
58
|
+
* @param recipient - Human label for the recipient (chat name or JID)
|
|
59
|
+
* @returns Instruction text
|
|
60
|
+
*/
|
|
61
|
+
export function buildOwnerConfirmPrompt(draft, recipient) {
|
|
62
|
+
return (`Draft ${draft.code} is saved but NOT sent. Show the owner the recipient (${recipient}), ` +
|
|
63
|
+
`the exact draft text, and the code ${draft.code}, and ask them to reply 「发 ${draft.code}」 to send it ` +
|
|
64
|
+
`(or send/discard it from the dashboard). Do not send it until they do.`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Human message for a refused agent send.
|
|
68
|
+
*
|
|
69
|
+
* @param draft - The draft
|
|
70
|
+
* @param reason - Refusal reason
|
|
71
|
+
* @returns Message for the 403 body
|
|
72
|
+
*/
|
|
73
|
+
export function buildRefusalMessage(draft, reason) {
|
|
74
|
+
const windowMinutes = Math.round(WHATSAPP_CONSTANTS.DRAFT_CONFIRM_WINDOW_MS / MS_PER_MINUTE);
|
|
75
|
+
if (reason === 'window_expired') {
|
|
76
|
+
return (`Draft ${draft.code} is older than ${windowMinutes} minutes, so it can no longer be sent on the owner's ` +
|
|
77
|
+
`chat confirmation. Ask the owner to send it from the dashboard, or write a fresh draft and ask them to reply 「发 <new code>」.`);
|
|
78
|
+
}
|
|
79
|
+
return (`The owner has not confirmed draft ${draft.code}. Show the owner the draft (recipient and exact text) ` +
|
|
80
|
+
`and ask them to reply 「发 ${draft.code}」. Only then call send again. Never send without it.`);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=whatsapp-draft-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-draft-gate.js","sourceRoot":"","sources":["../../../../../../backend/src/services/whatsapp/whatsapp-draft-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,gEAAgE;AAChE,MAAM,aAAa,GAAG,EAAE,GAAG,IAAI,CAAC;AAsBhC;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAiC;IAC/E,MAAM,CAAC,GAAG,kBAAkB,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACrE,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAqB;IACnD,IAAI,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;IAC9D,IAAI,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,kBAAkB,CAAC,uBAAuB,EAAE,CAAC;QAC/E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IACtD,CAAC;IACD,MAAM,YAAY,GAAG,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9G,OAAO,YAAY,KAAK,SAAS;QAC/B,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,oBAAoB,EAAE,YAAY,EAAE;QAC5D,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAA2C,EAAE,SAAiB;IACpG,OAAO,CACL,SAAS,KAAK,CAAC,IAAI,yDAAyD,SAAS,KAAK;QAC1F,sCAAsC,KAAK,CAAC,IAAI,8BAA8B,KAAK,CAAC,IAAI,eAAe;QACvG,wEAAwE,CACzE,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAkC,EAAE,MAAwB;IAC9F,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,uBAAuB,GAAG,aAAa,CAAC,CAAC;IAC7F,IAAI,MAAM,KAAK,gBAAgB,EAAE,CAAC;QAChC,OAAO,CACL,SAAS,KAAK,CAAC,IAAI,kBAAkB,aAAa,uDAAuD;YACzG,8HAA8H,CAC/H,CAAC;IACJ,CAAC;IACD,OAAO,CACL,qCAAqC,KAAK,CAAC,IAAI,wDAAwD;QACvG,4BAA4B,KAAK,CAAC,IAAI,sDAAsD,CAC7F,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsApp Inbox Capture
|
|
3
|
+
*
|
|
4
|
+
* Turns Baileys events into inbox-store rows. Used only in `inbox` mode.
|
|
5
|
+
* Unlike the assistant path (`WhatsAppService.handleIncomingMessage`) nothing
|
|
6
|
+
* is dropped for being the owner's own message, a group message, media, or
|
|
7
|
+
* from a contact outside `allowedContacts` — the inbox has to know whether
|
|
8
|
+
* the owner already replied, and it is the owner's own account.
|
|
9
|
+
*
|
|
10
|
+
* Media are never downloaded: a message is recorded as its kind plus caption
|
|
11
|
+
* (or file name for documents).
|
|
12
|
+
*
|
|
13
|
+
* Baileys shapes relied on (v7, `@whiskeysockets/baileys` typings):
|
|
14
|
+
* - `messages.upsert`: `{ messages: WAMessage[], type: 'notify' | 'append' }`
|
|
15
|
+
* - `messaging-history.set`: `{ chats: Chat[], contacts: Contact[], messages: WAMessage[], syncType? }`
|
|
16
|
+
* - `contacts.upsert` / `contacts.update`: `Contact[]` (`id`, `name` = address book, `notify` = their pushName)
|
|
17
|
+
* - `chats.upsert` / `chats.update`: `Chat[]` (`id`, `name`, `conversationTimestamp`)
|
|
18
|
+
* - `groups.upsert` / `groups.update`: `GroupMetadata[]` (`id`, `subject`)
|
|
19
|
+
* - `WAMessage.key.remoteJidAlt` / `participantAlt` (v7 LID↔PN alternates)
|
|
20
|
+
* - `messageTimestamp`: seconds, as number, numeric string or protobuf Long
|
|
21
|
+
*
|
|
22
|
+
* @module services/whatsapp/whatsapp-inbox-capture
|
|
23
|
+
*/
|
|
24
|
+
import type { WhatsAppInboxMessage, WhatsAppMessageKind } from '../../types/whatsapp.types.js';
|
|
25
|
+
import type { UpsertChatInput, WhatsAppInboxStore } from './whatsapp-inbox.store.js';
|
|
26
|
+
/** Loose record type for untyped Baileys payloads. */
|
|
27
|
+
type Rec = Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Convert a Baileys timestamp (seconds as number, numeric string, or
|
|
30
|
+
* protobuf Long with `toNumber()` / `low`+`high`) to epoch ms.
|
|
31
|
+
*
|
|
32
|
+
* @param ts - Raw timestamp
|
|
33
|
+
* @returns Epoch ms, or null when unparseable / non-positive
|
|
34
|
+
*/
|
|
35
|
+
export declare function toEpochMs(ts: unknown): number | null;
|
|
36
|
+
/**
|
|
37
|
+
* Whether a JID is a group chat.
|
|
38
|
+
*
|
|
39
|
+
* @param jid - JID
|
|
40
|
+
* @returns True for `...@g.us`
|
|
41
|
+
*/
|
|
42
|
+
export declare function isGroupJid(jid: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Pick the canonical JID for a person: prefer the phone-number form when
|
|
45
|
+
* Baileys supplies it as the alternate of a LID, so the same person's
|
|
46
|
+
* messages (some keyed by LID, some by phone number) land in one chat.
|
|
47
|
+
*
|
|
48
|
+
* @param jid - Primary JID (`remoteJid` / `participant`)
|
|
49
|
+
* @param alt - Alternate JID (`remoteJidAlt` / `participantAlt`)
|
|
50
|
+
* @returns The canonical JID
|
|
51
|
+
*/
|
|
52
|
+
export declare function canonicalJid(jid: string, alt?: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Strip wrapper messages (disappearing, view-once, edited, doc-with-caption).
|
|
55
|
+
*
|
|
56
|
+
* @param content - `WAMessage.message`
|
|
57
|
+
* @returns The innermost content object, or undefined
|
|
58
|
+
*/
|
|
59
|
+
export declare function unwrapMessageContent(content: unknown): Rec | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Classify message content into a stored kind and its text/caption.
|
|
62
|
+
*
|
|
63
|
+
* @param content - `WAMessage.message` (wrappers allowed)
|
|
64
|
+
* @returns `{ kind, text }`, or null when this is not a user-visible message
|
|
65
|
+
* (protocol message, reaction, empty envelope)
|
|
66
|
+
*/
|
|
67
|
+
export declare function classifyMessageContent(content: unknown): {
|
|
68
|
+
kind: WhatsAppMessageKind;
|
|
69
|
+
text: string;
|
|
70
|
+
} | null;
|
|
71
|
+
/** Result of parsing one Baileys message. */
|
|
72
|
+
export interface ParsedWhatsAppMessage {
|
|
73
|
+
/** Row for the store */
|
|
74
|
+
message: WhatsAppInboxMessage;
|
|
75
|
+
/** Whether the chat is a group */
|
|
76
|
+
isGroup: boolean;
|
|
77
|
+
/** Sender's self-chosen name, when the message carried one (not for own messages) */
|
|
78
|
+
pushName: string | null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parse a Baileys `WAMessage` into an inbox row.
|
|
82
|
+
*
|
|
83
|
+
* @param raw - The WAMessage
|
|
84
|
+
* @param ownJid - The connected account's JID (sender of `fromMe` messages), if known
|
|
85
|
+
* @returns The parsed message, or null when it is not storable (no id/chat,
|
|
86
|
+
* status broadcast, protocol-only content, no timestamp)
|
|
87
|
+
*/
|
|
88
|
+
export declare function parseBaileysMessage(raw: unknown, ownJid?: string | null): ParsedWhatsAppMessage | null;
|
|
89
|
+
/**
|
|
90
|
+
* Chat row from a Baileys `Contact`: the address-book name outranks the
|
|
91
|
+
* contact's own pushName (`notify`) and business `verifiedName`.
|
|
92
|
+
*
|
|
93
|
+
* @param contact - Contact (full or partial update)
|
|
94
|
+
* @returns Upsert input, or null when the contact names nothing useful
|
|
95
|
+
*/
|
|
96
|
+
export declare function chatFromContact(contact: unknown): UpsertChatInput | null;
|
|
97
|
+
/**
|
|
98
|
+
* Chat row from a Baileys `Chat` (history sync, `chats.upsert/update`).
|
|
99
|
+
*
|
|
100
|
+
* @param chat - Chat
|
|
101
|
+
* @returns Upsert input, or null without an id / for the status pseudo-chat
|
|
102
|
+
*/
|
|
103
|
+
export declare function chatFromChat(chat: unknown): UpsertChatInput | null;
|
|
104
|
+
/**
|
|
105
|
+
* Chat row from Baileys `GroupMetadata` (subject = group name).
|
|
106
|
+
*
|
|
107
|
+
* @param group - Group metadata (full or partial)
|
|
108
|
+
* @returns Upsert input, or null without an id
|
|
109
|
+
*/
|
|
110
|
+
export declare function chatFromGroup(group: unknown): UpsertChatInput | null;
|
|
111
|
+
/** Optional hooks for {@link WhatsAppInboxCapture}. */
|
|
112
|
+
export interface WhatsAppInboxCaptureOptions {
|
|
113
|
+
/** The connected account's JID (sender of own messages) */
|
|
114
|
+
getOwnJid?: () => string | null;
|
|
115
|
+
/** Fetch a group's subject for a group we have no name for (best effort) */
|
|
116
|
+
fetchGroupSubject?: (jid: string) => Promise<string | null>;
|
|
117
|
+
/** Clock (tests) */
|
|
118
|
+
now?: () => number;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Writes Baileys events into a {@link WhatsAppInboxStore}.
|
|
122
|
+
*
|
|
123
|
+
* Every handler is defensive: a malformed payload is skipped, never thrown,
|
|
124
|
+
* because it runs inside the socket's event loop.
|
|
125
|
+
*/
|
|
126
|
+
export declare class WhatsAppInboxCapture {
|
|
127
|
+
private readonly store;
|
|
128
|
+
private readonly opts;
|
|
129
|
+
private readonly groupLookups;
|
|
130
|
+
/**
|
|
131
|
+
* @param store - Destination store
|
|
132
|
+
* @param opts - Own-JID accessor, group-subject fetcher, clock
|
|
133
|
+
*/
|
|
134
|
+
constructor(store: WhatsAppInboxStore, opts?: WhatsAppInboxCaptureOptions);
|
|
135
|
+
/**
|
|
136
|
+
* Store one message and name its chat.
|
|
137
|
+
*
|
|
138
|
+
* @param raw - A WAMessage
|
|
139
|
+
* @returns True when stored
|
|
140
|
+
*/
|
|
141
|
+
captureMessage(raw: unknown): boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Handle `messages.upsert` (both `notify` and `append`: own messages sent
|
|
144
|
+
* from the phone and backfills arrive as `append`).
|
|
145
|
+
*
|
|
146
|
+
* @param upsert - `{ messages, type }`
|
|
147
|
+
* @returns Number of messages stored
|
|
148
|
+
*/
|
|
149
|
+
onMessagesUpsert(upsert: unknown): number;
|
|
150
|
+
/**
|
|
151
|
+
* Handle `messaging-history.set`: seed chats, contact names and recent
|
|
152
|
+
* messages. Messages older than `HISTORY_SYNC_MAX_AGE_MS` are skipped.
|
|
153
|
+
*
|
|
154
|
+
* @param payload - `{ chats, contacts, messages }`
|
|
155
|
+
* @returns Number of messages stored
|
|
156
|
+
*/
|
|
157
|
+
onHistorySet(payload: unknown): number;
|
|
158
|
+
/**
|
|
159
|
+
* Handle `contacts.upsert` / `contacts.update`.
|
|
160
|
+
*
|
|
161
|
+
* @param contacts - Contact list
|
|
162
|
+
*/
|
|
163
|
+
onContacts(contacts: unknown): void;
|
|
164
|
+
/**
|
|
165
|
+
* Handle `chats.upsert` / `chats.update`.
|
|
166
|
+
*
|
|
167
|
+
* @param chats - Chat list
|
|
168
|
+
*/
|
|
169
|
+
onChats(chats: unknown): void;
|
|
170
|
+
/**
|
|
171
|
+
* Handle `groups.upsert` / `groups.update`.
|
|
172
|
+
*
|
|
173
|
+
* @param groups - Group metadata list
|
|
174
|
+
*/
|
|
175
|
+
onGroups(groups: unknown): void;
|
|
176
|
+
/**
|
|
177
|
+
* Look a group's subject up once per process when we have no name for it.
|
|
178
|
+
*
|
|
179
|
+
* @param jid - Group JID
|
|
180
|
+
*/
|
|
181
|
+
private maybeFetchGroupName;
|
|
182
|
+
}
|
|
183
|
+
export {};
|
|
184
|
+
//# sourceMappingURL=whatsapp-inbox-capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-inbox-capture.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/whatsapp/whatsapp-inbox-capture.ts"],"names":[],"mappings":"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"}
|