import type { ActorKind } from '../actor.js';
import type { EventRecord } from './events.js';
/** Recognizable milestone events, ordered first in any activity lane. */
export type MilestoneEvent = 'plan.created' | 'pr.opened' | 'pr.merged' | 'worktree.created' | 'worktree.removed' | 'commit.created' | 'pushed' | 'subagent.spawned' | 'artifact.created' | 'task.completed' | 'checklist.created'
/** Bash-driven deliverables detected by command parsing. */
 | 'video.rendered' | 'video.converted' | 'image.upscaled' | 'metadata.edited'
/** Deliberate agent-authored progress post (`agents feed post`). */
 | 'status.posted'
/**
 * An agent terminal spawned from AGI EXT, the VS Code extension. A milestone
 * because it is the BIRTH of a session — it carries the sessionId and the
 * terminalId that every later event on that session joins through, the same
 * way `subagent.spawned` roots a sub-agent. Written out of process by
 * `agents events emit`; the other factory.* kinds are operational-only.
 */
 | 'factory.launch'
/**
 * The same post, but the agent is STUCK (`agents feed post --blocked`).
 *
 * A distinct event rather than a flag on `status.posted` because it is a
 * different kind of thing in the stream: a benign update is history the
 * moment it lands, while a blocked post stays open until someone answers it.
 * Readers that show "what needs a human" select on this; readers that show
 * "what happened" get both.
 */
 | 'status.blocked';
/** Routine activity events, collapsed to counts by readers. */
export type ActivityKind = 'file.edited' | 'bash.executed';
export type ActivityEventKind = MilestoneEvent | ActivityKind;
export type ActivityTier = 'milestone' | 'activity';
/** Well-known attachment kinds; the field is an open string, not an enum. */
export type AttachmentKind = 'link' | 'file' | 'image' | 'audio' | 'video';
/**
 * A generic artifact carried by a progress update — a rendered plan, an audio
 * take, a preview URL. Deliberately domain-agnostic: `kind` is an open string,
 * and `meta` is an open bag (duration, width, …) so any producer can attach
 * anything without a schema change.
 */
export interface Attachment {
    /** Coarse kind for glyph/rendering; open string, not a closed enum. */
    kind: AttachmentKind | string;
    /** https:// URL, an absolute path, or a history-relative path. */
    href: string;
    /** Display name (defaults to basename(href) at render time). */
    name?: string;
    /** MIME type when known (e.g. `audio/wav`). */
    mediaType?: string;
    /** Size in bytes for local files, when stat succeeded. */
    bytes?: number;
    /** Open bag for kind-specific facts (duration, width, height, …). */
    meta?: Record<string, string | number>;
}
/** The set of milestone events, for tier classification and ordering. */
export declare const MILESTONE_EVENTS: readonly MilestoneEvent[];
export declare function tierForEvent(event: string): ActivityTier;
export interface ActivityEvent {
    /** Schema version, for forward-compatible readers. */
    v: number;
    /** ISO-8601 timestamp of the event. */
    ts: string;
    /** Event kind (see {@link ActivityEventKind}). */
    event: ActivityEventKind | string;
    /** Coarse importance tier -- stamped by the writer, recomputed if absent. */
    tier: ActivityTier;
    /** Owning session id. */
    sessionId: string;
    /** Mailbox id for routing/joins (falls back to sessionId). */
    mailboxId: string;
    /** Short, normalized hostname the event fired on. */
    host: string;
    /** Runtime label (headless, tmux, cloud, ...). */
    runtime: string;
    /** Working directory at event time -- the join key to a project/git repo. */
    cwd?: string;
    /**
     * Project/repo name stamped by the writer (basename of cwd, worktree-aware),
     * so a progress post carries its project even without a live-session join.
     * Read-time enrichment ({@link enrichActivityEvents}) still fills it for
     * hook-written events that lack it.
     */
    project?: string;
    /** Agent that produced the event (claude, codex, ...). */
    agent?: string;
    /** Resolved actor id shared with the operational event stream. */
    actor?: string;
    /** Resolved actor kind shared with the operational event stream. */
    kind?: ActorKind | 'unknown';
    /** Tool that triggered the event (Bash, Task, ExitPlanMode, feed.post, ...). */
    tool?: string;
    /** One-line human summary (plan title, PR command, sub-agent role, status text). */
    detail?: string;
    /**
     * Short subject for deliberate status posts (`feed post --title`). Phone
     * broadcasts put this on the first line; `detail` is the body. Optional on
     * older events that only carried `detail`.
     */
    title?: string;
    /** Extracted URL when the event has one (e.g. the opened PR). */
    url?: string;
    /** Auto-stamped process identity for deliberate posts (from pid registry / env). */
    pid?: number;
    /** Spawn-time join key (`AGENT_LAUNCH_ID`) when known. */
    launchId?: string;
    /** Session that spawned this session (`AGENTS_PARENT_SESSION_ID`) when known. */
    parentSessionId?: string;
    /** Factory terminal id when the launch inherited one. */
    terminalId?: string;
    /** `$TMUX_PANE` at launch when recorded. */
    tmuxPane?: string;
    /** Bash command taxonomy (set for bash.executed events). */
    category?: string;
    bashTool?: string;
    bashAction?: string;
    /** Generic artifacts attached to a deliberate progress post. */
    attachments?: Attachment[];
}
/**
 * Coerce an unknown value into a clean {@link Attachment}[] — a fail-open reader
 * over user/agent-written JSON. Drops entries without a usable `href`, clamps
 * `kind` to a string, and keeps only primitive `meta` values.
 */
