@overdeck/desktop 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/package.json +1 -1
  2. package/resources/icon.ico +0 -0
  3. package/resources/icon.png +0 -0
  4. package/server/public/assets/{architectureDiagram-3BPJPVTR-CDm4DtgX.js → architectureDiagram-3BPJPVTR-D1cX9STH.js} +1 -1
  5. package/server/public/assets/{blockDiagram-GPEHLZMM-B0CHnHbL.js → blockDiagram-GPEHLZMM-_U_GESDt.js} +1 -1
  6. package/server/public/assets/{c4Diagram-AAUBKEIU-CwHAm-Sh.js → c4Diagram-AAUBKEIU-DNF40ymm.js} +1 -1
  7. package/server/public/assets/channel-Dz_N3rZ1.js +1 -0
  8. package/server/public/assets/{chunk-2J33WTMH-BIWv54To.js → chunk-2J33WTMH-CphN4I1h.js} +1 -1
  9. package/server/public/assets/{chunk-3OPIFGDE-BIj0tSkO.js → chunk-3OPIFGDE-pHasFymF.js} +1 -1
  10. package/server/public/assets/{chunk-4BX2VUAB-xpGve5oU.js → chunk-4BX2VUAB-CDe6cmmN.js} +1 -1
  11. package/server/public/assets/chunk-55IACEB6-CB9aM4Zf.js +1 -0
  12. package/server/public/assets/{chunk-5ZQYHXKU-DYb235_b.js → chunk-5ZQYHXKU-CKfVKs9N.js} +1 -1
  13. package/server/public/assets/{chunk-727SXJPM-BYV0QAgl.js → chunk-727SXJPM-D4mt-WN2.js} +1 -1
  14. package/server/public/assets/{chunk-AQP2D5EJ-Cb8WTwxH.js → chunk-AQP2D5EJ-DKi5ScG8.js} +1 -1
  15. package/server/public/assets/chunk-BO2N2NFS-CaAvtgHW.js +103 -0
  16. package/server/public/assets/{chunk-BSJP7CBP-DNZJE3LD.js → chunk-BSJP7CBP-B1PFLITi.js} +1 -1
  17. package/server/public/assets/{chunk-CSCIHK7Q-Cd19qyMq.js → chunk-CSCIHK7Q-BnyS1BnJ.js} +1 -1
  18. package/server/public/assets/{chunk-FMBD7UC4-BCMOtjng.js → chunk-FMBD7UC4-BB-NMiJi.js} +1 -1
  19. package/server/public/assets/{chunk-K2UTITRG-BDx74R_n.js → chunk-K2UTITRG-CopEAH22.js} +3 -3
  20. package/server/public/assets/{chunk-KSCS5N6A-C9ZcXuPw.js → chunk-KSCS5N6A-BF9pAvxk.js} +1 -1
  21. package/server/public/assets/{chunk-L5ZTLDWV-DERZu8R6.js → chunk-L5ZTLDWV-CSCc1dpF.js} +1 -1
  22. package/server/public/assets/{chunk-LZXEDZCA-r8NSoc_I.js → chunk-LZXEDZCA-AFwhyAh-.js} +2 -2
  23. package/server/public/assets/{chunk-ND2GUHAM-U8N4hXOQ.js → chunk-ND2GUHAM-C63t7vfP.js} +1 -1
  24. package/server/public/assets/{chunk-NZK2D7GU-Cdikaqy1.js → chunk-NZK2D7GU-D1aXVmQS.js} +1 -1
  25. package/server/public/assets/{chunk-O5CBEL6O-NPUwuba7.js → chunk-O5CBEL6O-U50Oge0P.js} +1 -1
  26. package/server/public/assets/chunk-QZHKN3VN-DTNr_gzK.js +1 -0
  27. package/server/public/assets/chunk-WU5MYG2G-CI9Sd9Ej.js +1 -0
  28. package/server/public/assets/{chunk-XPW4576I-D4sOPQ12.js → chunk-XPW4576I-TDS4g8F-.js} +1 -1
  29. package/server/public/assets/classDiagram-4FO5ZUOK-ZZslPdIZ.js +1 -0
  30. package/server/public/assets/classDiagram-v2-Q7XG4LA2-ClxLOE6-.js +1 -0
  31. package/server/public/assets/{cose-bilkent-S5V4N54A-DbIsCp9l.js → cose-bilkent-S5V4N54A-CXK4TeG6.js} +1 -1
  32. package/server/public/assets/{dagre-BM42HDAG-BjoYNKH1.js → dagre-BM42HDAG-CekTT-9t.js} +1 -1
  33. package/server/public/assets/{diagram-2AECGRRQ-BQDDtbjI.js → diagram-2AECGRRQ-C-1uR13O.js} +1 -1
  34. package/server/public/assets/{diagram-5GNKFQAL-B5QGvpdq.js → diagram-5GNKFQAL-B-7wXipw.js} +1 -1
  35. package/server/public/assets/{diagram-KO2AKTUF-BPB4GnYb.js → diagram-KO2AKTUF-BkDyAgsg.js} +1 -1
  36. package/server/public/assets/{diagram-LMA3HP47-ChM6kUb7.js → diagram-LMA3HP47-D9jVbxR4.js} +1 -1
  37. package/server/public/assets/{diagram-OG6HWLK6-CILjxFV5.js → diagram-OG6HWLK6-C4flU2X9.js} +1 -1
  38. package/server/public/assets/{dist-BBtIkNyE.js → dist-DXAnG2e9.js} +1 -1
  39. package/server/public/assets/{dist-WopvNJhW.js → dist-DygIHsen.js} +5 -5
  40. package/server/public/assets/{erDiagram-TEJ5UH35-CHXlL8Uu.js → erDiagram-TEJ5UH35-BCzAAnUJ.js} +1 -1
  41. package/server/public/assets/{flowDiagram-I6XJVG4X-ChzzyfU2.js → flowDiagram-I6XJVG4X-WjKltgsz.js} +1 -1
  42. package/server/public/assets/ganttDiagram-6RSMTGT7-BJY25etK.js +292 -0
  43. package/server/public/assets/{gitGraphDiagram-PVQCEYII-Ba1IlQOc.js → gitGraphDiagram-PVQCEYII-DQNkTLKB.js} +1 -1
  44. package/server/public/assets/{highlighted-body-OFNGDK62-BTG45eDk.js → highlighted-body-OFNGDK62-DIEQLSG_.js} +1 -1
  45. package/server/public/assets/image-GAAHSSAO-rafcvWHh.js +1 -0
  46. package/server/public/assets/index-B-m-cSFg.css +1 -0
  47. package/server/public/assets/index-BsKNi2y5.js +796 -0
  48. package/server/public/assets/{infoDiagram-5YYISTIA-Du9oCbUR.js → infoDiagram-5YYISTIA-kt8p6J3W.js} +1 -1
  49. package/server/public/assets/{ishikawaDiagram-YF4QCWOH-BQBblkcl.js → ishikawaDiagram-YF4QCWOH-CXB66ltc.js} +1 -1
  50. package/server/public/assets/{journeyDiagram-JHISSGLW-DalMBcZT.js → journeyDiagram-JHISSGLW-CSevbeC_.js} +1 -1
  51. package/server/public/assets/{kanban-definition-UN3LZRKU-ZZJcEe1c.js → kanban-definition-UN3LZRKU-f3SXWdY0.js} +1 -1
  52. package/server/public/assets/{line-DSWSgYSV.js → line-D5OFHSZd.js} +1 -1
  53. package/server/public/assets/linear-CRa2o9KP.js +1 -0
  54. package/server/public/assets/mermaid-GHXKKRXX-uIkd-n0c.js +1 -0
  55. package/server/public/assets/{mermaid-parser.core-Yy_VTKKl.js → mermaid-parser.core-Cww26sHC.js} +1 -1
  56. package/server/public/assets/{mindmap-definition-RKZ34NQL-CZN2bKCU.js → mindmap-definition-RKZ34NQL-DCCvz6n-.js} +1 -1
  57. package/server/public/assets/{pieDiagram-4H26LBE5-C9XG5bKT.js → pieDiagram-4H26LBE5-Cb6OrK_c.js} +1 -1
  58. package/server/public/assets/{prod-BjCGQWZg.js → prod-DWpuiL-y.js} +5 -5
  59. package/server/public/assets/{quadrantDiagram-W4KKPZXB-BqyPPpJS.js → quadrantDiagram-W4KKPZXB-DhZzzcAO.js} +3 -3
  60. package/server/public/assets/{requirementDiagram-4Y6WPE33-qEFp8qJ8.js → requirementDiagram-4Y6WPE33-B5jvIMD7.js} +1 -1
  61. package/server/public/assets/{sankeyDiagram-5OEKKPKP-DOiWA53d.js → sankeyDiagram-5OEKKPKP-CVe6lE5A.js} +1 -1
  62. package/server/public/assets/{sequenceDiagram-3UESZ5HK-DtNkSqZR.js → sequenceDiagram-3UESZ5HK-CM_I9wrD.js} +1 -1
  63. package/server/public/assets/{src-Csh0f24o.js → src-BYanRXvC.js} +1 -1
  64. package/server/public/assets/src-CdPLAdYJ.js +1 -0
  65. package/server/public/assets/{stateDiagram-AJRCARHV-DgaB8R7z.js → stateDiagram-AJRCARHV-C6Dqck84.js} +1 -1
  66. package/server/public/assets/stateDiagram-v2-BHNVJYJU-Bz3dgE_B.js +1 -0
  67. package/server/public/assets/{timeline-definition-PNZ67QCA-vhc6Bdap.js → timeline-definition-PNZ67QCA-B7tFs4Cq.js} +1 -1
  68. package/server/public/assets/{vennDiagram-CIIHVFJN-D7yEMTpU.js → vennDiagram-CIIHVFJN-DH3LC-SZ.js} +1 -1
  69. package/server/public/assets/{wardleyDiagram-YWT4CUSO-DAFN50tK.js → wardleyDiagram-YWT4CUSO-CeJXk7lo.js} +1 -1
  70. package/server/public/assets/{xychartDiagram-2RQKCTM6-BkfiVkrb.js → xychartDiagram-2RQKCTM6-zukUz5pI.js} +1 -1
  71. package/server/public/favicon.svg +4 -4
  72. package/server/public/index.html +5 -5
  73. package/server/server.js +2075 -591
  74. package/server/public/assets/channel-cOUdzmRM.js +0 -1
  75. package/server/public/assets/chunk-55IACEB6-_2lFlbAe.js +0 -1
  76. package/server/public/assets/chunk-BO2N2NFS-DT2hpoe9.js +0 -103
  77. package/server/public/assets/chunk-QZHKN3VN-zpNB_C8l.js +0 -1
  78. package/server/public/assets/chunk-WU5MYG2G-DDr09vEJ.js +0 -1
  79. package/server/public/assets/classDiagram-4FO5ZUOK-ezwkz8ms.js +0 -1
  80. package/server/public/assets/classDiagram-v2-Q7XG4LA2-DAphqbn4.js +0 -1
  81. package/server/public/assets/ganttDiagram-6RSMTGT7-CByisuCB.js +0 -292
  82. package/server/public/assets/image-GAAHSSAO-CCe2yzQe.js +0 -1
  83. package/server/public/assets/index-etdSnOxp.js +0 -818
  84. package/server/public/assets/index-qrDOFwsw.css +0 -1
  85. package/server/public/assets/linear-BlUaWFF8.js +0 -1
  86. package/server/public/assets/mermaid-GHXKKRXX-7G86hyh-.js +0 -1
  87. package/server/public/assets/src-CsT-wIyx.js +0 -1
  88. package/server/public/assets/stateDiagram-v2-BHNVJYJU-52jRo40M.js +0 -1
  89. /package/server/public/assets/{html-void-elements-QR3T3anA.js → html-void-elements-DIyBxxO2.js} +0 -0
  90. /package/server/public/assets/{preload-helper-Dd-HcVz_.js → preload-helper-DzyYoeor.js} +0 -0
package/server/server.js CHANGED
@@ -1,103 +1,101 @@
1
1
  import { a as __toCommonJS, i as __require, o as __toESM, t as __commonJSMin } from "./rolldown-runtime-DNCzzKnI.js";
