feat: startup release announcement (TUI dialog + Desktop ignored msg + Pi notify)
Borrows AFT's announcement pattern: a one-shot "What's new" dialog on first start after upgrading to a version that bumps ANNOUNCEMENT_VERSION. Architecture: - packages/plugin/src/shared/announcement.ts — ANNOUNCEMENT_VERSION + ANNOUNCEMENT_FEATURES constants (decoupled from PLUGIN_VERSION so patch releases don't re-fire stale dialogs). State persisted as a single line in getMagicContextStorageDir()/last_announced_version, shared across OpenCode TUI/Desktop and Pi so dismissal in any surface suppresses the others. - OpenCode TUI: new \`get-announcement\` / \`mark-announced\` RPC handlers; the TUI plugin calls them on startup and renders an api.ui.DialogAlert with the bullet list. Confirm OR Escape both mark the current version dismissed. - OpenCode Desktop: sendStartupAnnouncement() in conflict-warning-hook.ts posts an ignored chat message to the last active session via the same Desktop-state-resolution path used by sendConflictWarning. Fired 8s after plugin init so the active session has stabilized; only marks dismissed AFTER successful delivery so transient send failures don't suppress. - Pi: hooks into before_agent_start. When ctx.hasUI && shouldShowAnnouncement(), calls ctx.ui.notify(message, "info") with the same bullets and marks dismissed. Auto-skips in print/rpc mode (no UI to render into). Coverage: 7 unit tests for the persistence helper covering round-trip, empty-input no-op, mkdir-on-write, whitespace trim, and shouldShowAnnouncement gating against persisted state. Initial ANNOUNCEMENT_FEATURES captures the 44-finding Pi audit fix wave that's landing alongside this in v0.21.7. Verification: plugin 1479/0, pi 347/0, cli 138/0+2skip, typecheck clean across all 3 packages, lint clean, build clean.
U
ualtinok committed
bd364a4645b4c5f147a67b8a863633699a4fee28
Parent: c79b428