| 168 | } from "./prompts.ts"; |
| 169 | |
| 170 | export class AgentManager { |
| 171 | readonly workspace: string; |
| 172 | readonly planPath: string; |
| 173 | readonly statePath: string; |
| 174 | readonly handoffsDir: string; |
| 175 | readonly attentionLog: string; |
| 176 | readonly plan: Plan; |
| 177 | state: State; |
| 178 | private readonly apiKey: string; |
| 179 | readonly slackAdapter?: SlackAdapter; |
| 180 | readonly andon: AndonPoller; |
| 181 | private readonly slackMirrorQueue = new Map<string, Promise<void>>(); |
| 182 | |
| 183 | private constructor(args: { |
| 184 | workspace: string; |
| 185 | plan: Plan; |
| 186 | state: State; |
| 187 | apiKey: string; |
| 188 | slackAdapter?: SlackAdapter; |
| 189 | }) { |
| 190 | this.workspace = args.workspace; |
| 191 | this.plan = args.plan; |
| 192 | this.state = args.state; |
| 193 | this.apiKey = args.apiKey; |
| 194 | this.slackAdapter = args.slackAdapter; |
| 195 | this.planPath = join(args.workspace, "plan.json"); |
| 196 | this.statePath = join(args.workspace, "state.json"); |
| 197 | this.handoffsDir = join(args.workspace, "handoffs"); |
| 198 | this.attentionLog = join(args.workspace, "attention.log"); |
| 199 | this.andon = new AndonPoller({ |
| 200 | source: |
| 201 | this.isRootPlanner() && this.slackAdapter && this.plan.slackKickoffRef |
| 202 | ? new SlackReactionAndonSource( |
| 203 | this.slackAdapter, |
| 204 | this.plan.slackKickoffRef |
| 205 | ) |
| 206 | : undefined, |
| 207 | getState: () => this.state, |
| 208 | saveState: reason => this.saveAndonState(reason), |
| 209 | logAttention: line => this.logAttention(line), |
| 210 | pollSource: this.isRootPlanner(), |
| 211 | cachedState: this.andonCachedState(), |
| 212 | }); |
| 213 | } |
| 214 | |
| 215 | static async load( |
| 216 | workspacePath: string, |
| 217 | opts: { slackChannel?: string } = {} |
| 218 | ): Promise<AgentManager> { |
| 219 | const workspace = resolve(workspacePath); |
| 220 | mkdirSync(workspace, { recursive: true }); |
| 221 | const planPath = join(workspace, "plan.json"); |
| 222 | if (!existsSync(planPath)) { |
| 223 | throw new PlanValidationError( |
| 224 | `missing ${planPath} — the planner writes plan.json first; see SKILL.md → Phase 1` |
| 225 | ); |
| 226 | } |
| 227 | const parsedPlan = parsePlanJson(readFileSync(planPath, "utf8"), planPath); |
nothing calls this directly
no outgoing calls
no test coverage detected