/** * The vocabulary the channel's modules share: the mutable state they read, * the type that says which of it each one may *write*, and the handful of * helpers that turned out to need no state at all. * * This file exists because `index.ts` was one closure over 42 `let`s, and a * closure cannot be split across files. What replaces it is a single mutable * `ChannelState` that `index.ts` owns and hands out as *narrowed views*: a * module receives `Owns` * and everything else arrives readonly, so "only the pipeline may write * `processing`" is a compile error rather than a comment. * * Cross-module *calls* go the other way, through each module's own `…Ops` * interface (declared in that module's file, not here). The call graph * between these sections has eight two-way cycles in it, so the registry is * built empty and filled by `Object.assign` as each factory returns — the * cycles resolve at call time, which is the only time they are real. * * Keeping each module's Ops interface in the module rather than collecting * them into one god interface here is deliberate: the list at the top of a * file *is* that module's coupling, and it should be read where the coupling * would be paid for. */ import type { ExtensionContext } from '@earendil-works/pi-coding-agent'; import type { PendingDeliveryRecord, QueuedMessage } from '../state.js'; import type { WorkerHandle } from '../worker-launch.js'; /** * The view of the shared state given to a module that owns `K`. * * Everything outside `K` is readonly, so a module that reaches over and * assigns to a field it does not own fails typecheck. That direction is the * whole point: the state this replaced had `processing` written from eight * places in six sections and `pendingReply` from seven, which is how the * transitions they encode ("a turn started", "a turn ended") stopped being * visible anywhere as transitions. * * A module that owns nothing takes `Readonly` and reaches the * writes it needs through named methods on an Ops interface instead. */ export type Owns = Readonly> & Pick; /** The turn handed to the agent, from dispatch until `agent_end`. */ export interface PendingReply { userId: string; /** * Every marker this turn carries. Usually one — but a message delivered * as a steer joins the running turn and becomes its last user message, * so the answer comes back tagged with the steer's marker instead of the * opener's, and matching only the opener would discard the answer. */ markers: string[]; /** Kept whole, not just its ids: a retirement mid-turn has to put the * messages back on the queue for the instance that replaces us. */ batch: QueuedMessage[]; } /** The last answer this channel threw away, and why. */ export interface DroppedDelivery { userId: string; at: string; why: string; } /** * Whether this pi session owns the channel, and what has it if not. * * One iLink account supports one poller, so ownership is exclusive and the * state file's pid + lease is the token. Expressed as a union rather than a * flag plus two loose fields because "not ours" is never bare: it always has * a reason worth showing the operator, and usually a pid. `holderPid: 0` * means nothing live holds it — an unreadable state file, or one written by * a newer version — which is a different situation from another pi session * actively renewing its lease, and only the pid tells them apart. */ export type Ownership = { kind: 'owned'; } | { kind: 'blocked'; holderPid: number; reason: string; }; /** * State shared across the channel's modules. * * It grows one module at a time: a field moves in here when the module that * *reads* it moves out of `index.ts`, and not before. Everything still held * as a `let` in the closure is state no extracted module has needed yet — * that is a fact about migration progress, not a design boundary. */ export interface ChannelState { /** * Set by `retireInstance()`, read almost everywhere: after it, every `pi` * call throws "stale after session replacement", so new async code needs a * check after every `await`. */ instanceDead: boolean; /** * The worker this instance has, if any — a `WorkerHandle` rather than a * `ChildProcess`, so the lifecycle below can be driven by a test that * spawns nothing. What a handle is and how one is made is `worker-launch.ts`. */ worker: WorkerHandle | null; workerReady: boolean; isLoggedIn: boolean; qrUrl: string; messageQueue: QueuedMessage[]; pendingReply: PendingReply | null; pendingDelivery: PendingDeliveryRecord | null; processedMsgIds: Set; lastDroppedDelivery: DroppedDelivery | null; ownerUserId: string; ownership: Ownership; } /** * Does this instance own the channel? * * Gates `startWorker()` (one iLink account, one poller) *and* * `persistStateNow()` (never write state you have not read). Those were two * flags; they are one question. */ export declare function ownsChannel(S: Readonly): boolean; /** The pid holding the channel instead, when one does. 0 = no live holder. */ export declare function ownershipHolderPid(S: Readonly): number; /** Why this instance is not persisting, or '' when it is. */ export declare function ownershipBlockedReason(S: Readonly): string; /** * Can anything actually be sent right now? * * One predicate, because "connected" was being spelled three different ways: * a worker that exists but is not ready, and a ready worker that is not * logged in, both look connected to some of them and not to others. */ export declare function channelReady(S: Readonly): boolean; /** * Either context pi hands over, or none. * * `ExtensionCommandContext` extends `ExtensionContext`, so this covers both — * and the nullability is the point: `lastCtx` is null before the first event * and again after retirement, and every helper is written to be a no-op * rather than a throw in that state. */ export type MaybeCtx = ExtensionContext | null | undefined; /** Severity accepted by `ctx.ui.notify`. */ export type NotifyLevel = 'info' | 'warning' | 'error'; /** * Did this error come from a session that pi has already replaced? * * It lives in `operator.ts` now, beside the single decision about what to do * when the answer is yes; re-exported here so the modules that only need the * question keep taking it from the shared vocabulary. */ export { isStaleError } from './operator.js'; /** * Open a URL in the operator's browser, where pi can. * * `ui.open()` is not on `ExtensionUIContext` as of pi 0.84, hence the lookup * rather than a call: this is a no-op on such a build. Kept because it costs * nothing, and because the QR URL is notified and logged beside every call — * that is the path that works, and on a machine reached over SSH the ASCII * render is the only one that can. */ export declare function safeOpen(ctx: MaybeCtx, url: string): Promise; //# sourceMappingURL=core.d.ts.map