File size: 2,363 Bytes
fc93158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import type { AcpRuntime, AcpRuntimeHandle, AcpRuntimeSessionMode } from "../runtime/types.js";

export type CachedRuntimeState = {
  runtime: AcpRuntime;
  handle: AcpRuntimeHandle;
  backend: string;
  agent: string;
  mode: AcpRuntimeSessionMode;
  cwd?: string;
  appliedControlSignature?: string;
};

type RuntimeCacheEntry = {
  state: CachedRuntimeState;
  lastTouchedAt: number;
};

export type CachedRuntimeSnapshot = {
  actorKey: string;
  state: CachedRuntimeState;
  lastTouchedAt: number;
  idleMs: number;
};

export class RuntimeCache {
  private readonly cache = new Map<string, RuntimeCacheEntry>();

  size(): number {
    return this.cache.size;
  }

  has(actorKey: string): boolean {
    return this.cache.has(actorKey);
  }

  get(
    actorKey: string,
    params: {
      touch?: boolean;
      now?: number;
    } = {},
  ): CachedRuntimeState | null {
    const entry = this.cache.get(actorKey);
    if (!entry) {
      return null;
    }
    if (params.touch !== false) {
      entry.lastTouchedAt = params.now ?? Date.now();
    }
    return entry.state;
  }

  peek(actorKey: string): CachedRuntimeState | null {
    return this.get(actorKey, { touch: false });
  }

  getLastTouchedAt(actorKey: string): number | null {
    return this.cache.get(actorKey)?.lastTouchedAt ?? null;
  }

  set(
    actorKey: string,
    state: CachedRuntimeState,
    params: {
      now?: number;
    } = {},
  ): void {
    this.cache.set(actorKey, {
      state,
      lastTouchedAt: params.now ?? Date.now(),
    });
  }

  clear(actorKey: string): void {
    this.cache.delete(actorKey);
  }

  snapshot(params: { now?: number } = {}): CachedRuntimeSnapshot[] {
    const now = params.now ?? Date.now();
    const entries: CachedRuntimeSnapshot[] = [];
    for (const [actorKey, entry] of this.cache.entries()) {
      entries.push({
        actorKey,
        state: entry.state,
        lastTouchedAt: entry.lastTouchedAt,
        idleMs: Math.max(0, now - entry.lastTouchedAt),
      });
    }
    return entries;
  }

  collectIdleCandidates(params: { maxIdleMs: number; now?: number }): CachedRuntimeSnapshot[] {
    if (!Number.isFinite(params.maxIdleMs) || params.maxIdleMs <= 0) {
      return [];
    }
    const now = params.now ?? Date.now();
    return this.snapshot({ now }).filter((entry) => entry.idleMs >= params.maxIdleMs);
  }
}