/** * policy.ts — The two decisions this channel must not get wrong. * * Who may drive the agent, and whether a reply that failed may be sent * again. Both were inline in index.ts's closure, and both are pure: they * read no state beyond their arguments. Lifting them out is what makes * the invariants in CLAUDE.md assertable instead of merely commented. * * Type-only import, so this file has no runtime dependency on ipc.ts — * tests run on node's native type stripper, which does not remap `.js` * specifiers to `.ts`, so a value import here would make the module * untestable. */ export type AuthorizationDecision = /** No owner yet — bind this sender and let the message through. */ 'bind' /** The bound owner. */ | 'allow' /** Anyone else, and anyone with no id at all. */ | 'drop'; /** * Decide whether a sender may drive the agent. * * Anyone who can reach the bot can otherwise run commands and read files * through pi, so this is the whole perimeter. An unbound channel trusts * the first sender it hears from; everyone else is dropped, and the * caller must drop them *silently* — a rejection notice turns the bot * into a probe for whether it is live. */ export declare function decideAuthorization(ownerUserId: string, senderUserId: string): AuthorizationDecision; /** * Is it safe to send this reply again? * * An allowlist, not a blocklist, and that direction is the whole point. * Resending is allowed only for failures that prove nothing reached * WeChat: the worker explicitly reporting zero chunks out, plus the two * failures that never left the extension's own process. Everything else * means *unknown* — above all the 120 s ack timeout, since a long * multi-chunk reply may simply have been slow, and resending on unknown * duplicates an answer the owner is already reading. */ export declare function isSafeToResend(errMsg: string): boolean; export type DeliveryVerdict = { action: 'retry'; waitMs: number; } | { action: 'giveup'; kind: 'partial' | 'unknown' | 'exhausted'; }; /** * What to do about a reply the worker could not deliver. * * Only one outcome retries. `partial` means some chunks are already on * the owner's screen, so a resend would duplicate them — worse than a * visible gap. `unknown` is everything the allowlist above does not * cover. `exhausted` is a genuinely undelivered reply we have already * tried `maxAttempts` times. The caller logs every giveup and surfaces it * through `/wechat history`, because a dropped answer that leaves no * trace is the one failure mode nothing else in the system would notice. */ export declare function classifyDeliveryFailure(errMsg: string, attempts: number, maxAttempts: number, retryDelayMs: number): DeliveryVerdict; export type ParkedDeliveryPlan = { /** When the wait started — the caller stores this back on the record, * since the first look-in is what starts the clock. */ parkedSince: number; parkedForMs: number; } & ({ action: 'giveup'; } /** How long until the next look-in. */ | { action: 'wait'; recheckMs: number; }); /** * How much longer an answer may wait for a channel that is down. * * `attemptDelivery` returns early while the channel is down and leaves * `processing` true, so without a bound the channel accepts messages and * answers none of them, silently. The paths that never fire `Worker ready` * or `login_success` again are real — an expired QR code, re-login giving * up, `/wechat stop` — so the wait is re-checked on a timer of its own and * abandoned once it runs out. * * `parkedSince` null means this is the first look-in: the clock starts * now, which is why the caller gets it back rather than setting it itself. */ export declare function planParkedDelivery(parkedSince: number | null | undefined, nowMs: number, parkTimeoutMs: number, maxRecheckMs: number): ParkedDeliveryPlan; export interface RestartPlan { delayMs: number; /** Whether this attempt is worth telling the operator about. */ announce: boolean; } /** * When to restart a worker that exited unexpectedly, and whether to say so. * * Exponential, capped: a worker that cannot start (a missing `npm install`, * a broken SDK) would otherwise be respawned once a second forever. The * announcement is throttled for the same reason from the other side — the * backoff tops out, so notifying on every attempt turns a permanently * broken worker into a siren. The first crash and every fifth after it is * enough to notice. */ export declare function planRestart(attempts: number, maxDelayMs: number): RestartPlan; //# sourceMappingURL=policy.d.ts.map