export declare function sanitizeAttachments(value: unknown): Attachment[] | undefined;
/**
 * Append one event to a session's activity log. Primarily the Python hook
 * writes these at runtime; this TS writer exists for tests and any in-process
 * emitter. Uses O_APPEND so concurrent appends never interleave a line.
 */
export declare function appendActivityEvent(event: Omit<ActivityEvent, 'v' | 'tier'> & {
    v?: number;
    tier?: ActivityTier;
}, root?: string): void;
/** Read all events for one session (bounded tail). */
export declare function readSessionActivity(sessionId: string, root?: string, maxBytes?: number): ActivityEvent[];
/** List session ids that have an activity log. */
export declare function listActivitySessions(root?: string): string[];
export interface RecentActivityOptions {
    /** Only include events at or after this epoch-ms. */
    sinceMs?: number;
    /** Cap the number of returned events (most recent first). */
    limit?: number;
    /** Override the activity dir (tests). */
    root?: string;
    /** Per-session tail budget in bytes. */
    maxBytesPerSession?: number;
    /**
     * Only include these event names. Applied BEFORE `limit`, so asking for a
     * rare event (a deliberate `status.posted`) returns that many of it instead
     * of however many survive a slice dominated by routine `file.edited` churn.
     */
    events?: string[];
    /** Only include events in these tiers. Applied BEFORE `limit`, as `events` is. */
    tier?: ActivityTier;
}
/**
 * Merge recent events across every session's log, newest first. Reads only the
 * tail of each file, so cost scales with active sessions, not transcript size.
 */
export declare function readRecentActivity(opts?: RecentActivityOptions): ActivityEvent[];
export interface CollapsedActivity {
    /** Milestone events, individually preserved, newest first. */
    milestones: ActivityEvent[];
    /** Routine events rolled up to counts, e.g. { 'file.edited': 12 }. */
    counts: Record<string, number>;
    /** Number of sub-agents spawned across the collapsed set. */
    subagentCount: number;
}
/**
 * Split a chronological event list into individual milestones plus a
 * count map for the routine (tier-2) events, so a reader shows recognizable
 * deliverables in full and collapses the noise.
 */
export declare function collapseActivity(events: ActivityEvent[]): CollapsedActivity;
/**
 * Normalize one activity event into the shared {@link EventRecord} shape so the
 * agent-semantic stream reads through the same reader as operational events.
 * `module` is stamped `activity` so `--module` filters partition the two cleanly.
 */
export declare function activityEventToRecord(ev: ActivityEvent): EventRecord;
/** Read recent activity across sessions as unified {@link EventRecord}s. */
export declare function readActivityAsEventRecords(opts?: RecentActivityOptions): EventRecord[];
/** Glyph + color + human label per event, so the lane reads at a glance. */
export declare const EVENT_STYLE: Record<string, {
    glyph: string;
    color: (s: string) => string;
    label: string;
}>;
export declare function styleForEvent(event: string): {
    glyph: string;
    color: (s: string) => string;
    label: string;
};
/** One rendered activity line: `  <rel>  [host] <glyph label> detail url`. */
export declare function formatActivityLine(ev: ActivityEvent, opts?: {
    showHost?: boolean;
}): string;
/** Glyph per attachment kind, so an artifact row reads at a glance. */
export declare const ATTACHMENT_GLYPH: Record<string, string>;
export declare function attachmentGlyph(kind: string): string;
/** Display name for an attachment: its `name`, else the basename of its href. */
export declare function attachmentName(att: Attachment): string;
/**
 * Short session id for a chip: strip a known prefix (`session_`, `ses_`) then
 * take the first 8 chars — enough to disambiguate, matching `ag sessions`.
 */
