| 1 | import type { PendingGuidance } from "../components/ComposerGuidanceShelf"; |
| 2 | import { asArray } from "./array"; |
| 3 | |
| 4 | export type InboxSnapshotLike = { |
| 5 | paused?: boolean; |
| 6 | recovered?: boolean; |
| 7 | recoveredCount?: number; |
| 8 | revision?: number; |
| 9 | sessionPath?: string; |
| 10 | items?: Array<{ |
| 11 | id: string; |
| 12 | preview?: string; |
| 13 | state?: string; |
| 14 | intent?: string; |
| 15 | source?: string; |
| 16 | }>; |
| 17 | }; |
| 18 | |
| 19 | export function inboxScopeKey(sessionPath?: string, workspaceScopeKey?: string): string { |
| 20 | return (sessionPath || "").trim() || workspaceScopeKey || ""; |
| 21 | } |
| 22 | |
| 23 | export function inboxSnapshotBelongsToScope(snapshotPath: string | undefined, scopeKey: string): boolean { |
| 24 | const path = (snapshotPath || "").trim(); |
| 25 | if (!path || !scopeKey) return true; |
| 26 | if (scopeKey === path) return true; |
| 27 | return scopeKey.split("\u0000").includes(path); |
| 28 | } |
| 29 | |
| 30 | export function localGuidanceFallback(previewKey: string): PendingGuidance[] { |
| 31 | return previewKey |
| 32 | .split("\n") |
| 33 | .filter(Boolean) |
| 34 | .map((text, i) => ({ id: `local-${i}`, text, submitText: text })); |
| 35 | } |
| 36 | |
| 37 | export function guidanceFromInboxSnapshot(snap: InboxSnapshotLike | null | undefined): PendingGuidance[] { |
| 38 | const visible = asArray(snap?.items).filter((it) => it.state !== "steer_consumed" && it.state !== "running"); |
| 39 | return visible.map((it) => ({ |
| 40 | id: it.id, |
| 41 | text: it.preview || "", |
| 42 | submitText: "", |
| 43 | state: it.state, |
| 44 | intent: it.intent, |
| 45 | source: it.source, |
| 46 | paused: Boolean(snap?.paused), |
| 47 | recoveredCount: snap?.paused && snap?.recovered |
| 48 | ? visible.length |
| 49 | : undefined, |
| 50 | })); |
| 51 | } |
| 52 | |
| 53 | export async function hydrateEmptyGuidancePreviews( |
| 54 | items: PendingGuidance[], |
| 55 | readItem: (id: string) => Promise<{ displayText?: string; submitText?: string; rawText?: string }>, |
| 56 | ): Promise<PendingGuidance[]> { |
| 57 | const missing = items.filter((item) => !item.text.trim() && !item.id.startsWith("local-")); |
| 58 | if (missing.length === 0) return items; |
| 59 | await Promise.all(missing.map(async (item) => { |
| 60 | try { |
| 61 | const env = await readItem(item.id); |
| 62 | const text = (env.displayText || env.submitText || env.rawText || "").trim(); |
| 63 | if (text) item.text = text; |
| 64 | } catch { |
| 65 | // Preview-only snapshot remains if the body cannot be read. |
| 66 | } |
| 67 | })); |
| 68 | return items; |
| 69 | } |
| 70 | |
| 71 | export function mergeGuidanceSnapshot(durable: PendingGuidance[], fallback: PendingGuidance[]): PendingGuidance[] { |
| 72 | return durable.length > 0 ? durable : fallback; |
| 73 | } |
| 74 |