* Run a sub-agent inline. Used by the `task` tool. * * Architecture: sub-agent gets a fresh AgentLoop with the same router/registry/perms * but a new session id and an isolated message history. Tools run in 'subagent' mode * (task and present_plan filtered). We drain the event stream and
(
prompt: string,
opts: {
maxIterations: number;
signal?: AbortSignal;
sessionId: string;
modelOverride?: string;
/** Role name — drives model selection, system prompt, tool restriction. Default 'subagent'. */
role?: string;
},
)
| 274 | this.cwd = path.resolve(cwd); |
| 275 | this.effectiveCwd = undefined; |
| 276 | if (this.snapshotService) { |
| 277 | this.snapshotService = new SnapshotService(this.cwd, 'pending', { |
| 278 | retentionTurns: (this.config as any).safety?.snapshotRetentionTurns ?? 50, |
| 279 | }); |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | workingDirectory(): string { |
| 284 | return this.effectiveCwd ?? this.cwd; |
| 285 | } |
| 286 | |
| 287 | /** Update the snapshot service's session id when a real session starts. */ |
| 288 | private setSessionForSnapshot(sessionId: string): void { |
| 289 | if (this.snapshotService) { |
| 290 | // Recreate with the proper session id — cleaner than mutating private state. |
| 291 | this.snapshotService = new SnapshotService(this.cwd, sessionId, { |
| 292 | retentionTurns: (this.config as any).safety?.snapshotRetentionTurns ?? 50, |
| 293 | }); |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | /** Public read accessor for slash commands to operate on snapshots. */ |
| 298 | getSnapshotService(): SnapshotService | undefined { |
| 299 | return this.snapshotService; |
| 300 | } |
| 301 | |
| 302 | private insightsFor(sessionId: string): SessionInsights { |
| 303 | let s = this.insightsBySession.get(sessionId); |
| 304 | if (!s) { |
| 305 | s = new SessionInsights(sessionId); |
| 306 | this.insightsBySession.set(sessionId, s); |
| 307 | } |
| 308 | return s; |
| 309 | } |
| 310 | |
| 311 | /** Live snapshot for `/insights`. */ |
| 312 | getInsights(sessionId: string): InsightsSnapshot { |
| 313 | return this.insightsFor(sessionId).snapshot(); |
| 314 | } |
| 315 | |
| 316 | resetInsights(sessionId: string): void { |
| 317 | this.insightsBySession.get(sessionId)?.reset(); |
| 318 | } |
| 319 | |
| 320 | persistInsights(sessionId: string): void { |
| 321 | const s = this.insightsBySession.get(sessionId); |
| 322 | if (!s || s.isEmpty()) return; |
| 323 | try { getSessionStore().saveInsights(sessionId, s.snapshot()); } catch { /* never stall the loop */ } |
| 324 | } |
| 325 | |
| 326 | /** |
| 327 | * Write a lean episode for THIS turn so the next similar task can recall it. |
| 328 | * Must be awaited — headless `process.exit` otherwise kills the append. |
| 329 | */ |
| 330 | private async maybeRecordEpisode(opts: { |
| 331 | prompt: string; |
| 332 | summary: string; |
| 333 | filesChanged: string[]; |
no test coverage detected