Something went wrong. Try again.
Where attention appears to go across agent threads: collector, web explorer, and native iOS client
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687// @ts-check/** @typedef {import("./contract.mjs").AttentionEvent} AttentionEvent *//** @typedef {import("./contract.mjs").Thread} Thread *//** @typedef {{thread: string, start: number, end: number}} Span *//** @typedef {{from: string, to: string, at: number, gap: number}} Switch *//** @typedef {"project" | "repo" | "thread"} GroupBy *//** @typedef {{key: string, label: string, seconds: number, count: number, threads: Set<string>}} Summary */
/** * Spans join consecutive same-thread signals within the gap. Switching threads * breaks the span; isolated signals add nothing. Run over all events before * filtering so a filter can never bridge hidden interactions. * @param {Pick<AttentionEvent, "at" | "thread">[]} events * @param {number} gapSeconds */export function estimate(events, gapSeconds) { const sorted = [...events].sort( (a, b) => a.at - b.at || a.thread.localeCompare(b.thread), ); /** @type {Span[]} */ const spans = []; /** @type {Switch[]} */ const switches = []; for (let i = 0; i < sorted.length - 1; i++) { const current = sorted[i]; const next = sorted[i + 1]; const gap = next.at - current.at; if (gap > gapSeconds || gap <= 0) continue; if (current.thread === next.thread) { spans.push({ thread: current.thread, start: current.at, end: next.at }); } else { switches.push({ from: current.thread, to: next.thread, at: next.at, gap }); } } return { spans, switches };}
/** * @param {Pick<AttentionEvent, "thread">[]} events * @param {Span[]} spans * @param {Record<string, Thread>} threads * @param {GroupBy} groupBy */export function summarize(events, spans, threads, groupBy) { /** @type {Map<string, Summary>} */ const groups = new Map(); /** @param {string} id */ const keyFor = (id) => { const thread = threads[id]; if (groupBy === "thread") return id; if (groupBy === "repo") return thread.repo || "unknown-repo"; return thread.repo || thread.project_key || ""; }; for (const event of events) { const thread = threads[event.thread]; if (!thread) continue; const key = keyFor(event.thread); let group = groups.get(key); if (!group) { group = { key, label: groupBy === "thread" ? thread.title : groupBy === "repo" ? thread.repo || "Repository not recorded" : thread.repo && !thread.repo.includes("/did:") ? thread.repo.split("/").at(-1) || thread.repo : thread.project, seconds: 0, count: 0, threads: new Set(), }; groups.set(key, group); } group.count++; group.threads.add(event.thread); } for (const span of spans) { const group = groups.get(keyFor(span.thread)); if (group) group.seconds += span.end - span.start; } return [...groups.values()].sort( (a, b) => b.seconds - a.seconds || b.count - a.count, );}