/ utils / teammateContext.ts
teammateContext.ts
 1  /**
 2   * TeammateContext - Runtime context for in-process teammates
 3   *
 4   * This module provides AsyncLocalStorage-based context for in-process teammates,
 5   * enabling concurrent teammate execution without global state conflicts.
 6   *
 7   * Relationship with other teammate identity mechanisms:
 8   * - Env vars (CLAUDE_CODE_AGENT_ID): Process-based teammates spawned via tmux
 9   * - dynamicTeamContext (teammate.ts): Process-based teammates joining at runtime
10   * - TeammateContext (this file): In-process teammates via AsyncLocalStorage
11   *
12   * The helper functions in teammate.ts check AsyncLocalStorage first, then
13   * dynamicTeamContext, then env vars.
14   */
15  
16  import { AsyncLocalStorage } from 'async_hooks'
17  
18  /**
19   * Runtime context for in-process teammates.
20   * Stored in AsyncLocalStorage for concurrent access.
21   */
22  export type TeammateContext = {
23    /** Full agent ID, e.g., "researcher@my-team" */
24    agentId: string
25    /** Display name, e.g., "researcher" */
26    agentName: string
27    /** Team name this teammate belongs to */
28    teamName: string
29    /** UI color assigned to this teammate */
30    color?: string
31    /** Whether teammate must enter plan mode before implementing */
32    planModeRequired: boolean
33    /** Leader's session ID (for transcript correlation) */
34    parentSessionId: string
35    /** Discriminator - always true for in-process teammates */
36    isInProcess: true
37    /** Abort controller for lifecycle management (linked to parent) */
38    abortController: AbortController
39  }
40  
41  const teammateContextStorage = new AsyncLocalStorage<TeammateContext>()
42  
43  /**
44   * Get the current in-process teammate context, if running as one.
45   * Returns undefined if not running within an in-process teammate context.
46   */
47  export function getTeammateContext(): TeammateContext | undefined {
48    return teammateContextStorage.getStore()
49  }
50  
51  /**
52   * Run a function with teammate context set.
53   * Used when spawning an in-process teammate to establish its execution context.
54   *
55   * @param context - The teammate context to set
56   * @param fn - The function to run with the context
57   * @returns The return value of fn
58   */
59  export function runWithTeammateContext<T>(
60    context: TeammateContext,
61    fn: () => T,
62  ): T {
63    return teammateContextStorage.run(context, fn)
64  }
65  
66  /**
67   * Check if current execution is within an in-process teammate.
68   * This is faster than getTeammateContext() !== undefined for simple checks.
69   */
70  export function isInProcessTeammate(): boolean {
71    return teammateContextStorage.getStore() !== undefined
72  }
73  
74  /**
75   * Create a TeammateContext from spawn configuration.
76   * The abortController is passed in by the caller. For in-process teammates,
77   * this is typically an independent controller (not linked to parent) so teammates
78   * continue running when the leader's query is interrupted.
79   *
80   * @param config - Configuration for the teammate context
81   * @returns A complete TeammateContext with isInProcess: true
82   */
83  export function createTeammateContext(config: {
84    agentId: string
85    agentName: string
86    teamName: string
87    color?: string
88    planModeRequired: boolean
89    parentSessionId: string
90    abortController: AbortController
91  }): TeammateContext {
92    return {
93      ...config,
94      isInProcess: true,
95    }
96  }