| 435 | } |
| 436 | |
| 437 | async initialize(): Promise<void> { |
| 438 | const startupStartedAt = Date.now(); |
| 439 | const stepDurationsMs: Record<string, number> = {}; |
| 440 | const recordStep = async <T>(name: string, fn: () => Promise<T>): Promise<T> => { |
| 441 | const stepStartedAt = Date.now(); |
| 442 | try { |
| 443 | return await fn(); |
| 444 | } finally { |
| 445 | stepDurationsMs[name] = Date.now() - stepStartedAt; |
| 446 | } |
| 447 | }; |
| 448 | |
| 449 | log.info("[startup] ServiceContainer.initialize starting"); |
| 450 | |
| 451 | await recordStep("extensionMetadata.initialize", () => this.extensionMetadata.initialize()); |
| 452 | // Initialize telemetry service |
| 453 | await recordStep("telemetryService.initialize", () => this.telemetryService.initialize()); |
| 454 | |
| 455 | // Initialize policy service (startup gating) |
| 456 | await recordStep("policyService.initialize", () => this.policyService.initialize()); |
| 457 | |
| 458 | await recordStep("experimentsService.initialize", () => this.experimentsService.initialize()); |
| 459 | // Kick off non-task chat restart recovery eagerly; task workspaces recover in TaskService.initialize(). |
| 460 | await recordStep("workspaceService.initialize", () => this.workspaceService.initialize()); |
| 461 | await recordStep("taskService.initialize", () => this.taskService.initialize()); |
| 462 | |
| 463 | const idleCompactionStartedAt = Date.now(); |
| 464 | // Start idle compaction checker |
| 465 | this.idleCompactionService.start(); |
| 466 | stepDurationsMs["idleCompactionService.start"] = Date.now() - idleCompactionStartedAt; |
| 467 | |
| 468 | const heartbeatStartedAt = Date.now(); |
| 469 | this.heartbeatService.start(); |
| 470 | stepDurationsMs["heartbeatService.start"] = Date.now() - heartbeatStartedAt; |
| 471 | |
| 472 | const agentStatusStartedAt = Date.now(); |
| 473 | this.agentStatusService.start(); |
| 474 | stepDurationsMs["agentStatusService.start"] = Date.now() - agentStatusStartedAt; |
| 475 | |
| 476 | // Dream launch sweep (PRD #3534): consolidate memory for workspaces idle |
| 477 | // ≥24h with writes since their last run. Fire-and-forget after the await |
| 478 | // chain — startup must never block or crash on background housekeeping. |
| 479 | void this.extensionMetadata |
| 480 | .getAllSnapshots() |
| 481 | .then((snapshots) => { |
| 482 | const recencyByWorkspace = new Map<string, number>(); |
| 483 | for (const [workspaceId, snapshot] of snapshots) { |
| 484 | recencyByWorkspace.set(workspaceId, snapshot.recency); |
| 485 | } |
| 486 | return this.memoryConsolidationService.runLaunchSweep(recencyByWorkspace); |
| 487 | }) |
| 488 | .catch((error: unknown) => { |
| 489 | log.warn("[MemoryConsolidation] launch sweep failed", { error }); |
| 490 | }); |
| 491 | |
| 492 | // Refresh mux-owned Coder SSH config in background (handles binary path changes on restart) |
| 493 | // Skip getCoderInfo() to avoid caching "unavailable" if coder isn't installed yet |
| 494 | void this.coderService.ensureMuxCoderSSHConfig().catch((error: unknown) => { |