Activity feed: drop busyChats — dock is background processes only (user decision 2026-09-11)
This commit is contained in:
+10
-21
@@ -5,17 +5,18 @@
|
|||||||
* THE ACTIVITY FEED (2026-09 observability design): the bottom-right dock is
|
* THE ACTIVITY FEED (2026-09 observability design): the bottom-right dock is
|
||||||
* visible in every view, so it needs the complete running-process picture —
|
* visible in every view, so it needs the complete running-process picture —
|
||||||
* sub-agent jobs across ALL chats (not just the open one) plus the global
|
* sub-agent jobs across ALL chats (not just the open one) plus the global
|
||||||
* extraction runs — in one poll. /api/jobs stays chat-scoped untouched: it
|
* extraction runs — in one poll. The dock is BACKGROUND PROCESSES ONLY
|
||||||
* is the transcript machinery's poll and carries turnInFlight for the
|
* (user decision, 2026-09-11): teaching sessions already have their own
|
||||||
* re-attach seam (chat-transport); the dock is a different consumer with a
|
* observability, so in-flight teacher turns are deliberately NOT in the
|
||||||
* different shape, served here. Single sources of truth are unchanged: the
|
* feed. /api/jobs stays chat-scoped untouched: it is the transcript
|
||||||
* job store, the pipeline registry, the turn-liveness registry — this file
|
* machinery's poll and carries turnInFlight for the re-attach seam
|
||||||
* only composes them.
|
* (chat-transport); the dock is a different consumer with a different
|
||||||
|
* shape, served here. Single sources of truth are unchanged: the job
|
||||||
|
* store and the pipeline registry — this file only composes them.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { extractionStatuses, type ExtractionPhase } from "../agent/subagents/extraction.ts";
|
import { extractionStatuses, type ExtractionPhase } from "../agent/subagents/extraction.ts";
|
||||||
import { chatStore } from "../store/chats.ts";
|
import { chatStore } from "../store/chats.ts";
|
||||||
import { isTurnRunning } from "./turn-liveness.ts";
|
|
||||||
import { listJobs } from "./jobs.ts";
|
import { listJobs } from "./jobs.ts";
|
||||||
import type { JobRecord } from "../core/contracts.ts";
|
import type { JobRecord } from "../core/contracts.ts";
|
||||||
|
|
||||||
@@ -35,32 +36,20 @@ export interface ActivityExtraction {
|
|||||||
startedAt: number;
|
startedAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A chat with a teacher turn in flight right now (the wake-turn process —
|
|
||||||
* the biggest spender; data made available, rendering it is a dock
|
|
||||||
* decision). */
|
|
||||||
export interface ActivityChatTurn {
|
|
||||||
chatId: string;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The dock's whole world in one payload: every running process, every view.
|
/** The dock's whole world in one payload: every running process, every view.
|
||||||
* Jobs are RUNNING-ONLY — finished jobs belong to their chat's transcript;
|
* Jobs are RUNNING-ONLY — finished jobs belong to their chat's transcript;
|
||||||
* the blob disappears when the process ends. Cost math is NOT done here:
|
* the blob disappears when the process ends. Cost math is NOT done here:
|
||||||
* raw usage rides on the records and the client folds it with the pure
|
* raw usage rides on the records and the client folds it with the pure
|
||||||
* helpers in core/prices.ts (transport stays thin). */
|
* helpers in core/prices.ts (transport stays thin). */
|
||||||
export function activityFeed(): { jobs: ActivityJob[]; extractions: ActivityExtraction[]; busyChats: ActivityChatTurn[] } {
|
export function activityFeed(): { jobs: ActivityJob[]; extractions: ActivityExtraction[] } {
|
||||||
const chats = chatStore.listChats();
|
const chats = chatStore.listChats();
|
||||||
const jobs: ActivityJob[] = [];
|
const jobs: ActivityJob[] = [];
|
||||||
const busyChats: ActivityChatTurn[] = [];
|
|
||||||
for (const chat of chats) {
|
for (const chat of chats) {
|
||||||
for (const job of listJobs(chat.id)) {
|
for (const job of listJobs(chat.id)) {
|
||||||
if (job.status === "running") {
|
if (job.status === "running") {
|
||||||
jobs.push({ ...job, title: chat.title ?? chat.id });
|
jobs.push({ ...job, title: chat.title ?? chat.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isTurnRunning(chat.id)) {
|
|
||||||
busyChats.push({ chatId: chat.id, title: chat.title ?? chat.id });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const extractions: ActivityExtraction[] = extractionStatuses().map((run) => ({
|
const extractions: ActivityExtraction[] = extractionStatuses().map((run) => ({
|
||||||
chatId: run.chatId,
|
chatId: run.chatId,
|
||||||
@@ -70,5 +59,5 @@ export function activityFeed(): { jobs: ActivityJob[]; extractions: ActivityExtr
|
|||||||
}));
|
}));
|
||||||
// oldest-first on last activity: the stable blob order (ISO strings sort chronologically)
|
// oldest-first on last activity: the stable blob order (ISO strings sort chronologically)
|
||||||
jobs.sort((a, b) => a.updatedAt.localeCompare(b.updatedAt));
|
jobs.sort((a, b) => a.updatedAt.localeCompare(b.updatedAt));
|
||||||
return { jobs, extractions: extractions.sort((a, b) => a.startedAt - b.startedAt), busyChats };
|
return { jobs, extractions: extractions.sort((a, b) => a.startedAt - b.startedAt) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ test("activityFeed: running jobs across ALL chats, running-only, title-joined, o
|
|||||||
|
|
||||||
// extraction runs are absent when the pipeline is idle (registry empty)
|
// extraction runs are absent when the pipeline is idle (registry empty)
|
||||||
assert.deepEqual(feed.extractions, []);
|
assert.deepEqual(feed.extractions, []);
|
||||||
// busyChats reports turn-liveness (none in flight here)
|
|
||||||
assert.deepEqual(feed.busyChats, []);
|
|
||||||
} finally {
|
} finally {
|
||||||
fs.unlinkSync(`${PATHS.chats}/${chatA.id}.json`);
|
fs.unlinkSync(`${PATHS.chats}/${chatA.id}.json`);
|
||||||
fs.unlinkSync(`${PATHS.chats}/${chatB.id}.json`);
|
fs.unlinkSync(`${PATHS.chats}/${chatB.id}.json`);
|
||||||
|
|||||||
Reference in New Issue
Block a user