export declare function shortSessionId(sessionId: string): string;
/** Extra identity resolved at display time by joining the session index. */
export interface ProgressJoin {
    ticketId?: string;
    prUrl?: string;
    label?: string;
}
/**
 * Render a deliberate progress update (`status.posted`) as an operator-facing,
 * multi-line row with full identity — not a 60-char one-liner. Shape:
 *
 * ```
 *   ▸ update · 3m ago
 *     grok · 0108441e · yosemite-s1 · agents
 *     "CHANGELOG pushed; watching CI"
 *     ♪ draft.wav   🖼 cover.png
 *     ↳ ag focus 0108441e · ag sessions 0108441e
 * ```
 *
 * Chips (agent, short session id, host, project, optional joined ticket/label)
 * are shown only when known. Non-progress milestones keep {@link formatActivityLine}.
 */
export declare function formatProgressUpdate(ev: ActivityEvent, opts?: {
    joined?: ProgressJoin;
}): string;
/** An activity event with the session-derived facts joined on at read time. */
export interface EnrichedActivityEvent extends ActivityEvent {
    /** Resolved project/repo name (from cwd or the session join), not the raw path. */
    project?: string;
    /** Tracker ticket the owning session is tied to (e.g. `RUSH-1234`). */
    ticket?: string;
    /** Machine the event actually ran on — session `provenance.host`, else `host`. */
    executionHost?: string;
}
/**
 * Facts pulled off a live session to enrich its activity events. Keyed by
 * `sessionId`; every field optional so callers pass whatever they resolved.
 */
export interface ActivitySessionHint {
    sessionId?: string | null;
    /** Tracker ticket id (from `ActiveSession.ticket`). */
    ticket?: string | null;
    /** Machine the session executes on (`provenance.host` / `machine`). */
    executionHost?: string | null;
    /** Resolved project/repo slug from the session cwd. */
    project?: string | null;
}
/**
 * Resolve a stable project/repo name from a working directory — the same fold
 * the `agents sessions` overview groups by ({@link projectKeyFromCwd}), so a
 * project reads identically in both views.
 */
export declare function projectFromCwd(cwd?: string | null): string | undefined;
/**
 * Join session facts (ticket / project / execution host) onto each event by
 * `sessionId`. A hint wins; otherwise a CANONICAL def match (`canonicalProject`)
 * upgrades whatever the event carries; otherwise pre-baked enriched fields (from
 * a remote peer that already enriched its own stream) are preserved, and
 * project/host fall back to what the event itself carries (`cwd`, `host`).
 *
 * The def match ranks above the pre-baked stamp because the stamp may be stale
 * (posted before the def existed) or skewed (a peer whose defs haven't synced);
 * the match is pure prefix compare against local defs, so a different-home
 * peer's path simply doesn't match and its stamp is honored as before.
 *
 * `resolveProject` is how a caller reading its OWN machine's logs upgrades the
 * cwd fold to real repository detection ({@link resolveProjectKey}); it defaults
 * to the pure {@link projectFromCwd}, which is all a path from another machine
 * can be resolved with. Pure given pure resolvers.
 */
export declare function enrichActivityEvents(events: EnrichedActivityEvent[], hints: ActivitySessionHint[], resolveProject?: (cwd?: string | null) => string | undefined, canonicalProject?: (cwd?: string | null) => string | undefined): EnrichedActivityEvent[];
/**
 * Validate + host-tag one peer's `activity --json` payload for the fan-out
 * merge. Mirrors {@link parseLine}: skip anything missing `event`/`sessionId`/
 * `ts`, drop corrupt items. The event's own `host` is the execution host and is
 * authoritative; only fall back to the dialed peer's `machine` when it's absent.
 * Enriched fields the peer stamped (project/ticket/executionHost) ride through.
 */
export declare function parseActivityPayload(stdout: string, machine: string): EnrichedActivityEvent[];
/**
 * Merge local + remote event lists, keeping the first copy of a given
 * host/session/ts/event and sorting newest first. Cross-machine activity dirs
 * can sync in a peer's own events, so the identity key dedupes those (mirrors
 * `mergeFeedBlocks`).
 */
export declare function mergeActivityEvents(...groups: EnrichedActivityEvent[][]): EnrichedActivityEvent[];
/**
 * Apply `--limit` to the events a reader will actually SHOW.
 *
 * The default view collapses routine work (`file.edited`) to a count and shows
 * milestones individually, so a plain `slice(limit)` spends the whole budget on
 * churn: one busy machine editing 40 files hid every other device's PRs behind
 * a single `file edited ×40` line. Milestones therefore carry the cap, and the
 * routine events that ride along are the ones newer than the last milestone
 * kept — so the collapsed counts describe exactly the window on screen.
 *
 * With `all` (routine shown inline) every event is displayed, so the cap is the
 * plain slice again. Input must be newest-first; output preserves that order.
 */
