Something went wrong. Try again.
last activity notifications for opencode
Something went wrong. Try again.
3.7 kB · 100 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101/** @jsxImportSource @opentui/solid */
/** * opencode-lastact v2 TUI entrypoint — the `{ id, setup(context) }` plugin * shape the OpenCode v2 TUI host loads from the package's `./tui` export. * * JUST one thing: a very short view of the last-used time of the current * session. Which footer carries the line is config (`slots`: "prompt" | * "sidebar", default "prompt") — both surfaces are session-bound on the * current host. * * The activity-ledger model: subscriptions (per the `refreshOn` families, * plus the always-observed `session.execution.*` terminals) do nothing but * touch a per-session ledger cell; the view's fixed-cadence ticker is the * one bucket that derives the two clocks (last prompt, last LLM part) and * paints per the `view` mode. Every claim is additive `append`, never * `replace`. Setup returns a cleanup that disposes every claim and event * subscription. */
import { ActivityTracker } from "../activity/tracker.ts";import { SLOT_PATHS, resolveConfig, surfaceGate, type Config, type SlotSurface, type SurfaceGate,} from "../config/config.ts";import { LastactStatusLine } from "./status.tsx";import { activityFromEvent, settleFromEvent, subscriptionTypes } from "./events.ts";import type { SlotMap, TuiContext, TuiV2Event } from "./context.ts";
export const PLUGIN_ID = "opencode-lastact";
// Drift check (compile-time): every configured surface must map to a path in// the locally-typed published SlotMap, or typecheck fails here.const slotPathsArePublished: Record<SlotSurface, keyof SlotMap> = SLOT_PATHS;void slotPathsArePublished;
/** Claim one published path; the line itself gates on session kind. */function claimPath( context: TuiContext, tracker: ActivityTracker, config: Config, path: (typeof SLOT_PATHS)[SlotSurface], gate: SurfaceGate,): () => void { return context.ui.slot({ append: path, // every claimed path publishes a session id (optional on prompt.footer) render: (input: { readonly sessionID?: string }) => ( <LastactStatusLine context={context} tracker={tracker} config={config} sessionID={input.sessionID} gate={gate} /> ), });}
export function setup(context: TuiContext): (() => void) | void { const config = resolveConfig(context.options ?? {}); if (!config.enabled) return;
const tracker = new ActivityTracker();
// The event path stays dumb: route the event to a ledger cell and touch // it. All deriving happens in the view's bucket (the per-surface ticker). const onEvent = (event: TuiV2Event): void => { const settle = settleFromEvent(event); if (settle) { tracker.settle(settle); return; } const touch = activityFromEvent(event, config.refreshOn); if (touch) tracker.touch(touch.sessionID, touch.clock, touch.atMs); }; const unsubscribers = subscriptionTypes(config.refreshOn).map((type) => context.data.on(type, onEvent));
const disposers = [ // One claim per unique published path across both surface lists; each // line paints only for the session kind(s) that asked for the path. // Children matter: the host suppresses prompt/sidebar in a child pane, // and `session.composer.top` (the default child surface) mounts in // regular panes too — without the gate the line would paint twice. ...new Set([...config.slots, ...config.childSlots].map((surface) => SLOT_PATHS[surface])), ].map((path) => claimPath(context, tracker, config, path, surfaceGate(config, path)));
return () => { for (const dispose of disposers) dispose(); for (const unsubscribe of unsubscribers) unsubscribe(); };}
const plugin = { id: PLUGIN_ID, setup };
export default plugin;