2
- import { C as init_paths, D as sessionFilePath, E as resolveOhmypiExtensionPath, S as getOverdeckHome, T as packageRoot, _ as SYNC_SOURCES, l as LOGS_DIR, o as CLAUDE_DIR, t as AGENTS_DIR, u as OVERDECK_HOME, w as isDevMode, x as getLegacyHome, y as encodeClaudeProjectDir } from "./paths-tknG80xs.js";
3
- import { $ as init_model_capabilities, A as UnknownModelError, C as validateTieredExecutionConfig, E as init_harness_policy, F as getProviderForModelSync, G as DEFAULT_WORKHORSES, H as defaultBackgroundAiFeatures, I as init_providers, J as MODEL_CAPABILITIES, K as computeModelOrigin, L as piProviderForModel, M as getBuiltInDefaultHarness, N as getKimiAnthropicBaseUrl, Q as hasModelCapabilitySync, S as resolveTieredExecutionEnabled, U as init_registry, W as DEFAULT_ROLES, Y as MODEL_DEPRECATIONS, Z as getModelEffortLevelsSync, _ as mergeTtsDaemonConfigs, at as require_dist$1, b as TieredExecutionConfigError, bt as init_errors, d as loadConfigSync, et as modelSupportsImagesSync, f as updateConversationsConfig, ft as GitHubApiError, g as getDefaultTtsDaemonConfig, gt as ProcessSpawnError, h as mergeConfigs, i as getConversationsConfig, it as ROLE_EFFORTS, l as isTldrEnabledSync, m as jsYaml, n as clearConfigCache, nt as COMPLIANCE_MODES, o as getGlobalConfigPath, p as init_js_yaml, q as resolveModel, r as getConversationSearchConfigSync, rt as PARENT_MODEL_REF, s as isClaudeCodeChannelsEnabled, t as init_config_yaml, tt as resolveModelIdSync, u as loadConfigNoMigration, ut as FsError, vt as TrackerError, w as canUseHarnessSync, x as init_tier_table } from "./config-yaml-B5z7J6BP.js";
4
- import { _ as getDashboardIdentity, c as startUatTrainReconciler, d as openFlywheelRunReportForDashboard, f as pauseFlywheelRunForDashboard, g as runDashboardDbJob, h as startFlywheelRunForDashboard, l as stopUatTrainReconciler, m as resumeFlywheelRunForDashboard, p as readCurrentFlywheelStatusForDashboard, u as abortFlywheelRunForDashboard, v as readHostDashboardApiPort, y as shouldRefuseHostDashboardPort } from "./uat-train-BX0_9wAC.js";
5
- import { A as jsonStringResponse, C as isTrustedOriginForHost, D as checkCodexAuthStatus, E as validateOriginHeaders, O as httpHandler, T as validateOrigin$1, _ as resolveClaudeSessionId, a as agentsRouteLayer, b as resolvePiSessionPath, c as invalidateAgentsCache, d as startSmeeProcessSync, f as resolveAgentGitInfo, g as resolveAgentHarness, h as readLauncherPinnedSessionId, k as jsonResponse, l as getSystemHealthSnapshot, o as saveAgentStateAndEmitEvent, p as resolveConversationGitInfo, s as saveAgentStateAndEmitEventProgram, t as specialistsRouteLayer, u as isSmeeConfiguredSync, v as resolveCodexRolloutPath, x as getHeaderFromMap, y as resolveJsonlPath } from "./specialists-lL_oaMMz.js";
6
- import { a as init_activity_logger, i as emitDashboardLifecycleSync, n as emitActivityEntrySync, r as emitActivityTtsSync } from "./activity-logger-C_Hsfouw.js";
7
- import { A as init_paths$1, O as OVERDECK_MIGRATION_PATH, d as getOverdeckDatabaseSync, f as init_infra, i as DbLive, k as getOverdeckDatabasePath, u as TmuxLive } from "./infra-BnUODQb7.js";
8
- import { n as openDatabase, t as init_driver } from "./driver-CBobl_fb.js";
9
- import { g as getIssueWorkspacePath, v as init_record } from "./record-BRRVhnOm.js";
10
- import { a as parseIssueIdSync, n as extractPrefixSync, r as init_issue_id } from "./issue-id-CtudqaOv.js";
11
- import { A as resolveProjectFromIssueSync, D as registerProjectSync, P as setProjectAutoMergeDefaultSync, S as listProjectsSync, f as getProjectSync, k as resolveProjectFromIssue, l as getIssuePrefix, r as extractTeamPrefix, s as findProjectByTeamSync, v as init_projects, x as listProjects } from "./projects-BWA18sW9.js";
12
- import { E as listPaneValues, F as sendKeysAsync, G as buildChildEnvSync, H as init_ui_theme, I as sendRawKeystroke, K as buildChildEnvWithoutTmuxSync, L as sessionExists, M as resizeWindow, O as listSessionNames, P as sendKeys, S as isHarnessProcessAlive, U as setUiTheme, W as BLANKED_PROVIDER_ENV, b as getWindowDimensions, c as ensureManagedTmuxContextOnce, d as exactPaneTarget, i as capturePane, k as listSessions, n as buildTmuxArgs, o as createSession, p as findManagedServerPidSync, q as init_child_env, w as killSession, x as init_tmux, z as setOption } from "./tmux-DwRDpZ6x.js";
13
- import { a as init_event_store, i as initEventStore, n as getEventStore, r as getSharedDb } from "./event-store-BMvjb-ek.js";
14
- import { _ as FlywheelRunId, a as getHarnessBehavior, b as DomainEvent, c as FlywheelStats, g as WS_METHODS, h as PanRpcGroup, m as PanRpcError, o as ContextLayerSaveRequest, s as ContextPreviewRequest, t as init_src, v as FlywheelStatus, x as MemoryObservation, y as EDITORS } from "./src-CEKNqdTd.js";
15
- import { a as fixStuckCommentedReviews, b as setReviewStatusSync, c as getReviewStatusSync, d as loadReviewStatuses, l as init_review_status, n as clearReviewStatus, o as fixStuckReadyForMerge, r as clearStuckMergeStatuses, s as getReviewStatus, u as loadReadyForMergeFlags, y as setReviewStatus } from "./review-status-Sdng4KbL.js";
16
- import { a as EventStoreService, c as ReadModelService, d as init_read_model, f as toAgentResolution, i as init_issue_lifecycle, l as ReadModelServiceLive, m as toRole, n as IssueLifecycleLive, o as EventStoreServiceLive, p as toAgentStatus, s as init_domain_services, t as IssueLifecycle } from "./issue-lifecycle-BQ7MbVQW.js";
17
- import { d as init_agents, i as recordBriefingSessionStart, n as ensureSessionContextBriefingFile, p as listAllAgentsSync, r as init_briefing_freshness, s as AgentsResolverLive, t as appendFreshBriefingUpdate } from "./briefing-freshness-to38wymF.js";
18
- import { i as init_paths$2, l as resolveObservationsFile, r as ensureParentDir, s as resolveIssueMemoryRoot, t as assertMemorySafeSegment } from "./paths-Dsd_dtCx.js";
19
- import { n as listFeatureRegistryEntries, t as init_feature_registry_storage } from "./feature-registry-storage-DCtE46iD.js";
20
- import { a as diffAgainstMainFiles, c as diffFilesAgainstHead, d as findCommitAtTime, i as diffAgainstMain, l as diffPatchFilesAgainstHead, m as listCheckpoints, o as diffCheckpointFiles, p as init_checkpoint_manager, s as diffCheckpoints, t as captureCheckpoint, u as diffPatchSinceCommit } from "./checkpoint-manager-CKcDJjLT.js";
21
- import { $ as init_concurrency, An as saveAgentStateSync, Bn as init_pty_token, D as messageAgent, Ft as getProviderAuthMode, G as listRunningAgents, Gt as getClaudeAuthStatus, Hn as clearReadySignal, Ht as piFifoPaths, Jn as getAgentRuntimeState, K as listRunningAgentsSync, Kt as init_claude_auth, Mt as getAgentRuntimeBaseCommand, O as init_operator_interventions, Rn as init_agent_record_sync, Rt as injectPiConversationMemory, St as writeBridgeTokenSync, Ut as init_ohmypi_fifo, Vn as writePtyToken, Vt as init_pi_fifo, W as listAgentStates, Wn as normalizeAgentId, Wt as ohmypiFifoPaths, Xt as getProviderEnvForModel, Yn as getAgentRuntimeStateSync, Z as emergencyBrake, Zt as getProviderExportsForModel, _n as getAgentStateFilePath, bt as probeTranscriptSince, c as init_tier_supervisor, ct as init_config, d as supervisorAgentId, dt as saveCloisterConfigSync, ft as shouldAutoStart, gn as getAgentState, hn as getAgentDir, k as operatorInterventionEvent, lt as loadCloisterConfig, nn as init_openai_auth, on as dismissDevChannelsDialog, pt as deliverAgentMessage, qn as waitForReadySignal, s as deliverCommitForReview, sn as writeChannelsBridgeMcpConfig, t as init_agents$1, tn as getOpenAIAuthStatus, ut as loadCloisterConfigSync, vn as getAgentStateSync, vt as captureTranscriptUserRecordSnapshot, xt as init_bridge_token, yt as init_transcript_landing, zn as readAgentHarnessModelRecordSync } from "./agents-B6tWmcXf.js";
22
- import { t as init_behavior } from "./behavior-RdPVwq8Z.js";
23
- import { a as getCostForIssueAggregateSync, c as getDailyTrendsSync, d as init_cost_sync, l as getModelRollupSync, n as getBackgroundCostBySourceSync, r as getCavemanExperimentDataSync, s as getCostsByIssueSync, u as getTodayCostSync } from "./cost-sync-DYokw49s.js";
24
- import { c as getAgentsNeedingAttention, d as CostResolverLive, n as readCloisterStateFile, o as generateHealthSummary, s as getAgentHealth, t as getCloisterService, u as CostResolver } from "./service-BCLgzZEy.js";
25
- import { n as AgentStateServiceLive } from "./agent-state-service-ATZbN2LT.js";
26
- import { a as init_tracker_config, i as initTrackerConfigCache, n as getLinearApiKey, r as getRallyConfig, t as getGitHubConfig } from "./tracker-config-JlYBFawD.js";
27
- import { d as init_typed_errors, l as WorkspaceCreateError, n as AgentStartError, r as BeadsNotInitialized, s as TrackerApiError, t as AgentAlreadyRunning, u as WorkspaceNotFound } from "./typed-errors-BQLS2-a2.js";
28
- import { i as init_linear_client, r as LinearClientOptionalLive, t as LinearClient } from "./linear-client-DtBd7iee.js";
29
- import { i as init_github_client, r as GitHubClientOptionalLive, t as GitHubClient } from "./github-client-BLwJoTrW.js";
30
- import { i as init_rally_client, r as RallyClientOptionalLive, t as RallyClient } from "./rally-client-BqrgQ0Pu.js";
31
- import { a as resolveTrackerTypeSync, i as resolveGitHubIssueSync, n as init_tracker_utils } from "./tracker-utils-DZaFGJUO.js";
32
- import { a as CacheService, i as startSharedIssueService, n as init_issue_service_singleton, o as init_cache_service, r as issue_service_singleton_exports, s as parseIntegerHeader, t as getSharedIssueService } from "./issue-service-singleton-B1aiia7z.js";
33
- import { i as ensureInternalTokenSync, o as getInternalTokenSync, s as init_internal_token, t as INTERNAL_TOKEN_HEADER } from "./internal-token-ByEURFQq.js";
34
- import { a as setPipelineHandlerSync, t as init_pipeline_notifier } from "./pipeline-notifier-BpZ4S0Hi.js";
35
- import { c as init_review_status_sync, i as getMergeBlockerReconcileCandidates } from "./review-status-sync-C4eeN5DR.js";
36
- import { C as transitionToMerging, _ as markFailed, c as isFlywheelGloballyPaused, d as listDuePendingAutoMerges, f as listProblemAutoMerges, g as markBlocked, n as getActionableAutoMerge, o as init_merge_sync, t as cancelPending, u as listActiveAutoMerges, v as markMerged, x as scheduleAutoMergeWithResult, y as requeueToPending } from "./merge-sync-B79eYXWq.js";
37
- import { E as writeSpecForIssue, L as asPanSpecDocument, M as PAN_CONTINUE_FILENAME, N as PAN_DIRNAME, R as init_types$1, S as init_specs, T as writeSpec, _ as init_types, a as findWorkspaceDraftPlan, b as findSpecByIssue, c as readPlan, d as readWorkspacePlan, f as readWorkspacePlanSync, o as init_io, r as findPlan, s as isPlanningComplete, v as subItemsOf } from "./io-Bh8VelYD.js";
38
- import { C as restartCliproxy, S as readPidFile, T as stopCliproxy, b as isCliproxyInstalled, d as getCliproxyAuthDir, o as bridgeCodexAuthToCliproxy, v as init_cliproxy, w as startCliproxy, x as isCliproxyRunning, y as installCliproxy } from "./cliproxy-BY-a7oH8.js";
39
- import { f as init_config$1, m as loadConfigSync$1, s as getDashboardApiUrlSync } from "./config-D9ULtCLv.js";
40
- import { n as require_auth_errors, r as require_token_error, t as require_token_util } from "./token-util-BEuuoN7z.js";
41
- import { $ as updateConversationTitle, A as listArchivedConversationsWithEnrichment, C as getConversationLedgerCosts, D as incrementForkRetryCount, E as importLegacyConversation, F as markConversationRunning, G as setConversationModel, H as setConversationClaudeSessionId, J as setImportedConversationLinks, K as setFavorite, M as listFavoritedIds, N as markConversationActive, O as init_conversations, P as markConversationEnded, Q as updateConversationForkFallbackReason, R as recordConversationHandoff, S as getConversationByTmuxSession, T as hasOtherActiveConversationOnTmuxSession, U as setConversationEffort, V as setClearedToConvId, W as setConversationHarness, X as updateConversationCost, Y as unarchiveConversation, Z as updateConversationDeliveryMethod, _ as canReplaceTitle, _t as init_discovered_sessions, b as getConversationById, et as updateForkStatus, g as backfillConversationModel, h as archiveConversation, j as listConversations, k as isAgentConversationName, mt as getDiscoveredSessionBySessionId, nt as updateSpawnError, q as setForkRequest, tt as updateLastAttached, v as createConversation, w as getStuckForks, x as getConversationByName, y as getConversationByClaudeSessionId, z as removeFavorite } from "./conversations-CSxhAzME.js";
42
- import { _ as init_codex_conversation_parser, a as contextUsageFromParseResult, c as parseEntireConversation, d as init_pi_conversation_parser, f as isPiSessionFile, g as parseOhmypiConversationMessages, h as isOhmypiSessionFile, i as computeContextUsage, l as parseFromLastCompactBoundary, m as init_ohmypi_conversation_parser, n as gateSnapshotEmission, o as summarizeConversationActivity, p as parsePiConversationMessages, r as watchConversation, s as parseConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages } from "./conversation-service-Dxivfe0w.js";
43
- import { n as isBackgroundFeatureEnabled, t as init_features } from "./features-D6BM-oE5.js";
44
- import { i as init_settings, r as getMemoryWorkerConcurrency, t as areMemoryObservationsEnabled } from "./settings-ncJepdos.js";
45
- import { i as updateMemoryHealth, n as extractWithProviderPolicy, r as init_health, t as init_providers$1 } from "./providers-DitjC5u4.js";
46
- import { n as init_launcher_generator, t as generateLauncherScriptSync } from "./launcher-generator-DrSU75_W.js";
47
- import { a as init_claude_permissions, o as resolvePermissionModeSync, r as getClaudePermissionFlagsStringSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-CfMr0eIT.js";
48
- import { a as globalContextFile$1, d as workspaceContextFile, l as projectContextFile, n as codexGlobalContextFile, o as init_layers, r as ensureProjectLayer, s as piGlobalContextFile } from "./layers-B1dMbFu0.js";
49
- import { l as init_codex, s as findRolloutPath } from "./codex-BcSHb3Oi.js";
50
- import { C as setMergeTrainEnabled, S as setFlywheelRequireUatBeforeMerge, b as setFlywheelAutoPickupBacklog, c as isCloisterSpawnsPausedSync, d as isFlywheelAutoPickupBacklog, f as isFlywheelGloballyPaused$1, g as setCloisterSpawnsPausedSync, h as setBootReconciliationDecision, m as isMergeTrainEnabled, o as getSetting, p as isFlywheelRequireUatBeforeMerge, r as getBootReconciliationState, s as init_control_settings, u as isDeaconGloballyPausedSync, v as setDeaconGloballyPausedSync, w as setSetting } from "./control-settings-CcllfQYK.js";
51
- import { c as init_docker_stats } from "./stack-health-BDjtNZ9V.js";
52
- import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-DzutoEqt.js";
53
- import { i as checkPrdGateSync, t as init_pan_dir } from "./pan-dir-BwlWqB_Z.js";
54
- import { a as init_lifecycle_io, t as appendContinueSessionEntryForIssue } from "./lifecycle-io-wMIX4D4p.js";
55
- import { r as init_swarm_readiness, t as analyzeSwarmReadiness } from "./swarm-readiness-B2JKxIBj.js";
56
- import { At as getAgentJsonlMtime, B as createInFlightGuard, Dt as getRuntimeForAgent, F as setAgentStatusChangedNotifier, Ft as detectAwaitingInputForAgent, I as setAgentStoppedNotifier, It as detectAwaitingInputFromPaneSync, J as startBootReconciliation, K as isBootReconciliationCandidate, L as setMergeReadyNotifier, O as loadConfig, Ot as computeAgentEnrichment, Pt as scanPendingInputsPromise, Rt as parseCodexApprovalPrompt, X as resolveBootGates, Y as formatBootGateState, k as loadState, kt as countPendingAskUserQuestionsForAgent, n as assessDeaconPatrolFreshness, q as listBootReconciliationCandidates, st as withConcurrencyLimit, t as applyBootReconciliationDecision, x as getDeaconLogs } from "./deacon-Da1gKxDd.js";
57
- import { i as CostThresholdError, l as embed, s as parseRelativeTime } from "./scanner-CzCiMLJf.js";
58
- import { n as deduplicateEventsSync, o as readEventsSync, s as tailEventsSync } from "./events-B3ytKOJM.js";
59
- import { n as recordBackgroundAiCost, t as init_cost } from "./cost-CGo8texI.js";
60
- import { S as writeLatestFlywheelStatus, _ as readCurrentLatestFlywheelStatus, b as subscribeLatestFlywheelStatus, f as getFlywheelRunDetail, h as listFlywheelRuns, l as buildClassifyLookups, m as isFlywheelRunId, y as resolveLiveFlywheelRunId } from "./flywheel-By5V03uu.js";
61
- import { C as rejectUnauthorizedDashboardSessionMintRequest, S as rejectUnauthorizedDashboardRequest, T as getWorkspacePathForIssue, _ as rebuildCacheSync, b as dashboardSessionCookieHeader, c as resumeQueuedMerges, d as resolveIssueHeadlineCost, f as syncWalFromAllProjects, g as getCostsForIssueSync, h as migrateAllSessionsSync, l as processPendingFeedbackDeliveries, m as reconcilePiTranscripts, p as reconcile, t as workspacesRouteLayer, u as getCachedRunningAgents, v as syncCacheSync, w as rejectUnsafeDashboardMutationRequest, x as hasDashboardInternalToken, y as dashboardCsrfToken } from "./workspaces-JkQdW0Rd.js";
62
- import { l as detectPlatform, u as init_platform } from "./workspace-manager-DA59Z70a.js";
63
- import { O as init_specialists, b as getReviewerSessionName } from "./specialists-DALlYmo_.js";
64
- import { t as withBdMutex } from "./bd-mutex-DXCIKL87.js";
65
- import { n as findPrdAtStatusSync, r as init_prd_locations } from "./prd-locations-D9J9Dei3.js";
66
- import { a as getShadowState, s as init_shadow_state } from "./shadow-state-bugG0OUd.js";
67
- import { c as getMergeBackendStatus, d as init_github_app, f as isGitHubAppConfigured, u as getPullRequestState } from "./github-app-DpbY6nKj.js";
68
- import { t as clearIssueClosedCache } from "./issue-closed-BEVoKUiE.js";
69
- import { a as writeFeedbackFile, i as init_feedback_writer } from "./feedback-writer-DUcHN5E1.js";
70
- import { n as parseSequenceMd, r as writeSequenceMd, t as init_sequence_io } from "./sequence-io-DzHGjEQG.js";
71
- import { C as selectNeedsPlanning, S as normalizeGate, _ as computeCohort, b as computeStats, d as OBJECTION_LABEL, f as PARKED_LABEL, g as classifyIssue, h as VETOED_LABEL, m as RELEASED_LABEL, p as READY_LABEL, u as BLOCKS_MAIN_LABEL, v as computeEpicGroups, x as computeWaves, y as computeLanes } from "./flywheel-merge-order-DS83DYov.js";
72
- import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-BWP0Gghc.js";
73
- import { i as resolveAutoSpawnOnFinalize, o as spawnPlanningSession } from "./spawn-planning-session-DyKxcVGi.js";
74
- import { _ as sendToRemoteAgent, a as getRemoteAgentOutput, n as checkRemoteSpendCap, o as init_remote_agents, p as loadRemoteAgentState, u as killRemoteAgent, v as spawnRemoteAgent } from "./remote-agents-DHPxoyuA.js";
75
- import { r as spawnInspectAgent } from "./inspect-agent-CCGhgZwF.js";
76
- import { a as init_pull_requests, c as getIssuePrTabCacheGeneration, d as githubPrLookupSource, f as init_github_pr_lookup, i as fetchIssuePullRequestDiff, l as init_pr_tab_cache, n as fetchIssuePullRequest, o as bumpIssuePrTabCacheGeneration, p as lookupPullRequestNumberForBranch, r as fetchIssuePullRequestDetails, s as getCachedIssuePrTabResponse, t as fetchIssueCheckRuns, u as setCachedIssuePrTabResponse } from "./pull-requests-R3t6SZ7b.js";
77
- import { t as enrichReviewStatus } from "./review-status-enrichment-Ddj7NTYe.js";
78
- import { i as runMemoryFtsTransaction, n as init_fts_db, r as runMemoryFtsStatement, t as closeMemoryFtsDatabases } from "./fts-db-BkKcBcY2.js";
79
- import { c as init_pending, l as maybeTriggerStatusRollup, o as readRecentObservations, r as init_rollup, u as writePendingTurn } from "./rollup-DjuYNjJA.js";
80
- import { n as getTldrDaemonServiceSync, r as init_tldr_daemon } from "./tldr-daemon-BUdzAlOX.js";
81
- import { _ as init_git_activity, v as listGitOperationsSync } from "./merge-agent-NmPcMe48.js";
82
- import { t as getAgentCommandSync } from "./settings-CHBd7dXM.js";
83
- import { n as init_review_agent, r as isExtendedReviewEnabled } from "./review-agent-Fy6N7Kgq.js";
84
- import { r as isRemoteAvailable, t as createFlyProviderFromConfig } from "./remote-D9u5oAKU.js";
85
- import { n as init_claude_settings_overlay, t as detectProviderEnvConflicts } from "./claude-settings-overlay-CwvL66eF.js";
86
- import { a as init_query_expansion, i as clearQueryExpansionCache, n as init_injection, o as buildMatchQuery, r as injectPromptTimeMemory, s as init_search } from "./injection-GRwakte4.js";
87
- import { _ as init_transcript_adapter, a as generateFallbackSummary, c as handoffPreconditionFallbackReason, d as prependFallbackFocus, f as requestHandoffFromAgent, g as getTranscriptAdapter, i as copySessionFromCompactBoundary, l as init_summary_fork, o as generateSummaryForFork, p as reserveSummaryForkSession, r as authorHandoffExternal, s as handoffFailureReason, u as logHandoffFallback } from "./summary-fork-B9qYAHl6.js";
88
- import { a as init_conversation_compaction, c as shouldInterceptManualCompact, n as compactConversationNative, o as isCompacting } from "./conversation-compaction-CKJRITVq.js";
89
- import { a as init_render, d as validateTemplate, i as hasManagedRegion, l as init_harness, o as userContentOutsideRegion, t as init_context_layers, u as renderForHarness } from "./context-layers-DGIdj1-C.js";
90
- import { r as parseArtifactRef } from "./forge-C7RLCYC_.js";
91
- import { t as getMergeBlockersPayload } from "./merge-blockers-B3Y4Sprl.js";
92
- import { a as spawnSequencerAgent, i as init_sequencer_agent, n as clearFinishedSequencerRun, r as getSequencerRunStatus, t as SEQUENCER_AGENT_ID } from "./sequencer-agent-BEVXaOSB.js";
93
- import "./supervisor-CNcM3RIc.js";
94
- import { i as listTranscriptCheckpoints, r as getTranscriptCheckpoint$1 } from "./checkpoints-0C9dS0-x.js";
2
+ import { $n as init_issue_id, Br as init_paths, C as resolveTieredExecutionEnabled, Cn as getIssuePrefix, Cr as CLAUDE_DIR, Dr as OVERDECK_HOME, E as canUseHarnessSync, En as getProjectSync, Er as LOGS_DIR, Et as getIssueWorkspacePath, F as getKimiAnthropicBaseUrl, Fn as listProjects, G as init_registry, Hr as packageRoot, In as listProjectsSync, Ir as encodeClaudeProjectDir, J as computeModelOrigin, Jn as setProjectAutoMergeDefaultSync, K as DEFAULT_ROLES, L as getProviderForModelSync, M as UnknownModelError, Mn as init_projects, O as init_harness_policy, Ot as init_record, P as getBuiltInDefaultHarness, Pr as SYNC_SOURCES, Q as MODEL_DEPRECATIONS, Qn as extractPrefixSync, R as init_providers, Rr as getLegacyHome, S as init_tier_table, T as validateTieredExecutionConfig, Un as resolveProjectFromIssue, Ur as resolveOhmypiExtensionPath, Vn as registerProjectSync, Vr as isDevMode, W as defaultBackgroundAiFeatures, Wn as resolveProjectFromIssueSync, Wr as sessionFilePath, X as resolveModel, Z as MODEL_CAPABILITIES, _ as getDefaultTtsDaemonConfig, _n as extractTeamPrefix, _r as init_errors, c as isClaudeCodeChannelsEnabled, ct as COMPLIANCE_MODES, d as loadConfigNoMigration, et as getModelEffortLevelsSync, f as loadConfigSync, g as mergeConfigs, h as jsYaml, hr as TrackerError, i as getConversationsConfig, it as resolveModelIdSync, lr as GitHubApiError, lt as PARENT_MODEL_REF, m as init_js_yaml, n as clearConfigCache, nn as flushAutoCommits, nt as init_model_capabilities, o as getGlobalConfigPath, p as updateConversationsConfig, pr as ProcessSpawnError, q as DEFAULT_WORKHORSES, r as getConversationSearchConfigSync, rn as init_auto_commit, rr as require_dist$1, rt as modelSupportsImagesSync, sr as FsError, t as init_config_yaml, tn as flushAllPendingAutoCommits, tr as parseIssueIdSync, tt as hasModelCapabilitySync, u as isTldrEnabledSync, ut as ROLE_EFFORTS, v as mergeTtsDaemonConfigs, vr as AGENTS_DIR, x as TieredExecutionConfigError, xn as findProjectByTeamSync, yn as findProjectByPathSync, z as piProviderForModel, zr as getOverdeckHome } from "./config-yaml-CFi3m2pp.js";
3
+ import { _ as getDashboardIdentity, c as startUatTrainReconciler, d as openFlywheelRunReportForDashboard, f as pauseFlywheelRunForDashboard, g as runDashboardDbJob, h as startFlywheelRunForDashboard, l as stopUatTrainReconciler, m as resumeFlywheelRunForDashboard, p as readCurrentFlywheelStatusForDashboard, u as abortFlywheelRunForDashboard, v as readHostDashboardApiPort, y as shouldRefuseHostDashboardPort } from "./uat-train-BK1II1Ug.js";
4
+ import { C as validateOrigin$1, D as jsonResponse, E as httpHandler, O as jsonStringResponse, T as checkCodexAuthStatus, _ as resolveJsonlPath, a as agentsRouteLayer, c as evaluateSpawnGuardrails, d as resolveConversationGitInfo, g as resolveCodexRolloutPath, h as resolveClaudeSessionId, l as invalidateAgentsCache, m as resolveAgentHarness, o as saveAgentStateAndEmitEvent, p as readLauncherPinnedSessionId, s as saveAgentStateAndEmitEventProgram, t as specialistsRouteLayer, u as resolveAgentGitInfo, v as resolvePiSessionPath, w as validateOriginHeaders, x as isTrustedOriginForHost, y as getHeaderFromMap } from "./specialists-IIjyUvYM.js";
5
+ import { a as init_activity_logger, i as emitDashboardLifecycleSync, n as emitActivityEntrySync, r as emitActivityTtsSync } from "./activity-logger-Dm95Ua07.js";
6
+ import { A as init_paths$1, O as OVERDECK_MIGRATION_PATH, d as getOverdeckDatabaseSync, f as init_infra, i as DbLive, k as getOverdeckDatabasePath, u as TmuxLive } from "./infra-NrG-t1jj.js";
7
+ import { n as openDatabase, t as init_driver } from "./driver-D-HBOQa_.js";
8
+ import { E as listPaneValues, F as sendKeysAsync, G as buildChildEnvSync, H as init_ui_theme, I as sendRawKeystroke, K as buildChildEnvWithoutTmuxSync, L as sessionExists, M as resizeWindow, O as listSessionNames, P as sendKeys, S as isHarnessProcessAlive, U as setUiTheme, W as BLANKED_PROVIDER_ENV, b as getWindowDimensions, c as ensureManagedTmuxContextOnce, d as exactPaneTarget, i as capturePane, k as listSessions, n as buildTmuxArgs, o as createSession, p as findManagedServerPidSync, q as init_child_env, w as killSession, x as init_tmux, z as setOption } from "./tmux-BvabkAC1.js";
9
+ import { a as init_event_store, i as initEventStore, n as getEventStore, r as getSharedDb } from "./event-store-BH0ESJtK.js";
10
+ import { _ as FlywheelRunId, a as getHarnessBehavior, b as DomainEvent, c as FlywheelStats, g as WS_METHODS, h as PanRpcGroup, m as PanRpcError, o as ContextLayerSaveRequest, s as ContextPreviewRequest, t as init_src, v as FlywheelStatus, x as MemoryObservation, y as EDITORS } from "./src-AXK4mjue.js";
11
+ import { a as fixStuckReadyForMerge, c as init_review_status, i as fixStuckCommentedReviews, l as loadReadyForMergeFlags, n as clearReviewStatus, o as getReviewStatus, r as clearStuckMergeStatuses, s as getReviewStatusSync, u as loadReviewStatuses, v as setReviewStatus, y as setReviewStatusSync } from "./review-status-_HZzpCJZ.js";
12
+ import { a as EventStoreService, c as ReadModelService, d as init_read_model, f as toAgentResolution, i as init_issue_lifecycle, l as ReadModelServiceLive, m as toRole, n as IssueLifecycleLive, o as EventStoreServiceLive, p as toAgentStatus, s as init_domain_services, t as IssueLifecycle } from "./issue-lifecycle-Dao_ID2V.js";
13
+ import { d as init_agents, i as recordBriefingSessionStart, n as ensureSessionContextBriefingFile, p as listAllAgentsSync, r as init_briefing_freshness, s as AgentsResolverLive, t as appendFreshBriefingUpdate } from "./briefing-freshness-B34zUtI6.js";
14
+ import { i as init_paths$2, l as resolveObservationsFile, r as ensureParentDir, s as resolveIssueMemoryRoot, t as assertMemorySafeSegment } from "./paths-CRDfPnBn.js";
15
+ import { n as listFeatureRegistryEntries, t as init_feature_registry_storage } from "./feature-registry-storage-C3iyyOnJ.js";
16
+ import { a as diffAgainstMain, c as diffCheckpoints, d as diffPatchSinceCommit, f as findCommitAtTime, h as listCheckpoints, l as diffFilesAgainstHead, m as init_checkpoint_manager, o as diffAgainstMainFiles, s as diffCheckpointFiles, t as captureCheckpoint, u as diffPatchFilesAgainstHead } from "./checkpoint-manager-DT3Lukzr.js";
17
+ import { An as getAgentDir, At as init_bridge_token, Dt as captureTranscriptUserRecordSnapshot, Gt as getAgentRuntimeBaseCommand, Jn as saveAgentStateSync, Jt as getProviderAuthMode, M as init_operator_interventions, Mn as getAgentState, N as operatorInterventionEvent, Nn as getAgentStateFilePath, Ot as init_transcript_landing, Pn as getAgentStateSync, Q as listRunningAgentsSync, St as deliverAgentMessage, X as listAgentStates, Z as listRunningAgents, Zt as injectPiConversationMemory, _t as init_config, an as init_claude_auth, ar as init_pty_token, at as init_concurrency, bn as writeChannelsBridgeMcpConfig, bt as saveCloisterConfigSync, c as init_tier_supervisor, en as init_pi_fifo, f as supervisorAgentId, fr as waitForReadySignal, hn as init_openai_auth, in as getClaudeAuthStatus, ir as readAgentHarnessModelRecordSync, j as messageAgent, jt as writeBridgeTokenSync, kt as probeTranscriptSince, ln as getProviderEnvForModel, lr as normalizeAgentId, mn as getOpenAIAuthStatus, mr as getAgentRuntimeStateSync, nn as init_ohmypi_fifo, or as writePtyToken, pr as getAgentRuntimeState, rn as ohmypiFifoPaths, rr as init_agent_record_sync, rt as emergencyBrake, s as deliverCommitForReview, sr as clearReadySignal, t as init_agents$1, tn as piFifoPaths, un as getProviderExportsForModel, vt as loadCloisterConfig, xt as shouldAutoStart, yn as dismissDevChannelsDialog, yt as loadCloisterConfigSync } from "./agents-hAslBDrt.js";
18
+ import { t as init_behavior } from "./behavior-ogCxWPtu.js";
19
+ import { a as getCostForIssueAggregateSync, c as getDailyTrendsSync, d as init_cost_sync, l as getModelRollupSync, n as getBackgroundCostBySourceSync, p as queryCostEventsSync, r as getCavemanExperimentDataSync, s as getCostsByIssueSync, u as getTodayCostSync } from "./cost-sync-B4NiigGa.js";
20
+ import { c as getAgentsNeedingAttention, d as CostResolverLive, f as CostWriter, l as CostDoorLive, n as readCloisterStateFile, o as generateHealthSummary, s as getAgentHealth, t as getCloisterService, u as CostResolver } from "./service-C1j2GOdD.js";
21
+ import { n as AgentStateServiceLive } from "./agent-state-service-ycEfmxxV.js";
22
+ import { a as init_tracker_config, i as initTrackerConfigCache, n as getLinearApiKey, r as getRallyConfig, t as getGitHubConfig } from "./tracker-config-CYfIfbH8.js";
23
+ import { d as init_typed_errors, l as WorkspaceCreateError, n as AgentStartError, r as BeadsNotInitialized, s as TrackerApiError, t as AgentAlreadyRunning, u as WorkspaceNotFound } from "./typed-errors-BEJhbjJg.js";
24
+ import { i as init_linear_client, r as LinearClientOptionalLive, t as LinearClient } from "./linear-client-CkeRcoyg.js";
25
+ import { i as init_github_client, r as GitHubClientOptionalLive, t as GitHubClient } from "./github-client-kAgT9udA.js";
26
+ import { i as init_rally_client, r as RallyClientOptionalLive, t as RallyClient } from "./rally-client-BgQ_z7av.js";
27
+ import { a as resolveTrackerTypeSync, i as resolveGitHubIssueSync, n as init_tracker_utils } from "./tracker-utils-BRZJC_bL.js";
28
+ import { a as CacheService, i as startSharedIssueService, n as init_issue_service_singleton, o as init_cache_service, r as issue_service_singleton_exports, s as parseIntegerHeader, t as getSharedIssueService } from "./issue-service-singleton-BwE8vBo8.js";
29
+ import { i as ensureInternalTokenSync, o as getInternalTokenSync, s as init_internal_token, t as INTERNAL_TOKEN_HEADER } from "./internal-token-CarQ-d2Z.js";
30
+ import { a as setPipelineHandlerSync, t as init_pipeline_notifier } from "./pipeline-notifier-Bu0V4jGJ.js";
31
+ import { c as init_review_status_sync, i as getMergeBlockerReconcileCandidates } from "./review-status-sync-C23_G71k.js";
32
+ import { a as PAN_DIRNAME, f as asPanSpecDocument, i as PAN_CONTINUE_FILENAME, p as init_types } from "./types-CE1m_daD.js";
33
+ import { C as transitionToMerging, _ as markFailed, c as isFlywheelGloballyPaused, d as listDuePendingAutoMerges, f as listProblemAutoMerges, g as markBlocked, n as getActionableAutoMerge, o as init_merge_sync, t as cancelPending, u as listActiveAutoMerges, v as markMerged, x as scheduleAutoMergeWithResult, y as requeueToPending } from "./merge-sync-Cayz43rC.js";
34
+ import { C as findSpecByIssue, O as writeSpec, T as init_specs, a as findWorkspaceDraftPlan, b as init_types$1, k as writeSpecForIssue, m as readWorkspacePlanSync, o as init_io, p as readWorkspacePlan, r as findPlan, s as isPlanningComplete, u as readPlan, x as subItemsOf } from "./io-CJp_69VI.js";
35
+ import { C as restartCliproxy, S as readPidFile, T as stopCliproxy, b as isCliproxyInstalled, d as getCliproxyAuthDir, o as bridgeCodexAuthToCliproxy, v as init_cliproxy, w as startCliproxy, x as isCliproxyRunning, y as installCliproxy } from "./cliproxy-C-j2CZKA.js";
36
+ import { f as init_config$1, m as loadConfigSync$1, s as getDashboardApiUrlSync } from "./config-D1FRRXcO.js";
37
+ import { n as require_auth_errors, r as require_token_error, t as require_token_util } from "./token-util-CROoF5ic.js";
38
+ import { $ as updateConversationForkFallbackReason, A as isAgentConversationName, B as removeFavorite, C as getConversationByTmuxSession, D as importLegacyConversation, E as hasOtherActiveConversationOnTmuxSession, F as markConversationEnded, G as setConversationHarness, H as setClearedToConvId, I as markConversationRunning, J as setForkRequest, K as setConversationModel, M as listConversations, N as listFavoritedIds, O as incrementForkRetryCount, P as markConversationActive, Q as updateConversationDeliveryMethod, S as getConversationByName, T as getStuckForks, U as setConversationClaudeSessionId, W as setConversationEffort, X as unarchiveConversation, Y as setImportedConversationLinks, Z as updateConversationCost, _ as canRefineTitle, b as getConversationByClaudeSessionId, et as updateConversationTitle, g as backfillConversationModel, gt as getDiscoveredSessionBySessionId, h as archiveConversation, j as listArchivedConversationsWithEnrichment, k as init_conversations, nt as updateLastAttached, q as setFavorite, rt as updateSpawnError, tt as updateForkStatus, v as canReplaceTitle, w as getConversationLedgerCosts, x as getConversationById, y as createConversation, yt as init_discovered_sessions, z as recordConversationHandoff } from "./conversations-Z-E71Ot6.js";
39
+ import { _ as init_codex_conversation_parser, a as contextUsageFromParseResult, c as parseEntireConversation, d as init_pi_conversation_parser, f as isPiSessionFile, g as parseOhmypiConversationMessages, h as isOhmypiSessionFile, i as computeContextUsage, l as parseFromLastCompactBoundary, m as init_ohmypi_conversation_parser, n as gateSnapshotEmission, o as summarizeConversationActivity, p as parsePiConversationMessages, r as watchConversation, s as parseConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages } from "./conversation-service-2pG8K2RQ.js";
40
+ import { n as isBackgroundFeatureEnabled, t as init_features } from "./features-CNGp14JQ.js";
41
+ import { i as init_settings, r as getMemoryWorkerConcurrency, t as areMemoryObservationsEnabled } from "./settings-V22-6LRB.js";
42
+ import { i as updateMemoryHealth, n as extractWithProviderPolicy, r as init_health, t as init_providers$1 } from "./providers-DS0lghjI.js";
43
+ import { a as init_launcher_generator, i as generateLauncherScriptSync, n as init_state_home, r as isStateMigrated } from "./state-home-Ct7Sc6c_.js";
44
+ import { a as init_claude_permissions, o as resolvePermissionModeSync, r as getClaudePermissionFlagsStringSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-yKBS-gdK.js";
45
+ import { a as globalContextFile$1, f as resolveProjectContextFile, h as workspaceContextFile, l as piGlobalContextFile, n as codexGlobalContextFile, o as init_layers, p as resolveWorkspaceContextFile, r as ensureProjectLayer } from "./layers-CTx4opMx.js";
46
+ import { l as init_codex, s as findRolloutPath } from "./codex-_9Yhw2cp.js";
47
+ import { C as setMergeTrainEnabled, S as setFlywheelRequireUatBeforeMerge, b as setFlywheelAutoPickupBacklog, c as isCloisterSpawnsPausedSync, d as isFlywheelAutoPickupBacklog, f as isFlywheelGloballyPaused$1, g as setCloisterSpawnsPausedSync, h as setBootReconciliationDecision, m as isMergeTrainEnabled, o as getSetting, p as isFlywheelRequireUatBeforeMerge, r as getBootReconciliationState, s as init_control_settings, u as isDeaconGloballyPausedSync, v as setDeaconGloballyPausedSync, w as setSetting } from "./control-settings-CDPBWsZY.js";
48
+ import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-Doo8mVky.js";
49
+ import { i as checkPrdGateSync, t as init_pan_dir } from "./pan-dir-O79s0Szv.js";
50
+ import { a as init_lifecycle_io, t as appendContinueSessionEntryForIssue } from "./lifecycle-io-Bays2Q9M.js";
51
+ import { r as init_swarm_readiness, t as analyzeSwarmReadiness } from "./swarm-readiness-1xj7dEqT.js";
52
+ import { $t as scanPendingInputsPromise, B as applyIssueBlocksMainLabel, Bt as formatUptime, F as setAgentStatusChangedNotifier, G as applyIssueVetoedLabel, Gt as isSmeeConfiguredSync, H as applyIssueParkedLabel, Ht as getCurrentDockerStats, I as setAgentStoppedNotifier, J as removeIssueParkedLabel, Jt as countPendingAskUserQuestionsForAgent, K as removeIssueBlocksMainLabel, Kt as startSmeeProcessSync, L as setMergeReadyNotifier, Lt as getRuntimeForAgent, O as loadConfig, Q as createInFlightGuard, U as applyIssueReadyLabel, V as applyIssueObjectionLabel, Vt as getContainerHistory, W as applyIssueReleasedLabel, Wt as getSystemHealthSnapshot, X as removeIssueReleasedLabel, Y as removeIssueReadyLabel, Yt as getAgentJsonlMtime, Z as removeIssueVetoedLabel, _t as withConcurrencyLimit, at as listBootReconciliationCandidates, en as detectAwaitingInputForAgent, it as isBootReconciliationCandidate, k as loadState, n as assessDeaconPatrolFreshness, ot as startBootReconciliation, q as removeIssueObjectionLabel, qt as computeAgentEnrichment, rn as parseCodexApprovalPrompt, t as applyBootReconciliationDecision, tn as detectAwaitingInputFromPaneSync, x as getDeaconLogs, zt as getResourceStacks } from "./deacon-580FfaDW.js";
53
+ import { i as CostThresholdError, l as embed, s as parseRelativeTime } from "./scanner-Dn9cqdgz.js";
54
+ import { n as deduplicateEventsSync, o as readEventsSync, s as tailEventsSync } from "./events-BILgSB1a.js";
55
+ import { n as recordBackgroundAiCost, t as init_cost } from "./cost-CDDkx0PI.js";
56
+ import { S as writeLatestFlywheelStatus, _ as readCurrentLatestFlywheelStatus, b as subscribeLatestFlywheelStatus, f as getFlywheelRunDetail, h as listFlywheelRuns, l as buildClassifyLookups, m as isFlywheelRunId, y as resolveLiveFlywheelRunId } from "./flywheel-CIiIHzVE.js";
57
+ import { C as hasDashboardInternalToken, D as getWorkspacePathForIssue, E as rejectUnsafeDashboardMutationRequest, S as dashboardSessionCookieHeader, T as rejectUnauthorizedDashboardSessionMintRequest, _ as syncCacheSync, b as resolveIssueHeadlineCost, d as reconcile, f as reconcilePiTranscripts, g as rebuildCacheSync, h as getCostsForIssueSync, l as resumeQueuedMerges, m as migrateAllSessionsSync, p as reclassifyUnknownCostEventsSync, t as workspacesRouteLayer, u as syncWalFromAllProjects, v as processPendingFeedbackDeliveries, w as rejectUnauthorizedDashboardRequest, x as dashboardCsrfToken, y as getCachedRunningAgents } from "./workspaces-CsMMlvt-.js";
58
+ import { l as detectPlatform, u as init_platform } from "./workspace-manager-VHSAXpA7.js";
59
+ import { O as init_specialists, b as getReviewerSessionName } from "./specialists-C1UrzX2F.js";
60
+ import { t as withBdMutex } from "./bd-mutex-CE6P_MlD.js";
61
+ import { c as findPrdAtStatusSync, l as init_prd_locations } from "./archive-planning-Bu8Pxfva.js";
62
+ import { a as getShadowState, s as init_shadow_state } from "./shadow-state-DWFtwdvF.js";
63
+ import { T as verifyAppCanMerge, d as getPullRequestState, f as init_github_app, l as getMergeBackendStatus, p as isGitHubAppConfigured } from "./github-app-D8_gUbWj.js";
64
+ import { n as init_issue_closed, t as clearIssueClosedCache } from "./issue-closed-ClkGWMyj.js";
65
+ import { a as writeFeedbackFile, i as init_feedback_writer } from "./feedback-writer-CUP2avGi.js";
66
+ import { a as init_boot_gates, i as formatBootGateState, o as resolveBootGates } from "./no-resume-mode-CKy2bwZL.js";
67
+ import { c as computeCohort, d as computeStats, f as computeWaves, h as selectNeedsPlanning, l as computeEpicGroups, m as normalizeGate, s as classifyIssue, u as computeLanes } from "./pickup-DRn-XNXS.js";
68
+ import { n as parseSequenceMd, r as writeSequenceMd, t as init_sequence_io } from "./sequence-io-DMwVfR77.js";
69
+ import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-DZSZ2rMu.js";
70
+ import { i as resolveAutoSpawnOnFinalize, o as spawnPlanningSession } from "./spawn-planning-session-DlYnE0aL.js";
71
+ import { d as killRemoteAgent, m as loadRemoteAgentState, o as getRemoteAgentOutput, r as checkRemoteSpendCap, s as init_remote_agents, v as sendToRemoteAgent, y as spawnRemoteAgent } from "./remote-agents-DyK_DtXM.js";
72
+ import { r as spawnInspectAgent } from "./inspect-agent-Cafn8B4M.js";
73
+ import { n as init_github_pr_lookup, r as lookupPullRequestNumberForBranch, t as githubPrLookupSource } from "./github-pr-lookup-B-aLAA0M.js";
74
+ import { a as init_pull_requests, c as getIssuePrTabCacheGeneration, i as fetchIssuePullRequestDiff, l as init_pr_tab_cache, n as fetchIssuePullRequest, o as bumpIssuePrTabCacheGeneration, r as fetchIssuePullRequestDetails, s as getCachedIssuePrTabResponse, t as fetchIssueCheckRuns, u as setCachedIssuePrTabResponse } from "./pull-requests-ZcXF1jwZ.js";
75
+ import { t as enrichReviewStatus } from "./review-status-enrichment-DiEp5P49.js";
76
+ import { i as runMemoryFtsTransaction, n as init_fts_db, r as runMemoryFtsStatement, t as closeMemoryFtsDatabases } from "./fts-db-CM78vdJ_.js";
77
+ import { c as init_pending, l as maybeTriggerStatusRollup, o as readRecentObservations, r as init_rollup, u as writePendingTurn } from "./rollup-DMkz8dIT.js";
78
+ import { n as getTldrDaemonServiceSync, r as init_tldr_daemon } from "./tldr-daemon-C99OUCjN.js";
79
+ import { C as init_git_activity, w as listGitOperationsSync } from "./merge-agent-CI0LXwm9.js";
80
+ import { t as getAgentCommandSync } from "./settings-j3qWqwCi.js";
81
+ import { n as init_review_agent, r as isExtendedReviewEnabled } from "./review-agent-Vg_iFpfd.js";
82
+ import { r as isRemoteAvailable, t as createFlyProviderFromConfig } from "./remote-BuX3q71f.js";
83
+ import { n as init_claude_settings_overlay, t as detectProviderEnvConflicts } from "./claude-settings-overlay-D6NffMmt.js";
84
+ import { a as init_query_expansion, i as clearQueryExpansionCache, n as init_injection, o as buildMatchQuery, r as injectPromptTimeMemory, s as init_search } from "./injection-eW0jWzjG.js";
85
+ import { _ as init_transcript_adapter, a as generateFallbackSummary, c as handoffPreconditionFallbackReason, d as prependFallbackFocus, f as requestHandoffFromAgent, g as getTranscriptAdapter, i as copySessionFromCompactBoundary, l as init_summary_fork, o as generateSummaryForFork, p as reserveSummaryForkSession, r as authorHandoffExternal, s as handoffFailureReason, u as logHandoffFallback } from "./summary-fork-CzPuVQAZ.js";
86
+ import { a as init_conversation_compaction, c as shouldInterceptManualCompact, n as compactConversationNative, o as isCompacting } from "./conversation-compaction-KcA3X6Yy.js";
87
+ import { a as init_render, d as validateTemplate, i as hasManagedRegion, l as init_harness, o as userContentOutsideRegion, t as init_context_layers, u as renderForHarness } from "./context-layers-bWw7hOOG.js";
88
+ import { r as parseArtifactRef } from "./forge-Dj4FMDac.js";
89
+ import { t as getMergeBlockersPayload } from "./merge-blockers-DvMepteF.js";
90
+ import { a as spawnSequencerAgent, i as init_sequencer_agent, n as clearFinishedSequencerRun, r as getSequencerRunStatus, t as SEQUENCER_AGENT_ID } from "./sequencer-agent-BLpcWZlb.js";
91
+ import "./supervisor-C6vsGMQJ.js";
92
+ import { i as listTranscriptCheckpoints, r as getTranscriptCheckpoint$1 } from "./checkpoints-BNs7sSvt.js";
95
93
  import { createRequire } from "node:module";
96
94
  import { Cause, Context, Data, Duration, Effect, Exit, Fiber, FileSystem, Layer, Option, Path, Queue, Result, Schedule, Schema, Stream } from "effect";
97
95
  import { createReadStream, createWriteStream, existsSync, fstatSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
98
96
  import { basename, dirname, join, resolve } from "path";
99
97
  import { homedir } from "os";
100
- import { homedir as homedir$1, type } from "node:os";
98
+ import { freemem as freemem$1, homedir as homedir$1, loadavg as loadavg$1, totalmem as totalmem$1, type } from "node:os";
101
99
  import { constants, createReadStream as createReadStream$1, existsSync as existsSync$1, mkdirSync as mkdirSync$1, promises as promises$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, readlinkSync, realpathSync as realpathSync$1, renameSync as renameSync$1, rmSync as rmSync$1, stat, statSync as statSync$1, symlinkSync as symlinkSync$1, unwatchFile, watch, watchFile, writeFileSync as writeFileSync$1 } from "node:fs";
102
100
  import * as sp from "node:path";
103
101
  import { basename as basename$1, delimiter, dirname as dirname$1, extname as extname$1, isAbsolute as isAbsolute$1, join as join$1, normalize, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "node:path";
@@ -106,9 +104,9 @@ import { EventEmitter } from "node:events";
106
104
  import { access, appendFile, chmod, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat as stat$1, unlink, writeFile } from "node:fs/promises";
107
105
  import { createHash, randomUUID } from "crypto";
108
106
  import { chmod as chmod$1, mkdir as mkdir$1, open as open$1, readFile as readFile$1, readdir as readdir$1, rename as rename$1, stat as stat$2, unlink as unlink$1, writeFile as writeFile$1 } from "fs/promises";
109
- import { createHmac, randomBytes, randomUUID as randomUUID$1, timingSafeEqual } from "node:crypto";
110
107
  import { exec, execFile, execFileSync, execSync, fork, spawn } from "node:child_process";
111
108
  import { promisify } from "node:util";
109
+ import { createHmac, randomBytes, randomUUID as randomUUID$1, timingSafeEqual } from "node:crypto";
112
110
  import * as pty from "@homebridge/node-pty-prebuilt-multiarch";
113
111
  import { Buffer as Buffer$1 } from "node:buffer";
114
112
  import { PassThrough, Readable } from "node:stream";
@@ -296,6 +294,12 @@ const ServerConfigLayer = Layer.effect(ServerConfig, Effect.sync(() => {
296
294
  const identity = getDashboardIdentity();
297
295
  const hostDashboardApiPort = readHostDashboardApiPort();
298
296
  const overrideAllowed = process.env["OVERDECK_WORKSPACE_DASHBOARD_ALLOW_PRIMARY"] === "1";
297
+ const agentId = process.env["OVERDECK_AGENT_ID"];
298
+ if (overrideAllowed && agentId !== void 0 && !agentId.startsWith("conv-")) {
299
+ const msg = `Refusing OVERDECK_WORKSPACE_DASHBOARD_ALLOW_PRIMARY=1 for pipeline-role identity OVERDECK_AGENT_ID=${agentId}. Work, planning, review, and flywheel agents must never bind the host dashboard port; use the workspace container endpoint instead. Only an operator-supervised conversation (conv-*) or a process with no agent identity may use this emergency override.`;
300
+ console.error(`[overdeck] ${msg}`);
301
+ throw new ServerConfigError("OVERDECK_WORKSPACE_DASHBOARD_ALLOW_PRIMARY", msg);
302
+ }
299
303
  if (shouldRefuseHostDashboardPort({
300
304
  repoRoot: identity.repoRoot,
301
305
  mode: identity.mode,
@@ -328,6 +332,7 @@ init_driver();
328
332
  var import_dist = /* @__PURE__ */ __toESM(require_dist$1(), 1);
329
333
  init_issue_id();
330
334
  init_projects();
335
+ init_auto_commit();
331
336
  init_record();
332
337
  init_model_capabilities();
333
338
  init_registry();
@@ -651,7 +656,7 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
651
656
  const beadsDir = join$1(workspacePath, ".beads");
652
657
  const projectBeadsDir = join$1(workspacePath, "..", "..", ".beads");
653
658
  if (!(existsSync$1(beadsDir) || existsSync$1(projectBeadsDir))) throw new BeadsNotInitialized({ workspace: workspacePath });
654
- const { getAgentState, spawnAgent, normalizeAgentId } = await import("./agents-Bi9Y6h5B.js");
659
+ const { getAgentState, spawnAgent, normalizeAgentId } = await import("./agents-2mgtTSeb.js");
655
660
  const normalizedId = normalizeAgentId(issueId);
656
661
  if ((await Effect.runPromise(getAgentState(normalizedId)))?.status === "running") throw new AgentAlreadyRunning({ id: issueId });
657
662
  return {
@@ -682,7 +687,7 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
682
687
  const { workspacePath, projectPath, issue } = opts;
683
688
  if (!existsSync$1(workspacePath)) throw new WorkspaceNotFound({ id: issueId });
684
689
  const sessionName = opts.sessionName ?? `planning-${issueId.toLowerCase()}`;
685
- const { spawnPlanningSession } = await import("./spawn-planning-session-B-vx-dop.js");
690
+ const { spawnPlanningSession } = await import("./spawn-planning-session-Qyw8s1SD.js");
686
691
  const result = await spawnPlanningSession({
687
692
  issue: {
688
693
  id: issue.id,
@@ -715,14 +720,14 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
715
720
  }),
716
721
  kill: (agentId) => Effect.tryPromise({
717
722
  try: async () => {
718
- const { stopAgent } = await import("./agents-Bi9Y6h5B.js");
723
+ const { stopAgent } = await import("./agents-2mgtTSeb.js");
719
724
  await Effect.runPromise(stopAgent(agentId));
720
725
  },
721
726
  catch: () => void 0
722
727
  }).pipe(Effect.ignore),
723
728
  message: (agentId, msg) => Effect.tryPromise({
724
729
  try: async () => {
725
- const { messageAgent } = await import("./agents-Bi9Y6h5B.js");
730
+ const { messageAgent } = await import("./agents-2mgtTSeb.js");
726
731
  await messageAgent(agentId, msg);
727
732
  },
728
733
  catch: (err) => new AgentStartError({
@@ -733,8 +738,8 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
733
738
  }),
734
739
  deepWipe: (issueId, opts) => Effect.tryPromise({
735
740
  try: async () => {
736
- const { deepWipe } = await import("./workflows-DaV5vcGg.js");
737
- const { resolveProjectFromIssueSync } = await import("./projects-CKnFqWB0.js");
741
+ const { deepWipe } = await import("./workflows-xeMCyhJE.js");
742
+ const { resolveProjectFromIssueSync } = await import("./projects-Daw2zpGk.js");
738
743
  const project = resolveProjectFromIssueSync(issueId);
739
744
  const projectPath = project?.projectPath ?? process.cwd();
740
745
  await Effect.runPromise(deepWipe({
@@ -763,7 +768,7 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
763
768
  * Route handlers and AgentSpawner use this instead of calling workspace-manager directly.
764
769
  */
765
770
  init_typed_errors();
766
- const execAsync$13 = promisify(exec);
771
+ const execAsync$11 = promisify(exec);
767
772
  var WorkspaceService = class extends Context.Service()("overdeck/dashboard/WorkspaceService") {};
768
773
  const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
769
774
  function getWorkspacePath(issueId) {
@@ -790,8 +795,8 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
790
795
  const { workspacePath, branch } = getWorkspacePath(issueId);
791
796
  const issueLower = issueId.toLowerCase();
792
797
  if (existsSync$1(workspacePath)) return workspacePath;
793
- const { createWorkspace } = await import("./workspace-manager-CK4yZQev.js");
794
- const { loadProjectsConfigSync } = await import("./projects-CKnFqWB0.js");
798
+ const { createWorkspace } = await import("./workspace-manager-uVfFRWJG.js");
799
+ const { loadProjectsConfigSync } = await import("./projects-Daw2zpGk.js");
795
800
  const { projects } = loadProjectsConfigSync();
796
801
  const project = resolveProjectFromIssueSync(issueId);
797
802
  if (!project) throw new WorkspaceCreateError({
@@ -826,7 +831,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
826
831
  const { workspacePath } = getWorkspacePath(issueId);
827
832
  const issueLower = issueId.toLowerCase();
828
833
  if (!existsSync$1(workspacePath)) throw new WorkspaceNotFound({ id: issueId });
829
- const { removeWorkspace } = await import("./workspace-manager-CK4yZQev.js");
834
+ const { removeWorkspace } = await import("./workspace-manager-uVfFRWJG.js");
830
835
  const project = resolveProjectFromIssueSync(issueId);
831
836
  if (!project) throw new WorkspaceCreateError({
832
837
  id: issueId,
@@ -857,7 +862,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
857
862
  try: async () => {
858
863
  const { workspacePath } = getWorkspacePath(issueId);
859
864
  const issueLower = issueId.toLowerCase();
860
- const { stopWorkspaceDocker } = await import("./workspace-manager-CK4yZQev.js");
865
+ const { stopWorkspaceDocker } = await import("./workspace-manager-uVfFRWJG.js");
861
866
  await Effect.runPromise(stopWorkspaceDocker(workspacePath, issueLower));
862
867
  },
863
868
  catch: () => void 0
@@ -926,7 +931,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
926
931
  join$1(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml")
927
932
  ];
928
933
  if (!composePaths.find((p) => existsSync$1(p))) {
929
- const { ensureDevcontainerSync } = await import("./ensure-devcontainer-C18oIaYa.js");
934
+ const { ensureDevcontainerSync } = await import("./ensure-devcontainer-BSPw9WeO.js");
930
935
  const ensure = ensureDevcontainerSync({
931
936
  workspacePath,
932
937
  issueId
@@ -937,7 +942,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
937
942
  });
938
943
  }
939
944
  const finalComposePath = composePaths.find((p) => existsSync$1(p));
940
- if (finalComposePath) await execAsync$13(`docker compose -f "${finalComposePath}" up -d --build`, {
945
+ if (finalComposePath) await execAsync$11(`docker compose -f "${finalComposePath}" up -d --build`, {
941
946
  cwd: dirname$1(finalComposePath),
942
947
  timeout: 3e5
943
948
  });
@@ -1100,7 +1105,7 @@ function getDefaultCwd() {
1100
1105
  //#endregion
1101
1106
  //#region services/open.ts
1102
1107
  init_src();
1103
- const execAsync$12 = promisify(exec);
1108
+ const execAsync$10 = promisify(exec);
1104
1109
  function getFileManagerCommand() {
1105
1110
  switch (process.platform) {
1106
1111
  case "linux": return "xdg-open";
@@ -1133,7 +1138,7 @@ function launchDetached(launch) {
1133
1138
  }
1134
1139
  async function isCommandAvailable(command) {
1135
1140
  try {
1136
- await execAsync$12(`${process.platform === "win32" ? "where" : "which"} ${command}`);
1141
+ await execAsync$10(`${process.platform === "win32" ? "where" : "which"} ${command}`);
1137
1142
  return true;
1138
1143
  } catch {
1139
1144
  return false;
@@ -4811,7 +4816,7 @@ init_tmux();
4811
4816
  init_config$1();
4812
4817
  const reauthSessions = /* @__PURE__ */ new Map();
4813
4818
  const SESSION_MAX_AGE_MS = 3600 * 1e3;
4814
- const readJsonBody$16 = Effect.gen(function* () {
4819
+ const readJsonBody$17 = Effect.gen(function* () {
4815
4820
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
4816
4821
  return text ? JSON.parse(text) : {};
4817
4822
  });
@@ -4916,7 +4921,7 @@ const postCodexReauthRoute = HttpRouter.add("POST", "/api/settings/codex-reauth"
4916
4921
  const postCodexReauthStatusRoute = HttpRouter.add("POST", "/api/settings/codex-reauth/status", httpHandler(Effect.gen(function* () {
4917
4922
  const originError = rejectInvalidOrigin(yield* HttpServerRequest.HttpServerRequest);
4918
4923
  if (originError) return originError;
4919
- const body = yield* readJsonBody$16;
4924
+ const body = yield* readJsonBody$17;
4920
4925
  const sessionName = typeof body.session === "string" ? body.session : "";
4921
4926
  const token = typeof body.token === "string" ? body.token : "";
4922
4927
  const session = getLiveReauthSession(sessionName);
@@ -15944,7 +15949,7 @@ var require_get_vercel_oidc_token = /* @__PURE__ */ __commonJSMin(((exports, mod
15944
15949
  err = error;
15945
15950
  }
15946
15951
  try {
15947
- const [{ getTokenPayload, isExpired }, { refreshToken }] = await Promise.all([await import("./token-util-DP7ukS32.js").then((m) => /* @__PURE__ */ __toESM(m.default)), await import("./token-Dn9hc4Ap.js").then((m) => /* @__PURE__ */ __toESM(m.default))]);
15952
+ const [{ getTokenPayload, isExpired }, { refreshToken }] = await Promise.all([await import("./token-util-xNaV-00f.js").then((m) => /* @__PURE__ */ __toESM(m.default)), await import("./token-CiS3w6Ma.js").then((m) => /* @__PURE__ */ __toESM(m.default))]);
15948
15953
  if (!token || isExpired(getTokenPayload(token), options?.expirationBufferMs)) {
15949
15954
  await refreshToken(options);
15950
15955
  token = getVercelOidcTokenSync();
@@ -28261,7 +28266,7 @@ var GoogleCloudTranscription = class {
28261
28266
  }
28262
28267
  async start(apiKey, model) {
28263
28268
  try {
28264
- const { SpeechClient } = await import("./src-DFWsHM-l.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1)).then((module) => module.v1p1beta1 ?? module.v1);
28269
+ const { SpeechClient } = await import("./src-CNLON0co.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1)).then((module) => module.v1p1beta1 ?? module.v1);
28265
28270
  if (this.closed) return;
28266
28271
  const stream = new SpeechClient({ apiKey }).streamingRecognize({
28267
28272
  config: {
@@ -28525,7 +28530,7 @@ const DEFAULT_VOICE_SETTINGS = {
28525
28530
  model: "gpt-4.1-mini"
28526
28531
  }
28527
28532
  };
28528
- const readJsonBody$15 = Effect.gen(function* () {
28533
+ const readJsonBody$16 = Effect.gen(function* () {
28529
28534
  const request = yield* HttpServerRequest.HttpServerRequest;
28530
28535
  const contentLength = Number(request.headers["content-length"] ?? 0);
28531
28536
  if (Number.isFinite(contentLength) && contentLength > MAX_VOICE_SETTINGS_BODY_BYTES) return {
@@ -28648,7 +28653,7 @@ const getVoiceSettingsRoute = HttpRouter.add("GET", "/api/voice/settings", httpH
28648
28653
  const putVoiceSettingsRoute = HttpRouter.add("PUT", "/api/voice/settings", httpHandler(Effect.gen(function* () {
28649
28654
  const originError = requireTrustedOrigin$2(yield* HttpServerRequest.HttpServerRequest);
28650
28655
  if (originError) return originError;
28651
- const parsed = yield* readJsonBody$15;
28656
+ const parsed = yield* readJsonBody$16;
28652
28657
  if (!parsed.ok) return jsonResponse({ error: parsed.error }, { status: 413 });
28653
28658
  const body = parsed.body;
28654
28659
  if (!isVoiceSettings(body)) return jsonResponse({ error: "Invalid voice settings payload" }, { status: 400 });
@@ -30044,7 +30049,7 @@ const websocketRpcRouteLayer = Layer.unwrap(Effect.gen(function* () {
30044
30049
  //#endregion
30045
30050
  //#region ../../lib/vbrief/quality-lint.ts
30046
30051
  init_briefing_freshness();
30047
- init_types$1();
30052
+ init_types();
30048
30053
  init_specs();
30049
30054
  init_io();
30050
30055
  init_pty_token();
@@ -30059,11 +30064,12 @@ init_pi_fifo();
30059
30064
  init_launcher_generator();
30060
30065
  init_bridge_token();
30061
30066
  init_transcript_landing();
30067
+ init_state_home();
30062
30068
  init_operator_interventions();
30063
30069
  init_pan_dir();
30064
30070
  init_agents$1();
30065
30071
  init_remote_agents();
30066
- init_types();
30072
+ init_types$1();
30067
30073
  init_swarm_readiness();
30068
30074
  var PlanQualityLintError = class extends Error {
30069
30075
  constructor(issues) {
@@ -30385,7 +30391,7 @@ function parseIssueIdFromTextSync(value) {
30385
30391
  //#region services/resource-discovery.ts
30386
30392
  init_projects();
30387
30393
  init_tmux();
30388
- const execFileAsync$9 = promisify(execFile);
30394
+ const execFileAsync$14 = promisify(execFile);
30389
30395
  const RESOURCE_DISCOVERY_TTL_MS = 3e4;
30390
30396
  const RECENT_ACTIVITY_WINDOW_MS = 5e3;
30391
30397
  let cachedResourceIssues = null;
@@ -30493,7 +30499,7 @@ function isDiscoverableAgentSession(sessionName) {
30493
30499
  async function loadTrackerIssues() {
30494
30500
  const map = /* @__PURE__ */ new Map();
30495
30501
  try {
30496
- const { getSharedIssueService } = await import("./issue-service-singleton-BSgap-ST.js");
30502
+ const { getSharedIssueService } = await import("./issue-service-singleton-Cyfvop7U.js");
30497
30503
  const issues = (await getSharedIssueService()).getIssues();
30498
30504
  for (const issue of issues) if (issue.identifier) map.set(issue.identifier.toUpperCase(), issue);
30499
30505
  } catch {
@@ -30510,7 +30516,7 @@ async function loadTmuxSessions() {
30510
30516
  }
30511
30517
  async function loadDockerContainers() {
30512
30518
  try {
30513
- const { stdout } = await execFileAsync$9("docker", [
30519
+ const { stdout } = await execFileAsync$14("docker", [
30514
30520
  "ps",
30515
30521
  "--format",
30516
30522
  "{{.Names}}"
@@ -30528,7 +30534,7 @@ async function loadOpenPullRequests() {
30528
30534
  const repos = getGitHubConfig()?.repos ?? [];
30529
30535
  await Promise.all(repos.map(async (repo) => {
30530
30536
  try {
30531
- const { stdout } = await execFileAsync$9("gh", [
30537
+ const { stdout } = await execFileAsync$14("gh", [
30532
30538
  "pr",
30533
30539
  "list",
30534
30540
  "--repo",
@@ -30559,7 +30565,7 @@ async function loadOpenPullRequests() {
30559
30565
  }
30560
30566
  async function loadProjectBranches(projectPath) {
30561
30567
  try {
30562
- const [localResult, remoteResult] = await Promise.all([execFileAsync$9("git", [
30568
+ const [localResult, remoteResult] = await Promise.all([execFileAsync$14("git", [
30563
30569
  "for-each-ref",
30564
30570
  "refs/heads/feature/*",
30565
30571
  "--format=%(refname:short)"
@@ -30567,7 +30573,7 @@ async function loadProjectBranches(projectPath) {
30567
30573
  cwd: projectPath,
30568
30574
  encoding: "utf-8",
30569
30575
  timeout: 1e4
30570
- }).catch(() => ({ stdout: "" })), execFileAsync$9("git", [
30576
+ }).catch(() => ({ stdout: "" })), execFileAsync$14("git", [
30571
30577
  "for-each-ref",
30572
30578
  "refs/remotes/origin/feature/*",
30573
30579
  "--format=%(refname:short)"
@@ -30692,7 +30698,7 @@ async function computeResourceAllocatedIssues() {
30692
30698
  if (!issue.resourceDetails.tmuxSessions.includes(sessionName)) issue.resourceDetails.tmuxSessions.push(sessionName);
30693
30699
  }
30694
30700
  try {
30695
- const { listActiveRemoteAgentStates } = await import("./remote-agents-tBpE_KXg.js");
30701
+ const { listActiveRemoteAgentStates } = await import("./remote-agents-YROPFM-n.js");
30696
30702
  for (const remoteState of listActiveRemoteAgentStates()) {
30697
30703
  const issue = ensureIssue(remoteState.issueId);
30698
30704
  if (!issue) continue;
@@ -30896,7 +30902,7 @@ function groupResourceAllocatedIssuesByProject(issues) {
30896
30902
  //#region ../../lib/overdeck/issue-reads.ts
30897
30903
  init_tracker_config();
30898
30904
  init_projects();
30899
- const execFileAsync$8 = promisify(execFile);
30905
+ const execFileAsync$13 = promisify(execFile);
30900
30906
  function isGitHubIssue$6(issueId) {
30901
30907
  const resolved = resolveGitHubIssueSync(issueId);
30902
30908
  if (resolved.isGitHub) return {
@@ -31077,7 +31083,7 @@ function getIssueBeads(id) {
31077
31083
  const { beads, querySource } = yield* Effect.promise(async () => {
31078
31084
  try {
31079
31085
  const bdSearchDir = workspacePath && existsSync$1(workspacePath) ? workspacePath : projectPath || homedir$1();
31080
- const { stdout } = await Effect.runPromise(withBdMutex(() => Effect.promise(() => execFileAsync$8("bd", [
31086
+ const { stdout } = await Effect.runPromise(withBdMutex(() => Effect.promise(() => execFileAsync$13("bd", [
31081
31087
  "list",
31082
31088
  "--json",
31083
31089
  "-l",
@@ -31198,7 +31204,7 @@ init_projects();
31198
31204
  init_remote_agents();
31199
31205
  init_tracker_utils();
31200
31206
  init_tmux();
31201
- const execFileAsync$7 = promisify(execFile);
31207
+ const execFileAsync$12 = promisify(execFile);
31202
31208
  function getIssueDataService$9() {
31203
31209
  return getSharedIssueService();
31204
31210
  }
@@ -31213,8 +31219,10 @@ function isGitHubIssue$5(issueId) {
31213
31219
  return { isGitHub: false };
31214
31220
  }
31215
31221
  const completePlanningGuard = createInFlightGuard();
31216
- function beginCompletePlanningLease(issueId) {
31222
+ const completePlanningAutoSpawnIntent = /* @__PURE__ */ new Set();
31223
+ function beginCompletePlanningLease(issueId, autoSpawnRequested = false) {
31217
31224
  const key = issueId.toLowerCase();
31225
+ if (autoSpawnRequested) completePlanningAutoSpawnIntent.add(key);
31218
31226
  let release;
31219
31227
  const lease = new Promise((resolve) => {
31220
31228
  release = resolve;
@@ -31222,7 +31230,11 @@ function beginCompletePlanningLease(issueId) {
31222
31230
  const started = completePlanningGuard.run(key, () => lease);
31223
31231
  return {
31224
31232
  started,
31225
- release: started ? release : () => void 0
31233
+ autoSpawnRequested: () => completePlanningAutoSpawnIntent.has(key),
31234
+ release: started ? () => {
31235
+ completePlanningAutoSpawnIntent.delete(key);
31236
+ release();
31237
+ } : () => void 0
31226
31238
  };
31227
31239
  }
31228
31240
  function emitCompletePlanningPhase(issueId, phase, status, reason, details = {}) {
@@ -31255,7 +31267,7 @@ async function completePlanningArtifacts(options) {
31255
31267
  if (workspaceIssueId && workspaceIssueId.toLowerCase() !== issueLower) throw new Error(`Workspace vBRIEF is for ${workspaceIssueId.toUpperCase()}, not ${upperIssueId}`);
31256
31268
  assertPlanQuality(workspaceDoc);
31257
31269
  const createBeads = options.createBeads ?? (async (path) => {
31258
- const mod = await import("./beads-BRYZM_Zz.js");
31270
+ const mod = await import("./beads-CuXF6X4l.js");
31259
31271
  return await Effect.runPromise(mod.createBeadsFromVBrief(path));
31260
31272
  });
31261
31273
  emitCompletePlanningPhase(upperIssueId, "specWrite", "start", "writing proposed vBRIEF spec", { projectPath });
@@ -31313,17 +31325,87 @@ async function completePlanningArtifacts(options) {
31313
31325
  beadsWarning: null
31314
31326
  };
31315
31327
  }
31316
- function completePlanningFilesToStage(projectPath, proposedFilename) {
31317
- const filesToStage = [`.pan/specs/${proposedFilename}`];
31318
- if (existsSync$1(join$1(projectPath, ".pan", "context", "codebase"))) filesToStage.push(".pan/context/codebase/");
31328
+ function completePlanningFilesToStage(projectPath, proposedFilename, migrated = false) {
31329
+ const filesToStage = migrated ? [] : [`.pan/specs/${proposedFilename}`];
31330
+ if (existsSync$1(join$1(projectPath, ".overdeck", "context", "codebase"))) filesToStage.push(".overdeck/context/codebase/");
31331
+ else if (!migrated && existsSync$1(join$1(projectPath, ".pan", "context", "codebase"))) filesToStage.push(".pan/context/codebase/");
31319
31332
  return filesToStage;
31320
31333
  }
31321
- function completePlanningWorkspaceGitAddCommands(gitRoot) {
31334
+ function completePlanningWorkspaceGitAddCommands(gitRoot, migrated = false) {
31322
31335
  const commands = [];
31323
- if (existsSync$1(join$1(gitRoot, ".pan"))) commands.push(["add", ".pan/"]);
31324
- if (existsSync$1(join$1(gitRoot, ".beads"))) commands.push(["add", ".beads/"]);
31336
+ if (!migrated && existsSync$1(join$1(gitRoot, ".pan"))) commands.push(["add", ".pan/"]);
31337
+ if (!migrated && existsSync$1(join$1(gitRoot, ".beads"))) commands.push(["add", ".beads/"]);
31338
+ if (existsSync$1(join$1(gitRoot, ".gitignore"))) commands.push(["add", ".gitignore"]);
31325
31339
  return commands;
31326
31340
  }
31341
+ /**
31342
+ * PAN-2386: complete-planning can leave `.pan/records/<issue>.json` modified after
31343
+ * the main `chore(plan): complete planning` commit because record writes are
31344
+ * queued for debounced auto-commit. If auto-start is requested, the subsequent
31345
+ * start-agent dirty-workspace guard refuses to spawn the work agent. Flush any
31346
+ * pending auto-commits and explicitly stage/commit the per-issue record so the
31347
+ * tree handed to auto-start is clean.
31348
+ */
31349
+ async function commitWorkspaceRecordBeforeAutoSpawn(gitRoot, issueId) {
31350
+ const project = findProjectByPathSync(gitRoot);
31351
+ if (project && await isStateMigrated(project)) {
31352
+ await Effect.runPromise(flushAutoCommits(project.path));
31353
+ return;
31354
+ }
31355
+ if (!existsSync$1(join$1(gitRoot, ".git"))) return;
31356
+ const issueLower = issueId.toLowerCase();
31357
+ try {
31358
+ await Effect.runPromise(flushAutoCommits(gitRoot));
31359
+ } catch {}
31360
+ const recordPath = join$1(".pan", "records", `${issueLower}.json`);
31361
+ try {
31362
+ const { stdout: statusOut } = await execFileAsync$12("git", [
31363
+ "status",
31364
+ "--porcelain",
31365
+ "--",
31366
+ recordPath
31367
+ ], {
31368
+ cwd: gitRoot,
31369
+ encoding: "utf-8"
31370
+ });
31371
+ if (!statusOut.trim()) return;
31372
+ await execFileAsync$12("git", [
31373
+ "add",
31374
+ "--",
31375
+ recordPath
31376
+ ], {
31377
+ cwd: gitRoot,
31378
+ encoding: "utf-8"
31379
+ });
31380
+ try {
31381
+ await execFileAsync$12("git", [
31382
+ "diff",
31383
+ "--cached",
31384
+ "--quiet",
31385
+ "--",
31386
+ recordPath
31387
+ ], {
31388
+ cwd: gitRoot,
31389
+ encoding: "utf-8"
31390
+ });
31391
+ return;
31392
+ } catch {}
31393
+ await execFileAsync$12("git", [
31394
+ "commit",
31395
+ "-m",
31396
+ `chore(records): update ${issueId.toUpperCase()} per-issue record before auto-start`,
31397
+ "--",
31398
+ recordPath
31399
+ ], {
31400
+ cwd: gitRoot,
31401
+ encoding: "utf-8"
31402
+ });
31403
+ console.log(`[complete-planning] Committed per-issue record for ${issueId.toUpperCase()} before auto-start`);
31404
+ } catch (err) {
31405
+ const message = err instanceof Error ? err.message : String(err);
31406
+ console.warn(`[complete-planning] Could not commit per-issue record for ${issueId.toUpperCase()}: ${message}`);
31407
+ }
31408
+ }
31327
31409
  function getInternalDashboardOrigin() {
31328
31410
  const port = Number.parseInt(process.env["API_PORT"] ?? process.env["PORT"] ?? "3011", 10);
31329
31411
  return process.env["OVERDECK_INTERNAL_DASHBOARD_URL"] ?? `http://127.0.0.1:${port}`;
@@ -31421,7 +31503,7 @@ async function completePlanningForIssue(options) {
31421
31503
  }
31422
31504
  const sessionName = `planning-${id.toLowerCase()}`;
31423
31505
  const issueLower = id.toLowerCase();
31424
- const completePlanningLease = beginCompletePlanningLease(id);
31506
+ const completePlanningLease = beginCompletePlanningLease(id, autoSpawn);
31425
31507
  if (!completePlanningLease.started) {
31426
31508
  console.log(`[complete-planning] ${id} already has an in-flight finalize; returning in-flight status`);
31427
31509
  return jsonResponse({
@@ -31520,10 +31602,14 @@ async function completePlanningForIssue(options) {
31520
31602
  });
31521
31603
  console.log(`[complete-planning] Wrote pan spec to ${proposed.path}`);
31522
31604
  console.log(`[complete-planning] Materialized ${beadCount} beads for ${upperIssueId}`);
31523
- const filesToStage = completePlanningFilesToStage(projectPath, proposed.filename);
31524
- if (!existsSync$1(join$1(projectPath, ".git"))) console.log(`[complete-planning] Project root ${projectPath} is not a git repository (polyrepo) — pan spec updated on disk but not committed`);
31605
+ const project = findProjectByPathSync(projectPath);
31606
+ const migrated = project ? await isStateMigrated(project) : false;
31607
+ const filesToStage = completePlanningFilesToStage(projectPath, proposed.filename, migrated);
31608
+ const projectIsGitRepo = existsSync$1(join$1(projectPath, ".git"));
31609
+ if (migrated) await Effect.runPromise(flushAutoCommits(projectPath));
31610
+ else if (!projectIsGitRepo) console.log(`[complete-planning] Project root ${projectPath} is not a git repository (polyrepo) — pan spec updated on disk but not committed`);
31525
31611
  else {
31526
- const { stdout: branchStdout } = await execFileAsync$7("git", [
31612
+ const { stdout: branchStdout } = await execFileAsync$12("git", [
31527
31613
  "rev-parse",
31528
31614
  "--abbrev-ref",
31529
31615
  "HEAD"
@@ -31533,7 +31619,7 @@ async function completePlanningForIssue(options) {
31533
31619
  });
31534
31620
  const currentBranch = branchStdout.trim();
31535
31621
  if (currentBranch === "main") {
31536
- await execFileAsync$7("git", [
31622
+ await execFileAsync$12("git", [
31537
31623
  "add",
31538
31624
  "--",
31539
31625
  ...filesToStage
@@ -31542,7 +31628,7 @@ async function completePlanningForIssue(options) {
31542
31628
  encoding: "utf-8"
31543
31629
  });
31544
31630
  try {
31545
- await execFileAsync$7("git", [
31631
+ await execFileAsync$12("git", [
31546
31632
  "diff",
31547
31633
  "--cached",
31548
31634
  "--quiet",
@@ -31553,7 +31639,7 @@ async function completePlanningForIssue(options) {
31553
31639
  encoding: "utf-8"
31554
31640
  });
31555
31641
  } catch {
31556
- await execFileAsync$7("git", [
31642
+ await execFileAsync$12("git", [
31557
31643
  "commit",
31558
31644
  "-m",
31559
31645
  `chore(scope): propose ${upperIssueId} vBRIEF`,
@@ -31566,7 +31652,7 @@ async function completePlanningForIssue(options) {
31566
31652
  });
31567
31653
  console.log(`[complete-planning] Committed pan spec on main for ${upperIssueId}`);
31568
31654
  try {
31569
- const { stdout: remotes } = await execFileAsync$7("git", ["remote"], {
31655
+ const { stdout: remotes } = await execFileAsync$12("git", ["remote"], {
31570
31656
  cwd: projectPath,
31571
31657
  encoding: "utf-8"
31572
31658
  });
@@ -31579,16 +31665,16 @@ async function completePlanningForIssue(options) {
31579
31665
  }
31580
31666
  } else console.log(`[complete-planning] Project root not on main (${currentBranch}) — pan spec updated on disk but not committed on main`);
31581
31667
  }
31582
- if (!existsSync$1(join$1(gitRoot, ".git"))) await execFileAsync$7("git", ["init"], {
31668
+ if (!existsSync$1(join$1(gitRoot, ".git"))) await execFileAsync$12("git", ["init"], {
31583
31669
  cwd: gitRoot,
31584
31670
  encoding: "utf-8"
31585
31671
  });
31586
- for (const args of completePlanningWorkspaceGitAddCommands(gitRoot)) await execFileAsync$7("git", args, {
31672
+ for (const args of completePlanningWorkspaceGitAddCommands(gitRoot, migrated)) await execFileAsync$12("git", args, {
31587
31673
  cwd: gitRoot,
31588
31674
  encoding: "utf-8"
31589
31675
  });
31590
31676
  try {
31591
- await execFileAsync$7("git", [
31677
+ await execFileAsync$12("git", [
31592
31678
  "diff",
31593
31679
  "--cached",
31594
31680
  "--quiet"
@@ -31597,7 +31683,7 @@ async function completePlanningForIssue(options) {
31597
31683
  encoding: "utf-8"
31598
31684
  });
31599
31685
  } catch {
31600
- await execFileAsync$7("git", [
31686
+ await execFileAsync$12("git", [
31601
31687
  "commit",
31602
31688
  "-m",
31603
31689
  `chore(plan): complete planning for ${id}`,
@@ -31608,7 +31694,7 @@ async function completePlanningForIssue(options) {
31608
31694
  });
31609
31695
  }
31610
31696
  try {
31611
- const { stdout: remotes } = await execFileAsync$7("git", ["remote"], {
31697
+ const { stdout: remotes } = await execFileAsync$12("git", ["remote"], {
31612
31698
  cwd: gitRoot,
31613
31699
  encoding: "utf-8"
31614
31700
  });
@@ -31686,14 +31772,16 @@ async function completePlanningForIssue(options) {
31686
31772
  source: "planning-agent",
31687
31773
  eventType: "planning.complete"
31688
31774
  });
31775
+ const effectiveAutoSpawn = autoSpawn || completePlanningLease.autoSpawnRequested();
31776
+ if (effectiveAutoSpawn && workspacePath) await commitWorkspaceRecordBeforeAutoSpawn(workspacePath, id);
31689
31777
  const autoSpawnResult = await completePlanningAutoSpawnAndKill({
31690
31778
  issueId: id,
31691
- autoSpawn,
31779
+ autoSpawn: effectiveAutoSpawn,
31692
31780
  skipKill,
31693
31781
  sessionName
31694
31782
  });
31695
31783
  emitCompletePlanningPhase(id, "terminal", "success", autoSpawnResult?.workAgentSpawned ? "planning complete and work agent spawn requested" : autoSpawnResult?.workAgentSkipReason ?? "planning complete", {
31696
- autoSpawn,
31784
+ autoSpawn: effectiveAutoSpawn,
31697
31785
  workAgentSpawned: autoSpawnResult?.workAgentSpawned ?? false,
31698
31786
  workAgentSkipReason: autoSpawnResult?.workAgentSkipReason
31699
31787
  });
@@ -31715,6 +31803,7 @@ async function completePlanningForIssue(options) {
31715
31803
  init_review_status();
31716
31804
  init_projects();
31717
31805
  init_lifecycle_io();
31806
+ init_issue_closed();
31718
31807
  async function reopenWorkspaceStatePromise(issueId, workspacePath, options = {}) {
31719
31808
  const result = {
31720
31809
  specialistStatesReset: false,
@@ -31785,8 +31874,8 @@ init_tracker_utils();
31785
31874
  init_agents$1();
31786
31875
  init_issue_id();
31787
31876
  init_io();
31788
- const execAsync$11 = promisify(exec);
31789
- const execFileAsync$6 = promisify(execFile);
31877
+ const execAsync$9 = promisify(exec);
31878
+ const execFileAsync$11 = promisify(execFile);
31790
31879
  function isGitHubIssue$4(issueId) {
31791
31880
  const resolved = resolveGitHubIssueSync(issueId);
31792
31881
  if (resolved.isGitHub) return {
@@ -31825,7 +31914,7 @@ async function closeIssuePullRequest(issueId, reason = "Canceled via Overdeck")
31825
31914
  if (!githubCheck.isGitHub || !githubCheck.owner || !githubCheck.repo) return ["No GitHub PR to close"];
31826
31915
  const branchName = `feature/${issueId.toLowerCase()}`;
31827
31916
  try {
31828
- const { stdout: prListRaw } = await execFileAsync$6("gh", [
31917
+ const { stdout: prListRaw } = await execFileAsync$11("gh", [
31829
31918
  "pr",
31830
31919
  "list",
31831
31920
  "--repo",
@@ -31844,7 +31933,7 @@ async function closeIssuePullRequest(issueId, reason = "Canceled via Overdeck")
31844
31933
  });
31845
31934
  const prNumber = prListRaw.trim();
31846
31935
  if (!prNumber) return ["No open PR found for branch"];
31847
- await execFileAsync$6("gh", [
31936
+ await execFileAsync$11("gh", [
31848
31937
  "pr",
31849
31938
  "close",
31850
31939
  prNumber,
@@ -31857,7 +31946,7 @@ async function closeIssuePullRequest(issueId, reason = "Canceled via Overdeck")
31857
31946
  timeout: 15e3
31858
31947
  });
31859
31948
  try {
31860
- const { setReviewStatusSync } = await import("./review-status-BzMWJsVk.js");
31949
+ const { setReviewStatusSync } = await import("./review-status-CGjyt4SK.js");
31861
31950
  setReviewStatusSync(issueId.toUpperCase(), { prUrl: void 0 });
31862
31951
  } catch {}
31863
31952
  return [`Closed PR #${prNumber} on ${githubCheck.owner}/${githubCheck.repo}`];
@@ -31911,7 +32000,7 @@ async function runDestructiveIssueLifecycle(id, mode, opts = {}) {
31911
32000
  const { ctx, projectConfig } = buildLifecycleContext(id, getIssueDataService$8().getIssueSource(id) ?? void 0);
31912
32001
  const deleteWorkspace = opts.deleteWorkspace ?? true;
31913
32002
  cleanupLog.push(...await closeIssuePullRequest(id, mode === "cancel" ? "Canceled via Overdeck" : "Reset to Todo via Overdeck"));
31914
- const { resetToTodo, cancelIssueWorkflow } = await import("./lifecycle-Bu2YCv15.js");
32003
+ const { resetToTodo, cancelIssueWorkflow } = await import("./lifecycle-DEkQYaAt.js");
31915
32004
  const workflow = mode === "cancel" ? cancelIssueWorkflow : resetToTodo;
31916
32005
  const result = await Effect.runPromise(workflow(ctx, {
31917
32006
  deleteWorkspace,
@@ -31926,7 +32015,7 @@ async function runDestructiveIssueLifecycle(id, mode, opts = {}) {
31926
32015
  }));
31927
32016
  cleanupLog.push(...result.steps.flatMap((step) => step.details || [step.error].filter(Boolean)));
31928
32017
  if (mode === "cancel") try {
31929
- const { transitionVBriefOnMain } = await import("./lifecycle-io-DmVYRVq5.js");
32018
+ const { transitionVBriefOnMain } = await import("./lifecycle-io-BB-r9Bl_.js");
31930
32019
  const tx = await Effect.runPromise(transitionVBriefOnMain(ctx.projectPath, id, "cancelled", "cancelled", `scope: cancel ${id.toUpperCase()} vBRIEF`));
31931
32020
  if (tx.moved) cleanupLog.push(`vBRIEF moved ${tx.fromDir} → cancelled`);
31932
32021
  if (tx.committed) cleanupLog.push(`Committed vBRIEF cancellation on main`);
@@ -31934,8 +32023,8 @@ async function runDestructiveIssueLifecycle(id, mode, opts = {}) {
31934
32023
  cleanupLog.push(`vBRIEF cancel transition failed (non-fatal): ${err?.message ?? err}`);
31935
32024
  }
31936
32025
  try {
31937
- const { killAllReviewerSessions } = await import("./review-agent-D-gfTx9G.js");
31938
- const { resolveProjectFromIssueSync } = await import("./projects-CKnFqWB0.js");
32026
+ const { killAllReviewerSessions } = await import("./review-agent-D0X_E1tF.js");
32027
+ const { resolveProjectFromIssueSync } = await import("./projects-Daw2zpGk.js");
31939
32028
  const projectKey = resolveProjectFromIssueSync(id)?.projectKey;
31940
32029
  if (projectKey) {
31941
32030
  const { killed } = await Effect.runPromise(killAllReviewerSessions(projectKey, id.toUpperCase()));
@@ -31949,7 +32038,7 @@ async function runDestructiveIssueLifecycle(id, mode, opts = {}) {
31949
32038
  cleanupLog.push("Cleared review status");
31950
32039
  } catch {}
31951
32040
  try {
31952
- const { resetPostMergeState } = await import("./merge-agent-CvVDCFOp.js");
32041
+ const { resetPostMergeState } = await import("./merge-agent-DzbCbjqg.js");
31953
32042
  resetPostMergeState(id);
31954
32043
  resetPostMergeState(id.toUpperCase());
31955
32044
  cleanupLog.push("Cleared merge state");
@@ -31969,7 +32058,7 @@ function closeIssueTransition(options) {
31969
32058
  const { issueId, body, eventStore } = options;
31970
32059
  const { reason } = body;
31971
32060
  const projectPath = getProjectPath$3(void 0, extractPrefixSync(issueId) ?? issueId.split("-")[0]);
31972
- const { close: closeWorkflow } = yield* Effect.promise(() => import("./lifecycle-Bu2YCv15.js"));
32061
+ const { close: closeWorkflow } = yield* Effect.promise(() => import("./lifecycle-DEkQYaAt.js"));
31973
32062
  const githubCheck = isGitHubIssue$4(issueId);
31974
32063
  const issueDataService = getIssueDataService$8();
31975
32064
  const issueSource = issueDataService.getIssueSource(issueId);
@@ -31992,7 +32081,7 @@ function closeIssueTransition(options) {
31992
32081
  };
31993
32082
  }
31994
32083
  const result = yield* closeWorkflow(ctx, { reason });
31995
- if (githubCheck.isGitHub) execAsync$11("pan sync", {
32084
+ if (githubCheck.isGitHub) execAsync$9("pan sync", {
31996
32085
  encoding: "utf-8",
31997
32086
  timeout: 3e4
31998
32087
  }).catch(() => {});
@@ -32203,13 +32292,13 @@ function reopenIssueTransition(options) {
32203
32292
  }
32204
32293
  yield* Effect.promise(async () => {
32205
32294
  try {
32206
- const { stdout } = await execAsync$11(`gh pr list --head ${`feature/${id.toLowerCase()}`} --state closed --json number,mergedAt --limit 1`, {
32295
+ const { stdout } = await execAsync$9(`gh pr list --head ${`feature/${id.toLowerCase()}`} --state closed --json number,mergedAt --limit 1`, {
32207
32296
  encoding: "utf-8",
32208
32297
  timeout: 15e3
32209
32298
  });
32210
32299
  const prs = JSON.parse(stdout.trim() || "[]");
32211
32300
  if (prs.length > 0 && !prs[0].mergedAt) {
32212
- await execAsync$11(`gh pr reopen ${prs[0].number}`, {
32301
+ await execAsync$9(`gh pr reopen ${prs[0].number}`, {
32213
32302
  encoding: "utf-8",
32214
32303
  timeout: 15e3
32215
32304
  });
@@ -32236,7 +32325,7 @@ function reopenIssueTransition(options) {
32236
32325
  else clearReviewStatus(id.toUpperCase());
32237
32326
  } catch {}
32238
32327
  try {
32239
- const { resetPostMergeState } = await import("./merge-agent-CvVDCFOp.js");
32328
+ const { resetPostMergeState } = await import("./merge-agent-DzbCbjqg.js");
32240
32329
  resetPostMergeState(id);
32241
32330
  resetPostMergeState(id.toUpperCase());
32242
32331
  } catch {}
@@ -32256,9 +32345,9 @@ function reopenIssueTransition(options) {
32256
32345
  const projectPath = (teamPrefix ? findProjectByTeamSync(teamPrefix) : null)?.path || "";
32257
32346
  if (projectPath) {
32258
32347
  const workspacePath = join$1(projectPath, "workspaces", `feature-${issueLower}`);
32259
- const { createBeadsFromVBrief } = await import("./beads-BRYZM_Zz.js");
32348
+ const { createBeadsFromVBrief } = await import("./beads-CuXF6X4l.js");
32260
32349
  if (existsSync$1(workspacePath) && await Effect.runPromise(findPlan(workspacePath))) try {
32261
- const { stdout: bdCheck } = await Effect.runPromise(withBdMutex(() => Effect.promise(() => execFileAsync$6("bd", [
32350
+ const { stdout: bdCheck } = await Effect.runPromise(withBdMutex(() => Effect.promise(() => execFileAsync$11("bd", [
32262
32351
  "list",
32263
32352
  "--json",
32264
32353
  "-l",
@@ -32333,7 +32422,7 @@ function moveIssueStatus(options) {
32333
32422
  "done"
32334
32423
  ];
32335
32424
  if (!targetStatus || !validStatuses.includes(targetStatus)) return jsonResponse({ error: `Invalid targetStatus. Must be one of: ${validStatuses.join(", ")}` }, { status: 400 });
32336
- const { updateShadowState } = yield* Effect.promise(() => import("./shadow-state-BQXOrIFn.js"));
32425
+ const { updateShadowState } = yield* Effect.promise(() => import("./shadow-state-B5mlwqQI.js"));
32337
32426
  const issueState = {
32338
32427
  backlog: "open",
32339
32428
  todo: "open",
@@ -32396,7 +32485,7 @@ function moveIssueStatus(options) {
32396
32485
  init_issue_service_singleton();
32397
32486
  init_issue_id();
32398
32487
  init_agents$1();
32399
- const execAsync$10 = promisify(exec);
32488
+ const execAsync$8 = promisify(exec);
32400
32489
  function getIssueDataService$7() {
32401
32490
  return getSharedIssueService();
32402
32491
  }
@@ -32421,28 +32510,28 @@ async function cleanupWorkspaceForIssue(rawId, eventStore) {
32421
32510
  if (projectRoot) {
32422
32511
  const workspacePath = join$1(projectRoot, "workspaces", `feature-${issueLower}`);
32423
32512
  try {
32424
- if ((await execAsync$10("git worktree list --porcelain", {
32513
+ if ((await execAsync$8("git worktree list --porcelain", {
32425
32514
  cwd: projectRoot,
32426
32515
  encoding: "utf-8"
32427
32516
  })).stdout.includes(workspacePath)) {
32428
- await execAsync$10(`git worktree remove "${workspacePath}" --force`, {
32517
+ await execAsync$8(`git worktree remove "${workspacePath}" --force`, {
32429
32518
  cwd: projectRoot,
32430
32519
  encoding: "utf-8"
32431
32520
  });
32432
32521
  cleanupLog.push(`Removed git worktree: ${workspacePath}`);
32433
32522
  } else if (existsSync$1(workspacePath)) {
32434
- await execAsync$10(`rm -rf "${workspacePath}"`, { encoding: "utf-8" });
32523
+ await execAsync$8(`rm -rf "${workspacePath}"`, { encoding: "utf-8" });
32435
32524
  cleanupLog.push(`Removed directory: ${workspacePath}`);
32436
32525
  }
32437
32526
  } catch {
32438
32527
  if (existsSync$1(workspacePath)) {
32439
- await execAsync$10(`rm -rf "${workspacePath}"`, { encoding: "utf-8" });
32528
+ await execAsync$8(`rm -rf "${workspacePath}"`, { encoding: "utf-8" });
32440
32529
  cleanupLog.push(`Removed directory: ${workspacePath}`);
32441
32530
  }
32442
32531
  }
32443
32532
  const branchName = `feature/${issueLower}`;
32444
32533
  try {
32445
- await execAsync$10(`git branch -D "${branchName}" 2>/dev/null || true`, {
32534
+ await execAsync$8(`git branch -D "${branchName}" 2>/dev/null || true`, {
32446
32535
  cwd: projectRoot,
32447
32536
  encoding: "utf-8"
32448
32537
  });
@@ -32451,7 +32540,7 @@ async function cleanupWorkspaceForIssue(rawId, eventStore) {
32451
32540
  }
32452
32541
  const agentDir = join$1(homedir$1(), ".overdeck", "agents", `agent-${issueLower}`);
32453
32542
  if (existsSync$1(agentDir)) {
32454
- await execAsync$10(`rm -rf "${agentDir}"`, { encoding: "utf-8" });
32543
+ await execAsync$8(`rm -rf "${agentDir}"`, { encoding: "utf-8" });
32455
32544
  cleanupLog.push(`Removed agent state: ${agentDir}`);
32456
32545
  }
32457
32546
  await Effect.runPromise(eventStore.append({
@@ -32560,7 +32649,7 @@ async function copySettingsToWorkspace(id) {
32560
32649
  success: false,
32561
32650
  error: "Workspace not found"
32562
32651
  }, { status: 404 });
32563
- const { copyOverdeckSettingsToWorkspaceSync } = await import("./workspace-manager-CK4yZQev.js");
32652
+ const { copyOverdeckSettingsToWorkspaceSync } = await import("./workspace-manager-uVfFRWJG.js");
32564
32653
  const result = copyOverdeckSettingsToWorkspaceSync(workspacePath);
32565
32654
  return jsonResponse({
32566
32655
  success: result.errors.length === 0 || result.copied.length > 0,
@@ -32570,6 +32659,8 @@ async function copySettingsToWorkspace(id) {
32570
32659
  }
32571
32660
  //#endregion
32572
32661
  //#region ../../lib/overdeck/task-generation.ts
32662
+ init_shadow_state();
32663
+ init_github_app();
32573
32664
  init_io();
32574
32665
  function buildChildStoriesFromRally(children) {
32575
32666
  return children.map((c) => ({
@@ -32593,7 +32684,7 @@ function generateTasksForIssue(id) {
32593
32684
  success: false,
32594
32685
  error: `No vBRIEF spec found on main for ${id} — run planning first.`
32595
32686
  }, { status: 409 });
32596
- const { createBeadsFromVBrief } = yield* Effect.promise(() => import("./beads-BRYZM_Zz.js"));
32687
+ const { createBeadsFromVBrief } = yield* Effect.promise(() => import("./beads-CuXF6X4l.js"));
32597
32688
  const result = yield* createBeadsFromVBrief(workspacePath);
32598
32689
  if (!result.success || result.created.length === 0) {
32599
32690
  const errors = result.errors.length > 0 ? result.errors : ["Beads creation produced no tasks"];
@@ -32623,8 +32714,8 @@ init_projects();
32623
32714
  init_tracker_utils();
32624
32715
  init_tmux();
32625
32716
  init_typed_errors();
32626
- const execAsync$9 = promisify(exec);
32627
- const execFileAsync$5 = promisify(execFile);
32717
+ const execAsync$7 = promisify(exec);
32718
+ const execFileAsync$10 = promisify(execFile);
32628
32719
  function getIssueDataService$6() {
32629
32720
  return getSharedIssueService();
32630
32721
  }
@@ -32924,7 +33015,7 @@ function abortPlanningForIssue(options) {
32924
33015
  const featureWorkspacePath = join$1(projectPath, "workspaces", `feature-${issueIdentifier.toLowerCase()}`);
32925
33016
  const plainWorkspacePath = join$1(projectPath, "workspaces", issueIdentifier.toLowerCase());
32926
33017
  if (existsSync$1(existsSync$1(featureWorkspacePath) ? featureWorkspacePath : plainWorkspacePath)) {
32927
- await execFileAsync$5("pan", [
33018
+ await execFileAsync$10("pan", [
32928
33019
  "workspace",
32929
33020
  "destroy",
32930
33021
  issueIdentifier.toLowerCase(),
@@ -33033,10 +33124,10 @@ function restartFromPlan(options) {
33033
33124
  });
33034
33125
  const resetResult = yield* Effect.promise(async () => {
33035
33126
  try {
33036
- const { runGitResetHard } = await import("./dangerous-git-ops-bz-261-k.js");
33127
+ const { runGitResetHard } = await import("./dangerous-git-ops-Pongxg2d.js");
33037
33128
  async function findPlanningCommit(grep, label) {
33038
33129
  try {
33039
- const { stdout } = await execAsync$9(`git log --grep="${grep.replace(/"/g, "\\\"")}" --format=%H -1`, {
33130
+ const { stdout } = await execAsync$7(`git log --grep="${grep.replace(/"/g, "\\\"")}" --format=%H -1`, {
33040
33131
  cwd: workspacePath,
33041
33132
  encoding: "utf-8",
33042
33133
  timeout: 1e4
@@ -33052,7 +33143,7 @@ function restartFromPlan(options) {
33052
33143
  }
33053
33144
  const found = await findPlanningCommit(`Complete planning for ${id}`, "complete-planning message") ?? await findPlanningCommit(`chore: planning artifacts for ${id}`, "agent-start message") ?? await (async () => {
33054
33145
  try {
33055
- const { stdout } = await execAsync$9(`git log --diff-filter=A --format=%H -1 -- .pan/spec.vbrief.json`, {
33146
+ const { stdout } = await execAsync$7(`git log --diff-filter=A --format=%H -1 -- .pan/spec.vbrief.json`, {
33056
33147
  cwd: workspacePath,
33057
33148
  encoding: "utf-8",
33058
33149
  timeout: 1e4
@@ -33179,14 +33270,14 @@ function getPlanningState(id) {
33179
33270
  }
33180
33271
  //#endregion
33181
33272
  //#region ../../lib/overdeck/discussions.ts
33182
- init_github_app();
33273
+ init_cost_sync();
33183
33274
  init_pull_requests();
33184
33275
  init_issue_id();
33185
33276
  init_github_pr_lookup();
33186
33277
  init_tracker_utils();
33187
33278
  init_tracker_config();
33188
33279
  init_pr_tab_cache();
33189
- const execAsync$8 = promisify(exec);
33280
+ const execAsync$6 = promisify(exec);
33190
33281
  function isGitHubIssue$2(issueId) {
33191
33282
  const resolved = resolveGitHubIssueSync(issueId);
33192
33283
  if (resolved.isGitHub) return {
@@ -33234,7 +33325,7 @@ async function fetchIssueDiscussions(issueId, deps = {}) {
33234
33325
  if (githubCheck.isGitHub && githubCheck.owner && githubCheck.repo && githubCheck.number) {
33235
33326
  const { owner, repo, number } = githubCheck;
33236
33327
  try {
33237
- const { stdout } = await execAsync$8(`gh api "repos/${owner}/${repo}/issues/${number}/comments?per_page=100"`, {
33328
+ const { stdout } = await execAsync$6(`gh api "repos/${owner}/${repo}/issues/${number}/comments?per_page=100"`, {
33238
33329
  encoding: "utf-8",
33239
33330
  timeout: 15e3,
33240
33331
  maxBuffer: 8 * 1024 * 1024
@@ -33297,7 +33388,7 @@ async function fetchIssueDiscussions(issueId, deps = {}) {
33297
33388
  const collectedItems = [];
33298
33389
  const prConversation = (async () => {
33299
33390
  try {
33300
- const { stdout } = await execAsync$8(`gh api "repos/${prOwner}/${prRepo}/issues/${prNumber}/comments?per_page=100"`, {
33391
+ const { stdout } = await execAsync$6(`gh api "repos/${prOwner}/${prRepo}/issues/${prNumber}/comments?per_page=100"`, {
33301
33392
  encoding: "utf-8",
33302
33393
  timeout: 15e3,
33303
33394
  maxBuffer: 8 * 1024 * 1024
@@ -33318,7 +33409,7 @@ async function fetchIssueDiscussions(issueId, deps = {}) {
33318
33409
  })();
33319
33410
  const prReviews = (async () => {
33320
33411
  try {
33321
- const { stdout } = await execAsync$8(`gh api "repos/${prOwner}/${prRepo}/pulls/${prNumber}/reviews?per_page=100"`, {
33412
+ const { stdout } = await execAsync$6(`gh api "repos/${prOwner}/${prRepo}/pulls/${prNumber}/reviews?per_page=100"`, {
33322
33413
  encoding: "utf-8",
33323
33414
  timeout: 15e3,
33324
33415
  maxBuffer: 8 * 1024 * 1024
@@ -33343,7 +33434,7 @@ async function fetchIssueDiscussions(issueId, deps = {}) {
33343
33434
  })();
33344
33435
  const prInlineComments = (async () => {
33345
33436
  try {
33346
- const { stdout } = await execAsync$8(`gh api "repos/${prOwner}/${prRepo}/pulls/${prNumber}/comments?per_page=100"`, {
33437
+ const { stdout } = await execAsync$6(`gh api "repos/${prOwner}/${prRepo}/pulls/${prNumber}/comments?per_page=100"`, {
33347
33438
  encoding: "utf-8",
33348
33439
  timeout: 15e3,
33349
33440
  maxBuffer: 8 * 1024 * 1024
@@ -33485,7 +33576,7 @@ function closeOutIssue(id) {
33485
33576
  }
33486
33577
  const closeOutResult = yield* Effect.promise(async () => {
33487
33578
  try {
33488
- const { closeOut } = await import("./lifecycle-Bu2YCv15.js");
33579
+ const { closeOut } = await import("./lifecycle-DEkQYaAt.js");
33489
33580
  return {
33490
33581
  ok: true,
33491
33582
  result: await Effect.runPromise(closeOut(ctx))
@@ -33571,7 +33662,7 @@ function bulkCloseOut(body) {
33571
33662
  const invalidIds = issueIds.filter((id) => !isValidIssueId(id));
33572
33663
  if (invalidIds.length > 0) return jsonResponse({ error: `Invalid issue ID format: ${invalidIds.join(", ")}` }, { status: 400 });
33573
33664
  const eventStore = yield* EventStoreService;
33574
- const { closeOut } = yield* Effect.promise(() => import("./lifecycle-Bu2YCv15.js"));
33665
+ const { closeOut } = yield* Effect.promise(() => import("./lifecycle-DEkQYaAt.js"));
33575
33666
  const issueDataService = getIssueDataService$5();
33576
33667
  const tasks = [];
33577
33668
  const agentChecks = yield* withConcurrencyLimit(issueIds.map((id) => Effect.promise(async () => {
@@ -33738,7 +33829,7 @@ promisify(execFile);
33738
33829
  function getIssueDataService$4() {
33739
33830
  return getSharedIssueService();
33740
33831
  }
33741
- const readJsonBody$14 = Effect.gen(function* () {
33832
+ const readJsonBody$15 = Effect.gen(function* () {
33742
33833
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
33743
33834
  try {
33744
33835
  return text ? JSON.parse(text) : {};
@@ -33763,12 +33854,28 @@ const getIssueAnalyzeRoute = HttpRouter.add("GET", "/api/issues/:id/analyze", ht
33763
33854
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33764
33855
  return yield* analyzeIssue(id);
33765
33856
  })));
33857
+ const getIssueShipLogRoute = HttpRouter.add("GET", "/api/issues/:id/ship-log", httpHandler(Effect.gen(function* () {
33858
+ const id = (yield* HttpRouter.params)["id"] ?? "";
33859
+ if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33860
+ return yield* Effect.promise(async () => {
33861
+ const { getShipLog } = await import("./ship-log-B2EEH__n.js");
33862
+ const { getReviewStatusSync } = await import("./review-status-CGjyt4SK.js");
33863
+ const log = getShipLog(id);
33864
+ const rs = getReviewStatusSync(id);
33865
+ return jsonResponse({
33866
+ issueId: id.toUpperCase(),
33867
+ mergeStatus: rs?.mergeStatus ?? null,
33868
+ mergeStep: rs?.mergeStep ?? null,
33869
+ log
33870
+ });
33871
+ });
33872
+ })));
33766
33873
  const postIssueCloseRoute = HttpRouter.add("POST", "/api/issues/:issueId/close", httpHandler(Effect.gen(function* () {
33767
33874
  const issueId = (yield* HttpRouter.params)["issueId"] ?? "";
33768
33875
  if (!parseIssueIdSync(issueId)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33769
33876
  return yield* closeIssueTransition({
33770
33877
  issueId,
33771
- body: yield* readJsonBody$14,
33878
+ body: yield* readJsonBody$15,
33772
33879
  eventStore: yield* EventStoreService
33773
33880
  });
33774
33881
  })));
@@ -33777,7 +33884,7 @@ const postIssueStartPlanningRoute = HttpRouter.add("POST", "/api/issues/:id/star
33777
33884
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33778
33885
  return yield* startPlanningForIssue({
33779
33886
  id,
33780
- body: yield* readJsonBody$14,
33887
+ body: yield* readJsonBody$15,
33781
33888
  eventStore: yield* EventStoreService,
33782
33889
  linear: yield* LinearClient,
33783
33890
  github: yield* GitHubClient,
@@ -33790,7 +33897,7 @@ const postIssueAbortPlanningRoute = HttpRouter.add("POST", "/api/issues/:id/abor
33790
33897
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33791
33898
  return yield* abortPlanningForIssue({
33792
33899
  id,
33793
- body: yield* readJsonBody$14,
33900
+ body: yield* readJsonBody$15,
33794
33901
  lifecycle: yield* IssueLifecycle,
33795
33902
  linear: yield* LinearClient,
33796
33903
  eventStore: yield* EventStoreService
@@ -33800,7 +33907,7 @@ const postIssueCompletePlanningRoute = HttpRouter.add("POST", "/api/issues/:id/c
33800
33907
  const request = yield* HttpServerRequest.HttpServerRequest;
33801
33908
  const id = (yield* HttpRouter.params)["id"] ?? "";
33802
33909
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33803
- const body = yield* readJsonBody$14;
33910
+ const body = yield* readJsonBody$15;
33804
33911
  const eventStore = yield* EventStoreService;
33805
33912
  const linear = yield* LinearClient;
33806
33913
  const lifecycle = yield* IssueLifecycle;
@@ -33826,7 +33933,7 @@ const postIssueResetRoute = HttpRouter.add("POST", "/api/issues/:id/reset", http
33826
33933
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33827
33934
  return yield* resetIssueTransition({
33828
33935
  id,
33829
- body: yield* readJsonBody$14,
33936
+ body: yield* readJsonBody$15,
33830
33937
  eventStore: yield* EventStoreService
33831
33938
  });
33832
33939
  })));
@@ -33835,7 +33942,7 @@ const postIssueCancelRoute = HttpRouter.add("POST", "/api/issues/:id/cancel", ht
33835
33942
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33836
33943
  return yield* cancelIssueTransition({
33837
33944
  id,
33838
- body: yield* readJsonBody$14,
33945
+ body: yield* readJsonBody$15,
33839
33946
  eventStore: yield* EventStoreService
33840
33947
  });
33841
33948
  })));
@@ -33846,7 +33953,7 @@ const postIssueReopenRoute = HttpRouter.add("POST", "/api/issues/:id/reopen", ht
33846
33953
  if (authError) return authError;
33847
33954
  return yield* reopenIssueTransition({
33848
33955
  id,
33849
- body: yield* readJsonBody$14,
33956
+ body: yield* readJsonBody$15,
33850
33957
  lifecycle: yield* IssueLifecycle,
33851
33958
  linear: yield* LinearClient,
33852
33959
  eventStore: yield* EventStoreService
@@ -33866,7 +33973,7 @@ const postIssueMoveStatusRoute = HttpRouter.add("POST", "/api/issues/:id/move-st
33866
33973
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33867
33974
  return yield* moveIssueStatus({
33868
33975
  id,
33869
- body: yield* readJsonBody$14,
33976
+ body: yield* readJsonBody$15,
33870
33977
  eventStore: yield* EventStoreService,
33871
33978
  lifecycle: yield* IssueLifecycle
33872
33979
  });
@@ -33878,7 +33985,7 @@ const postIssueCleanupWorkspaceRoute = HttpRouter.add("POST", "/api/issues/:id/c
33878
33985
  })));
33879
33986
  const postIssueDeepWipeRoute = HttpRouter.add("POST", "/api/issues/:id/deep-wipe", httpHandler(Effect.gen(function* () {
33880
33987
  const id = (yield* HttpRouter.params)["id"] ?? "";
33881
- const body = yield* readJsonBody$14;
33988
+ const body = yield* readJsonBody$15;
33882
33989
  const eventStore = yield* EventStoreService;
33883
33990
  return yield* Effect.promise(() => deepWipeIssue(id, body, eventStore));
33884
33991
  })));
@@ -33918,7 +34025,7 @@ const postIssueBeadInspectRoute = HttpRouter.add("POST", "/api/issues/:id/beads/
33918
34025
  return yield* inspectIssueBead({
33919
34026
  id: (params["id"] ?? "").toUpperCase(),
33920
34027
  beadId: params["beadId"] ?? "",
33921
- body: yield* readJsonBody$14
34028
+ body: yield* readJsonBody$15
33922
34029
  });
33923
34030
  })));
33924
34031
  const getIssuePlanningStateRoute = HttpRouter.add("GET", "/api/issues/:id/planning-state", httpHandler(Effect.gen(function* () {
@@ -33981,7 +34088,7 @@ const getIssueDiscussionsRoute = HttpRouter.add("GET", "/api/issues/:id/discussi
33981
34088
  const getIssueCostsRoute = HttpRouter.add("GET", "/api/issues/:id/costs", httpHandler(Effect.gen(function* () {
33982
34089
  const id = (yield* HttpRouter.params)["id"] ?? "";
33983
34090
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
33984
- const issueData = getCostsForIssueSync(id);
34091
+ const issueData = getCostForIssueAggregateSync(id);
33985
34092
  const agents = yield* Effect.promise(() => getCachedRunningAgents());
33986
34093
  const resolvedCost = resolveIssueHeadlineCost({
33987
34094
  issueId: id,
@@ -34029,7 +34136,7 @@ const getIssueCostsRoute = HttpRouter.add("GET", "/api/issues/:id/costs", httpHa
34029
34136
  cost: stats.cost,
34030
34137
  tokens: stats.tokens
34031
34138
  }])),
34032
- budget: issueData.budget,
34139
+ budget: void 0,
34033
34140
  budgetWarning: issueData.budgetWarning,
34034
34141
  lastUpdated: issueData.lastUpdated
34035
34142
  });
@@ -34040,7 +34147,7 @@ const getResourceAllocatedIssuesRoute = HttpRouter.add("GET", "/api/issues/resou
34040
34147
  const getIssueResourceDetailsRoute = HttpRouter.add("GET", "/api/issues/:id/resource-details", httpHandler(Effect.gen(function* () {
34041
34148
  return yield* getIssueResourceDetails((yield* HttpRouter.params)["id"] ?? "");
34042
34149
  })));
34043
- const issuesRouteLayer = Layer.mergeAll(getIssuesRoute, getIssueAnalyzeRoute, postIssueCloseRoute, postIssueStartPlanningRoute, postIssueAbortPlanningRoute, postIssueCompletePlanningRoute, postIssueAbortRoute, postIssueResetRoute, postIssueCancelRoute, postIssueReopenRoute, postIssueRestartFromPlanRoute, postIssueMoveStatusRoute, postIssueCleanupWorkspaceRoute, postIssueDeepWipeRoute, postIssueCopySettingsRoute, postIssueCloseOutRoute, postIssuesBulkCloseOutRoute, getIssueBeadsRoute, postIssueBeadInspectRoute, getIssuePlanningStateRoute, postIssueGenerateTasksRoute, getIssueCostsRoute, getResourceAllocatedIssuesRoute, getIssueResourceDetailsRoute, getIssuePrRoute, getIssuePrDiffRoute, getIssuePrDetailsRoute, getIssueCheckRunsRoute, getIssueDiscussionsRoute);
34150
+ const issuesRouteLayer = Layer.mergeAll(getIssuesRoute, getIssueAnalyzeRoute, getIssueShipLogRoute, postIssueCloseRoute, postIssueStartPlanningRoute, postIssueAbortPlanningRoute, postIssueCompletePlanningRoute, postIssueAbortRoute, postIssueResetRoute, postIssueCancelRoute, postIssueReopenRoute, postIssueRestartFromPlanRoute, postIssueMoveStatusRoute, postIssueCleanupWorkspaceRoute, postIssueDeepWipeRoute, postIssueCopySettingsRoute, postIssueCloseOutRoute, postIssuesBulkCloseOutRoute, getIssueBeadsRoute, postIssueBeadInspectRoute, getIssuePlanningStateRoute, postIssueGenerateTasksRoute, getIssueCostsRoute, getResourceAllocatedIssuesRoute, getIssueResourceDetailsRoute, getIssuePrRoute, getIssuePrDiffRoute, getIssuePrDetailsRoute, getIssueCheckRunsRoute, getIssueDiscussionsRoute);
34044
34151
  //#endregion
34045
34152
  //#region routes/costs.ts
34046
34153
  /**
@@ -34075,7 +34182,7 @@ const getCostsSummaryRoute = HttpRouter.add("GET", "/api/costs/summary", httpHan
34075
34182
  const monthEntries = scope(readEventsSync({ startDate: monthAgo }));
34076
34183
  const summarize = (entries) => ({
34077
34184
  totalCost: entries.reduce((sum, e) => sum + (e.cost || 0), 0),
34078
- totalTokens: entries.reduce((sum, e) => sum + ((e.input || 0) + (e.output || 0)), 0),
34185
+ totalTokens: entries.reduce((sum, e) => sum + ((e.input || 0) + (e.output || 0) + (e.cacheRead || 0) + (e.cacheWrite || 0)), 0),
34079
34186
  entryCount: entries.length,
34080
34187
  byModel: entries.reduce((acc, e) => {
34081
34188
  if (e.model) acc[e.model] = (acc[e.model] || 0) + (e.cost || 0);
@@ -34262,16 +34369,45 @@ const postCostsSyncWalRoute = HttpRouter.add("POST", "/api/costs/sync-wal", http
34262
34369
  })
34263
34370
  });
34264
34371
  })));
34372
+ const runOverdeckCostReconcileSource = (source) => Effect.runPromise(CostWriter.use((writer) => writer.reconcile({ source })).pipe(Effect.provide(CostDoorLive)));
34373
+ const runClaudeTranscriptReconcile = () => Effect.runPromise(reconcile());
34374
+ async function runCostReconcileSources(runSource = runOverdeckCostReconcileSource, runClaude = runClaudeTranscriptReconcile) {
34375
+ const [claude, ohmypi, codex] = await Promise.all([
34376
+ runClaude(),
34377
+ runSource("ohmypi"),
34378
+ runSource("codex")
34379
+ ]);
34380
+ return {
34381
+ claude,
34382
+ ohmypi,
34383
+ codex
34384
+ };
34385
+ }
34386
+ function buildCostReconcileResponse(overdeck) {
34387
+ return {
34388
+ success: true,
34389
+ ...overdeck.claude,
34390
+ claude: overdeck.claude,
34391
+ ohmypi: overdeck.ohmypi,
34392
+ codex: overdeck.codex,
34393
+ overdeck,
34394
+ skipped: {
34395
+ ohmypi: overdeck.ohmypi.skipped,
34396
+ codex: overdeck.codex.skipped
34397
+ },
34398
+ warnings: {
34399
+ ohmypi: overdeck.ohmypi.warnings,
34400
+ codex: overdeck.codex.warnings
34401
+ }
34402
+ };
34403
+ }
34265
34404
  const postCostsReconcileRoute = HttpRouter.add("POST", "/api/costs/reconcile", httpHandler(Effect.gen(function* () {
34266
- const result = yield* Effect.tryPromise({
34267
- try: () => Effect.runPromise(reconcile()),
34405
+ const overdeck = yield* Effect.tryPromise({
34406
+ try: () => runCostReconcileSources(),
34268
34407
  catch: (err) => new Error(err instanceof Error ? err.message : String(err))
34269
34408
  });
34270
- console.log(`[reconciler] Sweep complete: ${result.eventsImported ?? 0} imported`);
34271
- return jsonResponse({
34272
- success: true,
34273
- ...result
34274
- });
34409
+ console.log(`[reconciler] Sweep complete: claude=${overdeck.claude.eventsImported} imported, ohmypi=${overdeck.ohmypi.eventsImported} imported, codex=${overdeck.codex.eventsImported} imported`);
34410
+ return jsonResponse(buildCostReconcileResponse(overdeck));
34275
34411
  })));
34276
34412
  const getCostsExperimentsRoute = HttpRouter.add("GET", "/api/costs/experiments", httpHandler(Effect.try({
34277
34413
  try: () => jsonResponse({ experiments: getCavemanExperimentDataSync() }),
@@ -34548,7 +34684,7 @@ function reloadDurableCloisterConfig(deps = {}) {
34548
34684
  */
34549
34685
  init_config();
34550
34686
  init_agents$1();
34551
- const readJsonBody$13 = Effect.gen(function* () {
34687
+ const readJsonBody$14 = Effect.gen(function* () {
34552
34688
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
34553
34689
  try {
34554
34690
  return text ? JSON.parse(text) : {};
@@ -34651,7 +34787,7 @@ const getCloisterConfigRoute = HttpRouter.add("GET", "/api/cloister/config", htt
34651
34787
  catch: (err) => new Error(err instanceof Error ? err.message : String(err))
34652
34788
  })));
34653
34789
  const putCloisterConfigRoute = HttpRouter.add("PUT", "/api/cloister/config", httpHandler(Effect.gen(function* () {
34654
- const updates = yield* readJsonBody$13;
34790
+ const updates = yield* readJsonBody$14;
34655
34791
  yield* Effect.try({
34656
34792
  try: () => {
34657
34793
  saveCloisterConfigSync(updates);
@@ -34675,72 +34811,852 @@ const getCloisterAgentsHealthRoute = HttpRouter.add("GET", "/api/cloister/agents
34675
34811
  })));
34676
34812
  const cloisterRouteLayer = Layer.mergeAll(getCloisterStatusRoute, postCloisterStartRoute, postCloisterStopRoute, postCloisterEmergencyStopRoute, postCloisterBrakeRoute, postCloisterResumeSpawnsRoute, getCloisterSpawnStatusRoute, getCloisterConfigRoute, putCloisterConfigRoute, getCloisterAgentsHealthRoute);
34677
34813
  //#endregion
34678
- //#region routes/resources.ts
34679
- /**
34680
- * Resources route module — Effect HttpRouter.Layer (PAN-428 B12)
34681
- *
34682
- * Implements all /api/resources/* endpoints from the Express server:
34683
- * GET /api/resources
34684
- * GET /api/resources/:containerId/history
34685
- * GET /api/resources/:containerId/details
34686
- * DELETE /api/resources/docker/container/:id
34687
- * POST /api/resources/docker/prune-containers
34688
- * DELETE /api/resources/docker/network/:name
34689
- * DELETE /api/resources/docker/volume/:name
34690
- * POST /api/resources/docker/prune-volumes
34691
- */
34692
- init_docker_stats();
34693
- init_domain_services();
34814
+ //#region routes/resources/agents-stats.ts
34694
34815
  init_agents$1();
34816
+ init_cost_sync();
34695
34817
  init_tmux();
34696
- const execAsync$7 = promisify(exec);
34697
- function formatUptime(startedAt) {
34698
- const ms = Date.now() - new Date(startedAt).getTime();
34699
- const h = Math.floor(ms / 36e5);
34700
- const m = Math.floor(ms % 36e5 / 6e4);
34701
- if (h >= 24) return `${Math.floor(h / 24)}d ${h % 24}h`;
34702
- if (h > 0) return `${h}h ${m}m`;
34703
- return `${m}m`;
34818
+ const execFileAsync$9 = promisify(execFile);
34819
+ const BURN_WINDOW_MS = 1800 * 1e3;
34820
+ function buildAgentStatsSnapshot(options) {
34821
+ const nowMs = options.nowMs ?? Date.now();
34822
+ const processTotalsByRoot = buildProcessTotalsByRoot(options.sessionRoots, options.processes);
34823
+ const rows = options.agents.filter((agent) => agent.status !== "stopped").map((agent) => {
34824
+ const root = options.sessionRoots.find((sessionRoot) => sessionRoot.agentId === agent.id);
34825
+ const processTotals = root ? processTotalsByRoot.get(root.rootPid) : void 0;
34826
+ const costStats = computeAgentCostStats(options.costEventsByAgent?.get(agent.id) ?? [], nowMs);
34827
+ const idleMinutes = getIdleMinutes(agent.lastActivity ?? agent.startedAt, nowMs);
34828
+ return {
34829
+ id: agent.id,
34830
+ issueId: agent.issueId,
34831
+ role: agent.role,
34832
+ model: agent.model,
34833
+ status: agent.status,
34834
+ statusChip: {
34835
+ state: idleMinutes > 5 ? "idle" : "working",
34836
+ idleMinutes,
34837
+ fanOut: (processTotals?.processCount ?? 0) >= 8
34838
+ },
34839
+ rootPid: root?.rootPid ?? null,
34840
+ processCount: processTotals?.processCount ?? 0,
34841
+ cpuPercent: processTotals?.cpuPercent ?? 0,
34842
+ memoryBytes: processTotals?.memoryBytes ?? 0,
34843
+ burnUsdPerHour: costStats.burnUsdPerHour,
34844
+ ...costStats.hypotheticalUsdPerHour > 0 ? { hypotheticalUsdPerHour: costStats.hypotheticalUsdPerHour } : {},
34845
+ totalUsd: costStats.totalUsd
34846
+ };
34847
+ });
34848
+ return {
34849
+ agents: rows,
34850
+ hostVitals: { agents: {
34851
+ burnUsdPerHour: roundCurrency(rows.reduce((sum, row) => sum + row.burnUsdPerHour, 0)),
34852
+ hypotheticalUsdPerHour: roundCurrency(rows.reduce((sum, row) => sum + (row.hypotheticalUsdPerHour ?? 0), 0)),
34853
+ totalUsd: roundCurrency(rows.reduce((sum, row) => sum + row.totalUsd, 0))
34854
+ } }
34855
+ };
34704
34856
  }
34857
+ function getAgentStatsSnapshotEffect(deps = {}) {
34858
+ return Effect.gen(function* () {
34859
+ const nowMs = deps.nowMs ?? Date.now();
34860
+ const agents = (deps.listAgents ?? listAgentStates)().filter((agent) => agent.status !== "stopped");
34861
+ const sessionNames = yield* (deps.listSessionNames ?? defaultListSessionNames)().pipe(Effect.catch(() => Effect.succeed([])));
34862
+ const liveSessionNames = new Set(sessionNames);
34863
+ const sessionRoots = [];
34864
+ for (const agent of agents) {
34865
+ if (!liveSessionNames.has(agent.id)) continue;
34866
+ const rootPid = (yield* (deps.listPanePids ?? defaultListPanePids)(agent.id).pipe(Effect.catch(() => Effect.succeed([]))))[0];
34867
+ if (rootPid) sessionRoots.push({
34868
+ agentId: agent.id,
34869
+ rootPid
34870
+ });
34871
+ }
34872
+ const processes = sessionRoots.length > 0 ? yield* Effect.promise(() => (deps.readProcessTable ?? readBatchedProcessTable)()).pipe(Effect.catch(() => Effect.succeed([]))) : [];
34873
+ const costEventsByAgent = /* @__PURE__ */ new Map();
34874
+ const costQuery = deps.queryCostEvents ?? queryCostEventsSync;
34875
+ for (const agent of agents) {
34876
+ const recentStartTs = new Date(nowMs - BURN_WINDOW_MS).toISOString();
34877
+ costEventsByAgent.set(agent.id, [...costQuery({ agentId: agent.id }), ...costQuery({
34878
+ agentId: agent.id,
34879
+ startTs: recentStartTs
34880
+ })]);
34881
+ }
34882
+ return buildAgentStatsSnapshot({
34883
+ agents,
34884
+ sessionRoots,
34885
+ processes,
34886
+ costEventsByAgent,
34887
+ nowMs
34888
+ });
34889
+ });
34890
+ }
34891
+ function parseProcessTable$1(psTable) {
34892
+ return psTable.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
34893
+ const match = line.match(/^(\d+)\s+(\d+)\s+([0-9.]+)\s+(\d+)$/);
34894
+ if (!match) return [];
34895
+ return [{
34896
+ pid: Number(match[1]),
34897
+ ppid: Number(match[2]),
34898
+ cpuPercent: Number(match[3]),
34899
+ rssBytes: Number(match[4]) * 1024
34900
+ }];
34901
+ });
34902
+ }
34903
+ async function readBatchedProcessTable() {
34904
+ const { stdout } = await execFileAsync$9("ps", ["-eo", "pid=,ppid=,pcpu=,rss="], { encoding: "utf8" });
34905
+ return parseProcessTable$1(stdout);
34906
+ }
34907
+ function defaultListSessionNames() {
34908
+ return listSessions().pipe(Effect.map((sessions) => sessions.map((session) => session.name)));
34909
+ }
34910
+ function defaultListPanePids(sessionName) {
34911
+ return listPaneValues(sessionName, "#{pane_pid}").pipe(Effect.map((values) => values.map((value) => Number(value.trim())).filter((value) => Number.isFinite(value) && value > 0)));
34912
+ }
34913
+ function buildProcessTotalsByRoot(roots, processes) {
34914
+ const processByPid = new Map(processes.map((process) => [process.pid, process]));
34915
+ const childrenByPpid = /* @__PURE__ */ new Map();
34916
+ const totals = /* @__PURE__ */ new Map();
34917
+ for (const process of processes) {
34918
+ const siblings = childrenByPpid.get(process.ppid);
34919
+ if (siblings) siblings.push(process);
34920
+ else childrenByPpid.set(process.ppid, [process]);
34921
+ }
34922
+ for (const root of roots) {
34923
+ const queue = [root.rootPid];
34924
+ const seen = /* @__PURE__ */ new Set();
34925
+ let cpuPercent = 0;
34926
+ let memoryBytes = 0;
34927
+ while (queue.length > 0) {
34928
+ const pid = queue.pop();
34929
+ if (seen.has(pid)) continue;
34930
+ seen.add(pid);
34931
+ const process = processByPid.get(pid);
34932
+ if (process) {
34933
+ cpuPercent += process.cpuPercent;
34934
+ memoryBytes += process.rssBytes;
34935
+ }
34936
+ for (const child of childrenByPpid.get(pid) ?? []) queue.push(child.pid);
34937
+ }
34938
+ totals.set(root.rootPid, {
34939
+ processCount: seen.size,
34940
+ cpuPercent: roundOneDecimal$3(cpuPercent),
34941
+ memoryBytes
34942
+ });
34943
+ }
34944
+ return totals;
34945
+ }
34946
+ function computeAgentCostStats(events, nowMs) {
34947
+ const burnStartMs = nowMs - BURN_WINDOW_MS;
34948
+ let recentBillable = 0;
34949
+ let recentHypothetical = 0;
34950
+ let totalUsd = 0;
34951
+ for (const event of events) {
34952
+ const eventCost = event.hypotheticalCost ?? event.cost;
34953
+ if (isSubscriptionCovered(event)) recentHypothetical += Date.parse(event.ts) >= burnStartMs ? eventCost : 0;
34954
+ else {
34955
+ totalUsd += event.cost;
34956
+ recentBillable += Date.parse(event.ts) >= burnStartMs ? event.cost : 0;
34957
+ }
34958
+ }
34959
+ return {
34960
+ burnUsdPerHour: roundCurrency(recentBillable * 2),
34961
+ hypotheticalUsdPerHour: roundCurrency(recentHypothetical * 2),
34962
+ totalUsd: roundCurrency(totalUsd)
34963
+ };
34964
+ }
34965
+ function isSubscriptionCovered(event) {
34966
+ return event.subscriptionCovered === true || event.source === "subscription-covered";
34967
+ }
34968
+ function getIdleMinutes(lastActivity, nowMs) {
34969
+ if (!lastActivity) return 0;
34970
+ const lastActivityMs = Date.parse(lastActivity);
34971
+ if (!Number.isFinite(lastActivityMs)) return 0;
34972
+ return Math.max(0, Math.floor((nowMs - lastActivityMs) / 6e4));
34973
+ }
34974
+ function roundOneDecimal$3(value) {
34975
+ return Math.round(value * 10) / 10;
34976
+ }
34977
+ function roundCurrency(value) {
34978
+ return Math.round(value * 100) / 100;
34979
+ }
34980
+ //#endregion
34981
+ //#region services/event-loop-monitor.ts
34982
+ init_activity_logger();
34983
+ const EVENT_LOOP_MONITOR_WINDOW_MS = 6e4;
34984
+ let lastSample = {
34985
+ p50: 0,
34986
+ p99: 0,
34987
+ max: 0,
34988
+ unit: "ms",
34989
+ sampledAt: null,
34990
+ windowMs: EVENT_LOOP_MONITOR_WINDOW_MS
34991
+ };
34992
+ let timer$3 = null;
34993
+ let activeHistogram = null;
34994
+ function nsToRoundedMs(ns) {
34995
+ if (!Number.isFinite(ns) || ns <= 0) return 0;
34996
+ return Math.round(ns / 1e6 * 100) / 100;
34997
+ }
34998
+ function sampleEventLoopDelay(histogram, deps = {}) {
34999
+ const warnThresholdMs = deps.warnThresholdMs ?? 100;
35000
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
35001
+ const warn = deps.warn ?? ((message) => console.warn(message));
35002
+ const emit = deps.emit ?? emitActivityEntrySync;
35003
+ const sample = {
35004
+ p50: nsToRoundedMs(histogram.percentile(50)),
35005
+ p99: nsToRoundedMs(histogram.percentile(99)),
35006
+ max: nsToRoundedMs(histogram.max),
35007
+ unit: "ms",
35008
+ sampledAt: now().toISOString(),
35009
+ windowMs: EVENT_LOOP_MONITOR_WINDOW_MS
35010
+ };
35011
+ lastSample = sample;
35012
+ if (sample.p99 > warnThresholdMs) {
35013
+ const message = `Dashboard event loop p99 delay ${sample.p99}ms exceeded ${warnThresholdMs}ms`;
35014
+ warn(`[event-loop-monitor] ${message}`);
35015
+ emit({
35016
+ source: "dashboard",
35017
+ level: "warn",
35018
+ message,
35019
+ details: `p50=${sample.p50}ms p99=${sample.p99}ms max=${sample.max}ms over ${sample.windowMs}ms`
35020
+ });
35021
+ }
35022
+ histogram.reset();
35023
+ return sample;
35024
+ }
35025
+ function getEventLoopDelaySample() {
35026
+ return lastSample;
35027
+ }
35028
+ function startEventLoopMonitor(deps = {}) {
35029
+ if (timer$3) return;
35030
+ const intervalMs = deps.intervalMs ?? 6e4;
35031
+ activeHistogram = (deps.createHistogram ?? (() => monitorEventLoopDelay({ resolution: 20 })))();
35032
+ activeHistogram.enable();
35033
+ timer$3 = setInterval(() => {
35034
+ if (!activeHistogram) return;
35035
+ sampleEventLoopDelay(activeHistogram, deps);
35036
+ }, intervalMs);
35037
+ timer$3.unref?.();
35038
+ }
35039
+ function stopEventLoopMonitor() {
35040
+ if (timer$3) {
35041
+ clearInterval(timer$3);
35042
+ timer$3 = null;
35043
+ }
35044
+ activeHistogram?.disable();
35045
+ activeHistogram = null;
35046
+ }
35047
+ //#endregion
35048
+ //#region routes/resources/core-services.ts
35049
+ function buildCoreServices(options = {}) {
35050
+ const nowMs = options.nowMs ?? Date.now();
35051
+ const processInfo = options.processInfo ?? getCurrentProcessInfo();
35052
+ const eventLoopSample = options.eventLoopSample ?? getEventLoopDelaySample();
35053
+ const deaconStatus = options.deaconStatus ?? readDurableDeaconStatus();
35054
+ const supportFleet = aggregateSupportFleet(options.supportProcesses ?? []);
35055
+ return [
35056
+ {
35057
+ id: "dashboard",
35058
+ label: "Dashboard server",
35059
+ status: "running",
35060
+ cpuPercent: processInfo.cpuPercent,
35061
+ memoryBytes: processInfo.memoryBytes,
35062
+ memberCount: 1,
35063
+ nodeVersion: processInfo.nodeVersion,
35064
+ distPath: processInfo.distPath,
35065
+ uptime: formatDuration(processInfo.uptimeSeconds),
35066
+ uptimeSeconds: Math.round(processInfo.uptimeSeconds),
35067
+ eventLoopP99Ms: eventLoopSample.p99,
35068
+ actions: [{
35069
+ label: "Metrics",
35070
+ href: "/api/metrics/summary",
35071
+ method: "GET"
35072
+ }]
35073
+ },
35074
+ {
35075
+ id: "deacon",
35076
+ label: "Deacon",
35077
+ status: deaconStatus.isRunning ? "running" : "stopped",
35078
+ cpuPercent: 0,
35079
+ memoryBytes: 0,
35080
+ memberCount: deaconStatus.pid ? 1 : 0,
35081
+ pid: deaconStatus.pid,
35082
+ lastTickAgeSeconds: getLastTickAgeSeconds(deaconStatus, nowMs),
35083
+ patrolCycle: deaconStatus.lastPatrol?.cycle ?? deaconStatus.state?.patrolCycle ?? 0,
35084
+ patrolSummaryCount: deaconStatus.lastPatrol?.actions.length ?? 0,
35085
+ stuckCount: deaconStatus.state?.stuckCount ?? 0,
35086
+ actions: [
35087
+ {
35088
+ label: "Status",
35089
+ href: "/api/deacon/status",
35090
+ method: "GET"
35091
+ },
35092
+ {
35093
+ label: "Logs",
35094
+ href: "/api/deacon/logs",
35095
+ method: "GET"
35096
+ },
35097
+ {
35098
+ label: "Patrol",
35099
+ href: "/api/deacon/patrol",
35100
+ method: "POST"
35101
+ }
35102
+ ]
35103
+ },
35104
+ {
35105
+ id: "support-fleet",
35106
+ label: "Support fleet",
35107
+ status: supportFleet.memberCount > 0 ? "running" : "unknown",
35108
+ cpuPercent: supportFleet.cpuPercent,
35109
+ memoryBytes: supportFleet.memoryBytes,
35110
+ memberCount: supportFleet.memberCount,
35111
+ members: supportFleet.members,
35112
+ actions: []
35113
+ }
35114
+ ];
35115
+ }
35116
+ function getCoreServicesSnapshot() {
35117
+ return buildCoreServices();
35118
+ }
35119
+ function getCurrentProcessInfo() {
35120
+ return {
35121
+ uptimeSeconds: process.uptime(),
35122
+ nodeVersion: process.version,
35123
+ distPath: process.argv.find((arg) => arg.includes("/dist/")) ?? process.argv[1] ?? "",
35124
+ cpuPercent: 0,
35125
+ memoryBytes: process.memoryUsage().rss
35126
+ };
35127
+ }
35128
+ function aggregateSupportFleet(processes) {
35129
+ const supportProcesses = processes.filter(isSupportFleetProcess);
35130
+ const members = supportProcesses.map((process) => process.command);
35131
+ return {
35132
+ cpuPercent: roundOneDecimal$2(supportProcesses.reduce((sum, process) => sum + process.cpuPercent, 0)),
35133
+ memoryBytes: supportProcesses.reduce((sum, process) => sum + process.memoryBytes, 0),
35134
+ memberCount: supportProcesses.length,
35135
+ members
35136
+ };
35137
+ }
35138
+ function isSupportFleetProcess(process) {
35139
+ return /\b(traefik|smee|pty-supervisor|pty-supervisors|tldr)\b/i.test(process.command);
35140
+ }
35141
+ function getLastTickAgeSeconds(deaconStatus, nowMs) {
35142
+ const timestamp = deaconStatus.lastPatrol?.timestamp ?? deaconStatus.state?.lastPatrol;
35143
+ if (!timestamp) return null;
35144
+ const tickMs = Date.parse(timestamp);
35145
+ if (!Number.isFinite(tickMs)) return null;
35146
+ return Math.max(0, Math.floor((nowMs - tickMs) / 1e3));
35147
+ }
35148
+ function formatDuration(totalSeconds) {
35149
+ const roundedSeconds = Math.max(0, Math.floor(totalSeconds));
35150
+ const days = Math.floor(roundedSeconds / 86400);
35151
+ const hours = Math.floor(roundedSeconds % 86400 / 3600);
35152
+ const minutes = Math.floor(roundedSeconds % 3600 / 60);
35153
+ const seconds = roundedSeconds % 60;
35154
+ if (days > 0) return `${days}d ${hours}h`;
35155
+ if (hours > 0) return `${hours}h ${minutes}m`;
35156
+ if (minutes > 0) return `${minutes}m ${seconds}s`;
35157
+ return `${seconds}s`;
35158
+ }
35159
+ function roundOneDecimal$2(value) {
35160
+ return Math.round(value * 10) / 10;
35161
+ }
35162
+ //#endregion
35163
+ //#region routes/resources/forecast.ts
35164
+ init_paths();
35165
+ let forecastStateFileForTests = null;
35166
+ function buildCapacityForecast(stacks, options) {
35167
+ const records = loadForecastStore().peaks;
35168
+ const median = medianPeak(Object.values(records).map((record) => record.peakRamBytes));
35169
+ return {
35170
+ stacks: stacks.filter((stack) => stack.services.every((service) => service.status === "stopped")).map((stack) => {
35171
+ const record = records[stack.id];
35172
+ const predictedRamBytes = record?.peakRamBytes ?? median;
35173
+ return {
35174
+ stackId: stack.id,
35175
+ issueId: stack.issueId,
35176
+ composeProject: stack.composeProject,
35177
+ predictedRamBytes,
35178
+ predictedLoad: record?.peakCpuPercent ?? 0,
35179
+ approximate: true,
35180
+ source: record ? "last-run-peak" : "fleet-median"
35181
+ };
35182
+ }),
35183
+ headroom: {
35184
+ freeRamBytes: options.hostVitals.mem.availableBytes,
35185
+ loadHeadroom: Math.max(0, 100 - options.hostVitals.cpu.percent)
35186
+ }
35187
+ };
35188
+ }
35189
+ function forecastStateFile() {
35190
+ return forecastStateFileForTests ?? join$1(getOverdeckHome(), "resource-forecast-peaks.json");
35191
+ }
35192
+ function loadForecastStore() {
35193
+ const file = forecastStateFile();
35194
+ if (!existsSync$1(file)) return { peaks: {} };
35195
+ try {
35196
+ return { peaks: JSON.parse(readFileSync$1(file, "utf8")).peaks ?? {} };
35197
+ } catch {
35198
+ return { peaks: {} };
35199
+ }
35200
+ }
35201
+ function medianPeak(values) {
35202
+ const sorted = values.filter((value) => value > 0).sort((a, b) => a - b);
35203
+ if (sorted.length === 0) return 0;
35204
+ return sorted[Math.floor(sorted.length / 2)] ?? 0;
35205
+ }
35206
+ //#endregion
35207
+ //#region routes/resources/host-processes.ts
35208
+ const DEFAULT_LIMIT$1 = 10;
35209
+ const DEFAULT_BURST_CPU_THRESHOLD = 50;
35210
+ const DEFAULT_BURST_RETENTION_MS = 3600 * 1e3;
35211
+ const retainedBursts = /* @__PURE__ */ new Map();
35212
+ function buildHostProcesses(processes, options = {}) {
35213
+ const nowMs = options.nowMs ?? Date.now();
35214
+ const limit = options.limit ?? DEFAULT_LIMIT$1;
35215
+ const burstCpuThreshold = options.burstCpuThreshold ?? DEFAULT_BURST_CPU_THRESHOLD;
35216
+ const burstRetentionMs = options.burstRetentionMs ?? DEFAULT_BURST_RETENTION_MS;
35217
+ const coreServicePids = new Set(options.coreServicePids ?? []);
35218
+ const processByPid = new Map(processes.map((process) => [process.pid, process]));
35219
+ const agentRootByPid = new Map((options.agentSessions ?? []).map((session) => [session.rootPid, session]));
35220
+ const groups = /* @__PURE__ */ new Map();
35221
+ for (const process of processes) {
35222
+ if (coreServicePids.has(process.pid) || agentRootByPid.has(process.pid) || isDockerProcess(process)) continue;
35223
+ const family = classifyCommandFamily(process.command);
35224
+ const owner = resolveOwner(process, processByPid, agentRootByPid, family);
35225
+ const id = [
35226
+ family,
35227
+ owner.agentId ?? owner.label,
35228
+ owner.issueId ?? "host"
35229
+ ].join(":");
35230
+ const existing = groups.get(id);
35231
+ if (existing) {
35232
+ existing.pidCount += 1;
35233
+ existing.pids.push(process.pid);
35234
+ existing.cpuPercent += process.cpuPercent;
35235
+ existing.memoryBytes += process.memoryBytes;
35236
+ continue;
35237
+ }
35238
+ groups.set(id, {
35239
+ id,
35240
+ family,
35241
+ label: family,
35242
+ owner,
35243
+ pidCount: 1,
35244
+ pids: [process.pid],
35245
+ cpuPercent: process.cpuPercent,
35246
+ memoryBytes: process.memoryBytes
35247
+ });
35248
+ }
35249
+ const activeRows = [...groups.values()].map((group) => {
35250
+ const retained = retainedBursts.get(group.id);
35251
+ const peakCpuPercent = Math.max(group.cpuPercent, retained?.row.peakCpuPercent ?? 0);
35252
+ const peakMemoryBytes = Math.max(group.memoryBytes, retained?.row.peakMemoryBytes ?? 0);
35253
+ const row = {
35254
+ ...group,
35255
+ pids: group.pids.sort((a, b) => a - b),
35256
+ cpuPercent: roundOneDecimal$1(group.cpuPercent),
35257
+ peakCpuPercent: roundOneDecimal$1(peakCpuPercent),
35258
+ peakMemoryBytes
35259
+ };
35260
+ if (peakCpuPercent >= burstCpuThreshold) retainedBursts.set(group.id, {
35261
+ row,
35262
+ lastSeenMs: nowMs
35263
+ });
35264
+ else retainedBursts.delete(group.id);
35265
+ return row;
35266
+ });
35267
+ const activeGroupIds = new Set(activeRows.map((row) => row.id));
35268
+ const retainedRows = [];
35269
+ for (const [groupId, retained] of retainedBursts) {
35270
+ if (activeGroupIds.has(groupId)) continue;
35271
+ const retainedUntilMs = retained.lastSeenMs + burstRetentionMs;
35272
+ if (retainedUntilMs <= nowMs) {
35273
+ retainedBursts.delete(groupId);
35274
+ continue;
35275
+ }
35276
+ retainedRows.push({
35277
+ ...retained.row,
35278
+ cpuPercent: 0,
35279
+ memoryBytes: 0,
35280
+ retainedUntil: new Date(retainedUntilMs).toISOString(),
35281
+ note: buildRetainedBurstNote(retained.row, options.spikeAnnotations)
35282
+ });
35283
+ }
35284
+ return selectTopRows([...activeRows, ...retainedRows], limit);
35285
+ }
35286
+ function getHostProcessesSnapshot() {
35287
+ return buildHostProcesses([]);
35288
+ }
35289
+ function isDockerProcess(process) {
35290
+ return /\b(docker|containerd|kubepods)\b/i.test(process.cgroup ?? "");
35291
+ }
35292
+ function classifyCommandFamily(command) {
35293
+ const normalized = command.toLowerCase();
35294
+ if (normalized.includes("vitest")) return "vitest workers";
35295
+ if (normalized.includes("chrome") || normalized.includes("chromium")) return "chrome renderers";
35296
+ if (/\bmvn\b|maven/.test(normalized)) return "mvn";
35297
+ if (/\bnode\b/.test(normalized)) return "node";
35298
+ if (/\bbun\b/.test(normalized)) return "bun";
35299
+ return command.trim().split(/\s+/)[0] || "unknown";
35300
+ }
35301
+ function resolveOwner(process, processByPid, agentRootByPid, family) {
35302
+ if (family === "chrome renderers") return { label: "your browser" };
35303
+ let current = process;
35304
+ const seenPids = /* @__PURE__ */ new Set();
35305
+ while (current && !seenPids.has(current.pid)) {
35306
+ seenPids.add(current.pid);
35307
+ const agentSession = agentRootByPid.get(current.pid);
35308
+ if (agentSession) return {
35309
+ label: `spawned by ${agentSession.agentId}`,
35310
+ agentId: agentSession.agentId,
35311
+ issueId: agentSession.issueId
35312
+ };
35313
+ current = processByPid.get(current.ppid);
35314
+ }
35315
+ return { label: "host" };
35316
+ }
35317
+ function buildRetainedBurstNote(row, annotations) {
35318
+ const matchingAnnotation = annotations?.find((annotation) => {
35319
+ if (annotation.targetId && annotation.targetId === row.owner.agentId) return true;
35320
+ return annotation.label.includes(row.family) || annotation.label.includes(row.owner.label);
35321
+ });
35322
+ if (!matchingAnnotation) return "caused spike";
35323
+ return `caused spike: ${matchingAnnotation.label}`;
35324
+ }
35325
+ function selectTopRows(rows, limit) {
35326
+ const topByCpu = [...rows].sort((a, b) => b.cpuPercent - a.cpuPercent || b.peakCpuPercent - a.peakCpuPercent).slice(0, limit);
35327
+ const topByMemory = [...rows].sort((a, b) => b.memoryBytes - a.memoryBytes || b.peakMemoryBytes - a.peakMemoryBytes).slice(0, limit);
35328
+ const merged = /* @__PURE__ */ new Map();
35329
+ for (const row of [...topByCpu, ...topByMemory]) merged.set(row.id, row);
35330
+ return [...merged.values()].sort((a, b) => b.cpuPercent - a.cpuPercent || b.peakCpuPercent - a.peakCpuPercent).slice(0, limit);
35331
+ }
35332
+ function roundOneDecimal$1(value) {
35333
+ return Math.round(value * 10) / 10;
35334
+ }
35335
+ //#endregion
35336
+ //#region routes/resources/host-vitals.ts
35337
+ const CPU_SPARK_MAX = 30;
35338
+ let cpuSpark = [];
35339
+ let cachedDockerCounts = {
35340
+ containers: 0,
35341
+ running: 0,
35342
+ stacks: 0,
35343
+ networks: 0,
35344
+ networkPool: {
35345
+ used: 0,
35346
+ total: 31
35347
+ }
35348
+ };
35349
+ function buildHostVitalsSnapshot(options = {}) {
35350
+ const cpuPercent = roundOneDecimal(options.cpuPercent ?? 0);
35351
+ cpuSpark.push(cpuPercent);
35352
+ cpuSpark = cpuSpark.slice(-CPU_SPARK_MAX);
35353
+ const dockerCounts = options.dockerStale ? cachedDockerCounts : computeDockerCounts(options.containers ?? [], options.networkCount ?? 0, options.networkPoolTotal ?? 31);
35354
+ if (!options.dockerStale) cachedDockerCounts = dockerCounts;
35355
+ const memory = options.mem ?? getDefaultMemoryVitals();
35356
+ const disk = options.disk ?? {
35357
+ usedBytes: 0,
35358
+ freeBytes: 0,
35359
+ reclaimableBytes: 0
35360
+ };
35361
+ const agentCounts = computeAgentCounts(options.agents ?? [], options.agentFleet ?? {}, options.nowMs ?? Date.now());
35362
+ return {
35363
+ stale: options.dockerStale === true,
35364
+ cpu: {
35365
+ percent: cpuPercent,
35366
+ load: options.load ?? normalizeLoad(loadavg$1()),
35367
+ spark: [...cpuSpark]
35368
+ },
35369
+ mem: memory,
35370
+ disk: {
35371
+ usedBytes: disk.usedBytes,
35372
+ freeBytes: disk.freeBytes,
35373
+ reclaimableBytes: disk.reclaimableBytes ?? 0
35374
+ },
35375
+ docker: {
35376
+ ...dockerCounts,
35377
+ stale: options.dockerStale === true
35378
+ },
35379
+ agents: agentCounts
35380
+ };
35381
+ }
35382
+ function computeDockerCounts(containers, networkCount, networkPoolTotal) {
35383
+ const stacks = /* @__PURE__ */ new Set();
35384
+ for (const container of containers) {
35385
+ const project = container.labels?.["com.docker.compose.project"];
35386
+ if (project) stacks.add(project);
35387
+ }
35388
+ return {
35389
+ containers: containers.length,
35390
+ running: containers.filter((container) => container.status === "running").length,
35391
+ stacks: stacks.size,
35392
+ networks: networkCount,
35393
+ networkPool: {
35394
+ used: networkCount,
35395
+ total: networkPoolTotal
35396
+ }
35397
+ };
35398
+ }
35399
+ function computeAgentCounts(agents, fleet, nowMs) {
35400
+ return {
35401
+ sessions: agents.length,
35402
+ active: agents.filter((agent) => agent.hasLiveTmuxSession).length,
35403
+ idleOver15m: agents.filter((agent) => {
35404
+ const lastActivityMs = Date.parse(agent.lastActivity ?? "");
35405
+ return Number.isFinite(lastActivityMs) && nowMs - lastActivityMs > 900 * 1e3;
35406
+ }).length,
35407
+ burnUsdPerHour: fleet.burnUsdPerHour ?? 0,
35408
+ hypotheticalUsdPerHour: fleet.hypotheticalUsdPerHour ?? 0,
35409
+ totalUsd: fleet.totalUsd ?? 0
35410
+ };
35411
+ }
35412
+ function getDefaultMemoryVitals() {
35413
+ const total = totalmem$1();
35414
+ const available = freemem$1();
35415
+ return {
35416
+ usedBytes: Math.max(0, total - available),
35417
+ availableBytes: available,
35418
+ swapUsedBytes: 0,
35419
+ swapTotalBytes: 0
35420
+ };
35421
+ }
35422
+ function normalizeLoad(load) {
35423
+ return [
35424
+ roundOneDecimal(load[0] ?? 0),
35425
+ roundOneDecimal(load[1] ?? 0),
35426
+ roundOneDecimal(load[2] ?? 0)
35427
+ ];
35428
+ }
35429
+ function roundOneDecimal(value) {
35430
+ return Math.round(value * 10) / 10;
35431
+ }
35432
+ //#endregion
35433
+ //#region routes/resources/limits.ts
35434
+ const OOM_WINDOW_MS = 1440 * 60 * 1e3;
35435
+ let cachedOomEvents = [];
35436
+ function enrichContainersWithLimits(containers, options = {}) {
35437
+ const nowMs = options.nowMs ?? Date.now();
35438
+ const oomEvents = options.oomEvents ?? cachedOomEvents;
35439
+ return containers.map((container) => {
35440
+ const limitBytes = getLimitBytes(container.memoryLimit);
35441
+ const percent = limitBytes === null ? void 0 : Math.round((container.memoryUsage ?? 0) / limitBytes * 100);
35442
+ return {
35443
+ ...container,
35444
+ memLimitBytes: limitBytes,
35445
+ ...percent === void 0 ? {} : { memPercentOfLimit: percent },
35446
+ oomKills24h: countRecentOomEvents(container, oomEvents, nowMs),
35447
+ ...composeFileHint(container.labels)
35448
+ };
35449
+ });
35450
+ }
35451
+ function getLimitBytes(memoryLimit) {
35452
+ if (!memoryLimit || memoryLimit <= 0 || !Number.isFinite(memoryLimit)) return null;
35453
+ return memoryLimit;
35454
+ }
35455
+ function countRecentOomEvents(container, events, nowMs) {
35456
+ const cutoffMs = nowMs - OOM_WINDOW_MS;
35457
+ return events.filter((event) => {
35458
+ const eventMs = Date.parse(event.timestamp);
35459
+ if (!Number.isFinite(eventMs) || eventMs < cutoffMs || eventMs > nowMs) return false;
35460
+ if (event.containerId && container.id.startsWith(event.containerId)) return true;
35461
+ if (event.containerName && event.containerName === container.name) return true;
35462
+ return false;
35463
+ }).length;
35464
+ }
35465
+ function composeFileHint(labels) {
35466
+ const composeFile = labels?.["com.docker.compose.project.config_files"];
35467
+ if (!composeFile) return {};
35468
+ return { composeFile: composeFile.split(",")[0]?.trim() };
35469
+ }
35470
+ //#endregion
35471
+ //#region routes/resources/reclaim.ts
35472
+ init_review_status();
35473
+ const RECLAIM_THRESHOLD_BYTES = 500 * 1024 ** 2;
35474
+ const VENV_THRESHOLD_BYTES = 1024 ** 3;
35475
+ let cachedVenvCandidates = [];
35476
+ let venvDelete = async (path) => {
35477
+ await rm(path, {
35478
+ recursive: true,
35479
+ force: true
35480
+ });
35481
+ };
35482
+ let issueClosedReader = (issueId) => isClosedReviewStatus(issueId);
35483
+ let projectRootReader = () => resolve$1(process.cwd(), "..", "..");
35484
+ function buildReclaimPayload(stacks, agents, options = {}) {
35485
+ const liveIssueIds = new Set(agents.filter((agent) => agent.hasLiveTmuxSession === true).map((agent) => agent.issueId?.toUpperCase()).filter((issueId) => Boolean(issueId)));
35486
+ const candidates = [...stacks.filter((stack) => stack.issueId && isClosedStack(stack) && !liveIssueIds.has(stack.issueId.toUpperCase())).map((stack) => ({
35487
+ kind: "stack",
35488
+ label: stack.issueId ?? stack.composeProject,
35489
+ why: "Issue is merged or closed and no live agent references it.",
35490
+ ramBytes: stack.aggregates.memoryBytes,
35491
+ diskBytes: stack.aggregates.diskBytes,
35492
+ action: `GET /api/resources/stacks/${stack.issueId}/teardown-estimate`,
35493
+ issueId: stack.issueId ?? void 0
35494
+ })), ...(options.venvs ?? cachedVenvCandidates).filter((venv) => venv.diskBytes >= VENV_THRESHOLD_BYTES && issueClosedReader(venv.issueId)).map((venv) => ({
35495
+ kind: "venv",
35496
+ label: `${venv.issueId} virtualenv`,
35497
+ why: "Closed issue has an orphaned workspace .venv over 1 GB.",
35498
+ ramBytes: 0,
35499
+ diskBytes: venv.diskBytes,
35500
+ action: `DELETE /api/resources/venvs/${venv.issueId}`,
35501
+ issueId: venv.issueId
35502
+ }))];
35503
+ return {
35504
+ reclaimCandidates: candidates,
35505
+ reclaimTotals: {
35506
+ ramBytes: candidates.reduce((sum, candidate) => sum + candidate.ramBytes, 0),
35507
+ diskBytes: candidates.reduce((sum, candidate) => sum + candidate.diskBytes, 0)
35508
+ },
35509
+ reclaimThresholdBytes: RECLAIM_THRESHOLD_BYTES
35510
+ };
35511
+ }
35512
+ const deleteResourceVenvRoute = HttpRouter.add("DELETE", "/api/resources/venvs/:issue", httpHandler(Effect.gen(function* () {
35513
+ return yield* deleteResourceVenvEffect((yield* HttpRouter.schemaParams()).issue);
35514
+ })));
35515
+ function deleteResourceVenvEffect(issue) {
35516
+ return Effect.gen(function* () {
35517
+ const issueId = issue.toUpperCase();
35518
+ if (!issueClosedReader(issueId)) return jsonResponse({
35519
+ ok: false,
35520
+ error: `${issueId} is not closed; refusing to delete workspace venv.`
35521
+ }, { status: 409 });
35522
+ const venvPath = join$1(projectRootReader(), "workspaces", `feature-${issueId.toLowerCase()}`, ".venv");
35523
+ yield* Effect.tryPromise({
35524
+ try: () => venvDelete(venvPath),
35525
+ catch: (error) => error
35526
+ }).pipe(Effect.catch(() => Effect.succeed(void 0)));
35527
+ return jsonResponse({
35528
+ ok: true,
35529
+ issueId,
35530
+ path: venvPath
35531
+ });
35532
+ });
35533
+ }
35534
+ function isClosedStack(stack) {
35535
+ return stack.phase === "merged";
35536
+ }
35537
+ function isClosedReviewStatus(issueId) {
35538
+ return getReviewStatusSync(issueId)?.mergeStatus === "merged";
35539
+ }
35540
+ //#endregion
35541
+ //#region routes/resources/spawn-gate.ts
35542
+ let readSystemHealthSnapshot = () => getSystemHealthSnapshot();
35543
+ function mapSpawnGateDecision(decision) {
35544
+ const reason = decision.error ?? decision.warnings[0]?.message ?? "";
35545
+ return {
35546
+ state: decision.blocked ? "BLOCKED" : decision.requiresAcknowledgement ? "SOFT" : "OPEN",
35547
+ reason,
35548
+ warnings: decision.warnings,
35549
+ pressure: spawnGatePressure(decision)
35550
+ };
35551
+ }
35552
+ function getSpawnGatePayloadEffect() {
35553
+ return Effect.tryPromise({
35554
+ try: () => readSystemHealthSnapshot(),
35555
+ catch: (error) => error
35556
+ }).pipe(Effect.map((health) => mapSpawnGateDecision(evaluateSpawnGuardrails(health))), Effect.catch(() => Effect.succeed({
35557
+ state: "SOFT",
35558
+ reason: "Spawn gate health is unavailable.",
35559
+ warnings: [],
35560
+ pressure: 0,
35561
+ stale: true
35562
+ })));
35563
+ }
35564
+ function spawnGatePressure(decision) {
35565
+ if (decision.blocked) return 100;
35566
+ if (decision.requiresAcknowledgement) return 67;
35567
+ return 12;
35568
+ }
35569
+ //#endregion
35570
+ //#region routes/resources/snapshot.ts
35571
+ init_agents$1();
35572
+ init_tmux();
34705
35573
  /** Build the GET /api/resources response from the SQLite agents table. */
34706
35574
  function getResourcesEffect() {
34707
35575
  return Effect.gen(function* () {
34708
- const containers = [];
35576
+ const containers = enrichContainersWithLimits(getCurrentDockerStats());
35577
+ const agentStats = yield* getAgentStatsSnapshotEffect();
35578
+ const spawnGate = yield* getSpawnGatePayloadEffect();
34709
35579
  const stoppedContainers = [];
34710
35580
  const sessions = yield* listSessions().pipe(Effect.catch(() => Effect.succeed([])));
34711
35581
  const tmuxSessionNames = new Set(sessions.map((s) => s.name));
35582
+ const agents = listAgentStates().filter((state) => state.status !== "stopped").map((state) => ({
35583
+ ...state,
35584
+ hasLiveTmuxSession: tmuxSessionNames.has(state.id)
35585
+ }));
35586
+ const agentStatsById = new Map(agentStats.agents.map((agent) => [agent.id, agent]));
35587
+ const baseHostVitals = buildHostVitalsSnapshot({
35588
+ containers,
35589
+ agents: agents.map((agent) => ({
35590
+ id: String(agent.id),
35591
+ lastActivity: typeof agent.lastActivity === "string" ? agent.lastActivity : void 0,
35592
+ hasLiveTmuxSession: agent.hasLiveTmuxSession === true
35593
+ })),
35594
+ agentFleet: agentStats.hostVitals.agents
35595
+ });
35596
+ const stacks = getResourceStacks(containers);
35597
+ const reclaim = buildReclaimPayload(stacks, agents);
35598
+ const hostVitals = {
35599
+ ...baseHostVitals,
35600
+ disk: {
35601
+ ...baseHostVitals.disk,
35602
+ reclaimableBytes: reclaim.reclaimTotals.diskBytes
35603
+ }
35604
+ };
34712
35605
  return jsonResponse({
35606
+ agents: agents.map((agent) => ({
35607
+ ...agent,
35608
+ resourceStats: agentStatsById.get(String(agent.id)) ?? null
35609
+ })),
35610
+ coreServices: getCoreServicesSnapshot(),
34713
35611
  containers,
35612
+ forecast: buildCapacityForecast(stacks, { hostVitals }),
35613
+ hostVitals,
35614
+ hostProcesses: getHostProcessesSnapshot(),
34714
35615
  stoppedContainers,
34715
35616
  networks: [],
35617
+ reclaimCandidates: reclaim.reclaimCandidates,
35618
+ reclaimThresholdBytes: reclaim.reclaimThresholdBytes,
35619
+ reclaimTotals: reclaim.reclaimTotals,
35620
+ spawnGate,
35621
+ stacks,
34716
35622
  volumes: [],
34717
- agents: listAgentStates().filter((state) => state.status !== "stopped").map((state) => ({
34718
- ...state,
34719
- hasLiveTmuxSession: tmuxSessionNames.has(state.id)
34720
- })),
34721
35623
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
34722
35624
  });
34723
35625
  });
34724
35626
  }
34725
35627
  const getResourcesRoute = HttpRouter.add("GET", "/api/resources", httpHandler(getResourcesEffect()));
35628
+ //#endregion
35629
+ //#region routes/resources/containers.ts
35630
+ init_domain_services();
35631
+ let dockerExec = promisify(execFile);
35632
+ function dockerActionErrorPayload(error) {
35633
+ const maybeError = error;
35634
+ return { error: (typeof maybeError.stderr === "string" ? maybeError.stderr.trim() : "") || (typeof maybeError.message === "string" ? maybeError.message : String(error)) };
35635
+ }
34726
35636
  const getContainerHistoryRoute = HttpRouter.add("GET", "/api/resources/:containerId/history", Effect.gen(function* () {
34727
35637
  const containerId = (yield* HttpRouter.params)["containerId"] ?? "";
34728
35638
  if (!/^[a-f0-9]{12,64}$/.test(containerId)) return jsonResponse({ error: "Invalid container ID" }, { status: 400 });
34729
- return jsonResponse({
34730
- timestamps: [],
34731
- cpuPercent: [],
34732
- memoryPercent: []
34733
- });
35639
+ return jsonResponse(getContainerHistory(containerId));
34734
35640
  }));
34735
35641
  const getContainerDetailsRoute = HttpRouter.add("GET", "/api/resources/:containerId/details", Effect.gen(function* () {
34736
35642
  const containerId = (yield* HttpRouter.params)["containerId"] ?? "";
34737
35643
  if (!/^[a-f0-9]{12,64}$/.test(containerId)) return jsonResponse({ error: "Invalid container ID" }, { status: 400 });
34738
35644
  return yield* httpHandler(Effect.gen(function* () {
34739
35645
  const [inspectResult, logsResult] = yield* Effect.tryPromise({
34740
- try: () => Promise.all([execAsync$7(`docker inspect --format '{{json .}}' "${containerId}" 2>/dev/null`, {
35646
+ try: () => Promise.all([dockerExec("docker", [
35647
+ "inspect",
35648
+ "--format",
35649
+ "{{json .}}",
35650
+ containerId
35651
+ ], {
34741
35652
  encoding: "utf-8",
34742
35653
  timeout: 5e3
34743
- }).catch(() => ({ stdout: "null" })), execAsync$7(`docker logs --tail 100 "${containerId}" 2>&1`, {
35654
+ }).catch(() => ({ stdout: "null" })), dockerExec("docker", [
35655
+ "logs",
35656
+ "--tail",
35657
+ "100",
35658
+ containerId
35659
+ ], {
34744
35660
  encoding: "utf-8",
34745
35661
  timeout: 5e3
34746
35662
  }).catch(() => ({ stdout: "" }))]),
@@ -34779,7 +35695,7 @@ const deleteDockerContainerRoute = HttpRouter.add("DELETE", "/api/resources/dock
34779
35695
  const eventStore = yield* EventStoreService;
34780
35696
  if (!/^[a-f0-9]{12,64}$/.test(id)) return jsonResponse({ error: "Invalid container ID" }, { status: 400 });
34781
35697
  yield* Effect.tryPromise({
34782
- try: () => execAsync$7(`docker rm "${id}" 2>&1`, {
35698
+ try: () => dockerExec("docker", ["rm", id], {
34783
35699
  encoding: "utf-8",
34784
35700
  timeout: 1e4
34785
35701
  }),
@@ -34788,14 +35704,16 @@ const deleteDockerContainerRoute = HttpRouter.add("DELETE", "/api/resources/dock
34788
35704
  yield* eventStore.append({
34789
35705
  type: "resources.updated",
34790
35706
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
34791
- payload: { resources: { containers: [] } }
35707
+ payload: { resources: { containers: getCurrentDockerStats() } }
34792
35708
  });
34793
35709
  return jsonResponse({ ok: true });
34794
35710
  })));
34795
- const postPruneContainersRoute = HttpRouter.add("POST", "/api/resources/docker/prune-containers", httpHandler(Effect.gen(function* () {
35711
+ const postRestartContainerRoute = HttpRouter.add("POST", "/api/resources/docker/container/:id/restart", httpHandler(Effect.gen(function* () {
35712
+ const id = (yield* HttpRouter.params)["id"] ?? "";
34796
35713
  const eventStore = yield* EventStoreService;
35714
+ if (!isDockerIdentifier$2(id)) return jsonResponse({ error: "Invalid container ID" }, { status: 400 });
34797
35715
  const { stdout } = yield* Effect.tryPromise({
34798
- try: () => execAsync$7("docker container prune -f 2>&1", {
35716
+ try: () => dockerExec("docker", ["restart", id], {
34799
35717
  encoding: "utf-8",
34800
35718
  timeout: 3e4
34801
35719
  }),
@@ -34804,53 +35722,121 @@ const postPruneContainersRoute = HttpRouter.add("POST", "/api/resources/docker/p
34804
35722
  yield* eventStore.append({
34805
35723
  type: "resources.updated",
34806
35724
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
34807
- payload: { resources: { containers: [] } }
35725
+ payload: { resources: { containers: getCurrentDockerStats() } }
34808
35726
  });
34809
35727
  return jsonResponse({
34810
35728
  ok: true,
35729
+ container: id,
34811
35730
  output: stdout.trim()
34812
35731
  });
34813
35732
  })));
34814
- const deleteDockerNetworkRoute = HttpRouter.add("DELETE", "/api/resources/docker/network/:name", httpHandler(Effect.gen(function* () {
34815
- const name = (yield* HttpRouter.params)["name"] ?? "";
35733
+ const postStartContainerRoute = HttpRouter.add("POST", "/api/resources/docker/container/:id/start", httpHandler(Effect.gen(function* () {
35734
+ const id = (yield* HttpRouter.params)["id"] ?? "";
34816
35735
  const eventStore = yield* EventStoreService;
34817
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name)) return jsonResponse({ error: "Invalid network name" }, { status: 400 });
34818
- yield* Effect.tryPromise({
34819
- try: () => execAsync$7(`docker network rm "${name}" 2>&1`, {
35736
+ if (!isDockerIdentifier$2(id)) return jsonResponse({ error: "Invalid container ID" }, { status: 400 });
35737
+ const { stdout } = yield* Effect.tryPromise({
35738
+ try: () => dockerExec("docker", ["start", id], {
34820
35739
  encoding: "utf-8",
34821
- timeout: 1e4
35740
+ timeout: 3e4
34822
35741
  }),
34823
35742
  catch: (err) => new Error(err instanceof Error ? err.message : String(err))
34824
35743
  });
34825
35744
  yield* eventStore.append({
34826
35745
  type: "resources.updated",
34827
35746
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
34828
- payload: { resources: { containers: [] } }
35747
+ payload: { resources: { containers: getCurrentDockerStats() } }
35748
+ });
35749
+ return jsonResponse({
35750
+ ok: true,
35751
+ container: id,
35752
+ output: stdout.trim()
34829
35753
  });
34830
- return jsonResponse({ ok: true });
34831
35754
  })));
34832
- const deleteDockerVolumeRoute = HttpRouter.add("DELETE", "/api/resources/docker/volume/:name", httpHandler(Effect.gen(function* () {
34833
- const name = (yield* HttpRouter.params)["name"] ?? "";
34834
- const eventStore = yield* EventStoreService;
34835
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name)) return jsonResponse({ error: "Invalid volume name" }, { status: 400 });
34836
- yield* Effect.tryPromise({
34837
- try: () => execAsync$7(`docker volume rm "${name}" 2>&1`, {
35755
+ const getContainerLogsRoute = HttpRouter.add("GET", "/api/resources/docker/container/:id/logs", httpHandler(Effect.gen(function* () {
35756
+ const id = (yield* HttpRouter.params)["id"] ?? "";
35757
+ if (!isDockerIdentifier$2(id)) return jsonResponse({ error: "Invalid container ID" }, { status: 400 });
35758
+ const { stdout } = yield* Effect.tryPromise({
35759
+ try: () => dockerExec("docker", [
35760
+ "logs",
35761
+ "--tail",
35762
+ "200",
35763
+ "--timestamps",
35764
+ id
35765
+ ], {
34838
35766
  encoding: "utf-8",
34839
35767
  timeout: 1e4
34840
35768
  }),
34841
35769
  catch: (err) => new Error(err instanceof Error ? err.message : String(err))
34842
35770
  });
34843
- yield* eventStore.append({
34844
- type: "resources.updated",
34845
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
34846
- payload: { resources: { containers: [] } }
35771
+ return jsonResponse({ logs: stdout });
35772
+ })));
35773
+ function dockerContainerActionEffect(id, action) {
35774
+ return Effect.gen(function* () {
35775
+ if (!isDockerIdentifier$2(id)) return jsonResponse({ error: "Invalid container ID" }, { status: 400 });
35776
+ const eventStore = yield* EventStoreService;
35777
+ const args = action === "stop" ? [
35778
+ "stop",
35779
+ "--time",
35780
+ "30",
35781
+ id
35782
+ ] : [action, id];
35783
+ const timeout = action === "stop" ? 35e3 : 1e4;
35784
+ const result = yield* Effect.tryPromise({
35785
+ try: () => dockerExec("docker", args, {
35786
+ encoding: "utf-8",
35787
+ timeout
35788
+ }),
35789
+ catch: (error) => error
35790
+ }).pipe(Effect.matchEffect({
35791
+ onFailure: (error) => Effect.succeed({
35792
+ ok: false,
35793
+ error
35794
+ }),
35795
+ onSuccess: ({ stdout }) => Effect.succeed({
35796
+ ok: true,
35797
+ stdout
35798
+ })
35799
+ }));
35800
+ if (!result.ok) return jsonResponse(dockerActionErrorPayload(result.error), { status: 500 });
35801
+ const containers = getCurrentDockerStats();
35802
+ yield* eventStore.append({
35803
+ type: "resources.updated",
35804
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
35805
+ payload: { resources: { containers } }
35806
+ });
35807
+ return jsonResponse({
35808
+ ok: true,
35809
+ container: id,
35810
+ action,
35811
+ output: result.stdout.trim(),
35812
+ containers
35813
+ });
34847
35814
  });
34848
- return jsonResponse({ ok: true });
35815
+ }
35816
+ function isDockerIdentifier$2(value) {
35817
+ return /^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(value);
35818
+ }
35819
+ const postStopContainerRoute = HttpRouter.add("POST", "/api/resources/docker/container/:id/stop", httpHandler(Effect.gen(function* () {
35820
+ return yield* dockerContainerActionEffect((yield* HttpRouter.params)["id"] ?? "", "stop");
34849
35821
  })));
34850
- const postPruneVolumesRoute = HttpRouter.add("POST", "/api/resources/docker/prune-volumes", httpHandler(Effect.gen(function* () {
35822
+ const postPauseContainerRoute = HttpRouter.add("POST", "/api/resources/docker/container/:id/pause", httpHandler(Effect.gen(function* () {
35823
+ return yield* dockerContainerActionEffect((yield* HttpRouter.params)["id"] ?? "", "pause");
35824
+ })));
35825
+ const postUnpauseContainerRoute = HttpRouter.add("POST", "/api/resources/docker/container/:id/unpause", httpHandler(Effect.gen(function* () {
35826
+ return yield* dockerContainerActionEffect((yield* HttpRouter.params)["id"] ?? "", "unpause");
35827
+ })));
35828
+ //#endregion
35829
+ //#region routes/resources/prune.ts
35830
+ init_domain_services();
35831
+ const execFileAsync$7 = promisify(execFile);
35832
+ const postPruneContainersRoute = HttpRouter.add("POST", "/api/resources/docker/prune-containers", httpHandler(Effect.gen(function* () {
34851
35833
  const eventStore = yield* EventStoreService;
34852
35834
  const { stdout } = yield* Effect.tryPromise({
34853
- try: () => execAsync$7("docker volume prune -f 2>&1", {
35835
+ try: () => execFileAsync$7("docker", [
35836
+ "container",
35837
+ "prune",
35838
+ "-f"
35839
+ ], {
34854
35840
  encoding: "utf-8",
34855
35841
  timeout: 3e4
34856
35842
  }),
@@ -34859,41 +35845,65 @@ const postPruneVolumesRoute = HttpRouter.add("POST", "/api/resources/docker/prun
34859
35845
  yield* eventStore.append({
34860
35846
  type: "resources.updated",
34861
35847
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
34862
- payload: { resources: { containers: [] } }
35848
+ payload: { resources: { containers: getCurrentDockerStats() } }
34863
35849
  });
34864
35850
  return jsonResponse({
34865
35851
  ok: true,
34866
35852
  output: stdout.trim()
34867
35853
  });
34868
35854
  })));
34869
- const postRestartContainerRoute = HttpRouter.add("POST", "/api/resources/docker/container/:id/restart", httpHandler(Effect.gen(function* () {
34870
- const id = (yield* HttpRouter.params)["id"] ?? "";
35855
+ const deleteDockerNetworkRoute = HttpRouter.add("DELETE", "/api/resources/docker/network/:name", httpHandler(Effect.gen(function* () {
35856
+ const name = (yield* HttpRouter.params)["name"] ?? "";
34871
35857
  const eventStore = yield* EventStoreService;
34872
- if (!id) return jsonResponse({ error: "Container ID required" }, { status: 400 });
34873
- const { stdout } = yield* Effect.tryPromise({
34874
- try: () => execAsync$7(`docker restart "${id}"`, {
35858
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name)) return jsonResponse({ error: "Invalid network name" }, { status: 400 });
35859
+ yield* Effect.tryPromise({
35860
+ try: () => execFileAsync$7("docker", [
35861
+ "network",
35862
+ "rm",
35863
+ name
35864
+ ], {
34875
35865
  encoding: "utf-8",
34876
- timeout: 3e4
35866
+ timeout: 1e4
34877
35867
  }),
34878
35868
  catch: (err) => new Error(err instanceof Error ? err.message : String(err))
34879
35869
  });
34880
35870
  yield* eventStore.append({
34881
35871
  type: "resources.updated",
34882
35872
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
34883
- payload: { resources: { containers: [] } }
35873
+ payload: { resources: { containers: getCurrentDockerStats() } }
34884
35874
  });
34885
- return jsonResponse({
34886
- ok: true,
34887
- container: id,
34888
- output: stdout.trim()
35875
+ return jsonResponse({ ok: true });
35876
+ })));
35877
+ const deleteDockerVolumeRoute = HttpRouter.add("DELETE", "/api/resources/docker/volume/:name", httpHandler(Effect.gen(function* () {
35878
+ const name = (yield* HttpRouter.params)["name"] ?? "";
35879
+ const eventStore = yield* EventStoreService;
35880
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name)) return jsonResponse({ error: "Invalid volume name" }, { status: 400 });
35881
+ yield* Effect.tryPromise({
35882
+ try: () => execFileAsync$7("docker", [
35883
+ "volume",
35884
+ "rm",
35885
+ name
35886
+ ], {
35887
+ encoding: "utf-8",
35888
+ timeout: 1e4
35889
+ }),
35890
+ catch: (err) => new Error(err instanceof Error ? err.message : String(err))
35891
+ });
35892
+ yield* eventStore.append({
35893
+ type: "resources.updated",
35894
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
35895
+ payload: { resources: { containers: getCurrentDockerStats() } }
34889
35896
  });
35897
+ return jsonResponse({ ok: true });
34890
35898
  })));
34891
- const postStartContainerRoute = HttpRouter.add("POST", "/api/resources/docker/container/:id/start", httpHandler(Effect.gen(function* () {
34892
- const id = (yield* HttpRouter.params)["id"] ?? "";
35899
+ const postPruneVolumesRoute = HttpRouter.add("POST", "/api/resources/docker/prune-volumes", httpHandler(Effect.gen(function* () {
34893
35900
  const eventStore = yield* EventStoreService;
34894
- if (!id) return jsonResponse({ error: "Container ID required" }, { status: 400 });
34895
35901
  const { stdout } = yield* Effect.tryPromise({
34896
- try: () => execAsync$7(`docker start "${id}"`, {
35902
+ try: () => execFileAsync$7("docker", [
35903
+ "volume",
35904
+ "prune",
35905
+ "-f"
35906
+ ], {
34897
35907
  encoding: "utf-8",
34898
35908
  timeout: 3e4
34899
35909
  }),
@@ -34902,27 +35912,374 @@ const postStartContainerRoute = HttpRouter.add("POST", "/api/resources/docker/co
34902
35912
  yield* eventStore.append({
34903
35913
  type: "resources.updated",
34904
35914
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
34905
- payload: { resources: { containers: [] } }
35915
+ payload: { resources: { containers: getCurrentDockerStats() } }
34906
35916
  });
34907
35917
  return jsonResponse({
34908
35918
  ok: true,
34909
- container: id,
34910
35919
  output: stdout.trim()
34911
35920
  });
34912
35921
  })));
34913
- const getContainerLogsRoute = HttpRouter.add("GET", "/api/resources/docker/container/:id/logs", httpHandler(Effect.gen(function* () {
34914
- const id = (yield* HttpRouter.params)["id"] ?? "";
34915
- if (!id) return jsonResponse({ error: "Container ID required" }, { status: 400 });
34916
- const { stdout } = yield* Effect.tryPromise({
34917
- try: () => execAsync$7(`docker logs --tail 200 --timestamps "${id}"`, {
34918
- encoding: "utf-8",
34919
- timeout: 1e4
34920
- }),
34921
- catch: (err) => new Error(err instanceof Error ? err.message : String(err))
35922
+ //#endregion
35923
+ //#region routes/resources/history.ts
35924
+ init_domain_services();
35925
+ const WINDOW_MS = 1440 * 60 * 1e3;
35926
+ const BUCKET_MS = 300 * 1e3;
35927
+ const MAX_POINTS = 288;
35928
+ const MAX_ACTIVITY_EVENTS = 1e3;
35929
+ const samples = [];
35930
+ function nowMs() {
35931
+ return Date.now();
35932
+ }
35933
+ function pruneSamples(now = nowMs()) {
35934
+ const cutoff = now - WINDOW_MS;
35935
+ while (samples.length > 0 && samples[0].ts < cutoff) samples.shift();
35936
+ }
35937
+ function average(values) {
35938
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
35939
+ }
35940
+ function downsample(metric) {
35941
+ pruneSamples();
35942
+ const buckets = /* @__PURE__ */ new Map();
35943
+ for (const sample of samples) {
35944
+ const bucket = Math.floor(sample.ts / BUCKET_MS) * BUCKET_MS;
35945
+ const values = buckets.get(bucket) ?? [];
35946
+ values.push(sample[metric]);
35947
+ buckets.set(bucket, values);
35948
+ }
35949
+ return [...buckets.entries()].sort(([a], [b]) => a - b).slice(-MAX_POINTS).map(([bucket, values]) => ({
35950
+ ts: new Date(bucket).toISOString(),
35951
+ value: Number(average(values).toFixed(2))
35952
+ }));
35953
+ }
35954
+ function asRecord(value) {
35955
+ if (!value) return null;
35956
+ if (typeof value === "object" && !Array.isArray(value)) return value;
35957
+ if (typeof value !== "string") return null;
35958
+ try {
35959
+ const parsed = JSON.parse(value);
35960
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
35961
+ } catch {
35962
+ return null;
35963
+ }
35964
+ }
35965
+ function resourceAnnotationFromEvent(event, now = nowMs()) {
35966
+ const eventTs = new Date(event.timestamp).getTime();
35967
+ if (!Number.isFinite(eventTs) || eventTs <= now - WINDOW_MS || eventTs > now) return null;
35968
+ const payload = asRecord(event.payload);
35969
+ const details = asRecord(payload?.["details"]);
35970
+ if (details?.["category"] !== "resources") return null;
35971
+ const targetKind = details["targetKind"];
35972
+ const targetId = details["targetId"];
35973
+ if (typeof targetKind !== "string" || typeof targetId !== "string") return null;
35974
+ const message = payload?.["message"];
35975
+ return {
35976
+ ts: event.timestamp,
35977
+ label: typeof message === "string" ? message : event.type,
35978
+ targetKind,
35979
+ targetId
35980
+ };
35981
+ }
35982
+ function buildResourceHistoryResponse(activityEvents, now = nowMs()) {
35983
+ pruneSamples(now);
35984
+ const startedAt = samples[0]?.ts ?? now;
35985
+ return {
35986
+ startedAt: new Date(startedAt).toISOString(),
35987
+ cpu: downsample("cpuPercent"),
35988
+ mem: downsample("memoryPercent"),
35989
+ annotations: activityEvents.map((event) => resourceAnnotationFromEvent(event, now)).filter((annotation) => Boolean(annotation))
35990
+ };
35991
+ }
35992
+ function getResourceHistoryEffect() {
35993
+ return Effect.gen(function* () {
35994
+ return jsonResponse(buildResourceHistoryResponse(yield* (yield* EventStoreService).queryByType("activity.entry", MAX_ACTIVITY_EVENTS)));
34922
35995
  });
34923
- return jsonResponse({ logs: stdout });
35996
+ }
35997
+ const getResourceHistoryRoute = HttpRouter.add("GET", "/api/resources/history/24h", httpHandler(getResourceHistoryEffect()));
35998
+ //#endregion
35999
+ //#region routes/resources/stack-verbs.ts
36000
+ init_domain_services();
36001
+ let dockerStackVerbExec = promisify(execFile);
36002
+ const postStartStackRoute = HttpRouter.add("POST", "/api/resources/stacks/:issueId/start", httpHandler(Effect.gen(function* () {
36003
+ return yield* dockerStackVerbEffect((yield* HttpRouter.params)["issueId"] ?? "", "start");
36004
+ })));
36005
+ const postStopStackRoute = HttpRouter.add("POST", "/api/resources/stacks/:issueId/stop", httpHandler(Effect.gen(function* () {
36006
+ return yield* dockerStackVerbEffect((yield* HttpRouter.params)["issueId"] ?? "", "stop");
36007
+ })));
36008
+ const postPauseStackRoute = HttpRouter.add("POST", "/api/resources/stacks/:issueId/pause", httpHandler(Effect.gen(function* () {
36009
+ return yield* dockerStackVerbEffect((yield* HttpRouter.params)["issueId"] ?? "", "pause");
34924
36010
  })));
34925
- const resourcesRouteLayer = Layer.mergeAll(getResourcesRoute, getContainerHistoryRoute, getContainerDetailsRoute, deleteDockerContainerRoute, postPruneContainersRoute, deleteDockerNetworkRoute, deleteDockerVolumeRoute, postPruneVolumesRoute, postRestartContainerRoute, postStartContainerRoute, getContainerLogsRoute);
36011
+ function dockerStackVerbEffect(issue, verb) {
36012
+ return Effect.gen(function* () {
36013
+ const issueId = issue.trim().toUpperCase();
36014
+ const stack = findStack$1(issueId);
36015
+ if (!stack) return jsonResponse({
36016
+ ok: false,
36017
+ error: `Stack not found for ${issueId || issue}`
36018
+ }, { status: 404 });
36019
+ const results = [];
36020
+ for (const service of stack.services) {
36021
+ if (!isDockerIdentifier$1(service.id)) {
36022
+ results.push({
36023
+ containerId: service.id,
36024
+ ok: false,
36025
+ error: "Invalid container ID"
36026
+ });
36027
+ continue;
36028
+ }
36029
+ const args = argsFor(service.id, service.status, verb);
36030
+ const timeout = verb === "stop" ? 35e3 : 3e4;
36031
+ const result = yield* Effect.tryPromise({
36032
+ try: () => dockerStackVerbExec("docker", args, {
36033
+ encoding: "utf-8",
36034
+ timeout
36035
+ }),
36036
+ catch: (error) => error
36037
+ }).pipe(Effect.matchEffect({
36038
+ onFailure: (error) => Effect.succeed({
36039
+ containerId: service.id,
36040
+ ok: false,
36041
+ error: dockerActionErrorPayload(error).error
36042
+ }),
36043
+ onSuccess: ({ stdout }) => Effect.succeed({
36044
+ containerId: service.id,
36045
+ ok: true,
36046
+ output: stdout.trim()
36047
+ })
36048
+ }));
36049
+ results.push(result);
36050
+ }
36051
+ const containers = getCurrentDockerStats();
36052
+ yield* (yield* EventStoreService).append({
36053
+ type: "resources.updated",
36054
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
36055
+ payload: { resources: { containers } }
36056
+ });
36057
+ return jsonResponse({
36058
+ ok: results.every((result) => result.ok),
36059
+ issueId,
36060
+ action: verb,
36061
+ results,
36062
+ containers
36063
+ });
36064
+ });
36065
+ }
36066
+ function argsFor(containerId, status, verb) {
36067
+ if (verb === "stop") return [
36068
+ "stop",
36069
+ "--time",
36070
+ "30",
36071
+ containerId
36072
+ ];
36073
+ if (verb === "pause") return ["pause", containerId];
36074
+ if (status === "paused") return ["unpause", containerId];
36075
+ return ["start", containerId];
36076
+ }
36077
+ function isDockerIdentifier$1(value) {
36078
+ return /^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(value);
36079
+ }
36080
+ function findStack$1(issueId) {
36081
+ return getResourceStacks(getCurrentDockerStats()).find((stack) => stack.issueId?.toUpperCase() === issueId);
36082
+ }
36083
+ //#endregion
36084
+ //#region routes/resources/teardown.ts
36085
+ init_activity_logger();
36086
+ init_domain_services();
36087
+ const execFileAsync$5 = promisify(execFile);
36088
+ const CONFIRM_TOKEN_TTL_MS = 300 * 1e3;
36089
+ let dockerTeardownExec = execFileAsync$5;
36090
+ let teardownTokenGenerator = () => `td-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
36091
+ let activityEmitter = emitActivityEntrySync;
36092
+ const teardownTokens = /* @__PURE__ */ new Map();
36093
+ const getStackTeardownEstimateRoute = HttpRouter.add("GET", "/api/resources/stacks/:issueId/teardown-estimate", httpHandler(Effect.gen(function* () {
36094
+ return yield* getStackTeardownEstimateEffect((yield* HttpRouter.params)["issueId"] ?? "");
36095
+ })));
36096
+ const postStackTeardownRoute = HttpRouter.add("POST", "/api/resources/stacks/:issueId/teardown", httpHandler(Effect.gen(function* () {
36097
+ const params = yield* HttpRouter.params;
36098
+ const body = yield* readJsonBody$13;
36099
+ return yield* postStackTeardownEffect(params["issueId"] ?? "", body);
36100
+ })));
36101
+ function getStackTeardownEstimateEffect(issue) {
36102
+ return Effect.gen(function* () {
36103
+ const issueId = normalizeIssueId$1(issue);
36104
+ const stack = findStack(issueId);
36105
+ if (!stack) return jsonResponse({ error: `Stack not found for ${issueId || issue}` }, { status: 404 });
36106
+ const token = teardownTokenGenerator();
36107
+ const expiresAt = Date.now() + CONFIRM_TOKEN_TTL_MS;
36108
+ teardownTokens.set(token, {
36109
+ issueId,
36110
+ composeProject: stack.composeProject,
36111
+ expiresAt
36112
+ });
36113
+ return jsonResponse(buildStackTeardownEstimate(stack, token, expiresAt));
36114
+ });
36115
+ }
36116
+ function postStackTeardownEffect(issue, input) {
36117
+ return Effect.gen(function* () {
36118
+ const issueId = normalizeIssueId$1(issue);
36119
+ const confirmToken = typeof input.confirmToken === "string" ? input.confirmToken : "";
36120
+ const typedText = typeof input.typedText === "string" ? input.typedText : "";
36121
+ const token = teardownTokens.get(confirmToken);
36122
+ if (!token || token.issueId !== issueId || token.expiresAt <= Date.now()) {
36123
+ teardownTokens.delete(confirmToken);
36124
+ return jsonResponse({
36125
+ ok: false,
36126
+ error: "Invalid or expired confirmation token."
36127
+ }, { status: 422 });
36128
+ }
36129
+ if (typedText !== token.composeProject) return jsonResponse({
36130
+ ok: false,
36131
+ error: `Type ${token.composeProject} to confirm stack teardown.`
36132
+ }, { status: 422 });
36133
+ const stack = findStack(issueId);
36134
+ if (!stack) return jsonResponse({
36135
+ ok: false,
36136
+ error: `Stack not found for ${issueId}`
36137
+ }, { status: 404 });
36138
+ const estimate = buildStackTeardownEstimate(stack, confirmToken, token.expiresAt);
36139
+ const result = yield* runDockerStackTeardown(stack);
36140
+ teardownTokens.delete(confirmToken);
36141
+ yield* (yield* EventStoreService).append({
36142
+ type: "resources.updated",
36143
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
36144
+ payload: { resources: { containers: getCurrentDockerStats() } }
36145
+ });
36146
+ activityEmitter({
36147
+ source: "dashboard",
36148
+ level: "info",
36149
+ issueId,
36150
+ message: `Tore down Docker stack for ${issueId}, freeing ${estimate.ramBytes} RAM bytes and ${estimate.diskBytes} disk bytes.`,
36151
+ details: JSON.stringify({
36152
+ issueId,
36153
+ composeProject: stack.composeProject,
36154
+ ramBytes: estimate.ramBytes,
36155
+ diskBytes: estimate.diskBytes,
36156
+ removedContainers: result.containers.length,
36157
+ removedNetworks: result.networks.length,
36158
+ removedVolumes: result.volumes.length
36159
+ })
36160
+ });
36161
+ return jsonResponse({
36162
+ ok: true,
36163
+ issueId,
36164
+ composeProject: stack.composeProject,
36165
+ ramBytes: estimate.ramBytes,
36166
+ diskBytes: estimate.diskBytes,
36167
+ removedContainers: result.containers,
36168
+ removedNetworks: result.networks,
36169
+ removedVolumes: result.volumes
36170
+ });
36171
+ });
36172
+ }
36173
+ function buildStackTeardownEstimate(stack, confirmToken, expiresAt) {
36174
+ return {
36175
+ issueId: stack.issueId ?? stack.id,
36176
+ composeProject: stack.composeProject,
36177
+ ramBytes: stack.aggregates.memoryBytes,
36178
+ diskBytes: stack.aggregates.diskBytes,
36179
+ confirmToken,
36180
+ expiresAt: new Date(expiresAt).toISOString()
36181
+ };
36182
+ }
36183
+ function runDockerStackTeardown(stack) {
36184
+ return Effect.tryPromise({
36185
+ try: async () => {
36186
+ const containers = stack.services.map((service) => service.id).filter(Boolean);
36187
+ for (const container of containers) {
36188
+ if (!isDockerIdentifier(container)) throw new Error(`Invalid container ID: ${container}`);
36189
+ await dockerTeardownExec("docker", [
36190
+ "stop",
36191
+ "--time",
36192
+ "30",
36193
+ container
36194
+ ], {
36195
+ encoding: "utf-8",
36196
+ timeout: 35e3
36197
+ });
36198
+ }
36199
+ for (const container of containers) await dockerTeardownExec("docker", [
36200
+ "rm",
36201
+ "-f",
36202
+ container
36203
+ ], {
36204
+ encoding: "utf-8",
36205
+ timeout: 3e4
36206
+ });
36207
+ const networks = lines((await dockerTeardownExec("docker", [
36208
+ "network",
36209
+ "ls",
36210
+ "--filter",
36211
+ `label=com.docker.compose.project=${stack.composeProject}`,
36212
+ "--format",
36213
+ "{{.Name}}"
36214
+ ], {
36215
+ encoding: "utf-8",
36216
+ timeout: 1e4
36217
+ })).stdout);
36218
+ for (const network of networks) {
36219
+ if (!isDockerIdentifier(network)) throw new Error(`Invalid network name: ${network}`);
36220
+ await dockerTeardownExec("docker", [
36221
+ "network",
36222
+ "rm",
36223
+ network
36224
+ ], {
36225
+ encoding: "utf-8",
36226
+ timeout: 1e4
36227
+ });
36228
+ }
36229
+ const volumes = lines((await dockerTeardownExec("docker", [
36230
+ "volume",
36231
+ "ls",
36232
+ "--filter",
36233
+ `label=com.docker.compose.project=${stack.composeProject}`,
36234
+ "--format",
36235
+ "{{.Name}}"
36236
+ ], {
36237
+ encoding: "utf-8",
36238
+ timeout: 1e4
36239
+ })).stdout);
36240
+ for (const volume of volumes) {
36241
+ if (!isDockerIdentifier(volume)) throw new Error(`Invalid volume name: ${volume}`);
36242
+ await dockerTeardownExec("docker", [
36243
+ "volume",
36244
+ "rm",
36245
+ volume
36246
+ ], {
36247
+ encoding: "utf-8",
36248
+ timeout: 1e4
36249
+ });
36250
+ }
36251
+ return {
36252
+ containers,
36253
+ networks,
36254
+ volumes
36255
+ };
36256
+ },
36257
+ catch: (error) => error
36258
+ });
36259
+ }
36260
+ function findStack(issueId) {
36261
+ return getResourceStacks(getCurrentDockerStats()).find((stack) => stack.issueId?.toUpperCase() === issueId);
36262
+ }
36263
+ function normalizeIssueId$1(issue) {
36264
+ return issue.trim().toUpperCase();
36265
+ }
36266
+ function lines(value) {
36267
+ return value.split("\n").map((line) => line.trim()).filter(Boolean);
36268
+ }
36269
+ function isDockerIdentifier(value) {
36270
+ return /^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(value);
36271
+ }
36272
+ const readJsonBody$13 = Effect.gen(function* () {
36273
+ const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
36274
+ try {
36275
+ return text ? JSON.parse(text) : {};
36276
+ } catch {
36277
+ return {};
36278
+ }
36279
+ });
36280
+ //#endregion
36281
+ //#region routes/resources/index.ts
36282
+ const resourcesRouteLayer = Layer.mergeAll(getResourcesRoute, getResourceHistoryRoute, getContainerHistoryRoute, getContainerDetailsRoute, deleteDockerContainerRoute, postPruneContainersRoute, deleteDockerNetworkRoute, deleteDockerVolumeRoute, postPruneVolumesRoute, postRestartContainerRoute, postStartContainerRoute, postStopContainerRoute, postPauseContainerRoute, postUnpauseContainerRoute, getContainerLogsRoute, deleteResourceVenvRoute, postStartStackRoute, postStopStackRoute, postPauseStackRoute, getStackTeardownEstimateRoute, postStackTeardownRoute);
34926
36283
  //#endregion
34927
36284
  //#region services/model-origin-enrich.ts
34928
36285
  init_agents$1();
@@ -35084,7 +36441,8 @@ const ROLE_NAMES = [
35084
36441
  "ship",
35085
36442
  "flywheel",
35086
36443
  "strike",
35087
- "sequencer"
36444
+ "sequencer",
36445
+ "knowledge"
35088
36446
  ];
35089
36447
  const WORKHORSE_SLOTS = [
35090
36448
  "expensive",
@@ -35813,17 +37171,20 @@ function getAvailableModelsApi() {
35813
37171
  }
35814
37172
  }
35815
37173
  const openaiOrder = {
35816
- "gpt-5.5": 0,
35817
- "gpt-5.5-pro": 1,
35818
- "gpt-5.4": 10,
35819
- "gpt-5.4-pro": 11,
35820
- "gpt-5.4-mini": 12,
35821
- "gpt-5.3-codex": 20,
35822
- "gpt-5.2": 30,
35823
- "o3": 40,
35824
- "o4-mini": 41,
35825
- "gpt-4o": 50,
35826
- "gpt-4o-mini": 51
37174
+ "gpt-5.6-sol": 0,
37175
+ "gpt-5.6-terra": 1,
37176
+ "gpt-5.6-luna": 2,
37177
+ "gpt-5.5": 10,
37178
+ "gpt-5.5-pro": 11,
37179
+ "gpt-5.4": 20,
37180
+ "gpt-5.4-pro": 21,
37181
+ "gpt-5.4-mini": 22,
37182
+ "gpt-5.3-codex": 30,
37183
+ "gpt-5.2": 40,
37184
+ "o3": 50,
37185
+ "o4-mini": 51,
37186
+ "gpt-4o": 60,
37187
+ "gpt-4o-mini": 61
35827
37188
  };
35828
37189
  result.openai.sort((a, b) => (openaiOrder[a.id] ?? 99) - (openaiOrder[b.id] ?? 99));
35829
37190
  return result;
@@ -36191,7 +37552,7 @@ init_linear_client();
36191
37552
  init_config_yaml();
36192
37553
  init_issue_service_singleton();
36193
37554
  init_io();
36194
- const execAsync$6 = promisify(exec);
37555
+ const execAsync$5 = promisify(exec);
36195
37556
  const execFileAsync$4 = promisify(execFile);
36196
37557
  function getIssueDataService$3() {
36197
37558
  return getSharedIssueService();
@@ -36292,16 +37653,21 @@ async function fetchActivityDataWithContext(issueId, context = {}) {
36292
37653
  const workspacePath = join$1(projectPath, "workspaces", `feature-${issueLower}`);
36293
37654
  const agentId = `agent-${issueLower}`;
36294
37655
  const planningAgentId = `planning-${issueLower}`;
37656
+ const knowledgeAgentId = `agent-${issueLower}-knowledge`;
36295
37657
  const agentsDir = join$1(homedir$1(), ".overdeck", "agents");
36296
37658
  let hasPlanningSection = false;
36297
- for (const checkId of [planningAgentId, agentId]) {
37659
+ for (const checkId of [
37660
+ planningAgentId,
37661
+ agentId,
37662
+ knowledgeAgentId
37663
+ ]) {
36298
37664
  const agentDir = join$1(agentsDir, checkId);
36299
37665
  if (!await pathExists$4(agentDir)) continue;
36300
37666
  const state = getAgentStateSync(checkId);
36301
37667
  if (!state) continue;
36302
37668
  try {
36303
37669
  const isPlanning = checkId.startsWith("planning-");
36304
- const sectionType = isPlanning ? "planning" : "work";
37670
+ const sectionType = isPlanning ? "planning" : checkId.endsWith("-knowledge") ? "knowledge" : "work";
36305
37671
  if (isPlanning) hasPlanningSection = true;
36306
37672
  let transcript = "";
36307
37673
  let transcriptFromPane = false;
@@ -36729,7 +38095,7 @@ async function fetchPlanningData(issueId, options = {}) {
36729
38095
  const cached = stashCountCache.get(cacheKey);
36730
38096
  if (cached && cached.timestamp > Date.now() - STASH_COUNT_CACHE_TTL_MS) result.stashCount = cached.count;
36731
38097
  else {
36732
- const { stdout: stashList } = await execAsync$6("git stash list", {
38098
+ const { stdout: stashList } = await execAsync$5("git stash list", {
36733
38099
  cwd: workspacePath,
36734
38100
  encoding: "utf-8"
36735
38101
  });
@@ -36817,7 +38183,7 @@ async function generateStatusReview(issueId) {
36817
38183
  };
36818
38184
  const execSafe = async (cmd, opts) => {
36819
38185
  try {
36820
- const { stdout } = await execAsync$6(cmd, {
38186
+ const { stdout } = await execAsync$5(cmd, {
36821
38187
  encoding: "utf-8",
36822
38188
  timeout: 1e4,
36823
38189
  ...opts
@@ -36919,7 +38285,7 @@ Be specific: reference actual file names, function names, requirement text, disc
36919
38285
  const { command: cliCmd, args: cliArgs } = getAgentCommandSync(statusModelId);
36920
38286
  const modelFlag = cliArgs.length > 0 ? ` ${cliArgs.join(" ")}` : "";
36921
38287
  const promptFile = join$1(planningDir, ".status-review-prompt.tmp");
36922
- const { getProviderForModelSync, getProviderEnvSync } = await import("./providers-B0dqjvFa.js");
38288
+ const { getProviderForModelSync, getProviderEnvSync } = await import("./providers-pkuT25pY.js");
36923
38289
  let providerEnvStr = "";
36924
38290
  let providerEnv = {};
36925
38291
  const statusProvider = getProviderForModelSync(statusModelId);
@@ -36936,7 +38302,7 @@ Be specific: reference actual file names, function names, requirement text, disc
36936
38302
  try {
36937
38303
  const env = buildChildEnvSync(process.env, providerEnv);
36938
38304
  const promptContent = await readFile(promptFile, "utf-8");
36939
- const { stdout: aiReview } = await execAsync$6(`${cliCmd} -p${modelFlag} --no-session-persistence`, {
38305
+ const { stdout: aiReview } = await execAsync$5(`${cliCmd} -p${modelFlag} --no-session-persistence`, {
36940
38306
  encoding: "utf-8",
36941
38307
  timeout: 12e4,
36942
38308
  maxBuffer: 1024 * 1024,
@@ -37002,7 +38368,7 @@ const postMissionControlUploadRoute = HttpRouter.add("POST", "/api/command-deck/
37002
38368
  let safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "-");
37003
38369
  let processedContent = content;
37004
38370
  if (safeName.endsWith(".vtt")) {
37005
- const { vttToMarkdown } = yield* Effect.promise(() => import("./vtt-parser-DxyN_zxJ.js"));
38371
+ const { vttToMarkdown } = yield* Effect.promise(() => import("./vtt-parser-D-q5-8il.js"));
37006
38372
  processedContent = vttToMarkdown(content);
37007
38373
  safeName = safeName.replace(/\.vtt$/, ".md");
37008
38374
  }
@@ -41487,6 +42853,9 @@ const readJsonBody$10 = Effect.gen(function* () {
41487
42853
  });
41488
42854
  /** Model ID to API model ID mapping */
41489
42855
  const MODEL_API_IDS = {
42856
+ "gpt-5.6-sol": { apiModel: "gpt-5.6-sol" },
42857
+ "gpt-5.6-terra": { apiModel: "gpt-5.6-terra" },
42858
+ "gpt-5.6-luna": { apiModel: "gpt-5.6-luna" },
41490
42859
  "gpt-5.5-pro": { apiModel: "gpt-5.5-pro" },
41491
42860
  "gpt-5.5": { apiModel: "gpt-5.5" },
41492
42861
  "gpt-5.4-pro": { apiModel: "gpt-5.4-pro" },
@@ -42340,73 +43709,6 @@ const resetRoute = HttpRouter.add("POST", "/api/autopreso/session/reset", httpHa
42340
43709
  })));
42341
43710
  const autopresoRouteLayer = Layer.mergeAll(startRoute, backToStagingRoute, resetRoute);
42342
43711
  //#endregion
42343
- //#region services/event-loop-monitor.ts
42344
- init_activity_logger();
42345
- const EVENT_LOOP_MONITOR_WINDOW_MS = 6e4;
42346
- let lastSample = {
42347
- p50: 0,
42348
- p99: 0,
42349
- max: 0,
42350
- unit: "ms",
42351
- sampledAt: null,
42352
- windowMs: EVENT_LOOP_MONITOR_WINDOW_MS
42353
- };
42354
- let timer$3 = null;
42355
- let activeHistogram = null;
42356
- function nsToRoundedMs(ns) {
42357
- if (!Number.isFinite(ns) || ns <= 0) return 0;
42358
- return Math.round(ns / 1e6 * 100) / 100;
42359
- }
42360
- function sampleEventLoopDelay(histogram, deps = {}) {
42361
- const warnThresholdMs = deps.warnThresholdMs ?? 100;
42362
- const now = deps.now ?? (() => /* @__PURE__ */ new Date());
42363
- const warn = deps.warn ?? ((message) => console.warn(message));
42364
- const emit = deps.emit ?? emitActivityEntrySync;
42365
- const sample = {
42366
- p50: nsToRoundedMs(histogram.percentile(50)),
42367
- p99: nsToRoundedMs(histogram.percentile(99)),
42368
- max: nsToRoundedMs(histogram.max),
42369
- unit: "ms",
42370
- sampledAt: now().toISOString(),
42371
- windowMs: EVENT_LOOP_MONITOR_WINDOW_MS
42372
- };
42373
- lastSample = sample;
42374
- if (sample.p99 > warnThresholdMs) {
42375
- const message = `Dashboard event loop p99 delay ${sample.p99}ms exceeded ${warnThresholdMs}ms`;
42376
- warn(`[event-loop-monitor] ${message}`);
42377
- emit({
42378
- source: "dashboard",
42379
- level: "warn",
42380
- message,
42381
- details: `p50=${sample.p50}ms p99=${sample.p99}ms max=${sample.max}ms over ${sample.windowMs}ms`
42382
- });
42383
- }
42384
- histogram.reset();
42385
- return sample;
42386
- }
42387
- function getEventLoopDelaySample() {
42388
- return lastSample;
42389
- }
42390
- function startEventLoopMonitor(deps = {}) {
42391
- if (timer$3) return;
42392
- const intervalMs = deps.intervalMs ?? 6e4;
42393
- activeHistogram = (deps.createHistogram ?? (() => monitorEventLoopDelay({ resolution: 20 })))();
42394
- activeHistogram.enable();
42395
- timer$3 = setInterval(() => {
42396
- if (!activeHistogram) return;
42397
- sampleEventLoopDelay(activeHistogram, deps);
42398
- }, intervalMs);
42399
- timer$3.unref?.();
42400
- }
42401
- function stopEventLoopMonitor() {
42402
- if (timer$3) {
42403
- clearInterval(timer$3);
42404
- timer$3 = null;
42405
- }
42406
- activeHistogram?.disable();
42407
- activeHistogram = null;
42408
- }
42409
- //#endregion
42410
43712
  //#region routes/metrics.ts
42411
43713
  /**
42412
43714
  * Metrics route module — Effect HttpRouter.Layer (PAN-428 B16)
@@ -42880,7 +44182,7 @@ const postRallyValidateRoute = HttpRouter.add("POST", "/api/rally/validate", Eff
42880
44182
  }, { status: 400 });
42881
44183
  return yield* Effect.promise(async () => {
42882
44184
  try {
42883
- const { RallyRestApi } = await import("./rally-api-ukAD0De8.js");
44185
+ const { RallyRestApi } = await import("./rally-api-DE3UOdHw.js");
42884
44186
  const api = new RallyRestApi({
42885
44187
  apiKey,
42886
44188
  server: server || "https://rally1.rallydev.com"
@@ -43176,7 +44478,7 @@ const deaconRouteLayer = Layer.mergeAll(getNoResumeModeRoute, postResumeAllRoute
43176
44478
  //#region routes/misc/planning.ts
43177
44479
  init_config_yaml();
43178
44480
  init_issue_id();
43179
- init_types$1();
44481
+ init_types();
43180
44482
  init_projects();
43181
44483
  init_remote_agents();
43182
44484
  init_tmux();
@@ -43208,7 +44510,7 @@ const getPlanningStatusRoute = HttpRouter.add("GET", "/api/planning/:issueId/sta
43208
44510
  const remoteState = loadRemoteAgentState(sessionName);
43209
44511
  const isRemote = !!remoteState;
43210
44512
  const vmName = remoteState?.vmName ?? "";
43211
- const { getAgentStateSync } = await import("./agents-Bi9Y6h5B.js");
44513
+ const { getAgentStateSync } = await import("./agents-2mgtTSeb.js");
43212
44514
  const agentStarting = getAgentStateSync(sessionName)?.status === "starting";
43213
44515
  let tmuxSessionAlive = false;
43214
44516
  if (!isRemote) try {
@@ -43351,7 +44653,7 @@ Continue the PLANNING session. Do NOT implement anything.
43351
44653
  `);
43352
44654
  const agentCwd = workspacePath;
43353
44655
  if (existsSync$1(outputFile)) await rename(outputFile, join$1(planningDir, `output-${Date.now()}.jsonl`));
43354
- const { getAgentCommandSync } = await import("./settings-DPQ5nEA-.js");
44656
+ const { getAgentCommandSync } = await import("./settings-B27w9z21.js");
43355
44657
  let msgPlanningModel = "claude-sonnet-5";
43356
44658
  try {
43357
44659
  msgPlanningModel = resolveModel("plan", void 0, loadConfigSync().config);
@@ -43472,7 +44774,7 @@ async function getIndexStats(rootPath, isMain) {
43472
44774
  }
43473
44775
  const getTldrStatusRoute = HttpRouter.add("GET", "/api/services/tldr/status", Effect.promise(async () => {
43474
44776
  try {
43475
- const { getTldrDaemonServiceSync } = await import("./tldr-daemon-LV_URGSC.js");
44777
+ const { getTldrDaemonServiceSync } = await import("./tldr-daemon-DkRIsKUT.js");
43476
44778
  const projectRoot = process.cwd();
43477
44779
  const venvPath = join$1(projectRoot, ".venv");
43478
44780
  const results = [];
@@ -43517,7 +44819,7 @@ const getTldrStatusRoute = HttpRouter.add("GET", "/api/services/tldr/status", Ef
43517
44819
  }));
43518
44820
  const postTldrStartRoute = HttpRouter.add("POST", "/api/services/tldr/start", Effect.promise(async () => {
43519
44821
  try {
43520
- const { getTldrDaemonServiceSync } = await import("./tldr-daemon-LV_URGSC.js");
44822
+ const { getTldrDaemonServiceSync } = await import("./tldr-daemon-DkRIsKUT.js");
43521
44823
  const projectRoot = process.cwd();
43522
44824
  const venvPath = join$1(projectRoot, ".venv");
43523
44825
  if (!existsSync$1(venvPath)) return jsonResponse({ error: "No .venv found in project root" }, { status: 404 });
@@ -43534,7 +44836,7 @@ const postTldrStartRoute = HttpRouter.add("POST", "/api/services/tldr/start", Ef
43534
44836
  }));
43535
44837
  const postTldrStopRoute = HttpRouter.add("POST", "/api/services/tldr/stop", Effect.promise(async () => {
43536
44838
  try {
43537
- const { getTldrDaemonServiceSync } = await import("./tldr-daemon-LV_URGSC.js");
44839
+ const { getTldrDaemonServiceSync } = await import("./tldr-daemon-DkRIsKUT.js");
43538
44840
  const projectRoot = process.cwd();
43539
44841
  const venvPath = join$1(projectRoot, ".venv");
43540
44842
  if (!existsSync$1(venvPath)) return jsonResponse({ error: "No .venv found in project root" }, { status: 404 });
@@ -43551,7 +44853,7 @@ const postTldrStopRoute = HttpRouter.add("POST", "/api/services/tldr/stop", Effe
43551
44853
  }));
43552
44854
  const postTldrReloadRoute = HttpRouter.add("POST", "/api/services/tldr/reload", Effect.promise(async () => {
43553
44855
  try {
43554
- const { getTldrDaemonServiceSync } = await import("./tldr-daemon-LV_URGSC.js");
44856
+ const { getTldrDaemonServiceSync } = await import("./tldr-daemon-DkRIsKUT.js");
43555
44857
  const enabled = isTldrEnabledSync();
43556
44858
  const projectRoot = process.cwd();
43557
44859
  const targets = [];
@@ -43603,7 +44905,7 @@ init_tracker_config();
43603
44905
  init_issue_id();
43604
44906
  init_projects();
43605
44907
  init_tmux();
43606
- const execAsync$5 = promisify(exec);
44908
+ const execAsync$4 = promisify(exec);
43607
44909
  function getIssueDataService$1() {
43608
44910
  const { getSharedIssueService } = (init_issue_service_singleton(), __toCommonJS(issue_service_singleton_exports));
43609
44911
  return getSharedIssueService();
@@ -43627,7 +44929,7 @@ const getVersionRoute = HttpRouter.add("GET", "/api/version", Effect.promise(asy
43627
44929
  const version = await getOverdeckVersion();
43628
44930
  let supervisorUrl = null;
43629
44931
  try {
43630
- const { getSupervisorUrlSync } = await import("./supervisor-DyNL2rTl.js");
44932
+ const { getSupervisorUrlSync } = await import("./supervisor-BzxM2EIo.js");
43631
44933
  supervisorUrl = getSupervisorUrlSync();
43632
44934
  } catch {}
43633
44935
  return jsonResponse({
@@ -43791,7 +45093,7 @@ const postShadowMonitorRoute = HttpRouter.add("POST", "/api/shadow/:issueId/moni
43791
45093
  const projectPath = await getProjectPath(issuePrefix);
43792
45094
  const workspacePath = join$1(projectPath, "workspaces", `feature-${issueLower}`);
43793
45095
  if (!existsSync$1(workspacePath)) return jsonResponse({ error: "Workspace not found" }, { status: 404 });
43794
- const { gatherArtifacts, generateBasicInference, updateInferenceDocumentSync } = await import("./shadow-engineering-CStyQ6yp.js");
45096
+ const { gatherArtifacts, generateBasicInference, updateInferenceDocumentSync } = await import("./shadow-engineering-C1DS3IT9.js");
43795
45097
  const config = {
43796
45098
  issueId,
43797
45099
  workspacePath,
@@ -43821,7 +45123,7 @@ const postShadowObserveRoute = HttpRouter.add("POST", "/api/shadow/:issueId/obse
43821
45123
  if (!existsSync$1(workspacePath)) return jsonResponse({ error: "Workspace not found" }, { status: 404 });
43822
45124
  const ghConfig = getGitHubConfig();
43823
45125
  if (!ghConfig) return jsonResponse({ error: "GitHub not configured - Observer requires GitHub" }, { status: 400 });
43824
- const { runObserverCycle } = await import("./shadow-engineering-CStyQ6yp.js");
45126
+ const { runObserverCycle } = await import("./shadow-engineering-C1DS3IT9.js");
43825
45127
  const firstRepo = ghConfig.repos[0];
43826
45128
  const config = {
43827
45129
  issueId,
@@ -43853,7 +45155,7 @@ const postDevRebuildRoute = HttpRouter.add("POST", "/api/dev/rebuild", Effect.ge
43853
45155
  if (!overdeckDevMode || !overdeckProjectRoot) return jsonResponse({ error: "Rebuild only available in dev mode" }, { status: 403 });
43854
45156
  return yield* Effect.promise(async () => {
43855
45157
  try {
43856
- const { stdout, stderr } = await execAsync$5("npm run build", {
45158
+ const { stdout, stderr } = await execAsync$4("npm run build", {
43857
45159
  cwd: overdeckProjectRoot,
43858
45160
  timeout: 12e4
43859
45161
  });
@@ -44886,7 +46188,7 @@ async function cleanupConversationAttachments(name) {
44886
46188
  * sessions that the lifecycle poller missed. */
44887
46189
  async function cleanupOrphanedConversationAttachments() {
44888
46190
  try {
44889
- const { listConversations } = await import("./conversations-DYz9OyMT.js");
46191
+ const { listConversations } = await import("./conversations-CucKmkR7.js");
44890
46192
  const convNames = new Set(listConversations().map((c) => c.name));
44891
46193
  const root = getConversationAttachmentsRoot();
44892
46194
  await runInBatches((await readdir(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !convNames.has(entry.name)).map((entry) => join$1(root, entry.name)), 10, (path) => rm(path, {
@@ -45365,7 +46667,7 @@ init_layers();
45365
46667
  init_briefing_freshness();
45366
46668
  init_paths();
45367
46669
  init_event_store();
45368
- const execAsync$4 = promisify(exec);
46670
+ const execAsync$3 = promisify(exec);
45369
46671
  const execFileAsync$2 = promisify(execFile);
45370
46672
  const PROCESS_CLEANUP_GRACE_MS = 750;
45371
46673
  const PTY_SUPERVISOR_SOCKET_WAIT_MS = 3e4;
@@ -45473,7 +46775,7 @@ async function resolveAllowedHarness(requested, model) {
45473
46775
  }
45474
46776
  async function isInsideGitWorkTree(dir) {
45475
46777
  try {
45476
- const { stdout } = await execAsync$4("git rev-parse --is-inside-work-tree", {
46778
+ const { stdout } = await execAsync$3("git rev-parse --is-inside-work-tree", {
45477
46779
  cwd: dir,
45478
46780
  encoding: "utf-8"
45479
46781
  });
@@ -45506,9 +46808,9 @@ async function waitForClaudeReady(tmuxSession) {
45506
46808
  console.warn(`[conversations] Timed out waiting for Claude Code prompt in ${tmuxSession}`);
45507
46809
  }
45508
46810
  function isPiTuiInputReady(snapshot) {
45509
- return /^\s*[❯›>]\s/m.test(snapshot) || /(?:^|\s)0(?:\.\d+)?%\s+context\s+used\b/i.test(snapshot);
46811
+ return /^\s*[❯›>]\s/m.test(snapshot) || /(?:^|\s)0(?:\.\d+)?%\s+context\s+used\b/i.test(snapshot) || /⬢[^\n]*◕/.test(snapshot);
45510
46812
  }
45511
- async function waitForPiTuiReady(tmuxSession, timeoutMs = 3e4) {
46813
+ async function waitForPiTuiReady(tmuxSession, timeoutMs = 6e4) {
45512
46814
  const deadline = Date.now() + timeoutMs;
45513
46815
  while (Date.now() < deadline) {
45514
46816
  if (isPiTuiInputReady(await Effect.runPromise(capturePane(tmuxSession, 40).pipe(Effect.catch(() => Effect.succeed("")))))) {
@@ -45550,8 +46852,9 @@ function shouldUseSupervisorForConversation(harness) {
45550
46852
  }
45551
46853
  async function waitForConversationRuntimeReady(tmuxSession, harness, mode) {
45552
46854
  const transcriptKind = getHarnessBehavior(harness).transcriptKind;
45553
- if (transcriptKind === "ohmypi-jsonl") await waitForPiTuiReady(tmuxSession);
45554
- else if (transcriptKind !== "codex-rollout-jsonl" && mode === "spawn") {
46855
+ if (transcriptKind === "ohmypi-jsonl") {
46856
+ if (!await waitForPiTuiReady(tmuxSession)) throw new Error(`Pi (ohmypi) did not become interactive in ${tmuxSession} within the startup window — MCP server connections are likely still in progress. Retry once MCP servers finish connecting, or check ~/.claude/mcp.json for a slow/hanging server.`);
46857
+ } else if (transcriptKind !== "codex-rollout-jsonl" && mode === "spawn") {
45555
46858
  await waitForClaudeReady(tmuxSession);
45556
46859
  console.log(`[conversations] Claude ready in ${tmuxSession}`);
45557
46860
  } else if (transcriptKind !== "codex-rollout-jsonl") await waitForReadySignal(tmuxSession, 30);
@@ -45754,7 +47057,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
45754
47057
  const codexApprovalPolicy = codexPermMode === "full-access" ? "never" : "on-request";
45755
47058
  const codexSandboxMode = codexPermMode === "full-access" ? "danger-full-access" : codexPermMode === "read-only" ? "read-only" : "workspace-write";
45756
47059
  const codexApprovalsReviewer = codexPermMode === "auto-review" ? "auto_review" : void 0;
45757
- const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-RRg-7JsH.js");
47060
+ const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-DG2Dyp0K.js");
45758
47061
  initCodexHome(codexHome, {
45759
47062
  trustedDir: cwd,
45760
47063
  approvalPolicy: codexApprovalPolicy,
@@ -45824,7 +47127,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
45824
47127
  } catch {}
45825
47128
  console.log(`[claude-invoke] purpose=conversation-session | model=${model || "default"} | source=conversations.ts:spawnConversationSession | session=${tmuxSession} | resume=${resume} | command="${runtimeCommand}"`);
45826
47129
  try {
45827
- const { preTrustDirectory } = await import("./workspace-manager-CK4yZQev.js");
47130
+ const { preTrustDirectory } = await import("./workspace-manager-uVfFRWJG.js");
45828
47131
  preTrustDirectory(cwd);
45829
47132
  } catch {}
45830
47133
  try {
@@ -45841,7 +47144,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
45841
47144
  const codexHomeDir = codexFields.codexHome;
45842
47145
  (async () => {
45843
47146
  try {
45844
- const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-RRg-7JsH.js");
47147
+ const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-DG2Dyp0K.js");
45845
47148
  const rollout = await waitForCodexRollout(codexHomeDir, 12e4);
45846
47149
  if (rollout) {
45847
47150
  const threadId = extractThreadIdFromRollout(rollout);
@@ -46472,9 +47775,21 @@ function sanitizeTitle(raw) {
46472
47775
  */
46473
47776
  function fallbackTranscriptTitle(transcript) {
46474
47777
  const lines = transcript.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("[…"));
46475
- const compact = (lines.filter((line) => line.startsWith("User:")).map((line) => line.slice(5).trim()).filter(Boolean).at(-1) ?? lines[0]?.replace(/^(User|Assistant):\s*/i, "").trim() ?? "").replace(/```[\s\S]*?```/g, " ").replace(/`([^`]+)`/g, "$1").replace(/\b(?:please|can you|could you|would you)\b/gi, " ").replace(/\b(?:i need you to|i want you to|help me|let's|lets)\b/gi, " ").replace(/https?:\/\/\S+/gi, " ").replace(/[^\p{L}\p{N}#/_+.-]+/gu, " ").replace(/\s+/g, " ").trim();
46476
- if (!compact) return "";
46477
- return sanitizeTitle(compact.split(/\s+/).slice(0, 8).join(" ").replace(/\s+(?:and|or|to|for|with)$/i, "").replace(/[.,:;!?]+$/g, ""));
47778
+ return derivePromptTitle(lines.filter((line) => line.startsWith("User:")).map((line) => line.slice(5).trim()).filter(Boolean).at(-1) ?? lines[0]?.replace(/^(User|Assistant):\s*/i, "").trim() ?? "");
47779
+ }
47780
+ /**
47781
+ * Generate a deterministic title directly from a raw prompt string.
47782
+ * Zero model calls; returns '' only for empty/whitespace-only input.
47783
+ */
47784
+ function derivePromptTitle(prompt) {
47785
+ const trimmed = prompt.trim();
47786
+ if (!trimmed) return "";
47787
+ const compact = trimmed.replace(/```[\s\S]*?```/g, " ").replace(/`([^`]+)`/g, "$1").replace(/\b(?:please|can you|could you|would you)\b/gi, " ").replace(/\b(?:i need you to|i want you to|help me|let's|lets)\b/gi, " ").replace(/https?:\/\/\S+/gi, " ").replace(/[^\p{L}\p{N}#/_+.-]+/gu, " ").replace(/\s+/g, " ").trim();
47788
+ const MAX_TITLE_LEN = 60;
47789
+ if (!compact) return trimmed.slice(0, MAX_TITLE_LEN) + (trimmed.length > MAX_TITLE_LEN ? "…" : "");
47790
+ const sanitized = sanitizeTitle(compact.split(/\s+/).slice(0, 8).join(" ").replace(/\s+(?:and|or|to|for|with)$/i, "").replace(/[.,:;!?]+$/g, ""));
47791
+ if (sanitized.length <= MAX_TITLE_LEN) return sanitized;
47792
+ return sanitized.slice(0, MAX_TITLE_LEN) + "…";
46478
47793
  }
46479
47794
  /**
46480
47795
  * Keep title-generation prompts small and latency-bounded. The serialized
@@ -46767,6 +48082,19 @@ async function resolveSessionFile(conv) {
46767
48082
  console.error(`[conversations] UNRESOLVED claude-code session for conversation '${conv.name}' (tmux=${conv.tmuxSession}, status=${conv.status}, cwd=${conv.cwd}): no --session-id pinned in launcher.sh and no recorded claudeSessionId. The transcript panel cannot be trusted.`);
46768
48083
  return null;
46769
48084
  }
48085
+ /**
48086
+ * True when a conversation that demonstrably had activity (tokens recorded, or
48087
+ * a title derived from its messages) no longer has a transcript file on disk.
48088
+ * This is the operator-facing "history was lost" signal — e.g. the 2026-07-05
48089
+ * incident where a boot-time cleanup deleted conv-* dirs holding ohmypi/codex
48090
+ * transcripts. Live sessions are excluded: a claude-code session writes its
48091
+ * JSONL only on the first message, so a missing file is normal while alive.
48092
+ */
48093
+ function conversationTranscriptMissing(conv, sessionAlive, sessionFile) {
48094
+ if (sessionAlive) return false;
48095
+ if (!((conv.totalTokens ?? 0) > 0 || conv.titleSource === "ai" || conv.titleSource === "auto")) return false;
48096
+ return !sessionFile || !existsSync$1(sessionFile);
48097
+ }
46770
48098
  const MESSAGES_CACHE_MAX = 100;
46771
48099
  const messagesCache = /* @__PURE__ */ new Map();
46772
48100
  function isCodexSessionFile(sessionFile) {
@@ -46935,7 +48263,8 @@ async function getConversationRead(rawId, deps) {
46935
48263
  isWorktree: gitInfo.isWorktree,
46936
48264
  pendingInputCount,
46937
48265
  pendingInputKinds,
46938
- pendingAskUserQuestion
48266
+ pendingAskUserQuestion,
48267
+ transcriptMissing: conversationTranscriptMissing(conv, sessionAlive, convSf)
46939
48268
  });
46940
48269
  } catch (error) {
46941
48270
  const msg = error instanceof Error ? error.message : String(error);
@@ -47109,86 +48438,6 @@ async function generateAiTitle(conversationName, firstMessage, deps) {
47109
48438
  }
47110
48439
  updateConversationTitle(conversationName, sanitized, "ai");
47111
48440
  console.log(`[claude-invoke] SUCCESS purpose=conversation-title | model=${CONVERSATION_TITLE_MODEL} | conversation=${conversationName} | outputChars=${sanitized.length}`);
47112
- scheduleTitleRefinement(conversationName, deps);
47113
- }
47114
- const refinementScheduled = /* @__PURE__ */ new Set();
47115
- function scheduleTitleRefinement(conversationName, deps) {
47116
- if (!isBackgroundFeatureEnabled("titleRefinement")) return;
47117
- if (refinementScheduled.has(conversationName)) return;
47118
- refinementScheduled.add(conversationName);
47119
- const TIMEOUT_MS = 600 * 1e3;
47120
- const POLL_INTERVAL_MS = 1500;
47121
- const startedAt = Date.now();
47122
- let timer = null;
47123
- let running = false;
47124
- let done = false;
47125
- const stop = () => {
47126
- done = true;
47127
- if (timer) clearTimeout(timer);
47128
- refinementScheduled.delete(conversationName);
47129
- };
47130
- async function tick() {
47131
- if (done) return;
47132
- if (running) {
47133
- timer = setTimeout(tick, POLL_INTERVAL_MS);
47134
- return;
47135
- }
47136
- running = true;
47137
- try {
47138
- if (Date.now() - startedAt > TIMEOUT_MS) {
47139
- console.log(`[title-refine] Timed out waiting for first assistant response in "${conversationName}"`);
47140
- stop();
47141
- return;
47142
- }
47143
- const conv = getConversationByName(conversationName);
47144
- if (!conv) {
47145
- stop();
47146
- return;
47147
- }
47148
- if (conv.titleSource !== "ai") {
47149
- stop();
47150
- return;
47151
- }
47152
- const sessionFile = await deps.resolveSessionFile(conv);
47153
- if (!sessionFile || !existsSync$1(sessionFile)) {
47154
- timer = setTimeout(tick, POLL_INTERVAL_MS);
47155
- return;
47156
- }
47157
- const { messages } = await getCachedMessages(sessionFile, false);
47158
- if (!messages.find((m) => m.role === "assistant" && m.completedAt)) {
47159
- timer = setTimeout(tick, POLL_INTERVAL_MS);
47160
- return;
47161
- }
47162
- const transcript = serializeConversationTranscript(messages);
47163
- if (!transcript.trim()) {
47164
- timer = setTimeout(tick, POLL_INTERVAL_MS);
47165
- return;
47166
- }
47167
- console.log(`[claude-invoke] purpose=conversation-title-refine | model=${CONVERSATION_TITLE_MODEL} | conversation=${conversationName} | transcriptChars=${transcript.length}`);
47168
- const refined = await summarizeTranscriptTitle(transcript, configuredTitleModel());
47169
- if (!refined) {
47170
- console.warn(`[title-refine] Model returned empty refined title for "${conversationName}"`);
47171
- stop();
47172
- return;
47173
- }
47174
- const freshConv = getConversationByName(conversationName);
47175
- if (!freshConv || freshConv.titleSource !== "ai") {
47176
- console.log(`[title-refine] Conversation "${conversationName}" no longer eligible (source=${freshConv?.titleSource ?? "missing"}); skipping`);
47177
- stop();
47178
- return;
47179
- }
47180
- updateConversationTitle(conversationName, refined, "ai-refined");
47181
- console.log(`[claude-invoke] SUCCESS purpose=conversation-title-refine | conversation=${conversationName} | title="${refined}"`);
47182
- stop();
47183
- } catch (err) {
47184
- const msg = err instanceof Error ? err.message : String(err);
47185
- console.error(`[title-refine] failed for "${conversationName}":`, msg);
47186
- stop();
47187
- } finally {
47188
- running = false;
47189
- }
47190
- }
47191
- timer = setTimeout(tick, POLL_INTERVAL_MS);
47192
48441
  }
47193
48442
  const MAX_TITLE_LENGTH = 200;
47194
48443
  function patchConversationTitle(name, body) {
@@ -47240,7 +48489,7 @@ async function retitleConversation(name, deps) {
47240
48489
  console.warn(`[conversations] retitle timed out for "${name}"; using deterministic fallback title "${title}"`);
47241
48490
  }
47242
48491
  if (!title) return result({ error: "Title model returned an empty result" }, 502);
47243
- updateConversationTitle(name, title, "ai");
48492
+ updateConversationTitle(name, title, "ai-explicit");
47244
48493
  console.log(`[claude-invoke] SUCCESS purpose=conversation-retitle | conversation=${name} | title="${title}"`);
47245
48494
  return result({ title });
47246
48495
  } finally {
@@ -47444,7 +48693,8 @@ async function enrichConversationList(limit, offset) {
47444
48693
  isWorktree: gitInfo.isWorktree,
47445
48694
  pendingInputCount,
47446
48695
  pendingInputKinds,
47447
- pendingAskUserQuestion
48696
+ pendingAskUserQuestion,
48697
+ transcriptMissing: conversationTranscriptMissing(row, sessionAlive, convSf)
47448
48698
  };
47449
48699
  })), CONVERSATION_LIST_ENRICHMENT_CONCURRENCY));
47450
48700
  }
@@ -47693,10 +48943,14 @@ async function handleConversationMessage(name, body, deps = {
47693
48943
  console.error(`[conversations] eaten-message watcher failed for ${conv.name}: ${msg}`);
47694
48944
  });
47695
48945
  }
47696
- if (conv.titleSource === "default") deps.generateAiTitle(name, message).catch((err) => {
47697
- const msg = err instanceof Error ? err.message : String(err);
47698
- console.error(`[TITLE-GEN-FAILED] AI title generation FAILED for "${name}" — NO RETRY, NO FALLBACK:`, msg);
47699
- });
48946
+ if (conv.titleSource === "default") {
48947
+ const derivedTitle = derivePromptTitle(message);
48948
+ if (derivedTitle) updateConversationTitle(name, derivedTitle, "auto");
48949
+ deps.generateAiTitle(name, message).catch((err) => {
48950
+ const msg = err instanceof Error ? err.message : String(err);
48951
+ console.error(`[TITLE-GEN-FAILED] AI title generation FAILED for "${name}" — NO RETRY, NO FALLBACK:`, msg);
48952
+ });
48953
+ }
47700
48954
  return jsonResponse({
47701
48955
  ok: true,
47702
48956
  ...droppedImageCount > 0 ? { imagesDropped: droppedImageCount } : {}
@@ -48812,7 +50066,6 @@ const eventsRouteLayer = Layer.mergeAll(getEventStreamRoute, getEventVersionRout
48812
50066
  * GET /api/show/:issueId/shadow — shadow state details
48813
50067
  * GET /api/show/:issueId/health — health + heartbeat only
48814
50068
  */
48815
- init_shadow_state();
48816
50069
  const getShowRoute = HttpRouter.add("GET", "/api/show/:issueId", httpHandler(Effect.gen(function* () {
48817
50070
  const issueId = (yield* HttpRouter.params)["issueId"] ?? "";
48818
50071
  const shadowState = yield* getShadowState(issueId);
@@ -48910,7 +50163,7 @@ async function registerProjectFromPath(opts) {
48910
50163
  registerProjectSync(key, projectConfig);
48911
50164
  const seededContextLayer = ensureProjectLayer(fullPath);
48912
50165
  try {
48913
- const { preTrustDirectorySync } = await import("./workspace-manager-CK4yZQev.js");
50166
+ const { preTrustDirectorySync } = await import("./workspace-manager-uVfFRWJG.js");
48914
50167
  preTrustDirectorySync(fullPath);
48915
50168
  } catch {}
48916
50169
  let hooksInstalled = 0;
@@ -48944,9 +50197,9 @@ init_read_model();
48944
50197
  init_agents$1();
48945
50198
  init_pan_dir();
48946
50199
  init_paths();
48947
- const execAsync$3 = promisify(exec);
50200
+ const execAsync$2 = promisify(exec);
48948
50201
  async function getIssueDataService() {
48949
- const { getSharedIssueService } = await import("./issue-service-singleton-BSgap-ST.js");
50202
+ const { getSharedIssueService } = await import("./issue-service-singleton-Cyfvop7U.js");
48950
50203
  return getSharedIssueService();
48951
50204
  }
48952
50205
  /** Returns true if the path exists (any type). */
@@ -49058,13 +50311,15 @@ async function collectSessionTreeNodes(issueId, workspacePath, projectPath, cont
49058
50311
  const agentId = `agent-${issueLower}`;
49059
50312
  const planningAgentId = `planning-${issueLower}`;
49060
50313
  const strikeAgentId = `strike-${issueLower}`;
50314
+ const knowledgeAgentId = `agent-${issueLower}-knowledge`;
49061
50315
  const slotWorkSessionPattern = getSlotWorkSessionPattern(issueLower);
49062
50316
  const sections = [];
49063
50317
  let hasPlanningSection = false;
49064
50318
  const candidateSessionIds = new Set([
49065
50319
  planningAgentId,
49066
50320
  agentId,
49067
- strikeAgentId
50321
+ strikeAgentId,
50322
+ knowledgeAgentId
49068
50323
  ]);
49069
50324
  const agentEntries = await readdir(agentsDir, { withFileTypes: true }).catch(() => []);
49070
50325
  for (const entry of agentEntries) {
@@ -49079,7 +50334,7 @@ async function collectSessionTreeNodes(issueId, workspacePath, projectPath, cont
49079
50334
  try {
49080
50335
  const isPlanning = checkId.startsWith("planning-");
49081
50336
  const isStrike = checkId.startsWith("strike-");
49082
- const sectionType = isPlanning ? "planning" : isStrike ? "strike" : "work";
50337
+ const sectionType = isPlanning ? "planning" : isStrike ? "strike" : state.role === "knowledge" ? "knowledge" : "work";
49083
50338
  if (isPlanning) hasPlanningSection = true;
49084
50339
  const rtState = await Effect.runPromise(getAgentRuntimeState(checkId));
49085
50340
  const presence = await deriveSessionPresence(checkId, rtState, context.tmuxSessionNames);
@@ -49089,7 +50344,7 @@ async function collectSessionTreeNodes(issueId, workspacePath, projectPath, cont
49089
50344
  sections.push({
49090
50345
  type: sectionType,
49091
50346
  sessionId: checkId,
49092
- tmuxSession: sectionType === "work" || sectionType === "planning" || sectionType === "strike" ? checkId : void 0,
50347
+ tmuxSession: sectionType === "work" || sectionType === "planning" || sectionType === "strike" || sectionType === "knowledge" ? checkId : void 0,
49093
50348
  model: state.model || "unknown",
49094
50349
  startedAt: state.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
49095
50350
  endedAt: void 0,
@@ -49443,7 +50698,7 @@ const postProjectsRoute = HttpRouter.add("POST", "/api/projects", httpHandler(Ef
49443
50698
  if ((await readdir(target).catch(() => [])).length > 0) return jsonResponse({ error: `target directory already exists and is non-empty: ${target}` }, { status: 409 });
49444
50699
  }
49445
50700
  await mkdir(target, { recursive: true });
49446
- await execAsync$3("git init", { cwd: target });
50701
+ await execAsync$2("git init", { cwd: target });
49447
50702
  try {
49448
50703
  const result = await registerProjectFromPath({
49449
50704
  path: target,
@@ -49641,7 +50896,7 @@ async function describeSyncTarget(harness, layerKind, projectKey, label, path) {
49641
50896
  async function buildSyncTargets(projects) {
49642
50897
  const targets = [await describeSyncTarget("claude-code", "global", void 0, "Claude Code · global", join$1(CLAUDE_DIR, "CLAUDE.md")), await describeSyncTarget("codex", "global", void 0, "Codex · global (codex-global.md)", codexGlobalContextFile())];
49643
50898
  for (const { key, config } of projects) {
49644
- if (!(await readOptionalFile(projectContextFile(config.path))).exists) continue;
50899
+ if (!(await readOptionalFile(resolveProjectContextFile(config.path))).exists) continue;
49645
50900
  targets.push(await describeSyncTarget("claude-code", "project", key, `${config.name} · CLAUDE.md`, join$1(config.path, "CLAUDE.md")));
49646
50901
  targets.push(await describeSyncTarget("pi", "project", key, `${config.name} · AGENTS.md`, join$1(config.path, "AGENTS.md")));
49647
50902
  }
@@ -49650,11 +50905,11 @@ async function buildSyncTargets(projects) {
49650
50905
  async function buildContextLayerState(projects, overdeckHome = getOverdeckHome()) {
49651
50906
  const catalog = await buildContextCatalog(projects);
49652
50907
  const resolvedLayers = [await layerRecord(globalContextFile(overdeckHome), { kind: "global" })];
49653
- for (const project of catalog.projects) resolvedLayers.push(await layerRecord(projectContextFile(project.config.path), {
50908
+ for (const project of catalog.projects) resolvedLayers.push(await layerRecord(resolveProjectContextFile(project.config.path), {
49654
50909
  kind: "project",
49655
50910
  projectKey: project.key
49656
50911
  }));
49657
- for (const workspace of catalog.workspaces) resolvedLayers.push(await layerRecord(workspaceContextFile(workspace.path), {
50912
+ for (const workspace of catalog.workspaces) resolvedLayers.push(await layerRecord(resolveWorkspaceContextFile(workspace.path), {
49658
50913
  kind: "workspace",
49659
50914
  projectKey: workspace.projectKey,
49660
50915
  workspacePath: workspace.path
@@ -49907,14 +51162,16 @@ const postContextSyncRoute = HttpRouter.add("POST", "/api/context/sync", httpHan
49907
51162
  const contextRouteLayer = Layer.mergeAll(getContextLayersRoute, postContextPreviewRoute, putContextLayerRoute, postContextSyncRoute);
49908
51163
  //#endregion
49909
51164
  //#region routes/admin.ts
51165
+ init_tldr_daemon();
51166
+ init_projects();
51167
+ init_conversations();
49910
51168
  /**
49911
51169
  * Admin route module — plumbing endpoints
49912
51170
  *
49913
51171
  * Implements /api/admin/* endpoints mirroring the `pan admin` CLI namespace:
49914
- * GET /api/admin/tldr/:issueId — TLDR daemon status for a workspace
51172
+ * GET /api/admin/tldr/:issueId — TLDR daemon status for a workspace
51173
+ * POST /api/admin/conversations/backfill-titles — retitle stuck conversations
49915
51174
  */
49916
- init_tldr_daemon();
49917
- init_projects();
49918
51175
  const getAdminTldrRoute = HttpRouter.add("GET", "/api/admin/tldr/:issueId", httpHandler(Effect.gen(function* () {
49919
51176
  const issueId = (yield* HttpRouter.params)["issueId"] ?? "";
49920
51177
  const workspacePath = join$1(resolveProjectFromIssueSync(issueId)?.projectPath ?? process.cwd(), "workspaces", `feature-${issueId.toLowerCase()}`);
@@ -49935,7 +51192,76 @@ const getAdminTldrRoute = HttpRouter.add("GET", "/api/admin/tldr/:issueId", http
49935
51192
  });
49936
51193
  });
49937
51194
  })));
49938
- const adminRouteLayer = Layer.mergeAll(getAdminTldrRoute);
51195
+ async function handleBackfillTitlesBody(body, deps) {
51196
+ const dryRun = body.dryRun === true;
51197
+ const stuck = listConversations().filter((conv) => conv.title === "New conversation" && conv.titleSource === "default");
51198
+ const updated = [];
51199
+ const skipped = [];
51200
+ for (const conv of stuck) try {
51201
+ const sessionFile = await deps.resolveSessionFile(conv);
51202
+ if (!sessionFile) {
51203
+ skipped.push({
51204
+ name: conv.name,
51205
+ reason: "no transcript"
51206
+ });
51207
+ continue;
51208
+ }
51209
+ const { messages } = await deps.getCachedMessages(sessionFile, false);
51210
+ const firstUser = messages.find((m) => m.role === "user" && typeof m.text === "string" && m.text.trim().length > 0);
51211
+ let candidate = "";
51212
+ let reason;
51213
+ if (firstUser) {
51214
+ candidate = derivePromptTitle(firstUser.text);
51215
+ reason = "transcript";
51216
+ }
51217
+ if (!candidate) {
51218
+ candidate = `Untitled — ${new Date(conv.createdAt).toISOString().slice(0, 10)}`;
51219
+ reason = "no transcript";
51220
+ }
51221
+ const fresh = getConversationByName(conv.name);
51222
+ if (!fresh || fresh.title !== "New conversation" || fresh.titleSource !== "default") {
51223
+ skipped.push({
51224
+ name: conv.name,
51225
+ reason: "no longer eligible"
51226
+ });
51227
+ continue;
51228
+ }
51229
+ if (!dryRun) updateConversationTitle(conv.name, candidate, "auto");
51230
+ updated.push({
51231
+ name: conv.name,
51232
+ title: candidate,
51233
+ reason
51234
+ });
51235
+ } catch (err) {
51236
+ const msg = err instanceof Error ? err.message : String(err);
51237
+ skipped.push({
51238
+ name: conv.name,
51239
+ reason: `transcript error: ${msg}`
51240
+ });
51241
+ }
51242
+ return {
51243
+ updated,
51244
+ skipped,
51245
+ dryRun
51246
+ };
51247
+ }
51248
+ const postAdminBackfillTitlesRoute = HttpRouter.add("POST", "/api/admin/conversations/backfill-titles", httpHandler(Effect.gen(function* () {
51249
+ const request = yield* HttpServerRequest.HttpServerRequest;
51250
+ if (!hasDashboardInternalToken(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
51251
+ const rawBody = yield* request.text;
51252
+ let body;
51253
+ try {
51254
+ body = JSON.parse(rawBody);
51255
+ } catch {
51256
+ return jsonResponse({ error: "invalid JSON" }, { status: 400 });
51257
+ }
51258
+ const dryRun = body.dryRun === true;
51259
+ return jsonResponse(yield* Effect.promise(() => handleBackfillTitlesBody({ dryRun }, {
51260
+ resolveSessionFile,
51261
+ getCachedMessages
51262
+ })));
51263
+ })));
51264
+ const adminRouteLayer = Layer.mergeAll(getAdminTldrRoute, postAdminBackfillTitlesRoute);
49939
51265
  //#endregion
49940
51266
  //#region ../../lib/prereqs/registry.ts
49941
51267
  /**
@@ -49944,7 +51270,7 @@ const adminRouteLayer = Layer.mergeAll(getAdminTldrRoute);
49944
51270
  * requiring a heavy `pan install` step before first use.
49945
51271
  */
49946
51272
  init_platform();
49947
- const execAsync$2 = promisify(exec);
51273
+ const execAsync$1 = promisify(exec);
49948
51274
  const PREREQ_REGISTRY = {
49949
51275
  addGitHubProject: ["gh"],
49950
51276
  addGitLabProject: ["glab"],
@@ -50004,8 +51330,8 @@ async function installTool(tool) {
50004
51330
  }
50005
51331
  }
50006
51332
  async function installTmux() {
50007
- if (await Effect.runPromise(detectPlatform()) === "darwin") await execAsync$2("brew install tmux", { timeout: 12e4 });
50008
- else await execAsync$2("sudo apt-get update && sudo apt-get install -y tmux", { timeout: 12e4 });
51333
+ if (await Effect.runPromise(detectPlatform()) === "darwin") await execAsync$1("brew install tmux", { timeout: 12e4 });
51334
+ else await execAsync$1("sudo apt-get update && sudo apt-get install -y tmux", { timeout: 12e4 });
50009
51335
  return {
50010
51336
  tool: "tmux",
50011
51337
  success: true,
@@ -50018,14 +51344,14 @@ async function installTtyd() {
50018
51344
  mkdirSync$1(binDir, { recursive: true });
50019
51345
  const ttydPath = join$1(binDir, "ttyd");
50020
51346
  if (plat === "darwin") try {
50021
- await execAsync$2("brew install ttyd", { timeout: 12e4 });
51347
+ await execAsync$1("brew install ttyd", { timeout: 12e4 });
50022
51348
  return {
50023
51349
  tool: "ttyd",
50024
51350
  success: true,
50025
51351
  message: "ttyd installed via Homebrew"
50026
51352
  };
50027
51353
  } catch {}
50028
- await execAsync$2(`curl -sL "https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.x86_64" -o "${ttydPath}" && chmod +x "${ttydPath}"`, { timeout: 6e4 });
51354
+ await execAsync$1(`curl -sL "https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.x86_64" -o "${ttydPath}" && chmod +x "${ttydPath}"`, { timeout: 6e4 });
50029
51355
  return {
50030
51356
  tool: "ttyd",
50031
51357
  success: true,
@@ -50034,8 +51360,8 @@ async function installTtyd() {
50034
51360
  }
50035
51361
  async function installMkcert() {
50036
51362
  if (await Effect.runPromise(detectPlatform()) === "darwin") {
50037
- await execAsync$2("brew install mkcert", { timeout: 12e4 });
50038
- await execAsync$2("mkcert -install", { timeout: 3e4 });
51363
+ await execAsync$1("brew install mkcert", { timeout: 12e4 });
51364
+ await execAsync$1("mkcert -install", { timeout: 3e4 });
50039
51365
  return {
50040
51366
  tool: "mkcert",
50041
51367
  success: true,
@@ -50045,8 +51371,8 @@ async function installMkcert() {
50045
51371
  const binDir = join$1(homedir$1(), ".local", "bin");
50046
51372
  mkdirSync$1(binDir, { recursive: true });
50047
51373
  const mkcertPath = join$1(binDir, "mkcert");
50048
- await execAsync$2(`curl -sL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${process.arch === "x64" ? "amd64" : process.arch}" -o "${mkcertPath}" && chmod +x "${mkcertPath}"`, { timeout: 6e4 });
50049
- await execAsync$2("mkcert -install", { timeout: 3e4 });
51374
+ await execAsync$1(`curl -sL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${process.arch === "x64" ? "amd64" : process.arch}" -o "${mkcertPath}" && chmod +x "${mkcertPath}"`, { timeout: 6e4 });
51375
+ await execAsync$1("mkcert -install", { timeout: 3e4 });
50050
51376
  return {
50051
51377
  tool: "mkcert",
50052
51378
  success: true,
@@ -50055,14 +51381,14 @@ async function installMkcert() {
50055
51381
  }
50056
51382
  async function installBeads() {
50057
51383
  if (await Effect.runPromise(detectPlatform()) === "darwin") try {
50058
- await execAsync$2("brew install gastownhall/beads/bd", { timeout: 12e4 });
51384
+ await execAsync$1("brew install gastownhall/beads/bd", { timeout: 12e4 });
50059
51385
  return {
50060
51386
  tool: "bd",
50061
51387
  success: true,
50062
51388
  message: "beads installed via Homebrew"
50063
51389
  };
50064
51390
  } catch {}
50065
- await execAsync$2("curl -sSL https://raw.githubusercontent.com/gastownhall/beads/main/scripts/install.sh | bash", { timeout: 12e4 });
51391
+ await execAsync$1("curl -sSL https://raw.githubusercontent.com/gastownhall/beads/main/scripts/install.sh | bash", { timeout: 12e4 });
50066
51392
  return {
50067
51393
  tool: "bd",
50068
51394
  success: true,
@@ -50076,7 +51402,7 @@ async function installOx() {
50076
51402
  const binDir = join$1(homedir$1(), ".local", "bin");
50077
51403
  const oxPath = join$1(binDir, "ox");
50078
51404
  mkdirSync$1(binDir, { recursive: true });
50079
- await execAsync$2(`curl -sL "https://github.com/eltmon/ox/releases/download/latest/ox-${platform}-${arch}" -o "${oxPath}" && chmod +x "${oxPath}"`, { timeout: 6e4 });
51405
+ await execAsync$1(`curl -sL "https://github.com/eltmon/ox/releases/download/latest/ox-${platform}-${arch}" -o "${oxPath}" && chmod +x "${oxPath}"`, { timeout: 6e4 });
50080
51406
  return {
50081
51407
  tool: "ox",
50082
51408
  success: true,
@@ -50873,7 +52199,7 @@ async function refreshMergeStateFromGitHub(issueId, repo, prNumber) {
50873
52199
  let mergeState;
50874
52200
  let isDraft;
50875
52201
  let checksFailed;
50876
- const { isGitHubAppConfigured, getPullRequestState } = await import("./github-app-BG36jYzm.js");
52202
+ const { isGitHubAppConfigured, getPullRequestState } = await import("./github-app-BFaGREfW.js");
50877
52203
  if (isGitHubAppConfigured()) {
50878
52204
  const prState = await Effect.runPromise(getPullRequestState(owner, repoName, prNumber));
50879
52205
  mergeable = prState.mergeable === null ? "UNKNOWN" : prState.mergeable ? "MERGEABLE" : "CONFLICTING";
@@ -51022,8 +52348,8 @@ async function handlePullRequestPromise(payload) {
51022
52348
  bumpIssuePrTabCacheGeneration(issueId);
51023
52349
  const repo = payload.repository.full_name;
51024
52350
  if (payload.action === "closed" && pr.merged === true) try {
51025
- const { postMergeLifecycle } = await import("./merge-agent-CvVDCFOp.js");
51026
- const { resolveProjectFromIssueSync } = await import("./projects-CKnFqWB0.js");
52351
+ const { postMergeLifecycle } = await import("./merge-agent-DzbCbjqg.js");
52352
+ const { resolveProjectFromIssueSync } = await import("./projects-Daw2zpGk.js");
51027
52353
  const project = resolveProjectFromIssueSync(issueId);
51028
52354
  if (project) {
51029
52355
  const branchName = pr.head.ref;
@@ -51440,9 +52766,75 @@ const webhooksRouteLayer = HttpRouter.add("POST", "/api/webhooks/github", httpHa
51440
52766
  return yield* runWebhookHandler(body, headers);
51441
52767
  })));
51442
52768
  //#endregion
52769
+ //#region ../../lib/overdeck/title-refinement.ts
52770
+ init_paths$2();
52771
+ /**
52772
+ * Hook-driven progressive conversation title refinement.
52773
+ *
52774
+ * Replaces the old polling-based refinement scheduler with an event-driven
52775
+ * turn-complete handler. First refinement happens as soon as a completed
52776
+ * assistant message is available; later refinements are debounced by turn count
52777
+ * and wall-clock interval.
52778
+ */
52779
+ init_features();
52780
+ init_conversations();
52781
+ const LATER_REFINE_MIN_TURNS = 5;
52782
+ const LATER_REFINE_MIN_INTERVAL_MS = 600 * 1e3;
52783
+ const cadenceByConversation = /* @__PURE__ */ new Map();
52784
+ const inFlight$1 = /* @__PURE__ */ new Set();
52785
+ async function handleTurnComplete(conv, deps) {
52786
+ if (!(deps.isBackgroundFeatureEnabled ?? isBackgroundFeatureEnabled)("titleRefinement")) return;
52787
+ if (!canRefineTitle(conv)) return;
52788
+ const name = conv.name;
52789
+ let cadence = cadenceByConversation.get(name);
52790
+ if (!cadence) {
52791
+ cadence = {
52792
+ turnsSinceRefine: 0,
52793
+ lastRefinedAtMs: conv.titleSource === "ai-refined" ? Date.now() : 0
52794
+ };
52795
+ cadenceByConversation.set(name, cadence);
52796
+ }
52797
+ cadence.turnsSinceRefine += 1;
52798
+ if (!(conv.titleSource === "default" || conv.titleSource === "auto" || conv.titleSource === "ai")) {
52799
+ if (!(cadence.turnsSinceRefine >= LATER_REFINE_MIN_TURNS && Date.now() - cadence.lastRefinedAtMs >= LATER_REFINE_MIN_INTERVAL_MS)) return;
52800
+ }
52801
+ if (inFlight$1.has(name)) return;
52802
+ inFlight$1.add(name);
52803
+ const getCachedMessages$1 = deps.getCachedMessages ?? getCachedMessages;
52804
+ const configuredTitleModel$1 = deps.configuredTitleModel ?? configuredTitleModel;
52805
+ const summarizeTranscriptTitle$1 = deps.summarizeTranscriptTitle ?? summarizeTranscriptTitle;
52806
+ try {
52807
+ const sessionFile = await deps.resolveSessionFile(conv);
52808
+ if (!sessionFile || !existsSync$1(sessionFile)) return;
52809
+ const { messages } = await getCachedMessages$1(sessionFile, false);
52810
+ if (!messages.find((m) => m.role === "assistant" && m.completedAt)) return;
52811
+ const transcript = serializeConversationTranscript(messages);
52812
+ if (!transcript.trim()) return;
52813
+ console.log(`[claude-invoke] purpose=conversation-title-refine | model=${configuredTitleModel$1()} | conversation=${name} | transcriptChars=${transcript.length}`);
52814
+ const refined = await summarizeTranscriptTitle$1(transcript, configuredTitleModel$1());
52815
+ if (!refined) {
52816
+ console.warn(`[title-refine] Model returned empty refined title for "${name}"`);
52817
+ return;
52818
+ }
52819
+ const freshConv = getConversationByName(name);
52820
+ if (!freshConv || !canRefineTitle(freshConv)) {
52821
+ console.log(`[title-refine] Conversation "${name}" no longer eligible (source=${freshConv?.titleSource ?? "missing"}); skipping`);
52822
+ return;
52823
+ }
52824
+ updateConversationTitle(name, refined, "ai-refined");
52825
+ console.log(`[claude-invoke] SUCCESS purpose=conversation-title-refine | conversation=${name} | title="${refined}"`);
52826
+ cadence.turnsSinceRefine = 0;
52827
+ cadence.lastRefinedAtMs = Date.now();
52828
+ } catch (err) {
52829
+ const msg = err instanceof Error ? err.message : String(err);
52830
+ console.error(`[title-refine] failed for "${name}":`, msg);
52831
+ } finally {
52832
+ inFlight$1.delete(name);
52833
+ }
52834
+ }
52835
+ //#endregion
51443
52836
  //#region ../../lib/compliance/advisory-warning.ts
51444
52837
  init_config_yaml();
51445
- init_paths$2();
51446
52838
  init_rollup();
51447
52839
  const COMPLIANCE_ADVISORY_WARNING = "Last turn included a memory-first trigger but the search wasn't called. Try pan memory search first next time.";
51448
52840
  async function resolveComplianceAdvisoryWarning(input) {
@@ -51522,7 +52914,7 @@ function getCheckpointWorker() {
51522
52914
  return worker;
51523
52915
  }
51524
52916
  async function runInline(operation, payload) {
51525
- const { claimTranscriptRange, commitTranscriptRange, releaseTranscriptRange, getTranscriptCheckpoint, listTranscriptCheckpoints } = await import("./checkpoints-DMO68jIJ.js");
52917
+ const { claimTranscriptRange, commitTranscriptRange, releaseTranscriptRange, getTranscriptCheckpoint, listTranscriptCheckpoints } = await import("./checkpoints-DjIvtcNL.js");
51526
52918
  switch (operation) {
51527
52919
  case "claimTranscriptRange": return claimTranscriptRange(payload);
51528
52920
  case "commitTranscriptRange": return commitTranscriptRange(payload);
@@ -52676,7 +54068,7 @@ function buildPendingTurn(input, fromOffset, toOffset, compressed) {
52676
54068
  };
52677
54069
  }
52678
54070
  async function emitMemoryObservationCreated(observation, timestamp) {
52679
- const { initEventStore } = await import("./event-store-BQRWQeyx.js");
54071
+ const { initEventStore } = await import("./event-store-w76YFiCU.js");
52680
54072
  await (await initEventStore()).appendAsync({
52681
54073
  type: "memory.observation_created",
52682
54074
  timestamp,
@@ -53439,7 +54831,65 @@ const postPermissionEventRoute = HttpRouter.add("POST", "/api/hooks/permission-e
53439
54831
  waiting
53440
54832
  });
53441
54833
  })));
53442
- const hooksRouteLayer = Layer.mergeAll(postMemoryInjectRoute, postMemorySessionStartRoute, postMemoryTurnRoute, postPermissionEventRoute);
54834
+ async function handleUserPromptSubmitBody(body, options = {}) {
54835
+ const sessionId = typeof body.session_id === "string" ? body.session_id : null;
54836
+ const prompt = typeof body.prompt === "string" ? body.prompt : "";
54837
+ if (!sessionId) return { ok: true };
54838
+ const conv = getConversationByClaudeSessionId(sessionId);
54839
+ if (!conv) return { ok: true };
54840
+ if (conv.titleSource === "default") {
54841
+ const derivedTitle = derivePromptTitle(prompt);
54842
+ if (derivedTitle) updateConversationTitle(conv.name, derivedTitle, "auto");
54843
+ (options.generateAiTitle ?? generateAiTitle)(conv.name, prompt, { resolveSessionFile: options.resolveSessionFile ?? resolveSessionFile }).catch((err) => {
54844
+ const msg = err instanceof Error ? err.message : String(err);
54845
+ console.error(`[hooks/user-prompt-submit] AI title generation failed for "${conv.name}":`, msg);
54846
+ });
54847
+ }
54848
+ return {
54849
+ ok: true,
54850
+ conversationName: conv.name,
54851
+ updated: conv.titleSource === "default"
54852
+ };
54853
+ }
54854
+ const postUserPromptSubmitRoute = HttpRouter.add("POST", "/api/hooks/user-prompt-submit", httpHandler(Effect.gen(function* () {
54855
+ const request = yield* HttpServerRequest.HttpServerRequest;
54856
+ if (!hasDashboardInternalToken(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
54857
+ const rawBody = yield* request.text;
54858
+ let body;
54859
+ try {
54860
+ body = JSON.parse(rawBody);
54861
+ } catch {
54862
+ return jsonResponse({ error: "invalid JSON" }, { status: 400 });
54863
+ }
54864
+ return jsonResponse(yield* Effect.promise(() => handleUserPromptSubmitBody(body)));
54865
+ })));
54866
+ async function handleTurnCompleteBody(body, options = {}) {
54867
+ const sessionId = typeof body.session_id === "string" ? body.session_id : null;
54868
+ if (!sessionId) return { ok: true };
54869
+ const conv = getConversationByClaudeSessionId(sessionId);
54870
+ if (!conv) return { ok: true };
54871
+ (options.handleTurnComplete ?? handleTurnComplete)(conv, { resolveSessionFile }).catch((err) => {
54872
+ const msg = err instanceof Error ? err.message : String(err);
54873
+ console.error(`[hooks/turn-complete] title refinement failed for "${conv.name}":`, msg);
54874
+ });
54875
+ return {
54876
+ ok: true,
54877
+ conversationName: conv.name
54878
+ };
54879
+ }
54880
+ const postTurnCompleteRoute = HttpRouter.add("POST", "/api/hooks/turn-complete", httpHandler(Effect.gen(function* () {
54881
+ const request = yield* HttpServerRequest.HttpServerRequest;
54882
+ if (!hasDashboardInternalToken(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
54883
+ const rawBody = yield* request.text;
54884
+ let body;
54885
+ try {
54886
+ body = JSON.parse(rawBody);
54887
+ } catch {
54888
+ return jsonResponse({ error: "invalid JSON" }, { status: 400 });
54889
+ }
54890
+ return jsonResponse(yield* Effect.promise(() => handleTurnCompleteBody(body)));
54891
+ })));
54892
+ const hooksRouteLayer = Layer.mergeAll(postMemoryInjectRoute, postMemorySessionStartRoute, postMemoryTurnRoute, postPermissionEventRoute, postUserPromptSubmitRoute, postTurnCompleteRoute);
53443
54893
  function parseMemoryIdentity(value, sessionId) {
53444
54894
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
53445
54895
  const identity = value;
@@ -55715,7 +57165,7 @@ async function getFlywheelConversationPayload() {
55715
57165
  };
55716
57166
  }
55717
57167
  async function postFlywheelReportPayload() {
55718
- const { flywheelReportCommand } = await import("./flywheel-iZNbJfS_.js");
57168
+ const { flywheelReportCommand } = await import("./flywheel-DrNtUqWK.js");
55719
57169
  await flywheelReportCommand({ cwd: process.cwd() });
55720
57170
  return {
55721
57171
  status: 200,
@@ -55873,11 +57323,11 @@ const getMergeBlockersRoute = HttpRouter.add("GET", "/api/flywheel/merge-blocker
55873
57323
  async function defaultGetOrderedIssueIds() {
55874
57324
  const status = await readCurrentFlywheelStatusForDashboard();
55875
57325
  if (!status) return [];
55876
- const { computeMergeQueue } = await import("./flywheel-merge-order-PW8nIENP.js");
57326
+ const { computeMergeQueue } = await import("./flywheel-merge-order-CrZrhwbK.js");
55877
57327
  return (await Effect.runPromise(computeMergeQueue(status.activePipeline, process.cwd()).pipe(Effect.provide(layer)))).map((i) => i.issueId);
55878
57328
  }
55879
57329
  async function defaultMergeOne(issueId) {
55880
- const { triggerMerge } = await import("./merge-ops-iaG5kd-C.js");
57330
+ const { triggerMerge } = await import("./merge-ops-CgzhHkd4.js");
55881
57331
  const r = await triggerMerge(issueId);
55882
57332
  return r.success ? { ok: true } : {
55883
57333
  ok: false,
@@ -55897,7 +57347,7 @@ async function postFlywheelMergeNextPayload(payload, deps = {}) {
55897
57347
  body: { error: "n must be a positive integer" }
55898
57348
  };
55899
57349
  const issueIds = (await (deps.getOrderedIssueIds ?? defaultGetOrderedIssueIds)()).slice(0, n);
55900
- const { shipMergeBatch } = await import("./merge-batch-UhBPrssc.js");
57350
+ const { shipMergeBatch } = await import("./merge-batch-LLp_cXUR.js");
55901
57351
  return {
55902
57352
  status: 200,
55903
57353
  body: { outcomes: await shipMergeBatch(issueIds, { merge: deps.merge ?? defaultMergeOne }) }
@@ -56054,7 +57504,7 @@ const getFlywheelMergeQueueRoute = HttpRouter.add("GET", "/api/flywheel/merge-qu
56054
57504
  return yield* Effect.promise(async () => {
56055
57505
  const status = await readCurrentFlywheelStatusForDashboard();
56056
57506
  if (!status) return jsonResponse([]);
56057
- const { computeMergeQueue, resolveMergeQueuePrUrl } = await import("./flywheel-merge-order-PW8nIENP.js");
57507
+ const { computeMergeQueue, resolveMergeQueuePrUrl } = await import("./flywheel-merge-order-CrZrhwbK.js");
56058
57508
  return jsonResponse(await Effect.runPromise(computeMergeQueue(status.activePipeline, process.cwd(), { getPrUrl: resolveMergeQueuePrUrl }).pipe(Effect.provide(layer))));
56059
57509
  });
56060
57510
  })));
@@ -56068,18 +57518,18 @@ function uatGenerationNameFromParam(param) {
56068
57518
  return decoded.startsWith("uat/") ? decoded : `uat/${decoded}`;
56069
57519
  }
56070
57520
  const getUatGenerationsRoute = HttpRouter.add("GET", "/api/flywheel/uat-generations", httpHandler(Effect.gen(function* () {
56071
- const { getUatGenerationsPayload } = yield* Effect.promise(() => import("./uat-train-CwAd0sZ1.js"));
57521
+ const { getUatGenerationsPayload } = yield* Effect.promise(() => import("./uat-train-fTKeQcdW.js"));
56072
57522
  return jsonResponse(yield* Effect.promise(() => getUatGenerationsPayload()));
56073
57523
  })));
56074
57524
  const getUatCandidateRoute = HttpRouter.add("GET", "/api/flywheel/uat-candidate", httpHandler(Effect.gen(function* () {
56075
- const { getUatCandidatePayload } = yield* Effect.promise(() => import("./uat-train-CwAd0sZ1.js"));
57525
+ const { getUatCandidatePayload } = yield* Effect.promise(() => import("./uat-train-fTKeQcdW.js"));
56076
57526
  return jsonResponse(yield* Effect.promise(() => getUatCandidatePayload()));
56077
57527
  })));
56078
57528
  const postUatGenerationStackRoute = HttpRouter.add("POST", "/api/flywheel/uat-generations/:name/stack", httpHandler(Effect.gen(function* () {
56079
57529
  const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
56080
57530
  if (authError) return authError;
56081
57531
  const name = uatGenerationNameFromParam((yield* HttpRouter.params)["name"] ?? "");
56082
- const { postUatGenerationStackPayload } = yield* Effect.promise(() => import("./uat-train-CwAd0sZ1.js"));
57532
+ const { postUatGenerationStackPayload } = yield* Effect.promise(() => import("./uat-train-fTKeQcdW.js"));
56083
57533
  const result = yield* Effect.promise(() => postUatGenerationStackPayload(name));
56084
57534
  if (!result.ok) return jsonResponse({ error: result.error }, { status: result.status });
56085
57535
  return jsonResponse({
@@ -56091,8 +57541,8 @@ const postUatGenerationPromoteRoute = HttpRouter.add("POST", "/api/flywheel/uat-
56091
57541
  const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
56092
57542
  if (authError) return authError;
56093
57543
  const name = uatGenerationNameFromParam((yield* HttpRouter.params)["name"] ?? "");
56094
- const { postUatGenerationPromotePayload } = yield* Effect.promise(() => import("./uat-train-CwAd0sZ1.js"));
56095
- const { firePostMergeLifecycle } = yield* Effect.promise(() => import("./specialists-uq-O3uxF.js"));
57544
+ const { postUatGenerationPromotePayload } = yield* Effect.promise(() => import("./uat-train-fTKeQcdW.js"));
57545
+ const { firePostMergeLifecycle } = yield* Effect.promise(() => import("./specialists-BWNyQTq6.js"));
56096
57546
  const result = yield* Effect.promise(() => postUatGenerationPromotePayload(name, firePostMergeLifecycle));
56097
57547
  if (!result.success) return jsonResponse(result, { status: result.reason === "not-found" ? 404 : result.reason === "merge-failed" ? 500 : 409 });
56098
57548
  return jsonResponse(result);
@@ -56105,7 +57555,7 @@ const postUatGenerationPromoteRoute = HttpRouter.add("POST", "/api/flywheel/uat-
56105
57555
  const postFlywheelAssembleUatRoute = HttpRouter.add("POST", "/api/flywheel/assemble-uat", httpHandler(Effect.gen(function* () {
56106
57556
  const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
56107
57557
  if (authError) return authError;
56108
- const { runUatTrainReconcile } = yield* Effect.promise(() => import("./uat-train-CwAd0sZ1.js"));
57558
+ const { runUatTrainReconcile } = yield* Effect.promise(() => import("./uat-train-fTKeQcdW.js"));
56109
57559
  return jsonResponse(yield* Effect.promise(() => runUatTrainReconcile({ force: true })));
56110
57560
  })));
56111
57561
  const getFlywheelStateRoute = HttpRouter.add("GET", "/api/flywheel/state", httpHandler(Effect.gen(function* () {
@@ -56525,36 +57975,8 @@ const postArtifactUnshareRoute = HttpRouter.add("POST", "/api/artifacts/:slug/un
56525
57975
  })));
56526
57976
  const artifactsRouteLayer = Layer.mergeAll(getRawArtifactRoute, getArtifactDetailRoute, getWorkspaceArtifactsRoute, getArtifactThumbnailRoute, postArtifactUnshareRoute);
56527
57977
  //#endregion
56528
- //#region ../../lib/backlog/label-ops.ts
56529
- init_sequence_io();
56530
- init_tracker_utils();
56531
- const execAsync$1 = promisify(exec);
56532
- /**
56533
- * Add or remove a GitHub label on an issue. Non-GitHub issues are silently
56534
- * skipped; the `|| true` keeps a missing-label / already-applied no-op non-fatal.
56535
- */
56536
- async function editIssueLabel(issueId, op, label) {
56537
- const resolution = resolveGitHubIssueSync(issueId);
56538
- if (!resolution.isGitHub) return;
56539
- const { owner, repo, number } = resolution;
56540
- await execAsync$1(`gh issue edit ${number} --repo ${owner}/${repo} ${op === "add" ? "--add-label" : "--remove-label"} "${label}" 2>/dev/null || true`);
56541
- }
56542
- const addIssueLabel = (issueId, label) => editIssueLabel(issueId, "add", label);
56543
- const removeIssueLabel = (issueId, label) => editIssueLabel(issueId, "remove", label);
56544
- const applyIssueParkedLabel = (id) => addIssueLabel(id, PARKED_LABEL);
56545
- const removeIssueParkedLabel = (id) => removeIssueLabel(id, PARKED_LABEL);
56546
- const applyIssueVetoedLabel = (id) => addIssueLabel(id, VETOED_LABEL);
56547
- const removeIssueVetoedLabel = (id) => removeIssueLabel(id, VETOED_LABEL);
56548
- const applyIssueBlocksMainLabel = (id) => addIssueLabel(id, BLOCKS_MAIN_LABEL);
56549
- const removeIssueBlocksMainLabel = (id) => removeIssueLabel(id, BLOCKS_MAIN_LABEL);
56550
- const applyIssueReadyLabel = (id) => addIssueLabel(id, READY_LABEL);
56551
- const removeIssueReadyLabel = (id) => removeIssueLabel(id, READY_LABEL);
56552
- const applyIssueReleasedLabel = (id) => addIssueLabel(id, RELEASED_LABEL);
56553
- const removeIssueReleasedLabel = (id) => removeIssueLabel(id, RELEASED_LABEL);
56554
- const applyIssueObjectionLabel = (id) => addIssueLabel(id, OBJECTION_LABEL);
56555
- const removeIssueObjectionLabel = (id) => removeIssueLabel(id, OBJECTION_LABEL);
56556
- //#endregion
56557
57978
  //#region ../../lib/database/agent-mappers.ts
57979
+ init_sequence_io();
56558
57980
  function agentStateToDbAgent(state) {
56559
57981
  return {
56560
57982
  id: state.id,
@@ -56580,6 +58002,9 @@ function agentStateToDbAgent(state) {
56580
58002
  paused: state.paused ?? null,
56581
58003
  pausedReason: state.pausedReason ?? null,
56582
58004
  pausedAt: state.pausedAt ?? null,
58005
+ yieldedByScheduler: state.yieldedByScheduler ?? null,
58006
+ yieldedAt: state.yieldedAt ?? null,
58007
+ lastYieldResumeAt: state.lastYieldResumeAt ?? null,
56583
58008
  troubled: state.troubled ?? null,
56584
58009
  troubledAt: state.troubledAt ?? null,
56585
58010
  consecutiveFailures: state.consecutiveFailures ?? null,
@@ -56626,7 +58051,8 @@ const VALID_ROLES = new Set([
56626
58051
  "test",
56627
58052
  "ship",
56628
58053
  "flywheel",
56629
- "strike"
58054
+ "strike",
58055
+ "knowledge"
56630
58056
  ]);
56631
58057
  const COLUMN_MAP = {
56632
58058
  id: "id",
@@ -56652,6 +58078,9 @@ const COLUMN_MAP = {
56652
58078
  paused: "paused",
56653
58079
  pausedReason: "paused_reason",
56654
58080
  pausedAt: "paused_at",
58081
+ yieldedByScheduler: "yielded_by_scheduler",
58082
+ yieldedAt: "yielded_at",
58083
+ lastYieldResumeAt: "last_yield_resume_at",
56655
58084
  troubled: "troubled",
56656
58085
  troubledAt: "troubled_at",
56657
58086
  consecutiveFailures: "consecutive_failures",
@@ -57208,6 +58637,9 @@ function initSchema(db) {
57208
58637
  paused INTEGER,
57209
58638
  paused_reason TEXT,
57210
58639
  paused_at TEXT,
58640
+ yielded_by_scheduler INTEGER,
58641
+ yielded_at TEXT,
58642
+ last_yield_resume_at TEXT,
57211
58643
  troubled INTEGER,
57212
58644
  troubled_at TEXT,
57213
58645
  consecutive_failures INTEGER,
@@ -57497,7 +58929,7 @@ function initSchema(db) {
57497
58929
  CREATE INDEX IF NOT EXISTS idx_backlog_sequence_project_rank
57498
58930
  ON backlog_sequence(project_key, rank);
57499
58931
  `);
57500
- db.pragma(`user_version = 58`);
58932
+ db.pragma(`user_version = 59`);
57501
58933
  }
57502
58934
  /**
57503
58935
  * Run schema migrations if the database version is older than SCHEMA_VERSION.
@@ -57505,7 +58937,7 @@ function initSchema(db) {
57505
58937
  */
57506
58938
  function runMigrations(db, dbPath) {
57507
58939
  const currentVersion = db.pragma("user_version", { simple: true });
57508
- if (currentVersion >= 58) return;
58940
+ if (currentVersion >= 59) return;
57509
58941
  if (currentVersion === 0) {
57510
58942
  initSchema(db);
57511
58943
  return;
@@ -58239,7 +59671,18 @@ function runMigrations(db, dbPath) {
58239
59671
  } catch {}
58240
59672
  db.exec(`UPDATE discovered_sessions SET harness = 'claude-code' WHERE harness IS NULL`);
58241
59673
  }
58242
- db.pragma(`user_version = 58`);
59674
+ if (currentVersion < 59) {
59675
+ try {
59676
+ db.exec(`ALTER TABLE agents ADD COLUMN yielded_by_scheduler INTEGER`);
59677
+ } catch {}
59678
+ try {
59679
+ db.exec(`ALTER TABLE agents ADD COLUMN yielded_at TEXT`);
59680
+ } catch {}
59681
+ try {
59682
+ db.exec(`ALTER TABLE agents ADD COLUMN last_yield_resume_at TEXT`);
59683
+ } catch {}
59684
+ }
59685
+ db.pragma(`user_version = 59`);
58243
59686
  }
58244
59687
  //#endregion
58245
59688
  //#region ../../lib/database/index.ts
@@ -59621,7 +61064,7 @@ async function pollOnce(state) {
59621
61064
  try {
59622
61065
  const remoteState = await readFile(join$1(homedir$1(), ".overdeck", "agents", agentId, "remote-state.json"), "utf-8").then((text) => JSON.parse(text)).catch(() => null);
59623
61066
  if (remoteState?.location === "remote" && remoteState?.vmName) {
59624
- const { getRemoteAgentOutput } = await import("./remote-agents-tBpE_KXg.js");
61067
+ const { getRemoteAgentOutput } = await import("./remote-agents-YROPFM-n.js");
59625
61068
  stdout = await getRemoteAgentOutput(agentId, remoteState.vmName, 50);
59626
61069
  } else stdout = await Effect.runPromise(capturePane(agentId, 50));
59627
61070
  } catch {
@@ -59694,7 +61137,8 @@ const BACKFILL_ROLES = new Set([
59694
61137
  "work",
59695
61138
  "review",
59696
61139
  "test",
59697
- "ship"
61140
+ "ship",
61141
+ "knowledge"
59698
61142
  ]);
59699
61143
  const NON_AGENT_PREFIXES = [
59700
61144
  "conv-",
@@ -60804,7 +62248,7 @@ const RESTART_MARKER = join(homedir(), ".overdeck", "dashboard-restarting.json")
60804
62248
  * loads new content-hashed chunk filenames (no ERR_MODULE_NOT_FOUND after rebuild).
60805
62249
  */
60806
62250
  async function defaultLifecycleRunner(pending) {
60807
- const { postMergeLifecycle, notifyTldrDaemon } = await import("./merge-agent-CvVDCFOp.js");
62251
+ const { postMergeLifecycle, notifyTldrDaemon } = await import("./merge-agent-DzbCbjqg.js");
60808
62252
  await postMergeLifecycle(pending.issueId, pending.projectPath, pending.sourceBranch, { skipDeploy: true });
60809
62253
  if (pending.sourceBranch) await notifyTldrDaemon(pending.projectPath, pending.sourceBranch);
60810
62254
  }
@@ -61125,7 +62569,7 @@ function failureReason(result) {
61125
62569
  return result.error ?? result.message ?? `merge returned status ${result.statusCode ?? "unknown"}`;
61126
62570
  }
61127
62571
  async function defaultMergeIssue(issueId) {
61128
- const { triggerMerge } = await import("./merge-ops-iaG5kd-C.js");
62572
+ const { triggerMerge } = await import("./merge-ops-CgzhHkd4.js");
61129
62573
  return triggerMerge(issueId);
61130
62574
  }
61131
62575
  function defaultAnnounceFailure(issueId, reason) {
@@ -61229,6 +62673,34 @@ async function warnIfAutonomousMergeBackendUnavailable(deps = {}) {
61229
62673
  }
61230
62674
  }
61231
62675
  //#endregion
62676
+ //#region services/merge-app-scopes-health.ts
62677
+ init_activity_logger();
62678
+ init_github_app();
62679
+ function shouldWarnAppCannotMerge(result) {
62680
+ return result.configured === true && result.canMerge === false;
62681
+ }
62682
+ async function warnIfAppCannotMerge(deps = {}) {
62683
+ try {
62684
+ const result = await (deps.getResult ?? (() => verifyAppCanMerge()))();
62685
+ if (!shouldWarnAppCannotMerge(result)) return;
62686
+ const missing = result.missing ?? [];
62687
+ const message = [
62688
+ "[overdeck] WARNING: overdeck-agent GitHub App installation cannot merge pull requests.",
62689
+ `Missing required permissions: ${missing.join(", ")}.`,
62690
+ "Grant these permissions to the App installation to enable autonomous merges."
62691
+ ].join("\n");
62692
+ (deps.warn ?? console.warn)(message);
62693
+ (deps.emit ?? emitActivityEntrySync)({
62694
+ source: "dashboard",
62695
+ level: "warn",
62696
+ message: `GitHub App installation cannot merge: missing ${missing.join(", ")}`
62697
+ });
62698
+ } catch (error) {
62699
+ const message = error instanceof Error ? error.message : String(error);
62700
+ (deps.warn ?? console.warn)(`[overdeck] WARNING: failed to verify GitHub App merge permissions: ${message}`);
62701
+ }
62702
+ }
62703
+ //#endregion
61232
62704
  //#region services/cost-reconcile-service.ts
61233
62705
  const RECONCILE_INTERVAL_MS = 5 * 6e4;
61234
62706
  let timer = null;
@@ -61239,6 +62711,14 @@ async function runCostReconcileOnce(reason) {
61239
62711
  const result = await reconcilePiTranscripts();
61240
62712
  if (result.eventsImported > 0 || result.errors.length > 0) console.log(`[cost-reconciler] ${reason} sweep: ${result.eventsImported} imported, ${result.duplicatesSkipped} duplicate(s), ${result.errors.length} error(s)`);
61241
62713
  if (result.errors.length > 0) for (const err of result.errors.slice(0, 5)) console.warn(`[cost-reconciler] ${err.path}: ${err.error}`);
62714
+ const backfillResult = reclassifyUnknownCostEventsSync();
62715
+ if (backfillResult.updated > 0) console.log(`[cost-reconciler] ${reason} UNKNOWN backfill: ${backfillResult.updated} updated`);
62716
+ try {
62717
+ const codexResult = await Effect.runPromise(CostWriter.use((writer) => writer.reconcile({ source: "codex" })).pipe(Effect.provide(CostDoorLive)));
62718
+ if (codexResult.imported > 0) console.log(`[cost-reconciler] ${reason} codex sweep: ${codexResult.imported} imported`);
62719
+ } catch (err) {
62720
+ console.warn("[cost-reconciler] codex sweep failed:", err instanceof Error ? err.message : err);
62721
+ }
61242
62722
  })().finally(() => {
61243
62723
  inFlight = null;
61244
62724
  });
@@ -61264,6 +62744,7 @@ function stopCostReconcileService() {
61264
62744
  }
61265
62745
  //#endregion
61266
62746
  //#region ../../../scripts/create-overdeck-db.ts
62747
+ init_boot_gates();
61267
62748
  init_driver();
61268
62749
  function migrationStatements(migrationPath) {
61269
62750
  return readFileSync$1(migrationPath, "utf8").split("--> statement-breakpoint").map((statement) => statement.trim()).filter(Boolean);
@@ -61330,6 +62811,7 @@ init_paths();
61330
62811
  init_tmux();
61331
62812
  init_fts_db();
61332
62813
  init_paths$1();
62814
+ init_auto_commit();
61333
62815
  initDashboardLogFile();
61334
62816
  console.log(`[boot-timing] module graph loaded at +${Math.round(performance.now())}ms (since process start)`);
61335
62817
  console.log(`[overdeck] Boot gates: ${formatBootGateState(resolveBootGates())}`);
@@ -61338,6 +62820,7 @@ console.log("[overdeck] Event loop delay monitor started");
61338
62820
  await mkdir(getOverdeckHome(), { recursive: true });
61339
62821
  ensureInternalTokenSync();
61340
62822
  warnIfAutonomousMergeBackendUnavailable();
62823
+ warnIfAppCannotMerge();
61341
62824
  await ensureManagedTmuxContextOnce();
61342
62825
  await initTrackerConfigCache().catch((err) => {
61343
62826
  console.log("[tracker-config] Warning: failed to cache .overdeck.env:", err.message);
@@ -61674,6 +63157,7 @@ const handleShutdownSignal = async (signal) => {
61674
63157
  stopCostReconcileService();
61675
63158
  stopRestartAnnouncer();
61676
63159
  await stopDeaconChild().catch((err) => console.warn("[deacon-supervisor] child shutdown failed:", err));
63160
+ await Effect.runPromise(flushAllPendingAutoCommits()).catch((err) => console.warn("[pan-dir/auto-commit] shutdown flush failed:", err));
61677
63161
  await stopConversationSearchWatcher().catch((err) => console.warn("[conversation-search] watcher shutdown failed:", err));
61678
63162
  closeConversationSearchService();
61679
63163
  closeMemoryFtsDatabases();
@@ -61722,7 +63206,7 @@ reconcileStaleGitHubBlockers().then((n) => {
61722
63206
  }
61723
63207
  }).catch((err) => console.warn(`[overdeck] Startup blocker reconciliation failed: ${err.message}`));
61724
63208
  try {
61725
- const { resetProcessingToQueued } = await import("./merge-sync-Dz7-euH2.js");
63209
+ const { resetProcessingToQueued } = await import("./merge-sync-DD-v3GnC.js");
61726
63210
  const resetCount = resetProcessingToQueued();
61727
63211
  if (resetCount > 0) {
61728
63212
  console.log(`[overdeck] Reset ${resetCount} stuck merge queue entries to queued`);