/** * Harness re-login coordinator — re-login from the owner's phone, with no * agent involved. * * The instance backend (never an agent: every agent may be stuck on the very * login that expired) notices that a harness's login is gone, tells the owner * once, runs the harness's login itself when the owner replies, relays the * link and the code, and brings the agents back with the messages that * waited for them. * * Flow per harness (at most one at a time): * * 1. **Detect.** {@link HarnessReloginService.reportExpiry} — an agent's * output or screen matched an expiry rule (login-expiry-rules.ts, via the * OAuth re-login monitor), or the periodic check * ({@link HarnessReloginService.checkHarnesses}) found a harness in use * signed out. The periodic check covers every harness the orchestrator or * a team member is configured on, so it works with zero agents running. * 2. **Confirm.** The harness's status command, then — because Claude Code's * stored credential survives an expiry — a live probe * (harness-login-probe.ts). Only a confirmed `logged_out` goes on; * `unknown` never does (`codex login` revokes a working login). * 3. **Tell the owner once.** A stored API key is used silently if Crewly has * one. Otherwise one DM — "Claude Code on is signed out, so N * agents can't work. Reply `login` …" — then re-reminders with backoff * (3 h, doubling, 24 h cap), persisted so restarts do not re-send it. No * login is started yet: a link sent while the owner is away expires unused. * 4. **The owner replies `login`** (or 「重新登录」, `relogin claude`, …) in * the DM. The Slack bridge offers owner DMs to * {@link HarnessReloginService.handleOwnerReply} before anything else, so * this works with no agent awake. The login broker runs the harness's * login in its own PTY (`claude auth login` / `codex login --device-auth`) * and the link (and Codex's one-time code) is sent back. * 5. **The code.** For Claude the owner replies with the code from the page; * it is typed into the waiting login. A rejected code is reported once; an * expired link puts the flow back to "signed out" ("reply `login` for a * new link"). * 6. **Success** is confirmed with the probe, then the stuck agents are * restarted (conversation resumed) and the owner messages that waited are * re-delivered ({@link HarnessReloginDeps.onLoginRestored}). One DM says so. * * Owner-requested logins ({@link HarnessReloginService.startOwnerLogin}: * 「重新登录 claude」 / "relogin codex" / the orchestrator's `harness-login` * skill) start the broker at once, even while logged in (account switch). * * `login claude ` signs in one of the owner's OTHER Claude Code * accounts (issue #942, claude-accounts.ts): the same link-and-code flow, * run with that account's config dir; the token is kept for that account * only, no agent is restarted, and the runtime fallback is told the account * is usable ({@link HarnessReloginService.setAccountLoginHandler}). * * Secrets: Claude's code is passed to the broker and never stored, logged * or echoed; DMs are built from {@link LoginSession} snapshots, which never * contain a token, and the screen text is redacted again. * * @module services/harness/harness-relogin.service */ import type { HarnessApiKeyService } from './harness-api-key.service.js'; import { type HarnessCredentialsStore } from './harness-credentials.store.js'; import { type HarnessId, type HarnessLogger, type LoginSession, type LoginState, type ReloginPending } from './harness.types.js'; import { type LoginBrokerService } from './login-broker.service.js'; import { type ReloginStateStore } from './relogin-state.store.js'; /** Where an expiry report came from. */ export type ExpirySource = 'output' | 'screen' | 'status'; /** An expired-login report. */ export interface ExpiryReport { harnessId: HarnessId; /** Agent session whose output matched, or null when unknown (status check) */ sessionName: string | null; /** * `output`: a live PTY chunk; `screen`: the periodic screen sweep (may show * old scrollback); `status`: the harness's own status command or probe */ source: ExpirySource; } /** * A Slack conversation (and thread) to answer in. `agentSession` is set when * that conversation is a DM with an agent's own bot (the orc's "Crewly Orc" * app), whose token must be used to post there. */ export interface ReloginReplyTarget { channelId: string; threadTs?: string; agentSession?: string; } /** * Result of a DM: false when nothing was delivered; true, or the * conversation it landed in (so a code reply can be matched to it). */ export type ReloginDelivery = boolean | ReloginReplyTarget; /** Sends a Slack DM to the owner. */ export interface ReloginOwnerNotifier { /** * Send a DM to the owner. * * @param text - Slack mrkdwn text (never contains a secret) * @param target - Conversation to answer in; absent = this machine's own DM with the owner * @returns Where it was delivered (or true), false when it was not */ sendToOwner(text: string, target?: ReloginReplyTarget | null): Promise; /** * Whether a DM can be sent right now (Slack connected). * * @returns True when {@link sendToOwner} can deliver */ isAvailable?(): boolean; } /** What started a flow. */ export type ReloginTrigger = 'expiry' | 'owner'; /** Where an owner reply was written: the orc's / master DM, or another agent's bot DM. */ export type OwnerReplyScope = 'orc' | 'agent'; /** Options of an owner-requested login. */ export interface OwnerLoginOptions { /** The owner wants a different account (only changes the wording) */ switchAccount?: boolean; /** Conversation to answer in (null = this machine's DM with the owner) */ replyTarget?: ReloginReplyTarget | null; /** Who relayed the request, for logs */ requestedBy: 'owner_dm' | 'orchestrator' | 'dashboard'; /** One of the owner's other Claude Code accounts to sign in (issue #942); absent = the default login */ account?: string; } /** An owner-requested login that was started. */ export interface OwnerLoginStarted { /** `restarted`: a flow for the harness was running and was started over */ status: 'started' | 'restarted'; harnessId: HarnessId; /** The other Claude Code account being signed in, when it is one */ account?: string; /** Whether the owner can be DM'd right now (false = Slack is down) */ dmAvailable: boolean; } /** An owner-requested login for a harness without a link login. */ export interface OwnerLoginUnsupported { /** The harness has no link login (Antigravity: API key; Gemini: enterprise only) */ status: 'no_broker_login'; harnessId: HarnessId; /** What to tell the owner */ message: string; } /** Result of {@link HarnessReloginService.startOwnerLogin}. */ export type OwnerLoginResult = OwnerLoginStarted | OwnerLoginUnsupported; /** Restarts agents after a login succeeded. */ export interface ReloginAgentResumer { /** * Live agent sessions that run on a harness. * * @param harnessId - Harness (= runtime type) * @returns Session names */ listSessions(harnessId: HarnessId): string[]; /** * Restart sessions so they pick up the new login, resuming their conversation. * * @param sessionNames - Sessions to restart * @returns Which ones came back and which did not */ resume(sessionNames: readonly string[]): Promise<{ resumed: string[]; failed: string[]; }>; } /** An agent configured on this machine (running or not). */ export interface ConfiguredAgent { sessionName: string; /** Runtime type (= harness id for the harnesses Crewly can log in) */ harnessId: string; /** Name the owner knows ("Ella") */ displayName?: string; } /** The slice of the login broker the coordinator uses. */ export type ReloginBroker = Pick; /** Injectable dependencies. */ export interface HarnessReloginDeps { broker: ReloginBroker; credentials: Pick; apiKeys: Pick; /** The harness's login state from its own status command (cheap; a stored Claude credential reads as logged in) */ checkLoginState: (harnessId: HarnessId) => Promise; /** * Live sign-in probe (harness-login-probe.ts): `logged_out` / `logged_in` * when it could tell, `unknown` otherwise. Absent = status only. */ verifyLogin?: (harnessId: HarnessId) => Promise; /** The orchestrator's harness (null before setup recorded one) */ getOrcHarness: () => Promise; /** Every agent configured on this machine, running or not (orc included) */ listAgents?: () => Promise; /** Whether a live agent session is sitting at a sign-in screen (OAuth monitor flag) */ sessionNeedsLogin?: (sessionName: string) => boolean; /** * After a login came back and the agents were restarted: re-deliver what * waited for them (owner-message watchdog). Returns how many messages. */ onLoginRestored?: (harnessId: HarnessId, resumed: readonly string[]) => Promise | number; /** After one of the owner's other Claude Code accounts was signed in (the runtime fallback) */ onAccountLogin?: (account: string) => Promise | void; /** After a harness's default login was signed in again (the runtime fallback re-checks its usage mark) */ onHarnessLogin?: (harnessId: HarnessId) => Promise | void; /** * The owner's other Claude Code account an agent session runs on right now * (the runtime fallback moved it there), or null for the default login. * Default: the runtime fallback's {@link effectiveClaudeAccount}. */ accountOfSession?: (sessionName: string) => string | null; /** The owner's existing other Claude Code accounts (default: claude-accounts.ts) */ listClaudeAccounts?: () => string[]; /** This machine's name, for the owner ("iriss-air.lan") */ machineName?: () => string; /** Persisted notice / backoff state */ state?: ReloginStateStore; notifier?: ReloginOwnerNotifier | null; resumer?: ReloginAgentResumer | null; now?: () => number; logger?: HarnessLogger; } /** * The word the owner types after `relogin` for a harness. * * @param harnessId - Harness id * @returns e.g. "claude" */ export declare function harnessCommandWord(harnessId: HarnessId): string; /** * Whether a reply asks to start the login over. * * @param text - Owner reply * @returns True for `relogin`, `re-login` or `重新登录` */ export declare function isRetryKeyword(text: string): boolean; /** * Whether a bare reply asks to sign in (`login`, `sign in`, 「登录」, the retry keywords). * * @param text - Owner reply * @returns True when the whole reply is a login keyword * * @example * ```ts * isLoginKeyword('Login!'); // true * isLoginKeyword('login to gmail'); // false * ``` */ export declare function isLoginKeyword(text: string): boolean; /** * Strip formatting Slack clients add around a pasted value (backticks). * * @param text - Owner reply * @returns The bare value, trimmed */ export declare function unwrapReply(text: string): string; /** * Whether a reply plausibly is Claude's authorization code: one token with * no whitespace and a plausible length. * * @param text - Unwrapped owner reply * @returns True when it can be typed into `claude auth login` */ export declare function looksLikeAuthCode(text: string): boolean; /** * "Ella, Rex and 3 more" — capped list of agent names. * * @param agents - Agent names * @returns List text ('' when empty) */ export declare function describeWaitingAgents(agents: readonly string[]): string; /** * Prefix an owner reply needs before it is typed into an unrecognised login * screen. Without it, any one-line DM to the orc during the login would have * been swallowed by the terminal. */ export declare const SCREEN_REPLY_PREFIXES: readonly ["输入", "input"]; /** * The text after a screen-reply prefix ("input 1" → "1"), or null. * * @param reply - Owner reply (already unwrapped) * @returns Text to type, or null when the reply has no prefix */ export declare function stripScreenReplyPrefix(reply: string): string | null; /** How a link DM introduces the login. */ export interface LoginDmWording { /** The owner asked for this login (not an expiry) — kept for callers; the header is the same */ ownerRequested?: boolean; /** The owner asked for a different account */ switchAccount?: boolean; /** This machine's name */ machine?: string; /** One of the owner's other Claude Code accounts this login is for */ account?: string | null; } /** * The first "signed out" DM, a re-reminder, or the note after a link expired unused. * * @param harnessId - Harness * @param machine - This machine's name * @param agents - Names of the agents that run on it * @param kind - `notice` (first), `reminder`, or `link_expired` * @returns Slack mrkdwn text * * @example * ```ts * formatSignedOutDm('claude-code', 'iriss-air.lan', ['Ella', 'Rex'], 'notice'); * // "*Claude Code on iriss-air.lan is signed out*, so 2 agents can't work (Ella, Rex).\nReply `login` …" * ``` */ export declare function formatSignedOutDm(harnessId: HarnessId, machine: string, agents: readonly string[], kind?: 'notice' | 'reminder' | 'link_expired'): string; /** * DM with the sign-in link (and Codex's one-time code on its own line). * * @param session - Broker session (never contains a secret) * @param _waiting - Agents waiting on the login (unused; kept for callers) * @param wording - Account switch / machine * @returns Slack mrkdwn text */ export declare function formatLinkDm(session: LoginSession, _waiting?: readonly string[], wording?: LoginDmWording): string; /** * DM for a login screen the broker did not recognise. * * @param session - Broker session * @param _waiting - Agents waiting on the login (unused; kept for callers) * @param wording - Account switch / machine * @returns Slack mrkdwn text */ export declare function formatScreenDm(session: LoginSession, _waiting?: readonly string[], wording?: LoginDmWording): string; /** * DM after the harness rejected the owner's reply (e.g. a wrong code). * * @param session - Broker session (awaiting input again) * @returns Slack mrkdwn text */ export declare function formatRejectedDm(session: LoginSession): string; /** * DM after a login came back and the agents were restarted. * * @param harnessId - Harness * @param result - Resume result * @param redelivered - Owner messages re-delivered to them * @param machine - This machine's name * @returns Slack mrkdwn text */ export declare function formatSuccessDm(harnessId: HarnessId, result: { resumed: string[]; failed: string[]; }, redelivered?: number, machine?: string): string; /** * One-line DM after an owner-requested login succeeded. * * @param harnessId - Harness * @param result - Which agents were restarted onto the new login * @param redelivered - Owner messages re-delivered to them * @returns Slack mrkdwn text */ export declare function formatOwnerSuccessDm(harnessId: HarnessId, result: { resumed: string[]; failed: string[]; }, redelivered?: number): string; /** * DM after one of the owner's other Claude Code accounts was signed in. * * @param account - Account name * @param machine - This machine's name * @returns Slack mrkdwn text */ export declare function formatAccountSuccessDm(account: string, machine: string): string; /** * DM after the link for one of the owner's other Claude Code accounts expired unused. * * @param account - Account name * @param machine - This machine's name * @returns Slack mrkdwn text */ export declare function formatAccountLinkExpiredDm(account: string, machine: string): string; /** * Reply to a login request that named no harness Crewly can log in. * * @param name - What the owner wrote, or null when they named none * @returns Slack mrkdwn text */ export declare function formatWhichHarnessDm(name: string | null): string; /** * Reply for a harness that has no link login. * * @param harnessId - Harness without a broker login method * @returns Slack mrkdwn text */ export declare function formatNoBrokerLoginDm(harnessId: HarnessId): string; /** * DM after the login failed (sent once per failure). * * @param harnessId - Harness * @param reason - Broker message (redacted here) * @returns Slack mrkdwn text */ export declare function formatFailureDm(harnessId: HarnessId, reason: string | null): string; /** * Re-reminder text for a login whose last link expired unused. * * @param harnessId - Harness * @param waiting - Agents waiting on the login * @param machine - This machine's name * @returns DM text */ export declare function formatReminderDm(harnessId: HarnessId, waiting: readonly string[], machine?: string): string; /** Coordinates re-login flows, one per harness. */ export declare class HarnessReloginService { private readonly broker; private readonly credentials; private readonly apiKeys; private readonly checkLoginState; private readonly verifyLogin; private readonly getOrcHarness; private listAgents; private sessionNeedsLogin; private onLoginRestored; private onAccountLogin; private onHarnessLogin; private readonly accountOfSession; private readonly listClaudeAccounts; private readonly machineName; private readonly state; private notifier; private resumer; private readonly now; private readonly logger; /** Flows by {@link flowKey} (harness, or harness@account) */ private readonly flows; /** Harnesses whose expiry is being confirmed → sessions reported meanwhile */ private readonly confirming; /** Harnesses whose re-reminder is being prepared */ private readonly reminding; /** Expiry reports are ignored until then (resumed transcripts repeat old errors) */ private readonly quietUntil; /** Last silent API-key recovery per harness */ private readonly silentKeyAt; /** Sessions restarted after a login: screen-sweep reports are ignored until live output reports again */ private readonly mutedScreens; /** Last probe result per harness */ private readonly probeCache; /** Probes in flight per harness */ private readonly probeInFlight; /** Agents configured on this machine, from the last {@link listAgents} */ private agents; private statusTimer; private firstCheckTimer; private statusCheckInFlight; /** * @param deps - Dependencies */ constructor(deps: HarnessReloginDeps); /** * Set how the owner is DM'd (the Slack adapter; wired after Slack is up). * * @param notifier - Notifier, or null */ setNotifier(notifier: ReloginOwnerNotifier | null): void; /** * Set how agents are restarted after a login. * * @param resumer - Resumer, or null */ setResumer(resumer: ReloginAgentResumer | null): void; /** * Set what re-delivers the owner messages that waited for a login. * * @param handler - Handler, or null */ setLoginRestoredHandler(handler: HarnessReloginDeps['onLoginRestored'] | null): void; /** * Set what learns that one of the owner's other Claude Code accounts was signed in. * * @param handler - Handler (the runtime fallback), or null */ setAccountLoginHandler(handler: HarnessReloginDeps['onAccountLogin'] | null): void; /** * Set what learns that a harness's default login was signed in again. * * @param handler - Handler (the runtime fallback), or null */ setHarnessLoginHandler(handler: HarnessReloginDeps['onHarnessLogin'] | null): void; /** * Set how the configured agents (running or not) are listed. * * @param lister - Lister, or null */ setAgentLister(lister: HarnessReloginDeps['listAgents'] | null): void; /** * Set the per-session sign-in flag lookup (OAuth monitor). * * @param lookup - Lookup, or null */ setSessionNeedsLogin(lookup: HarnessReloginDeps['sessionNeedsLogin'] | null): void; /** * Report an expired harness login. * * @param report - Which harness, which session, from where * @returns True when the coordinator owns the harness's re-login (the * caller must not start its own `/login` or notice); false for a * harness Crewly cannot log in (no broker method) */ reportExpiry(report: ExpiryReport): boolean; /** * Offer an owner's Slack DM to the re-login coordinator. * * Consumed, in this order: a login keyword (`login`, 「重新登录」…) while a * harness is signed out or signing in; a login code or screen reply for a * running flow (only in the conversation the link went to); and finally an * owner login request (「重新登录 claude」, "relogin codex" — see * {@link parseOwnerLoginRequest}), which starts a forced login answered in * `target`. * * In another agent's bot DM (`scope: 'agent'`) only a request that names a * harness, or a reply to a flow, is taken — the rest is that agent's mail. * * @param text - Reply text (never logged) * @param target - Conversation the owner wrote in (answers go there) * @param scope - `orc` (orc / master DM) or `agent` (another agent's bot DM) * @returns True when consumed; the caller must then NOT pass it on as a chat message */ handleOwnerReply(text: string, target?: ReloginReplyTarget | null, scope?: OwnerReplyScope): boolean; /** * The other Claude Code account a session runs on, never throwing. * * @param sessionName - Agent session * @returns Account name, or null for the default login */ private safeAccountOf; /** * The owner's existing other Claude Code accounts (for "login claude work"). * * @returns Names; empty when they cannot be read */ private knownClaudeAccounts; /** * Start a login the owner asked for, through the broker: forced (runs * even when the harness is still logged in — the owner may be switching * accounts), no silent API key, answered in `replyTarget`. A flow already * running for the harness is started over with a fresh link. * * @param harnessId - Harness to log in * @param options - Account switch, reply target, who relayed the request * @returns What happened (never throws) */ startOwnerLogin(harnessId: HarnessId, options: OwnerLoginOptions): OwnerLoginResult; /** * Harnesses confirmed signed out on this machine (persisted). * * @param harnessId - Harness * @returns True while the owner has not signed it in again */ isSignedOut(harnessId: HarnessId): boolean; /** * The signed-out harness an agent is waiting on, if any — for the * owner-message watchdog, which must not wake an agent onto a dead login. * * @param sessionName - Agent session * @returns The harness, or null when its harness is not signed out */ signedOutHarnessOf(sessionName: string): HarnessId | null; /** * Act on a parsed owner login request from the DM. * * @param request - Parsed request * @param target - Conversation to answer in */ private handleOwnerLoginRequest; /** * Offer a DM to the flows: a login keyword, a login code, or a reply to * an unrecognised screen. * * @param text - Reply text (never logged) * @param target - Conversation it was written in * @returns True when consumed */ private consumeFlowReply; /** * The pending re-login for a harness (shown by `GET /api/harness`). * * @param harnessId - Harness id * @returns The pending re-login, or null when none is running */ getPending(harnessId: HarnessId): ReloginPending | null; /** * Check every harness in use on this machine — the orchestrator's and every * configured agent's, running or not — and report the ones that are signed * out. A harness the status command calls logged in is probed for real now * and then ({@link HARNESS_CONSTANTS.RELOGIN.PROBE_INTERVAL_MS}), because a * stored Claude credential survives its expiry. Signed-out harnesses get * their (backed-off) re-reminder here. * * @returns Resolves when the check is done; never rejects */ checkHarnesses(): Promise; /** * Compatibility name of {@link checkHarnesses}. * * @returns Resolves when the check is done */ checkOrcHarness(): Promise; /** * One harness of the periodic check. * * @param harnessId - Harness * @param inUse - The orchestrator or a configured agent runs on it */ private checkOne; /** * Start the periodic check (first run shortly after boot, so a machine * that comes up signed out says so without waiting a full interval). * * @param intervalMs - Cadence (defaults to STATUS_CHECK_INTERVAL_MS) * @param firstDelayMs - Delay of the first check (default 60 s) */ start(intervalMs?: number, firstDelayMs?: number): void; /** Stop the periodic check and forget every flow's timers. */ stop(): void; /** * Confirm a suspected expiry, then tell the owner (or use a stored key). * * @param harnessId - Harness */ private confirmExpiry; /** * Login state: the status command, then the live probe when the status * command cannot see an expiry (it says logged in / unknown). * * @param harnessId - Harness * @param fresh - Ignore a cached probe result * @returns The best-known state */ private currentState; /** * Run (or reuse) the live probe. * * @param harnessId - Harness * @param fresh - Ignore a cached result * @returns Probe result (`unknown` when there is no probe) */ private probe; /** * Re-remind the owner of a signed-out harness once its backoff elapsed. If * the harness is signed in again by then (the owner did it on the * machine or in Setup), the flow finishes and the agents resume instead. * * @param flow - A signed-out flow */ private maybeRemind; /** * Whether the backoff allows another "signed out" DM for a harness. * * @param harnessId - Harness * @returns True when no notice was sent yet, or the backoff elapsed */ private noticeDue; /** * Send the "signed out" DM (first notice or re-reminder), within the backoff. * * @param flow - The flow * @param kind - Which text */ private sendSignedOutNotice; /** * Replace a harness's flow with a new one and start its broker session. * * @param harnessId - Harness * @param previous - The flow it replaces (its stuck agents are kept), or null * @param options - Trigger, wording and reply target */ private replaceAndStart; /** * A new flow record. * * @param harnessId - Harness * @param stuck - Sessions known to be stuck * @param options - Trigger, wording and reply target * @param phase - Initial phase * @returns The flow (not registered) */ private newFlow; /** * Start (or adopt) the harness's broker login session for a flow. * * @param flow - The flow */ private startBrokerSession; /** * Use an API key Crewly holds for the harness, without asking the owner. * Not repeated within SILENT_KEY_RETRY_WINDOW_MS: a key that did not * help falls back to the phone login. * * @param flow - The flow * @returns True when the key was used and the agents were resumed */ private tryStoredApiKey; /** * Broker update: send the link once it is complete, report a rejected reply. * * @param session - Session snapshot */ private handleUpdate; /** * Broker session finished. * * @param session - Final snapshot */ private handleFinished; /** * A login finished outside any flow (dashboard chip, Setup, CLI): restart * only the sessions that sit at a sign-in screen and re-deliver what * waited for them. Working agents of the harness are left alone. * * @param harnessId - Harness that was signed in */ private adoptOutsideLogin; /** * The broker reported success: confirm it with the probe, then finish. * * @param flow - The flow */ private confirmAndSucceed; /** * The login is back: restart the stuck agents, re-deliver what waited, * and tell the owner. * * @param flow - The flow * @param notify - DM the owner (false for the silent API-key path) */ private succeed; /** * One of the owner's other Claude Code accounts is signed in: no agent is * restarted (none runs on it until the fallback moves one there); the * runtime fallback is told, and the owner gets one line. * * @param flow - The account's flow * @param account - Account name */ private succeedAccount; /** * Tell the runtime fallback that an account was signed in. * * @param account - Account name */ private tellAccountLogin; /** * A flow's key in the map. * * @param flow - The flow * @returns {@link flowKey} of its harness and account */ private keyOf; /** * Sessions to restart after a login: the ones whose output matched plus * every live session of the harness (they all hold the dead login). * * @param flow - The flow * @returns Session names */ private sessionsToRestart; /** * Names of the agents that run on a flow's harness, for the owner: every * configured agent on it, else the stuck / live ones. * * @param flow - The flow * @returns Display names */ private agentNamesOn; /** * Re-read the configured agents. * * @returns The agents (the last known list when the read fails) */ private refreshAgents; /** * Harness an agent session runs on. * * @param sessionName - Agent session * @returns Harness id, or null when unknown */ private harnessOfSession; /** * Record that a harness is signed out (first time only keeps the date). * * @param harnessId - Harness */ private markSignedOut; /** * Remember that a harness was seen signed in (written at most hourly). * * @param harnessId - Harness */ private markSeenLoggedIn; /** * DM wording for a flow. * * @param flow - The flow * @returns Account-switch flag and machine name */ private wordingOf; /** * Cancel the flow's live broker session, marking it as cancelled by the * coordinator so no failure DM is sent for it. * * @param flow - The flow */ private cancelOwnSession; /** * After UNRECOGNISED_SCREEN_MS without a link: send the screen as-is. * * @param flow - The flow */ private sendScreenIfUnrecognised; /** * DM the owner in the flow's conversation, and remember where it landed * so later DMs (the link, the result) follow it. * * @param flow - The flow * @param text - DM text * @returns The delivery */ private dm; /** * Send a message to the owner (logs when no notifier is wired; never logs the text). * * @param text - DM text * @param target - Conversation to answer in (null = this machine's DM) * @param harnessId - Harness, for logs * @returns The delivery (false when not delivered) */ private notify; /** * Read a broker session. * * @param sessionId - Session id * @returns The session, or null when the broker forgot it */ private readSession; /** * Clear a flow's unrecognised-screen timer. * * @param flow - The flow */ private clearScreenTimer; } /** * The backend's re-login coordinator, bound to the backend harness service * (its broker, credentials, API keys and status), the live sign-in probe and * the persisted notice state. Registers itself as the `reloginPending` * source of `GET /api/harness`. * * @returns The singleton */ export declare function getHarnessReloginService(): HarnessReloginService; /** * Replace or clear the singleton (tests). * * @param service - Service, or null */ export declare function setHarnessReloginServiceForTesting(service: HarnessReloginService | null): void; //# sourceMappingURL=harness-relogin.service.d.ts.map