export declare function capActivityEvents(events: EnrichedActivityEvent[], limit: number, opts?: {
    all?: boolean;
}): EnrichedActivityEvent[];
export type ActivityGroupBy = 'project' | 'device' | 'agent';
/** One bucket of the grouped view. */
export interface ActivityGroup {
    /** Grouping value; empty string for the "unknown" bucket. */
    key: string;
    /** Display label. */
    label: string;
    events: EnrichedActivityEvent[];
}
/** The (key, label) an event sorts under for a given grouping dimension. */
export declare function activityGroupKey(ev: EnrichedActivityEvent, by: ActivityGroupBy): {
    key: string;
    label: string;
};
/**
 * Bucket events by project, device, or agent. Groups are ordered by event count
 * (desc) then label; the "unknown" bucket always sorts last. Input order is
 * preserved within a group, so newest-first survives when the input is sorted.
 */
export declare function groupActivity(events: EnrichedActivityEvent[], by: ActivityGroupBy): ActivityGroup[];
/**
 * Narrow events to those whose project, device/host, agent, event kind, or
 * ticket contains `filter` (case-insensitive substring). An empty filter is a
 * no-op. Pure.
 */
export declare function filterActivityEvents(events: EnrichedActivityEvent[], filter: string): EnrichedActivityEvent[];
/**
 * Narrow events to one resolved project, exact match on the enriched label
 * (unlike {@link filterActivityEvents}'s cross-field substring). This is the
 * `--project` flag: the label is canonical (a defined project's name) once the
 * reader's resolver has run, so a multi-repo project matches as one bucket. Pure.
 */
export declare function filterActivityByProject(events: EnrichedActivityEvent[], project: string): EnrichedActivityEvent[];
/** One rendered enriched line: the base activity line + `· project · ticket` tags. */
export declare function formatEnrichedActivityLine(ev: EnrichedActivityEvent, opts?: {
    showHost?: boolean;
    showProject?: boolean;
    indent?: string;
}): string;
/** Max device names named in a group header before the rest collapse to `+N`. */
export declare const GROUP_HEADER_DEVICE_LIMIT = 3;
/**
 * The distinct machines a group's events ran on, most-active first then
 * alphabetical — so a project header can say WHERE the work happened without
 * sub-grouping the timeline. Prefers the session-joined `executionHost` (where
 * the process really lives) over the raw emitting `host`. Pure.
 */
export declare function activityGroupDevices(events: EnrichedActivityEvent[]): string[];
/**
 * The trailing facts for one grouped bucket: `N events · M milestones`, plus
 * the machines the work ran on when `showDevices` is set (redundant when the
 * grouping dimension IS the device, so the caller decides). The device list is
 * capped at {@link GROUP_HEADER_DEVICE_LIMIT} with a `+N` tail, so a project
 * touched by a dozen boxes stays one scannable line. Separate from the label so
 * a renderer can color the two differently without re-splitting a string.
 */
export declare function formatActivityGroupMeta(group: ActivityGroup, opts?: {
    showDevices?: boolean;
}): string;
/**
 * The activity-log hook (Python), sibling to 10-feed-publish.py. Classifies
 * PreToolUse/PostToolUse payloads into activity events and appends one JSONL
 * line per event to ~/.agents/.history/activity/<sessionId>.jsonl.
 *
 * Matcher-gated to mutating/milestone tools (Bash|Task|ExitPlanMode|Write|
 * Edit|MultiEdit|TodoWrite|update_plan|TaskUpdate|todo_write|TaskCreate) so
 * read-only tools never pay the hook cost. Fail-open: any error is swallowed so
 * a logging hiccup never blocks a tool call.
 */
export declare const ACTIVITY_LOG_HOOK_SCRIPT: string;
/** Hook manifest entries (agents.yaml shape) for the activity-log hook. */
export declare const ACTIVITY_HOOK_DEFINITIONS: Record<string, Record<string, unknown>>;
/**
 * Install the activity-log hook script into the user hooks dir and add its
 * manifest entries to the user agents.yaml. Mirrors {@link ensureFeedPublishHook}
 * in feed.ts -- idempotent, and never writes the read-only system repo.
 */
export declare function ensureActivityLogHook(userAgentsDir?: string): {
    installed: boolean;
    error?: string;
};
