File size: 2,584 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import {
  followSessionLifecycles,
  getLiveSessionById,
  type IDisposable,
  type Scope,
} from '@moonshot-ai/agent-core-v2';

import type { ServerMessage } from '../../protocol/messages';
import {
  SessionProjection,
  type ProjectionLogger,
} from './sessionProjection';

export interface ProjectionServiceDeps {
  readonly homeDir: string;
  readonly core: Scope;
  readonly logger?: ProjectionLogger;
}

export class ProjectionService {
  private readonly live = new Map<string, SessionProjection>();

  constructor(private readonly deps: ProjectionServiceDeps) {
    followSessionLifecycles(deps.core.accessor, (service) => {
      const d1 = service.onDidCloseSession(({ sessionId }) => {
        this.dropSession(sessionId);
      });
      const d2 = service.onDidArchiveSession(({ sessionId }) => {
        this.dropSession(sessionId);
      });
      return {
        dispose: () => {
          d1.dispose();
          d2.dispose();
        },
      };
    });
  }

  forSessionLive(sessionId: string): SessionProjection | undefined {
    const existing = this.live.get(sessionId);
    if (existing !== undefined) {
      if (getLiveSessionById(this.deps.core.accessor, sessionId) !== undefined) return existing;
      this.dropSession(sessionId);
      return undefined;
    }
    const session = getLiveSessionById(this.deps.core.accessor, sessionId);
    if (session === undefined) return undefined;
    let projection: SessionProjection;
    try {
      projection = new SessionProjection(sessionId, session, this.deps);
    } catch (error) {
      if (error instanceof Error && error.message === 'InstantiationService has been disposed') {
        return undefined;
      }
      throw error;
    }
    this.live.set(sessionId, projection);
    return projection;
  }

  onMessage(
    sessionId: string,
    listener: (message: ServerMessage) => void,
  ): IDisposable | undefined {
    return this.forSessionLive(sessionId)?.onMessage(listener);
  }

  recoveryMessages(sessionId: string): ServerMessage[] {
    return this.forSessionLive(sessionId)?.recoveryMessages() ?? [];
  }

  notifyContextCleared(sessionId: string, agentId: string): void {
    this.live.get(sessionId)?.notifyContextCleared(agentId);
  }

  inFlight(sessionId: string, agentId: string): { turn_id: string; step_id: string } | undefined {
    return this.forSessionLive(sessionId)?.inFlight(agentId);
  }

  dropSession(sessionId: string): void {
    const entry = this.live.get(sessionId);
    if (entry === undefined) return;
    this.live.delete(sessionId);
    entry.dispose();
  }
}