export type AgentChannel = "whatsapp" | "telegram"; /** * Reason codes are the true cause, not a symptom (Task 1439 observability): * - `not-registered`: the destination is in no authoritative list (house's * `adminPhones`/`accountManagers` for WhatsApp; the creating account's * `telegram.adminUsers` for Telegram). * - `cross-account`: the destination IS registered, but to an account other * than the one that created the schedule (and the creator is not the house). * Names both accounts so the mismatch is explained, not guessed. */ export type DestinationValidity = { valid: true; } | { valid: false; reason: "not-registered"; } | { valid: false; reason: "cross-account"; destinationAccount: string; scheduleAccount: string; }; /** * Replica of `normalize.ts:normalizeE164` — strip whitespace/dashes/parens, a * `whatsapp:` prefix, and a leading `+` or `00`; return digits-only or `''`. */ export declare function normalizeE164(value: string): string; /** Replica of `normalize.ts:phonesMatch` — normalized equality, non-empty. */ export declare function phonesMatch(a: string, b: string): boolean; interface WhatsAppAdmins { adminPhones: string[]; accountManagers: Record; } /** Read the channel admin config from `/account.json`. Missing or * malformed config yields empty lists (a fail-closed default: no destination * validates against empty config). */ export declare function readChannelAdmins(accountDir: string, channel: "whatsapp"): WhatsAppAdmins; export declare function readChannelAdmins(accountDir: string, channel: "telegram"): { adminUsers: number[]; }; /** * The account a WhatsApp `destination` is registered to on the HOUSE's lists, * or null if it is in neither list. A manager binding wins over an admin match: * if the number is an `accountManagers` key its registered account is the bound * sub-account (the isolation-relevant fact); otherwise if it is an `adminPhones` * entry its registered account is the house. `houseDir` is the house account's * directory; `houseAccountId` its id. */ export declare function registeredAccountForDestination(houseDir: string, houseAccountId: string, destination: string): string | null; /** * Validate a dispatch destination under the Task 1439 two-part rule. * `accountsDir` is the PARENT accounts directory * (`$PLATFORM_ROOT/../data/accounts`); `creatingAccountId` is the account that * created (or owns) the schedule. * * - whatsapp: resolve the socket-owning house, read ITS authoritative lists, * and compute the destination's registered account. `not-registered` if it * is in no house list. Otherwise valid iff the creator IS the house (owner * exception: it owns the single line and may target any registered * admin/manager) OR the registered account equals the creating account. * Any other case is `cross-account`. * - telegram: house-only (Task 1378; 1380 deferred). Reads the creating * account's own `telegram.adminUsers`; there is no manager surface and thus * no cross-account concept — an integer chat id present in the list is valid, * anything else is `not-registered`. */ export declare function validateAgentDestination(accountsDir: string, creatingAccountId: string, channel: AgentChannel, destination: string): DestinationValidity; /** * Validate an `agentDispatch` at write time (shared by `schedule-event` and * `schedule-update` so the two write paths cannot drift on the rule): reject an * empty prompt, and reject a destination that fails the two-part house-authority * rule. Throws with a caller-facing message naming the true reason; returns on * success. `accountId` is the account that created the schedule — the authority * lists are read from the house under `$PLATFORM_ROOT/../data/accounts`, and the * destination's registered account must match `accountId` (house/owner aside). */ export declare function validateAgentDispatch(accountId: string, agentDispatch: { channel: AgentChannel; destination: string; prompt: string; }): void; /** * Task 1516 — the operator-provenance marker carried on a scheduled agent * dispatch. Every field is stamped by the dispatcher path (or resolved by it * from the owning account), NEVER taken from the event's `agentPrompt` payload, * so an inbound message cannot forge it. Rendered by the channel notification * builder as a trusted preamble ABOVE `## Context`, outside the reframed * (untrusted) payload. */ export interface ScheduleProvenance { /** The owning (schedule-creating) account. */ accountId: string; /** The `:Event` this dispatch fired. */ eventId: string; /** The owning account's admin userId(s) from account.json — who authored/ * enabled the standing automation. `[]` when the config is unresolvable. */ authoredBy: string[]; /** The tool that created the event (`Event.createdByTool`), or null. */ createdByTool: string | null; /** ISO timestamp stamped at dispatch. */ dispatchedAt: string; } /** * The owning account's admin userIds, read from * `//account.json` (`admins[].userId`). Fail-safe: any * missing/malformed config yields `[]` — an honest "owner unknown" — never a * throw, so a scheduled dispatch is never aborted because the marker's identity * cannot be resolved. */ export declare function resolveAccountAdminUserIds(accountsDir: string, accountId: string): string[]; /** * Normalise a {@link ScheduleProvenance}: guarantee `authoredBy` is a string[] * of non-empty ids and `createdByTool` is a non-empty string or null. Pure — no * IO — so it is trivially unit-testable and safe to call on either write path. */ export declare function buildScheduleProvenance(input: ScheduleProvenance): ScheduleProvenance; /** * The sub-account a WhatsApp manager destination is bound to, or null. Telegram * has no account-manager surface (house-only, Task 1378 scope), so it always * returns null. Used by the standing audit to detect a deleted sub-account. */ export declare function boundSubAccountFor(accountDir: string, destination: string): string | null; export {}; //# sourceMappingURL=agent-dispatch.d.ts.map