agent-lyceum 0.33.0 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,8 +13,19 @@ export type WatchFrame = {
13
13
  * A task that was running in the previous frame and has finished (completed or partial) since is included once more, so the reader sees
14
14
  * it finish (and can toast) before it is dropped. `tracked` is that previous set; it is replaced by the ids running now.
15
15
  */
16
+ /**
17
+ * Reports of finished runs, kept so a sweep does not rebuild them (about 300 ms each). An entry is reused while the
18
+ * run's state.json is unchanged and it is younger than `FINISHED_TTL_MS`; the TTL picks up what lives outside that file
19
+ * (unread mail, memory advice).
20
+ */
21
+ export type FinishedCache = Map<string, {
22
+ stamp: string;
23
+ at: number;
24
+ report: StatusReport;
25
+ }>;
16
26
  export declare function buildWatchFrame(project: ResolvedProject, tracked: Set<string>, opts?: {
17
27
  allTasks?: boolean;
28
+ cache?: FinishedCache;
18
29
  }): WatchFrame;
19
30
  export interface WatchDeps {
20
31
  /** The project, loaded again each time so config edits and new runs show up. */
@@ -1,11 +1,16 @@
1
1
  import fs from "node:fs";
2
- import { buildStatusReport, buildTaskListReport, isFinishedRun } from "./status.js";
3
- /**
4
- * The frame for now. Every task that is not finished (running, interrupted, waiting for answers, or ended blocked,
5
- * failed or cancelled) gets its own report, so it stays listed until it is resumed or cleared.
6
- * A task that was running in the previous frame and has finished (completed or partial) since is included once more, so the reader sees
7
- * it finish (and can toast) before it is dropped. `tracked` is that previous set; it is replaced by the ids running now.
8
- */
2
+ import path from "node:path";
3
+ import { buildStatusReport, buildTaskListReport, currentRun, isFinishedRun } from "./status.js";
4
+ const FINISHED_TTL_MS = 5 * 60_000;
5
+ function stampOf(project, id) {
6
+ try {
7
+ const st = fs.statSync(path.join(project.paths.runs, id, "state.json"));
8
+ return `${st.mtimeMs}:${st.size}`;
9
+ }
10
+ catch {
11
+ return "";
12
+ }
13
+ }
9
14
  export function buildWatchFrame(project, tracked, opts) {
10
15
  const all = buildTaskListReport(project).runs;
11
16
  const running = all.filter((r) => r.state === "running").map((r) => r.run_id);
@@ -13,9 +18,27 @@ export function buildWatchFrame(project, tracked, opts) {
13
18
  const runs = {};
14
19
  // The web page keeps every task. The terminal watch drops one after it has finished, once the finish has been seen.
15
20
  const ids = opts?.allTasks ? all.map((r) => r.run_id) : [...open, ...tracked];
16
- for (const id of new Set(ids))
21
+ const finished = new Set(all.filter(isFinishedRun).map((r) => r.run_id));
22
+ const cache = opts?.cache;
23
+ const now = Date.now();
24
+ for (const id of new Set(ids)) {
25
+ const stamp = cache && finished.has(id) ? stampOf(project, id) : "";
26
+ const hit = stamp ? cache?.get(id) : undefined;
27
+ if (hit && hit.stamp === stamp && now - hit.at < FINISHED_TTL_MS) {
28
+ runs[id] = hit.report;
29
+ continue;
30
+ }
17
31
  runs[id] = buildStatusReport(project, id);
18
- const report = buildStatusReport(project);
32
+ if (stamp)
33
+ cache?.set(id, { stamp, at: now, report: runs[id] });
34
+ }
35
+ if (cache)
36
+ for (const id of cache.keys())
37
+ if (!finished.has(id))
38
+ cache.delete(id);
39
+ // The project's own report shows the current run, which is usually one already built above.
40
+ const currentId = currentRun(project)?.state.run_id;
41
+ const report = currentId && runs[currentId] ? runs[currentId] : buildStatusReport(project);
19
42
  tracked.clear();
20
43
  for (const id of running)
21
44
  tracked.add(id);
@@ -31,6 +54,7 @@ function fsWatch(dir, onChange) {
31
54
  */
32
55
  export function createStatusWatcher(d) {
33
56
  const tracked = new Set();
57
+ const cache = new Map();
34
58
  const watch = d.watch ?? fsWatch;
35
59
  let lastLine = "";
36
60
  let watching = "";
@@ -41,7 +65,7 @@ export function createStatusWatcher(d) {
41
65
  let project;
42
66
  try {
43
67
  project = d.load();
44
- frame = buildWatchFrame(project, tracked, { allTasks: d.allTasks });
68
+ frame = buildWatchFrame(project, tracked, { allTasks: d.allTasks, cache });
45
69
  }
46
70
  catch (e) {
47
71
  frame = { schema_version: 1, error: e.message };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-lyceum",
3
- "version": "0.33.0",
3
+ "version": "0.33.1",
4
4
  "description": "Config + dispatcher for teams of Claude Code / Codex agents with scoped context and file-based mailboxes",
5
5
  "keywords": [
6
6
  "ai",