import type { DashboardFeedbackCandidate, DashboardGuideSuggestion, DashboardRunUsage, DashboardSkillUsage, DashboardSyncAction, DashboardSyncRequest, RuleFileManifest } from '@auden.to/protocol'; export type SyncBatchInput = { actions: DashboardSyncAction[]; /** * Version of the CLI sending these batches, stamped on every payload so the * server can check it against its floor before ingesting. */ cliVersion?: string; guideSuggestions: DashboardGuideSuggestion[]; feedbackCandidates: DashboardFeedbackCandidate[]; runUsage: DashboardRunUsage[]; skillUsage: DashboardSkillUsage[]; /** * Set by the `SessionEnd` hook (`auden sync --session-complete`): the agent * context that produced this upload has ended, so the server may close the * runs for the sessions it carries. * * Turned into a per-batch `completedSessionIds` list below rather than * forwarded as a payload-level flag. A flag cannot survive chunking: one * `auden sync` uploads every session with pending actions, and a session's * actions can span several requests, so a flag on the first of them closes * the run before the rest of its actions exist. Naming the session on the * *last* batch that carries it is the only placement that is true when the * server reads it. */ sessionComplete?: boolean; /** * Sessions this upload declares finished **regardless of whether it carries * any of their actions** — in practice the one the `SessionEnd` hook payload * names on stdin (`sessionIdFromHookPayload`). * * Without this the flag above is inert in the ordinary case, which is how it * shipped and how review caught it. The `Stop` hook syncs after every * assistant turn and advances the run-log cursor, so by the time `SessionEnd` * runs there is usually nothing pending; with no action chunks there is no * session to derive, `completedSessionIds` goes out empty, and the run the * hook exists to close stays open until the idle sweep — which only fires on * a *later* sync that may never come. * * A derived session keeps its derived placement (the last chunk carrying it, * so the server never closes a run whose actions are still in flight); an id * named only here rides the last primary batch, which is safe precisely * because this upload carries none of its actions. */ completedSessionIds?: readonly string[]; /** * The rule files on disk in the repo this sync ran from * (`collectRuleManifest`). Attached to every batch that carries actions — * see the placement note in `buildSyncBatches`. */ ruleManifest?: RuleFileManifest; }; export type SyncBatch = { payload: DashboardSyncRequest; }; /** * Splits pending sync data into `POST /api/sync` request payloads that each * stay under the server's `MAX_SYNC_ACTIONS_BATCH` / `MAX_SYNC_AUX_BATCH` * caps. Returns a single empty-arrays batch when there is nothing to send, so * a caller can always iterate at least once. * * Nothing on disk is consumed by sending a batch: actions come from the * append-only run log and their re-send guard is a stable id plus a cursor * (`action-queue.ts`), not file deletion. Batches therefore carry no * per-action bookkeeping, and a failed batch costs a re-send rather than a * lost action. * * `runUsage`/`skillUsage` entries are keyed by session, not by action, and the * server only creates a session's `run` row (`materializeRuns`) once that * session's actions have actually been synced — so they are attached only to * batches *after* every action batch, never at the same index. Aligning them * index-for-index with the (differently-sized, differently-ordered) action * chunks would let a usage entry for a session whose actions land in a later * batch reach `/api/sync` before its run exists, silently dropping it as * `no_matching_run` on a sync large enough to span multiple action batches. * Batches are posted sequentially and awaited (`commands/sync.ts`), so by the * time the trailing usage batches send, every prior batch's `materializeRuns` * has already committed. */ export declare function buildSyncBatches(input: SyncBatchInput): SyncBatch[]; /** The counts a sync has on hand when it decides whether to contact the server. */ export type SyncPushCandidate = { pendingActionCount: number; guideSuggestionCount: number; skillSuggestionCount: number; runUsageCount: number; skillUsageCount: number; /** * A session the `SessionEnd` hook named as finished, or null when this sync * is not a session-end sync (or could not read the hook payload). */ endedSessionId: string | null; }; /** * Is there anything worth a request? * * An ended session counts on its own, and that is the whole point of this * predicate existing. In the ordinary `SessionEnd` case it is the *only* thing * the sync carries: the `Stop` hook syncs after every assistant turn and * advances the cursor, so the session-end invocation finds no pending actions * and no usage. Judging "is there anything to push?" by those counts alone * skipped exactly the request whose only job was to report the close, and the * run stayed `open` — never graded, because grading selects only closed runs, * and never swept, because the idle sweep runs inside a later sync that after * the final session never happens. */ export declare function hasSyncPayload(candidate: SyncPushCandidate): boolean; //# sourceMappingURL=sync-batches.d.ts.map