@agentcomms/whatsapp 0.15.2 → 0.17.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/README.md +4 -0
- package/dist/cli.mjs +2 -2
- package/dist/index.d.mts +344 -7
- package/dist/index.mjs +90 -5
- package/dist/{server-DFGzE4pA.mjs → server-DChdfrt8.mjs} +1 -1
- package/dist/{stdio-entry-Bjqf4Tz2.mjs → stdio-entry-ZWI-4ayI.mjs} +1 -1
- package/dist/{sync-Ce-u-zuv.mjs → sync-7LwbofMr.mjs} +816 -130
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@ npm install -g @agentcomms/whatsapp # or run it with npx -y @agentcomms/whats
|
|
|
11
11
|
It needs **macOS with WhatsApp for Mac** installed and signed in, and **Node 22.16 or newer** (it reads with Node's
|
|
12
12
|
own SQLite, which is complete from 22.16; an older Node is refused with what to install).
|
|
13
13
|
|
|
14
|
+
The local event daemon, [`@agentcomms/events-daemon`](../events-daemon/README.md), may call this package's internal
|
|
15
|
+
read-only event operation over a checked local copy.
|
|
16
|
+
It adds no WhatsApp command or MCP tool, and it cannot send.
|
|
17
|
+
|
|
14
18
|
## What it does, and what it never does, in plain words
|
|
15
19
|
|
|
16
20
|
- **It reads only local files.** Nothing it does to read, search, draft or keep its lists connects to WhatsApp, or
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as exemptFromUpdateGate, A as requireAccount, B as PATH_OPTIONS, Ct as writeResult, Et as VERSION, F as expandSource, G as approveAndWaitSentence, H as absoluteSearchPath, J as childEnvironment, K as approveChangeAtTerminal, L as CHANNEL_SERVERS, M as storeIdentity, N as checkStorePath, O as PLATFORM, P as defaultStorePath, Q as escapeForDisplay, R as CommsError, S as chatRefOf, St as withFileLock, U as agentMarker, W as approvalsOf, X as colorEnabled, Y as cliHandoffs, Z as commandPathOf, _ as WhatsAppContext, a as CHAT_KINDS, at as lookupName, b as requireSupportedNode, c as searchMessages, ct as openCore, dt as renderInstall, et as gatedChangeAtTerminal, f as draftMessage, ft as renderPrune, gt as serverPruneChange, ht as serverInstallChange, i as probeStore, it as isCommsError, j as requireNamedConfig, k as newWhatsAppAccount, l as Presenter, lt as paint, m as listKey, mt as runCommand, nt as handoffText, o as listChats, ot as nameAvailable, p as Visibility, pt as resolvePaths, q as canPrompt, r as whatsappStatus, rt as installExitStatus, s as readChat, st as nameShapeProblem, t as syncAccount, tt as handoffSentence, u as innerText, ut as pathOverridesFromCliOptions, v as WhatsAppIndex, vt as truncateDisplay, wt as CALLER, xt as windowsSystemProgram, yt as updateGateAtTerminal, z as EXIT_CODES } from "./sync-7LwbofMr.mjs";
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
import path, { delimiter, join } from "node:path";
|
|
5
5
|
import { stripVTControlCharacters } from "node:util";
|
|
@@ -3582,7 +3582,7 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
|
|
|
3582
3582
|
ran = true;
|
|
3583
3583
|
exitCode = await runCommand(output(), async () => {
|
|
3584
3584
|
requireSupportedNode();
|
|
3585
|
-
await (deps.startMcp ?? (await import("./stdio-entry-
|
|
3585
|
+
await (deps.startMcp ?? (await import("./stdio-entry-ZWI-4ayI.mjs")).startWhatsAppStdioServer)({
|
|
3586
3586
|
env,
|
|
3587
3587
|
platform,
|
|
3588
3588
|
...deps,
|
package/dist/index.d.mts
CHANGED
|
@@ -4277,7 +4277,7 @@ declare namespace external_d_exports {
|
|
|
4277
4277
|
export { $brand, $input, $output, BRAND, CompileOptions, GlobalMeta, INVALID, Infer$1 as Infer, IssueData, JSONType, NEVER, $RefinementCtx as RefinementCtx, SafeExtendShape, Schema, TimePrecision, TypeOf, ZodAny, ZodArray, ZodBase64, ZodBase64URL, ZodBigInt, ZodBigIntFormat, ZodBoolean, ZodCIDRv4, ZodCIDRv6, ZodCUID, ZodCUID2, ZodCatch, ZodCodec, ZodCoercedBigInt, ZodCoercedBoolean, ZodCoercedDate, ZodCoercedNumber, ZodCoercedString, ZodCompileAsyncError, ZodCompileUnsupportedError, ZodCreditCard, ZodCustom, ZodCustomStringFormat, ZodDate, ZodDefault, ZodDiscriminatedUnion, ZodE164, ZodEmail, ZodEmoji, ZodEnum, ZodError, ZodErrorMap, ZodExactOptional, ZodFile, ZodFirstPartySchemaTypes, ZodFirstPartyTypeKind, ZodFlattenedError, ZodFloat32, ZodFloat64, ZodFormattedError, ZodFunction, ZodGUID, ZodIBAN, ZodIPv4, ZodIPv6, ZodISODate, ZodISODateTime, ZodISODuration, ZodISOTime, ZodInstanceOf, ZodInt, ZodInt32, ZodInt64, ZodIntersection, ZodIssue, ZodIssueCode, ZodJSONSchema, ZodJSONSchemaInternals, ZodJWT, ZodKSUID, ZodLazy, ZodLiteral, ZodMAC, ZodMap, ZodNaN, ZodNanoID, ZodNever, ZodNonOptional, ZodNull, ZodNullable, ZodNumber, ZodNumberFormat, ZodObject, ZodOptional, ZodPipe, ZodPrefault, ZodPreprocess, ZodPromise, ZodRawShape$1 as ZodRawShape, ZodReadonly, ZodRealError, ZodRecord, ZodSafeParseError, ZodSafeParseResult, ZodSafeParseSuccess, ZodSchema, ZodSet, ZodStandardSchemaWithJSON, ZodString, ZodStringFormat, ZodSuccess, ZodSymbol, ZodTemplateLiteral, ZodTransform, ZodTuple, ZodType, ZodTypeAny, ZodUInt32, ZodUInt64, ZodULID, ZodURL, ZodUUID, ZodUndefined, ZodUnion, ZodUnknown, ZodVoid, ZodXID, ZodXor, _ZodBigInt, _ZodBoolean, _ZodDate, _ZodNumber, _ZodString, _ZodType, _default, _function, any, array, base64, base64url, bigint$1 as bigint, boolean$1 as boolean, _catch as catch, check, cidrv4, cidrv6, clone, codec, coerce_d_exports as coerce, compile, config, index_d_exports$1 as core, creditCard, cuid, cuid2, currencyCode, custom, date$2 as date, decode, decodeAsync, deepPartial, describe, discriminatedUnion, e164, email, emoji, encode, encodeAsync, _endsWith as endsWith, _enum as enum, exactOptional, file, flattenError, float32, float64, formatError, fromJSONSchema, _function as function, getDiscriminatedOption, getErrorMap, globalRegistry, _gt as gt, _gte as gte, guid, hash, hex, hostname, httpUrl, iban, _includes as includes, output$1 as infer, inferFlattenedErrors, inferFormattedError, input, _instanceof as instanceof, int, int32, int64, intersection, invertCodec, ipv4, ipv6, iso_d_exports as iso, json, jwt, keyof, ksuid, lazy, _length as length, literal, index_d_exports$2 as locales, looseObject, looseRecord, _lowercase as lowercase, _lt as lt, _lte as lte, mac, map, _maxLength as maxLength, _maxSize as maxSize, memoizer, meta, _mime as mime, _minLength as minLength, _minSize as minSize, _multipleOf as multipleOf, nan, nanoid, nativeEnum, _negative as negative, never, _nonnegative as nonnegative, nonoptional, _nonpositive as nonpositive, _normalize as normalize, _null as null, nullable, nullish, number$1 as number, object, optional, output, _overwrite as overwrite, parse, parseAsync, partialRecord, pipe, _positive as positive, prefault, preprocess, prettifyError, promise, _properties as properties, _property as property, readonly, record, refine, _regex as regex, regexes_d_exports as regexes, registry, safeDecode, safeDecodeAsync, safeEncode, safeEncodeAsync, safeParse, safeParseAsync, set, setErrorMap, _size as size, _slugify as slugify, _startsWith as startsWith, strictObject, string$1 as string, stringFormat, stringbool, success, superRefine, symbol, templateLiteral, toJSONSchema, _toLowerCase as toLowerCase, _toUpperCase as toUpperCase, toZod, transform, treeifyError, _trim as trim, tuple, uint32, uint64, ulid, _undefined as undefined, union, unknown, _uppercase as uppercase, url, util_d_exports as util, uuid, uuidv4, uuidv6, uuidv7, validate, validateAsync, _void as void, withParser, xid, xor };
|
|
4278
4278
|
}
|
|
4279
4279
|
//#endregion
|
|
4280
|
-
//#region ../core/dist/change-flow-
|
|
4280
|
+
//#region ../core/dist/change-flow-B_bGbrkO.d.mts
|
|
4281
4281
|
//#region src/config-hooks.d.ts
|
|
4282
4282
|
/**
|
|
4283
4283
|
* Test-only pauses inside `ConfigStore.convertToVersion3`, for the tests that prove a write racing the conversion is
|
|
@@ -4369,7 +4369,7 @@ interface ResolvedPaths {
|
|
|
4369
4369
|
*/
|
|
4370
4370
|
downloadsDir: string;
|
|
4371
4371
|
}
|
|
4372
|
-
type ErrorCode = "UNEXPECTED" | "APPROVAL_REQUIRED" | "APPROVAL_PENDING" | "APPROVAL_EXPIRED" | "APPROVAL_VOID" | "SEND_OUTCOME_UNKNOWN" | "SEND_REFUSED" | "POLICY_NEVER" | "RATE_CAPPED" | "UNSENDABLE_HTML" | "LOOSENING_REFUSED" | "UPDATE_REQUIRED" | "USAGE" | "CURSOR_MISMATCH" | "BAD_DATA" | "REPLY_INVALID" | "NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "SECRET_STORE_UNAVAILABLE" | "TRANSIENT" | "KEYCHAIN_APPROVAL_PENDING" | "LOCK_TIMEOUT" | "AUTH_REQUIRED" | "SCOPE_MISSING" | "CONFIG";
|
|
4372
|
+
type ErrorCode = "UNEXPECTED" | "APPROVAL_REQUIRED" | "APPROVAL_PENDING" | "APPROVAL_EXPIRED" | "APPROVAL_VOID" | "SEND_OUTCOME_UNKNOWN" | "SEND_REFUSED" | "POLICY_NEVER" | "RATE_CAPPED" | "UNSENDABLE_HTML" | "LOOSENING_REFUSED" | "UPDATE_REQUIRED" | "USAGE" | "CURSOR_MISMATCH" | "BAD_DATA" | "REPLY_INVALID" | "NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "SOURCE_UNAVAILABLE" | "SECRET_STORE_UNAVAILABLE" | "TRANSIENT" | "KEYCHAIN_APPROVAL_PENDING" | "LOCK_TIMEOUT" | "AUTH_REQUIRED" | "SCOPE_MISSING" | "WHATSAPP_VISIBILITY_SEAM_REQUIRED" | "CONFIG";
|
|
4373
4373
|
interface CommsErrorOptions {
|
|
4374
4374
|
hint?: string;
|
|
4375
4375
|
details?: Record<string, unknown>;
|
|
@@ -4999,6 +4999,28 @@ interface LooseningConsent {
|
|
|
4999
4999
|
* change's is the live change policy. It never changes when the configuration does.
|
|
5000
5000
|
*/
|
|
5001
5001
|
type ApprovalRoute = "chat" | "confirm";
|
|
5002
|
+
/** The four activation documents that may receive standing disclosure authority. */
|
|
5003
|
+
type DisclosureActivationKind = "rule" | "judge-budget" | "judge-kind" | "enable-all";
|
|
5004
|
+
/** An immutable version named by a standing disclosure authorisation. */
|
|
5005
|
+
type DisclosureVersionKind = "rule" | "target" | "subscriber" | "judge" | "judge-budget" | "judge-kind";
|
|
5006
|
+
interface DisclosureVersion {
|
|
5007
|
+
readonly kind: DisclosureVersionKind;
|
|
5008
|
+
readonly id: string;
|
|
5009
|
+
readonly version: number;
|
|
5010
|
+
}
|
|
5011
|
+
/**
|
|
5012
|
+
* The content-free, canonical authority for a future disclosure (local-event design D2).
|
|
5013
|
+
*
|
|
5014
|
+
* The event daemon derives this from one canonical activation document. Core does not construct
|
|
5015
|
+
* that document; it verifies the closed shape that makes an independently supplied version list
|
|
5016
|
+
* impossible to smuggle into a disclosure record.
|
|
5017
|
+
*/
|
|
5018
|
+
interface DisclosureBinding {
|
|
5019
|
+
readonly digest: string;
|
|
5020
|
+
readonly activationIntentId: string;
|
|
5021
|
+
readonly activationKind: DisclosureActivationKind;
|
|
5022
|
+
readonly versions: readonly DisclosureVersion[];
|
|
5023
|
+
}
|
|
5002
5024
|
/**
|
|
5003
5025
|
* What a record's `inboxId` names. `owner` — a mailbox or account that existed when it was made (every send and
|
|
5004
5026
|
* download, and a change to one). `prospective` — a change that creates its owner, which has no id yet. `global` — a
|
|
@@ -5130,7 +5152,10 @@ interface AuditRecord {
|
|
|
5130
5152
|
recipients?: string[];
|
|
5131
5153
|
approvalId?: string;
|
|
5132
5154
|
reason?: string;
|
|
5133
|
-
|
|
5155
|
+
/** The process that performed the work; daemon work keeps its caller separately in `origin`. */
|
|
5156
|
+
surface?: "cli" | "mcp" | "app" | "daemon";
|
|
5157
|
+
/** The requesting client surface; autonomous work is `daemon`/`daemon`. */
|
|
5158
|
+
origin?: "cli" | "mcp" | "app" | "daemon";
|
|
5134
5159
|
/**
|
|
5135
5160
|
* The policy that decided how this was approved. Written by change approvals, where it is the whole question
|
|
5136
5161
|
* afterwards: a loosening a person typed a code for and one an agent claimed after a yes in chat look the same in
|
|
@@ -5250,7 +5275,7 @@ interface CorruptStub {
|
|
|
5250
5275
|
readonly reason: UnreadableReason | IntegrityReason;
|
|
5251
5276
|
}
|
|
5252
5277
|
/** What an attribution-verified corrupt record may still show its owner: the fields its binding was made over. */
|
|
5253
|
-
interface
|
|
5278
|
+
interface OrdinarySafeFields {
|
|
5254
5279
|
readonly kind: ApprovalKind;
|
|
5255
5280
|
readonly channel: string;
|
|
5256
5281
|
readonly ownerScope: OwnerScope;
|
|
@@ -5260,6 +5285,11 @@ interface SafeFields {
|
|
|
5260
5285
|
readonly draftMessageId: string;
|
|
5261
5286
|
readonly expect: Expectation;
|
|
5262
5287
|
}
|
|
5288
|
+
/** A disclosure record has no channel owner or sender-controlled operational fields to expose. */
|
|
5289
|
+
interface DisclosureSafeFields {
|
|
5290
|
+
readonly kind: "disclosure";
|
|
5291
|
+
}
|
|
5292
|
+
type SafeFields = OrdinarySafeFields | DisclosureSafeFields;
|
|
5263
5293
|
type StoredApproval = {
|
|
5264
5294
|
readonly form: "v2";
|
|
5265
5295
|
readonly record: ApprovalRecord;
|
|
@@ -5385,7 +5415,7 @@ type ApprovalChannel = "elicitation" | "terminal";
|
|
|
5385
5415
|
* and a kind on every record, so that none can be spent as another. Without it, a person who approved a post at a
|
|
5386
5416
|
* terminal would also have approved whatever change an agent claimed under the same id.
|
|
5387
5417
|
*/
|
|
5388
|
-
type ApprovalKind = "send" | "change" | "download";
|
|
5418
|
+
type ApprovalKind = "send" | "change" | "download" | "disclosure";
|
|
5389
5419
|
/** The inbox or account a change is about. `id` is absent when the change connects it, and it does not exist yet. */
|
|
5390
5420
|
interface ChangeTarget {
|
|
5391
5421
|
kind: "inbox" | "account";
|
|
@@ -5562,6 +5592,17 @@ interface ApprovalRecord {
|
|
|
5562
5592
|
change?: ChangeBinding | undefined;
|
|
5563
5593
|
/** For a download's question: what it asked about, and the folders it offered. `contentDigest` is `downloadDigest`. */
|
|
5564
5594
|
download?: DownloadBinding | undefined;
|
|
5595
|
+
/** For a standing disclosure: the canonical, content-free event activation authority. */
|
|
5596
|
+
disclosure?: DisclosureBinding | undefined;
|
|
5597
|
+
}
|
|
5598
|
+
/**
|
|
5599
|
+
* The stored branch for standing disclosure. Its runtime decoder refuses every field belonging to send, change or
|
|
5600
|
+
* download records; this type is what the three disclosure-only methods return.
|
|
5601
|
+
*/
|
|
5602
|
+
interface DisclosureApprovalRecord extends Omit<ApprovalRecord, "kind" | "disclosure" | "approvedVia" | "channel" | "ownerScope" | "route" | "inboxId" | "inboxSub" | "draftId" | "draftMessageId" | "contentDigest" | "sendEpoch" | "policy" | "requiredPolicy" | "riskFlags" | "expect" | "approvedBindingDigest" | "sendingAt" | "sendingHeartbeatAt" | "sentAt" | "sentMessageId" | "failedAt" | "change" | "download"> {
|
|
5603
|
+
kind: "disclosure";
|
|
5604
|
+
disclosure: DisclosureBinding;
|
|
5605
|
+
approvedVia?: "terminal" | "app" | undefined;
|
|
5565
5606
|
}
|
|
5566
5607
|
interface CreateApprovalInput {
|
|
5567
5608
|
/** The manifest channel preparing the send. A channel this release does not know is refused. */
|
|
@@ -5706,6 +5747,8 @@ declare class ApprovalStore {
|
|
|
5706
5747
|
*/
|
|
5707
5748
|
ensurePruned(options?: MaintenanceOptions): Promise<MaintenanceStatus>;
|
|
5708
5749
|
create(input: CreateApprovalInput): Promise<ApprovalRecord>;
|
|
5750
|
+
/** Creates only a pending standing disclosure approval from a daemon-derived canonical binding. */
|
|
5751
|
+
createDisclosure(binding: DisclosureBinding): Promise<DisclosureApprovalRecord>;
|
|
5709
5752
|
/**
|
|
5710
5753
|
* One approval, in whichever of its four forms it is (`StoredApproval`), read against one configuration snapshot —
|
|
5711
5754
|
* or null when there is none. Nothing is written.
|
|
@@ -5748,6 +5791,21 @@ declare class ApprovalStore {
|
|
|
5748
5791
|
issueChallenge(approvalId: string, kind?: ApprovalKind, _platform?: NodeJS.Platform, options?: {
|
|
5749
5792
|
usedSaid?: UsedSaid | undefined;
|
|
5750
5793
|
}): Promise<string>;
|
|
5794
|
+
/** Issues the typed challenge that a terminal or the trusted desktop app must answer for disclosure. */
|
|
5795
|
+
issueDisclosureChallenge(approvalId: string): Promise<string>;
|
|
5796
|
+
/**
|
|
5797
|
+
* Approves one exact standing disclosure authority. MCP/chat forms are never an approval channel here: `app` means
|
|
5798
|
+
* the desktop application's trusted typed-challenge bridge, not a client form.
|
|
5799
|
+
*/
|
|
5800
|
+
approveDisclosure(approvalId: string, liveBinding: DisclosureBinding, answer: string, via: "terminal" | "app"): Promise<DisclosureApprovalRecord>;
|
|
5801
|
+
/**
|
|
5802
|
+
* Claims an approved disclosure once, persisting the exact `usedAt` that recovery must subsequently reuse.
|
|
5803
|
+
* A specialised caller may impose a shorter deadline; it is evaluated while this record's transition lock is held,
|
|
5804
|
+
* so a check made before waiting on that lock cannot accidentally consume the approval after it has expired.
|
|
5805
|
+
*/
|
|
5806
|
+
claimForDisclosure(approvalId: string, liveBinding: DisclosureBinding, options?: {
|
|
5807
|
+
readonly deadlineMs?: number | undefined;
|
|
5808
|
+
}): Promise<DisclosureApprovalRecord>;
|
|
5751
5809
|
/**
|
|
5752
5810
|
* A human approved through a confirm channel by typing the issued challenge. The draft must still be exactly what the
|
|
5753
5811
|
* record was prepared for; otherwise the record is voided, because the human would be approving content the record
|
|
@@ -6069,9 +6127,13 @@ interface TaintHandle {
|
|
|
6069
6127
|
id: string;
|
|
6070
6128
|
}
|
|
6071
6129
|
type TaintSource = "header" | "body";
|
|
6130
|
+
/** Why the content reached this process. This is separate from `TaintSource`, which remains the cap priority. */
|
|
6131
|
+
type TaintOrigin = "read" | "event";
|
|
6072
6132
|
interface TaintObservation {
|
|
6073
6133
|
address: string;
|
|
6074
6134
|
source: TaintSource;
|
|
6135
|
+
/** Old readers did not record this, so an omitted origin remains an ordinary read. */
|
|
6136
|
+
origin?: TaintOrigin | undefined;
|
|
6075
6137
|
inboxId: string;
|
|
6076
6138
|
messageId?: string | undefined;
|
|
6077
6139
|
}
|
|
@@ -6079,6 +6141,8 @@ interface TaintObservation {
|
|
|
6079
6141
|
interface TaintHandleObservation {
|
|
6080
6142
|
handle: TaintHandle;
|
|
6081
6143
|
source: TaintSource;
|
|
6144
|
+
/** Old readers did not record this, so an omitted origin remains an ordinary read. */
|
|
6145
|
+
origin?: TaintOrigin | undefined;
|
|
6082
6146
|
inboxId: string;
|
|
6083
6147
|
messageId?: string | undefined;
|
|
6084
6148
|
}
|
|
@@ -6102,6 +6166,8 @@ interface TaintAggregate {
|
|
|
6102
6166
|
readonly at: string;
|
|
6103
6167
|
readonly source: TaintSource;
|
|
6104
6168
|
readonly inboxIds: readonly string[];
|
|
6169
|
+
/** Sorted, duplicate-free provenance. A base entry with no sidecar record is a legacy read. */
|
|
6170
|
+
readonly origins: readonly TaintOrigin[];
|
|
6105
6171
|
}
|
|
6106
6172
|
interface TaintCheck {
|
|
6107
6173
|
address: boolean;
|
|
@@ -6111,10 +6177,15 @@ interface TaintCheck {
|
|
|
6111
6177
|
/** Its domain's, when that counts — not a public mailbox provider — and is in the window. */
|
|
6112
6178
|
domainSeen?: TaintAggregate | undefined;
|
|
6113
6179
|
}
|
|
6180
|
+
/** Testable interruption point for the crash-safe, two-lock sidecar protocol. */
|
|
6181
|
+
interface TaintStoreHooks {
|
|
6182
|
+
afterOriginsCommit?: (() => Promise<void>) | undefined;
|
|
6183
|
+
onLockAcquired?: ((lock: "origins" | "taint" | "handles") => void) | undefined;
|
|
6184
|
+
}
|
|
6114
6185
|
declare class TaintStore {
|
|
6115
6186
|
#private;
|
|
6116
6187
|
readonly directory: string;
|
|
6117
|
-
constructor(stateDir: string, now?: () => Date);
|
|
6188
|
+
constructor(stateDir: string, now?: () => Date, hooks?: TaintStoreHooks);
|
|
6118
6189
|
/**
|
|
6119
6190
|
* Records observations. Throws if it cannot — callers must fail the read rather than return content whose taint
|
|
6120
6191
|
* was not recorded.
|
|
@@ -6132,6 +6203,8 @@ declare class TaintStore {
|
|
|
6132
6203
|
* carries a domain: two ids sharing a workspace says nothing about either of them.
|
|
6133
6204
|
*/
|
|
6134
6205
|
checkHandle(handle: TaintHandle): Promise<boolean>;
|
|
6206
|
+
/** The merged provenance of a retained handle, or no origins when it is not in the taint window. */
|
|
6207
|
+
checkHandleOrigins(handle: TaintHandle): Promise<readonly TaintOrigin[]>;
|
|
6135
6208
|
}
|
|
6136
6209
|
//#endregion
|
|
6137
6210
|
//#region src/core.d.ts
|
|
@@ -13349,9 +13422,121 @@ type RegisteredPrompt = {
|
|
|
13349
13422
|
remove(): void;
|
|
13350
13423
|
};
|
|
13351
13424
|
//#endregion
|
|
13425
|
+
//#region src/config.d.ts
|
|
13426
|
+
interface WhatsAppAccount extends AccountConfig {
|
|
13427
|
+
/** The store, when it is not WhatsApp for Mac's default. Absent means the default. */
|
|
13428
|
+
source?: string | undefined;
|
|
13429
|
+
}
|
|
13430
|
+
//#endregion
|
|
13352
13431
|
//#region src/source/types.d.ts
|
|
13432
|
+
/**
|
|
13433
|
+
* What a `ZWAMESSAGE.ZMESSAGETYPE` number means.
|
|
13434
|
+
*
|
|
13435
|
+
* From [F] and [W] in `schema.ts` — two independent public tables that agree where they overlap — with [I] for 5 and
|
|
13436
|
+
* [K] for 6, 14 and 15. iLEAPP says plainly that it has no source for the rest; neither has this. A number not listed
|
|
13437
|
+
* is reported as `unknown` with the number, never guessed at.
|
|
13438
|
+
*/
|
|
13439
|
+
type MessageKind = "text" | "image" | "video" | "audio" | "contact" | "location" | "group-event" | "link" | "document" | "system" | "gif" | "waiting" | "deleted" | "sticker" | "poll" | "video-note" | "call" | "album" | "unknown";
|
|
13353
13440
|
/** What a chat is, from its JID — the one part of a chat WhatsApp assigns and nobody in it can choose. */
|
|
13354
13441
|
type ChatKind = "direct" | "hidden-number" | "group" | "status" | "broadcast" | "channel" | "unknown";
|
|
13442
|
+
/**
|
|
13443
|
+
* [M] imports exactly three kinds of conversation — `@s.whatsapp.net`, `@lid` and `@g.us` — and skips every other
|
|
13444
|
+
* session, comparing JIDs lower-cased; [I] names `@newsletter` chats as channels. The rest were seen rather than
|
|
13445
|
+
* sourced: a real store kept each contact's status posts in a session of its own, `<number>@status`, and WhatsApp's
|
|
13446
|
+
* own status feed and broadcast lists use `status@broadcast` and `<id>@broadcast`. A JID none of these match is
|
|
13447
|
+
* `unknown`, never guessed at.
|
|
13448
|
+
*/
|
|
13449
|
+
export declare function chatKindOf(jid: string): ChatKind;
|
|
13450
|
+
//#endregion
|
|
13451
|
+
//#region src/visibility.d.ts
|
|
13452
|
+
/**
|
|
13453
|
+
* Which chats an agent may see in an account: the person's allow and deny lists.
|
|
13454
|
+
*
|
|
13455
|
+
* - **deny**: chats an agent must never see. A denied chat is not listed, searched, read, counted or drafted to, and
|
|
13456
|
+
* asking for it by id gets the answer a chat that does not exist gets.
|
|
13457
|
+
* - **allow**: when it has anything on it, the only chats an agent may see.
|
|
13458
|
+
* - Denied wins over allowed. With neither list, every chat is visible — status updates are still left out of
|
|
13459
|
+
* listings and searches unless asked for, which is a default, not a list.
|
|
13460
|
+
*
|
|
13461
|
+
* An entry is a chat id, or a phone number kept as its `…@s.whatsapp.net` id. A number names a person rather than one
|
|
13462
|
+
* chat: their one-to-one chat and their own status posts (`<number>@status`) — and, in a status feed, where each post
|
|
13463
|
+
* is its author's, the posts they wrote.
|
|
13464
|
+
*
|
|
13465
|
+
* **A status post needs an author the lists can be checked against.** Its author is the person WhatsApp recorded as
|
|
13466
|
+
* sending it, or — in a contact's own `<number>@status` session — that contact; one the person posted is theirs. A post
|
|
13467
|
+
* with no author (no `ZFROMJID`, no group-member row, or an id that names no person) could be anyone's, including
|
|
13468
|
+
* someone denied, so it is shown only while the lists hide nobody, and otherwise hidden and counted
|
|
13469
|
+
* (`unattributed`). Failing closed costs posts nobody can be named for; failing open showed a denied person's.
|
|
13470
|
+
*
|
|
13471
|
+
* **A group is a chat**, allowed or denied whole: denying someone does not remove what they wrote in a group the agent
|
|
13472
|
+
* may see, whether WhatsApp recorded them as its author or not — so an unknown author in a group hides nothing either.
|
|
13473
|
+
* A status post is its author's alone, and a feed only files them together. A group message is part of a conversation
|
|
13474
|
+
* the others answer and quote, so hiding one person's lines would leave the rest describing them. It could not be
|
|
13475
|
+
* done reliably either: a member can appear under a hidden-number id (`…@lid`) that their number does not match, and
|
|
13476
|
+
* an allowed group would lose every member not on the allow list. Denying the group is what hides it.
|
|
13477
|
+
*
|
|
13478
|
+
* Every read applies this — `WhatsAppIndex` cannot be opened without one — and so does `sync`, which leaves what it
|
|
13479
|
+
* hides out of the index, so a hidden chat's messages are not kept in a second copy on disk either.
|
|
13480
|
+
*/
|
|
13481
|
+
interface ChatLists {
|
|
13482
|
+
readonly allow: readonly string[];
|
|
13483
|
+
readonly deny: readonly string[];
|
|
13484
|
+
}
|
|
13485
|
+
/** One entry per person or chat: a phone number and the ids that carry it compare equal. */
|
|
13486
|
+
export declare function listKey(jid: string): string;
|
|
13487
|
+
/**
|
|
13488
|
+
* The one visibility unit a raw row belongs to. The daemon persists this key, but only this channel defines the
|
|
13489
|
+
* vocabulary: an ordinary chat is its list key; a status post is the pair of its status chat and its author (or no
|
|
13490
|
+
* author), because the lists can hide either one — denying the status feed hides every author in it.
|
|
13491
|
+
*/
|
|
13492
|
+
export declare function visibilityUnitKey(chatId: string, chatKind: ChatKind | string, senderJid: string | null): string;
|
|
13493
|
+
export declare class Visibility {
|
|
13494
|
+
#private;
|
|
13495
|
+
constructor(lists: ChatLists | undefined);
|
|
13496
|
+
/** Whether an agent may see this chat at all. */
|
|
13497
|
+
seesChat(chatId: string): boolean;
|
|
13498
|
+
/**
|
|
13499
|
+
* Whether an agent may see this message: its chat must be visible, and in a status chat, its author — the person's
|
|
13500
|
+
* own posts always, anyone else's only when they are known and visible, or when the lists hide nobody at all.
|
|
13501
|
+
*/
|
|
13502
|
+
seesMessage(chatId: string, chatKind: ChatKind | string, senderJid: string | null, fromMe: boolean): boolean;
|
|
13503
|
+
/** Whether a durable source unit remains visible under these same lists. */
|
|
13504
|
+
seesUnit(unitKey: string): boolean;
|
|
13505
|
+
/** A status post someone else wrote with no author the lists can be checked against: hidden while they hide anyone. */
|
|
13506
|
+
unattributed(chatId: string, chatKind: ChatKind | string, senderJid: string | null, fromMe: boolean): boolean;
|
|
13507
|
+
}
|
|
13508
|
+
/**
|
|
13509
|
+
* Who wrote a status post, as an id the lists can be checked against — or null when nobody can be named.
|
|
13510
|
+
*
|
|
13511
|
+
* The sender WhatsApp recorded, when it is a person (a number, or a hidden number); otherwise, in a contact's own
|
|
13512
|
+
* session, that contact. A group's, a feed's or a broadcast's id is no author.
|
|
13513
|
+
*/
|
|
13514
|
+
export declare function statusAuthor(chatId: string, senderJid: string | null): string | null;
|
|
13515
|
+
//#endregion
|
|
13516
|
+
//#region src/lists.d.ts
|
|
13517
|
+
declare class ChatListStore {
|
|
13518
|
+
#private;
|
|
13519
|
+
readonly path: string;
|
|
13520
|
+
constructor(configDir: string);
|
|
13521
|
+
/** One account's lists; none when the account has no entry. */
|
|
13522
|
+
of(accountId: string): Promise<ChatLists>;
|
|
13523
|
+
/**
|
|
13524
|
+
* Reads the live list while holding the same inter-process gate as allow/deny/clear. Event consumers get the
|
|
13525
|
+
* parsed policy and its canonical digest, never a second mutable copy of the list file.
|
|
13526
|
+
*/
|
|
13527
|
+
withCurrent<T>(accountId: string, work: (current: Readonly<{
|
|
13528
|
+
version: 1;
|
|
13529
|
+
lists: ChatLists;
|
|
13530
|
+
digest: string;
|
|
13531
|
+
}>) => Promise<T> | T): Promise<T>;
|
|
13532
|
+
/** Changes one account's lists under the file's lock; an account left with neither list has no entry. */
|
|
13533
|
+
update(accountId: string, change: (lists: ChatLists) => ChatLists): Promise<{
|
|
13534
|
+
before: ChatLists;
|
|
13535
|
+
after: ChatLists;
|
|
13536
|
+
}>;
|
|
13537
|
+
/** Forgets an account's lists: when the account is removed. */
|
|
13538
|
+
forget(accountId: string): Promise<void>;
|
|
13539
|
+
}
|
|
13355
13540
|
//#endregion
|
|
13356
13541
|
//#region src/source/snapshot.d.ts
|
|
13357
13542
|
/**
|
|
@@ -13409,6 +13594,43 @@ interface SourceIo {
|
|
|
13409
13594
|
/** Opens a file read-only, refusing a link and never waiting on a pipe. */
|
|
13410
13595
|
open(path: string): Promise<SourceHandle>;
|
|
13411
13596
|
}
|
|
13597
|
+
interface SourceOptions {
|
|
13598
|
+
/**
|
|
13599
|
+
* What a refusal names to run — `status` at a terminal, an `add` for another store — located as the package printing
|
|
13600
|
+
* it finds its own command (`WhatsAppContext.handoffs`). Required: there is no bare `agent-whatsapp` to fall back on.
|
|
13601
|
+
*/
|
|
13602
|
+
handoffs: CliHandoffs;
|
|
13603
|
+
io?: SourceIo | undefined;
|
|
13604
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
13605
|
+
platform?: NodeJS.Platform | undefined;
|
|
13606
|
+
timeoutMs?: number | undefined;
|
|
13607
|
+
/** Copies attempted before a store that keeps changing is refused. */
|
|
13608
|
+
attempts?: number | undefined;
|
|
13609
|
+
sleep?: ((ms: number) => Promise<void>) | undefined;
|
|
13610
|
+
/** The account's default store, so "not found" can say WhatsApp may not be installed rather than blame a path. */
|
|
13611
|
+
isDefault?: boolean | undefined;
|
|
13612
|
+
}
|
|
13613
|
+
//#endregion
|
|
13614
|
+
//#region src/spike-migration.d.ts
|
|
13615
|
+
interface SpikeMigration {
|
|
13616
|
+
/** Moved into `config.json` by this run. */
|
|
13617
|
+
migrated: string[];
|
|
13618
|
+
/** Already there, by id, from an earlier run that stopped before it could put the file aside. */
|
|
13619
|
+
alreadyThere: string[];
|
|
13620
|
+
/**
|
|
13621
|
+
* Left in the old file, with why — and, when the spike built one for it, where its index still is: a plaintext copy
|
|
13622
|
+
* of its messages that no account reads now and no command deletes.
|
|
13623
|
+
*/
|
|
13624
|
+
skipped: {
|
|
13625
|
+
name: string;
|
|
13626
|
+
reason: string;
|
|
13627
|
+
index?: string | undefined;
|
|
13628
|
+
}[];
|
|
13629
|
+
/** Where the old file went, or null when it stayed because nothing could be moved yet (`deferred`). */
|
|
13630
|
+
keptAs: string | null;
|
|
13631
|
+
/** Why nothing moved at all, when nothing could. */
|
|
13632
|
+
deferred?: string | undefined;
|
|
13633
|
+
}
|
|
13412
13634
|
//#endregion
|
|
13413
13635
|
//#region src/context.d.ts
|
|
13414
13636
|
/**
|
|
@@ -13443,6 +13665,58 @@ interface WhatsAppContextOptions {
|
|
|
13443
13665
|
sourceTimeoutMs?: number | undefined;
|
|
13444
13666
|
platform?: NodeJS.Platform | undefined;
|
|
13445
13667
|
}
|
|
13668
|
+
interface ResolvedAccount {
|
|
13669
|
+
name: string;
|
|
13670
|
+
account: WhatsAppAccount;
|
|
13671
|
+
lists: ChatLists;
|
|
13672
|
+
}
|
|
13673
|
+
declare class WhatsAppContext {
|
|
13674
|
+
#private;
|
|
13675
|
+
readonly env: NodeJS.ProcessEnv;
|
|
13676
|
+
readonly now: () => Date;
|
|
13677
|
+
readonly surface: "cli" | "mcp";
|
|
13678
|
+
readonly platform: NodeJS.Platform;
|
|
13679
|
+
readonly core: Core;
|
|
13680
|
+
/** The commands this tells a person to run, located, and quoted for this context's shell. */
|
|
13681
|
+
readonly handoffs: CliHandoffs;
|
|
13682
|
+
readonly lists: ChatListStore;
|
|
13683
|
+
/** The name a pinned server was started for, as given. */
|
|
13684
|
+
readonly pinned: string | undefined;
|
|
13685
|
+
constructor(options?: WhatsAppContextOptions);
|
|
13686
|
+
/** The spike's accounts moved in, once per process; what happened, when there was a spike file to move. */
|
|
13687
|
+
migration(): Promise<SpikeMigration | null>;
|
|
13688
|
+
/** core's configuration, after the spike's accounts have moved into it. */
|
|
13689
|
+
config(): Promise<Config$1>;
|
|
13690
|
+
/**
|
|
13691
|
+
* The server's pin, checked to name a WhatsApp account, and remembered by its id.
|
|
13692
|
+
*
|
|
13693
|
+
* By id, as Slack's server does, because a rename can move the name under a running server: the pin follows the
|
|
13694
|
+
* account, and a new account that took the name afterwards is not one anybody pinned this server to.
|
|
13695
|
+
*/
|
|
13696
|
+
checkPin(): Promise<void>;
|
|
13697
|
+
/** One account, resolved and checked, with the person's lists for it — which every read applies. */
|
|
13698
|
+
account(named: string | undefined): Promise<ResolvedAccount>;
|
|
13699
|
+
/** The accounts a call without one covers: every WhatsApp account, or the pinned one alone. */
|
|
13700
|
+
accountNames(): Promise<string[]>;
|
|
13701
|
+
/** Where an account's store is, and whether that is the WhatsApp for Mac default. */
|
|
13702
|
+
storeOf(account: WhatsAppAccount): {
|
|
13703
|
+
path: string;
|
|
13704
|
+
isDefault: boolean;
|
|
13705
|
+
};
|
|
13706
|
+
sourceOptions(isDefault: boolean): SourceOptions;
|
|
13707
|
+
/** The account's own directory under core's state directory: its index and its brief snapshots. */
|
|
13708
|
+
accountDir(account: WhatsAppAccount): string;
|
|
13709
|
+
/**
|
|
13710
|
+
* The lock a sync and a remove of this account both hold, so neither runs while the other does. Beside the
|
|
13711
|
+
* account's directory rather than in it, because a remove deletes the directory while it holds the lock.
|
|
13712
|
+
*/
|
|
13713
|
+
syncLock(account: WhatsAppAccount): string;
|
|
13714
|
+
/**
|
|
13715
|
+
* The account with this id as it is now, under whatever name it has now, with its lists as they are now — or null
|
|
13716
|
+
* when it is gone. For a sync to look again, under its lock, at what it started with.
|
|
13717
|
+
*/
|
|
13718
|
+
accountById(id: string): Promise<ResolvedAccount | null>;
|
|
13719
|
+
}
|
|
13446
13720
|
//#endregion
|
|
13447
13721
|
//#region src/mcp/server.d.ts
|
|
13448
13722
|
/**
|
|
@@ -13495,4 +13769,67 @@ export declare function composeDraft(request: {
|
|
|
13495
13769
|
text: string;
|
|
13496
13770
|
}): DraftResult;
|
|
13497
13771
|
//#endregion
|
|
13498
|
-
|
|
13772
|
+
//#region src/source/event-reader.d.ts
|
|
13773
|
+
/**
|
|
13774
|
+
* The event reader deliberately keeps the source tuple before the presentation index derives sender information.
|
|
13775
|
+
* A row-id is available only to produce a stable checked-copy read order; it is never returned as identity.
|
|
13776
|
+
*/
|
|
13777
|
+
interface RawEventMessage {
|
|
13778
|
+
readonly sourceOrder: number;
|
|
13779
|
+
readonly chatJid: string | null;
|
|
13780
|
+
readonly chatKind: ChatKind | null;
|
|
13781
|
+
readonly senderJidRaw: string | null;
|
|
13782
|
+
readonly stanzaId: string | null;
|
|
13783
|
+
/** Exactly false is eligible. A malformed, absent, or non-boolean Core Data value remains unknown. */
|
|
13784
|
+
readonly fromMe: boolean | null;
|
|
13785
|
+
readonly at: string | null;
|
|
13786
|
+
readonly kind: MessageKind;
|
|
13787
|
+
readonly body: string | null;
|
|
13788
|
+
}
|
|
13789
|
+
//#endregion
|
|
13790
|
+
//#region src/operations/events.d.ts
|
|
13791
|
+
/**
|
|
13792
|
+
* The held event owner receives this deliberately narrow, read-only operation object. In particular it never
|
|
13793
|
+
* exposes ChatListStore, whose update/forget methods are local list writes rather than event-reader capabilities.
|
|
13794
|
+
*/
|
|
13795
|
+
interface WhatsAppEventOperations {
|
|
13796
|
+
withCurrentEventVisibility<T>(input: Readonly<{
|
|
13797
|
+
accountId: string;
|
|
13798
|
+
}>, work: (visibility: CurrentEventVisibility) => Promise<T> | T): Promise<T>;
|
|
13799
|
+
withEventSnapshot<T>(input: Readonly<{
|
|
13800
|
+
accountId: string;
|
|
13801
|
+
}>, work: (snapshot: EventSnapshot) => Promise<T> | T): Promise<T>;
|
|
13802
|
+
}
|
|
13803
|
+
/** Opens this channel's own context from the owner's config directory and returns no write-capable local-store handle. */
|
|
13804
|
+
export declare function openWhatsAppEventOperations(input: Readonly<{
|
|
13805
|
+
configDir: string;
|
|
13806
|
+
}>): WhatsAppEventOperations;
|
|
13807
|
+
interface CurrentEventVisibility {
|
|
13808
|
+
/** The list-file format version, not the daemon's durable journal version. */
|
|
13809
|
+
readonly version: 1;
|
|
13810
|
+
readonly digest: string;
|
|
13811
|
+
readonly seesMessage: (chatJid: string, chatKind: string, senderJidRaw: string, fromMe: boolean) => boolean;
|
|
13812
|
+
readonly seesUnit?: ((unitKey: string) => boolean) | undefined;
|
|
13813
|
+
}
|
|
13814
|
+
interface EventSnapshot {
|
|
13815
|
+
readonly accountId: string;
|
|
13816
|
+
readonly accountName: string;
|
|
13817
|
+
readonly visibility: CurrentEventVisibility;
|
|
13818
|
+
readonly messages: readonly RawEventMessage[];
|
|
13819
|
+
}
|
|
13820
|
+
/**
|
|
13821
|
+
* The channel's live-list capability boundary. The callback runs while `.whatsapp-chats.lock` is held, so a daemon
|
|
13822
|
+
* may place a synchronous SQLite decision immediately after this read without a list command racing it.
|
|
13823
|
+
*/
|
|
13824
|
+
export declare function withCurrentEventVisibility<T>(context: WhatsAppContext, input: Readonly<{
|
|
13825
|
+
accountId: string;
|
|
13826
|
+
}>, work: (visibility: CurrentEventVisibility) => Promise<T> | T): Promise<T>;
|
|
13827
|
+
/**
|
|
13828
|
+
* Runs the checked-copy lifecycle for the event source. The normal index is rebuilt first, but the source copies
|
|
13829
|
+
* only raw values from the checked copy and never observes the rebuilt index.
|
|
13830
|
+
*/
|
|
13831
|
+
export declare function withEventSnapshot<T>(context: WhatsAppContext, input: Readonly<{
|
|
13832
|
+
accountId: string;
|
|
13833
|
+
}>, work: (snapshot: EventSnapshot) => Promise<T> | T): Promise<T>;
|
|
13834
|
+
//#endregion
|
|
13835
|
+
export type { ChatKind, CurrentEventVisibility, DraftResult, EventSnapshot, RawEventMessage, WhatsAppEventOperations, WhatsAppMcpOptions, WhatsAppMcpServer };
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,96 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as createWhatsAppMcpServer } from "./server-
|
|
1
|
+
import { C as chatKindOf, D as text, E as numeric, Et as VERSION, R as CommsError, T as kindOf, Tt as PACKAGE_NAME, _ as WhatsAppContext, d as composeDraft, g as visibilityUnitKey, h as statusAuthor, m as listKey, n as withCheckedCopy, p as Visibility, w as coreDataToIso, x as inspectSchema, y as rebuildIndex } from "./sync-7LwbofMr.mjs";
|
|
2
|
+
import { t as createWhatsAppMcpServer } from "./server-DChdfrt8.mjs";
|
|
3
|
+
//#region src/source/event-reader.ts
|
|
4
|
+
function rawFromMe(value) {
|
|
5
|
+
const number = numeric(value);
|
|
6
|
+
if (number === 0) return false;
|
|
7
|
+
if (number === 1) return true;
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Reads only raw, checked-copy facts needed by the local event source. It intentionally does not call
|
|
12
|
+
* `rebuildIndex`, `WhatsAppIndex`, or any presentation sender fallback.
|
|
13
|
+
*/
|
|
14
|
+
function readRawEventMessages(database, _report) {
|
|
15
|
+
return database.prepare(`SELECT m.Z_PK AS sourceOrder, c.ZCONTACTJID AS chatJid, m.ZFROMJID AS senderJidRaw,
|
|
16
|
+
m.ZSTANZAID AS stanzaId, m.ZISFROMME AS fromMe, m.ZMESSAGEDATE AS at,
|
|
17
|
+
m.ZMESSAGETYPE AS typeCode, m.ZTEXT AS body
|
|
18
|
+
FROM ZWAMESSAGE AS m
|
|
19
|
+
LEFT JOIN ZWACHATSESSION AS c ON c.Z_PK = m.ZCHATSESSION
|
|
20
|
+
ORDER BY m.Z_PK`).all().map((row) => {
|
|
21
|
+
const chatJid = text(row.chatJid) || null;
|
|
22
|
+
const type = numeric(row.typeCode);
|
|
23
|
+
return {
|
|
24
|
+
sourceOrder: numeric(row.sourceOrder) ?? 0,
|
|
25
|
+
chatJid,
|
|
26
|
+
chatKind: chatJid === null ? null : chatKindOf(chatJid),
|
|
27
|
+
senderJidRaw: text(row.senderJidRaw) || null,
|
|
28
|
+
stanzaId: text(row.stanzaId) || null,
|
|
29
|
+
fromMe: rawFromMe(row.fromMe),
|
|
30
|
+
at: coreDataToIso(numeric(row.at)),
|
|
31
|
+
kind: kindOf(type).kind,
|
|
32
|
+
body: text(row.body)
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/operations/events.ts
|
|
38
|
+
/** Opens this channel's own context from the owner's config directory and returns no write-capable local-store handle. */
|
|
39
|
+
function openWhatsAppEventOperations(input) {
|
|
40
|
+
const context = new WhatsAppContext({ pathOverrides: { configDir: input.configDir } });
|
|
41
|
+
return {
|
|
42
|
+
withCurrentEventVisibility: (visibilityInput, work) => withCurrentEventVisibility(context, visibilityInput, work),
|
|
43
|
+
withEventSnapshot: (snapshotInput, work) => withEventSnapshot(context, snapshotInput, work)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The channel's live-list capability boundary. The callback runs while `.whatsapp-chats.lock` is held, so a daemon
|
|
48
|
+
* may place a synchronous SQLite decision immediately after this read without a list command racing it.
|
|
49
|
+
*/
|
|
50
|
+
async function withCurrentEventVisibility(context, input, work) {
|
|
51
|
+
return context.lists.withCurrent(input.accountId, async ({ version, lists, digest }) => {
|
|
52
|
+
const visibility = new Visibility(lists);
|
|
53
|
+
return work({
|
|
54
|
+
version,
|
|
55
|
+
digest,
|
|
56
|
+
seesMessage: (chatJid, chatKind, senderJidRaw, fromMe) => visibility.seesMessage(chatJid, chatKind, senderJidRaw, fromMe),
|
|
57
|
+
seesUnit: (unitKey) => visibility.seesUnit(unitKey)
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Runs the checked-copy lifecycle for the event source. The normal index is rebuilt first, but the source copies
|
|
63
|
+
* only raw values from the checked copy and never observes the rebuilt index.
|
|
64
|
+
*/
|
|
65
|
+
async function withEventSnapshot(context, input, work) {
|
|
66
|
+
return withCheckedCopy(context, input, async ({ account, accountName, directory, database, snapshot }) => {
|
|
67
|
+
const report = inspectSchema(database);
|
|
68
|
+
const current = await context.accountById(account.id);
|
|
69
|
+
if (current === null) throw new CommsError("NOT_FOUND", "the account was removed while its event snapshot was being read");
|
|
70
|
+
await rebuildIndex(directory, database, report, {
|
|
71
|
+
indexedAt: context.now().toISOString(),
|
|
72
|
+
copied: snapshot.copied
|
|
73
|
+
}, new Visibility(current.lists), async () => {
|
|
74
|
+
const latest = await context.accountById(account.id);
|
|
75
|
+
return latest !== null && JSON.stringify(latest.lists) === JSON.stringify(current.lists);
|
|
76
|
+
});
|
|
77
|
+
if (await context.accountById(account.id) === null) throw new CommsError("NOT_FOUND", "the account was removed while its event snapshot was being read");
|
|
78
|
+
const messages = readRawEventMessages(database, report).filter((message) => (message.fromMe === false || message.fromMe === true) && message.chatJid !== null && message.chatKind !== null);
|
|
79
|
+
return withCurrentEventVisibility(context, { accountId: account.id }, (visibility) => work({
|
|
80
|
+
accountId: account.id,
|
|
81
|
+
accountName,
|
|
82
|
+
visibility,
|
|
83
|
+
messages
|
|
84
|
+
}));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
3
88
|
//#region src/index.ts
|
|
4
89
|
/**
|
|
5
90
|
* The library entry of `@agentcomms/whatsapp`.
|
|
6
91
|
*
|
|
7
|
-
* Small on purpose, as the Slack package's is: the server, the draft composer
|
|
8
|
-
* reached through the `agent-whatsapp` command.
|
|
92
|
+
* Small on purpose, as the Slack package's is: the server, the draft composer, the event list-lock capability and the
|
|
93
|
+
* version. Everything else is reached through the `agent-whatsapp` command.
|
|
9
94
|
*/
|
|
10
95
|
//#endregion
|
|
11
|
-
export { PACKAGE_NAME, VERSION, composeDraft, createWhatsAppMcpServer };
|
|
96
|
+
export { PACKAGE_NAME, VERSION, Visibility, chatKindOf, composeDraft, createWhatsAppMcpServer, listKey, openWhatsAppEventOperations, statusAuthor, visibilityUnitKey, withCurrentEventVisibility, withEventSnapshot };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Et as VERSION, I as strictToolArguments, V as UNTRUSTED_NOTICE, _ as WhatsAppContext, _t as toCommsError, a as CHAT_KINDS, bt as updateToolGate, c as searchMessages, f as draftMessage, o as listChats, r as whatsappStatus, s as readChat, t as syncAccount } from "./sync-7LwbofMr.mjs";
|
|
2
2
|
import { c as boolean, h as number, x as string } from "./iso-USuVU_sk.mjs";
|
|
3
3
|
import { t as McpServer } from "./mcp-DXXb3Vv3-kCxsVLO4.mjs";
|
|
4
4
|
//#region src/mcp/server.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createWhatsAppMcpServer } from "./server-
|
|
1
|
+
import { t as createWhatsAppMcpServer } from "./server-DChdfrt8.mjs";
|
|
2
2
|
//#region src/mcp/stdio-entry.ts
|
|
3
3
|
/**
|
|
4
4
|
* Starting the stdio server. On stdio, stdout is the protocol, so the console is redirected to stderr first — the
|