@overdeck/desktop 0.30.1 → 0.31.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 (20) hide show
  1. package/package.json +1 -1
  2. package/server/public/assets/{chunk-BO2N2NFS-CjzCvQMQ.js → chunk-BO2N2NFS-Cwc4lXp8.js} +4 -4
  3. package/server/public/assets/{diagram-OG6HWLK6-B1mpK1Bv.js → diagram-OG6HWLK6-BV1cva-G.js} +1 -1
  4. package/server/public/assets/{dist-Bjux0aN_.js → dist-9_3cMWAu.js} +3 -3
  5. package/server/public/assets/{dist-D6NF8U3N.js → dist-B3-NBXNY.js} +1 -1
  6. package/server/public/assets/{ganttDiagram-6RSMTGT7-CMztT_U2.js → ganttDiagram-6RSMTGT7-pdYgAHyY.js} +1 -1
  7. package/server/public/assets/{highlighted-body-OFNGDK62-DFxe8Imf.js → highlighted-body-OFNGDK62-DmF_yp4o.js} +1 -1
  8. package/server/public/assets/index-CtG7LH4D.css +1 -0
  9. package/server/public/assets/{index-DtYTWOQ8.js → index-DfbVwpxY.js} +139 -138
  10. package/server/public/assets/{linear-DwYwoRdv.js → linear-DcZ83rN-.js} +1 -1
  11. package/server/public/assets/mermaid-GHXKKRXX-CtuRwlIT.js +1 -0
  12. package/server/public/assets/{pieDiagram-4H26LBE5-BkjaiAxk.js → pieDiagram-4H26LBE5-BzCaErrK.js} +1 -1
  13. package/server/public/assets/{prod-D5kET3Ov.js → prod-Dgylk8o0.js} +4 -4
  14. package/server/public/assets/{quadrantDiagram-W4KKPZXB-CwMk2BkV.js → quadrantDiagram-W4KKPZXB-BfeRAr-V.js} +1 -1
  15. package/server/public/assets/{sankeyDiagram-5OEKKPKP-CA549BVM.js → sankeyDiagram-5OEKKPKP-DQ6JjCv-.js} +1 -1
  16. package/server/public/assets/{xychartDiagram-2RQKCTM6-WyNBIQBE.js → xychartDiagram-2RQKCTM6-B5QvPHe8.js} +1 -1
  17. package/server/public/index.html +2 -2
  18. package/server/server.js +696 -991
  19. package/server/public/assets/index--mf7PRYx.css +0 -1
  20. package/server/public/assets/mermaid-GHXKKRXX-YYK67xv6.js +0 -1
package/server/server.js CHANGED
@@ -1,93 +1,93 @@
1
1
  import { a as __toCommonJS, i as __require, o as __toESM, t as __commonJSMin } from "./chunk-HkwdBwDg.js";
2
- import { C as isDevMode, S as init_paths, T as sessionFilePath, _ as SYNC_SOURCES, l as LOGS_DIR, o as CLAUDE_DIR, t as AGENTS_DIR, u as OVERDECK_HOME, w as packageRoot, x as getOverdeckHome, y as encodeClaudeProjectDir } from "./paths-wLUq63xa.js";
2
+ import { C as isDevMode, E as sessionFilePath, S as init_paths, T as resolveOhmypiExtensionPath, _ as SYNC_SOURCES, l as LOGS_DIR, o as CLAUDE_DIR, t as AGENTS_DIR, u as OVERDECK_HOME, w as packageRoot, x as getOverdeckHome, y as encodeClaudeProjectDir } from "./paths-CC7BDxY0.js";
3
3
  import { a as FsError, d as ProcessSpawnError, h as init_errors, p as TrackerError, s as GitHubApiError } from "./errors-LhRGtRIA.js";
4
- import { A as jsonResponse, C as isTrustedOriginForHost, D as checkCodexAuthStatus, E as validateOriginHeaders, O as httpHandler, T as validateOrigin$1, _ as scanPendingInputsPromise, a as saveAgentStateAndEmitEventProgram, b as parseCodexApprovalPrompt, c as getSystemHealthSnapshot, d as getRuntimeForAgent, f as getAgentHealth, g as getAgentWorkspace, h as getAgentJsonlMtime, i as saveAgentStateAndEmitEvent, p as computeAgentEnrichment, s as resolveConversationGitInfo, t as agentsRouteLayer, u as getCloisterService, v as detectAwaitingInputForAgent, x as getHeaderFromMap, y as detectAwaitingInputFromPaneSync } from "./agents-CRTpkAgz.js";
4
+ import { A as getAgentJsonlMtime, B as isTrustedOriginForHost, C as resolveAgentHarness, D as resolvePiSessionPath, E as resolveJsonlPath, F as init_agent_input_detection, G as init_codex_auth, H as validateOrigin$1, I as parseCodexApprovalPrompt, K as httpHandler, L as getHeaderFromMap, M as scanPendingInputsPromise, N as detectAwaitingInputForAgent, O as computeAgentEnrichment, P as detectAwaitingInputFromPaneSync, S as readLauncherPinnedSessionId, T as resolveCodexRolloutPath, U as validateOriginHeaders, W as checkCodexAuthStatus, X as init_http_helpers, Z as jsonResponse, _ as getRuntimeForAgent, a as init_agent_projection, b as init_health, c as init_git_info, d as getSystemHealthSnapshot, f as init_system_health_service, g as init_service, h as getCloisterService, j as init_agent_enrichment, n as init_agents, o as saveAgentStateAndEmitEvent, q as init_http_handler, s as saveAgentStateAndEmitEventProgram, t as agentsRouteLayer, u as resolveConversationGitInfo, v as init_runtimes, w as resolveClaudeSessionId, x as init_jsonl_resolver, y as getAgentHealth, z as init_origin_validation } from "./agents-DCDyboA-.js";
5
5
  import { a as init_activity_logger, i as emitDashboardLifecycleSync, n as emitActivityEntrySync, r as emitActivityTtsSync } from "./activity-logger-U396zbXm.js";
6
- import { A as getOverdeckDatabasePath, d as getOverdeckDatabaseSync, f as init_infra, i as DbLive, j as init_paths$1, k as OVERDECK_MIGRATION_PATH, u as TmuxLive } from "./infra-DkNKs3ZE.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-BUUcHyyN.js";
7
7
  import { n as openDatabase, t as init_driver } from "./driver-CmBD8_I7.js";
8
8
  import { t as require_dist$1 } from "./dist-CChAhNVv.js";
9
9
  import { a as parseIssueIdSync, n as extractPrefixSync, r as init_issue_id } from "./issue-id-ChfDs-cT.js";
10
- import { E as registerProjectSync, N as setProjectAutoMergeDefaultSync, O as resolveProjectFromIssue, _ as init_projects, b as listProjects, d as getProjectSync, k as resolveProjectFromIssueSync, l as getIssuePrefix, r as extractTeamPrefix, s as findProjectByTeamSync, x as listProjectsSync } from "./projects-Cowx0oPu.js";
11
- import { A as mergeTtsDaemonConfigs, C as isTldrEnabledSync, D as mergeConfigs, E as loadConfigSync, F as defaultBackgroundAiFeatures, I as init_registry, L as init_js_yaml, P as updateConversationsConfig, R as jsYaml, T as loadConfigNoMigration, a as PARENT_MODEL_REF, b as init_config_yaml, c as clearConfigCache, d as getConversationsConfig, g as getGlobalConfigPath, h as getDefaultTtsDaemonConfig, i as DEFAULT_WORKHORSES, o as ROLE_EFFORTS, r as DEFAULT_ROLES, t as COMPLIANCE_MODES, u as getConversationSearchConfigSync, x as isClaudeCodeChannelsEnabled } from "./config-yaml-Dbgr5RAI.js";
12
- import { a as getModelEffortLevelsSync, c as modelSupportsImagesSync, l as resolveModelIdSync, n as MODEL_CAPABILITIES, o as hasModelCapabilitySync, r as MODEL_DEPRECATIONS, s as init_model_capabilities, t as CLIPROXY_CODEX_CONTEXT_WINDOW } from "./model-capabilities-blGzeIJ2.js";
13
- import { B as init_ui_theme, D as listSessionNames, F as sessionExists, G as init_child_env, H as BLANKED_PROVIDER_ENV, L as setOption, N as sendKeys, P as sendRawKeystroke, S as isHarnessProcessAlive, U as buildChildEnvSync, V as setUiTheme, W as buildChildEnvWithoutTmuxSync, b as getWindowDimensions, c as ensureManagedTmuxContextOnce, d as exactPaneTarget, i as capturePane, j as resizeWindow, k as listSessionsSync, n as buildTmuxArgs, o as createSession, p as findManagedServerPidSync, w as killSession, x as init_tmux } from "./tmux-DBDERLCd.js";
14
- import { a as init_event_store, i as initEventStore, n as getEventStore, r as getSharedDb } from "./event-store-DYX1bQU6.js";
15
- import { d as WS_METHODS, f as FlywheelRunId, g as MemoryObservation, l as PanRpcError, m as EDITORS, n as ContextPreviewRequest, p as FlywheelStatus, r as FlywheelStats, t as ContextLayerSaveRequest, u as PanRpcGroup } from "./src-BZPsVCQI.js";
16
- import { i as recordBriefingSessionStart, n as ensureSessionContextBriefingFile, r as init_briefing_freshness, t as appendFreshBriefingUpdate } from "./briefing-freshness-CJKdk-JL.js";
17
- import { a as EventStoreServiceLive, d as toRole, i as EventStoreService, l as toAgentResolution, n as IssueLifecycleLive, o as ReadModelService, s as ReadModelServiceLive, u as toAgentStatus } from "./issue-lifecycle-CUG38hdz.js";
18
- import { n as AgentsResolverLive, o as init_agents } from "./agents-9qeRnPEQ.js";
19
- import { i as init_paths$2, l as resolveObservationsFile, r as ensureParentDir, s as resolveIssueMemoryRoot, t as assertMemorySafeSegment } from "./paths-DgfOFn_7.js";
20
- import { n as listFeatureRegistryEntries, t as init_feature_registry_storage } from "./feature-registry-storage-B3VDkSaU.js";
21
- import { a as diffAgainstMainFiles, c as diffFilesAgainstHead, d as findCommitAtTime, i as diffAgainstMain, l as diffPatchFilesAgainstHead, o as diffCheckpointFiles, p as listCheckpoints, s as diffCheckpoints, t as captureCheckpoint, u as diffPatchSinceCommit } from "./checkpoint-manager-DStOuMAH.js";
22
- import { a as migrateAllSessionsSync, c as syncCacheSync, i as reconcile, l as CostResolver, n as resolveIssueHeadlineCost, o as getCostsForIssueSync, r as syncWalFromAllProjects, s as rebuildCacheSync, t as getCachedRunningAgents, u as CostResolverLive } from "./running-agents-cache-B6qFwhYH.js";
23
- import { n as AgentStateServiceLive } from "./agent-state-service-weSkcN1i.js";
24
- import { a as init_tracker_config, i as initTrackerConfigCache, n as getLinearApiKey, r as getRallyConfig, t as getGitHubConfig } from "./tracker-config-CY1aIAUg.js";
25
- import { l as WorkspaceCreateError, n as AgentStartError, r as BeadsNotInitialized, t as AgentAlreadyRunning, u as WorkspaceNotFound } from "./typed-errors-zngUR5Fa.js";
26
- import { r as LinearClientOptionalLive, t as LinearClient } from "./linear-client-f2x21nCi.js";
27
- import { r as GitHubClientOptionalLive } from "./github-client-B9TlbtyU.js";
28
- import { r as RallyClientOptionalLive } from "./rally-client-BbAVOlvB.js";
29
- import { r as resolveGitHubIssueSync } from "./tracker-utils-CVIUkgod.js";
30
- 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-CGEMzi0p.js";
31
- import { i as ensureInternalTokenSync, s as init_internal_token } from "./internal-token-DeMSS93S.js";
32
- import { a as setPipelineHandlerSync, t as init_pipeline_notifier } from "./pipeline-notifier-xD_7wc0U.js";
33
- import { c as init_review_status_sync, i as getMergeBlockerReconcileCandidates, r as getAllReviewStatusesFromDb } from "./review-status-sync-6a1M-qUz.js";
34
- import { a as getCostForIssueAggregateSync, c as getDailyTrendsSync, l as getModelRollupSync, n as getBackgroundCostBySourceSync, r as getCavemanExperimentDataSync, s as getCostsByIssueSync, u as init_cost_sync } from "./cost-sync-BY2D98Ng.js";
35
- 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-CaApkXcY.js";
36
- import { a as fixStuckCommentedReviews, c as getReviewStatusSync, g as setReviewStatus, l as init_review_status, n as clearReviewStatus, o as fixStuckReadyForMerge, r as clearStuckMergeStatuses, s as getReviewStatus, u as loadReviewStatuses } from "./review-status-D7oxzZLw.js";
37
- import { A as PAN_CONTINUE_FILENAME, I as init_types, _ as findSpecByIssue, a as init_io, j as PAN_DIRNAME, l as readWorkspacePlan, n as findPlan, s as readPlan, y as init_specs } from "./io-B2VY861T.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-D1hyiowM.js";
39
- import { f as init_config, m as loadConfigSync$1, s as getDashboardApiUrlSync } from "./config-XdUlww0L.js";
10
+ import { E as registerProjectSync, N as setProjectAutoMergeDefaultSync, O as resolveProjectFromIssue, _ as init_projects, b as listProjects, d as getProjectSync, k as resolveProjectFromIssueSync, l as getIssuePrefix, r as extractTeamPrefix, s as findProjectByTeamSync, x as listProjectsSync } from "./projects-DF5T4DAb.js";
11
+ import { A as mergeTtsDaemonConfigs, C as isTldrEnabledSync, D as mergeConfigs, E as loadConfigSync, F as defaultBackgroundAiFeatures, I as init_registry, L as init_js_yaml, P as updateConversationsConfig, R as jsYaml, T as loadConfigNoMigration, a as PARENT_MODEL_REF, b as init_config_yaml, c as clearConfigCache, d as getConversationsConfig, g as getGlobalConfigPath, h as getDefaultTtsDaemonConfig, i as DEFAULT_WORKHORSES, o as ROLE_EFFORTS, r as DEFAULT_ROLES, t as COMPLIANCE_MODES, u as getConversationSearchConfigSync, x as isClaudeCodeChannelsEnabled } from "./config-yaml-DUGsL46H.js";
12
+ import { a as hasModelCapabilitySync, c as resolveModelIdSync, i as getModelEffortLevelsSync, n as MODEL_DEPRECATIONS, o as init_model_capabilities, s as modelSupportsImagesSync, t as MODEL_CAPABILITIES } from "./model-capabilities-DzrmNh9F.js";
13
+ import { B as init_ui_theme, D as listSessionNames, F as sessionExists, G as init_child_env, H as BLANKED_PROVIDER_ENV, L as setOption, N as sendKeys, P as sendRawKeystroke, S as isHarnessProcessAlive, U as buildChildEnvSync, V as setUiTheme, W as buildChildEnvWithoutTmuxSync, b as getWindowDimensions, c as ensureManagedTmuxContextOnce, d as exactPaneTarget, i as capturePane, j as resizeWindow, k as listSessionsSync, n as buildTmuxArgs, o as createSession, p as findManagedServerPidSync, w as killSession, x as init_tmux } from "./tmux-C0RZWQ5S.js";
14
+ import { a as init_event_store, i as initEventStore, n as getEventStore, r as getSharedDb } from "./event-store-DaXPWThb.js";
15
+ import { _ as MemoryObservation, d as PanRpcGroup, f as WS_METHODS, h as EDITORS, i as FlywheelStats, m as FlywheelStatus, n as ContextLayerSaveRequest, p as FlywheelRunId, r as ContextPreviewRequest, t as init_src, u as PanRpcError } from "./src-xz0_FrSc.js";
16
+ import { i as recordBriefingSessionStart, n as ensureSessionContextBriefingFile, r as init_briefing_freshness, t as appendFreshBriefingUpdate } from "./briefing-freshness-_gBwIK-o.js";
17
+ 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 } from "./issue-lifecycle-DQNY7Voi.js";
18
+ import { n as AgentsResolverLive, o as init_agents$1 } from "./agents-C2UPLUyP.js";
19
+ import { i as init_paths$2, l as resolveObservationsFile, r as ensureParentDir, s as resolveIssueMemoryRoot, t as assertMemorySafeSegment } from "./paths-C4a4MFTH.js";
20
+ import { n as listFeatureRegistryEntries, t as init_feature_registry_storage } from "./feature-registry-storage-Dxq9Ki0z.js";
21
+ 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-CyxF3jLY.js";
22
+ import { _ as init_cost, a as init_costs, c as init_reconciler, d as migrateAllSessionsSync, f as getCostsForIssueSync, g as CostResolverLive, h as CostResolver, i as resolveIssueHeadlineCost, l as reconcile, m as syncCacheSync, n as init_running_agents_cache, o as init_sync_wal, p as rebuildCacheSync, r as init_issue_cost_resolver, s as syncWalFromAllProjects, t as getCachedRunningAgents, u as reconcilePiTranscripts } from "./running-agents-cache-CYw5Xnmw.js";
23
+ import { i as init_agent_state_service, n as AgentStateServiceLive } from "./agent-state-service-r2kuZLya.js";
24
+ import { a as init_tracker_config, i as initTrackerConfigCache, n as getLinearApiKey, r as getRallyConfig, t as getGitHubConfig } from "./tracker-config-C0oOKrs5.js";
25
+ import { d as init_typed_errors, l as WorkspaceCreateError, n as AgentStartError, r as BeadsNotInitialized, t as AgentAlreadyRunning, u as WorkspaceNotFound } from "./typed-errors-DG3nrArx.js";
26
+ import { i as init_linear_client, r as LinearClientOptionalLive, t as LinearClient } from "./linear-client-LhrJ-6UJ.js";
27
+ import { i as init_github_client, r as GitHubClientOptionalLive } from "./github-client-CqmZy-AQ.js";
28
+ import { i as init_rally_client, r as RallyClientOptionalLive } from "./rally-client-DfPdFRtS.js";
29
+ import { i as resolveGitHubIssueSync, n as init_tracker_utils } from "./tracker-utils-CdTEubvB.js";
30
+ 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-Cemym5sC.js";
31
+ import { i as ensureInternalTokenSync, s as init_internal_token } from "./internal-token-V1t_XVES.js";
32
+ import { a as setPipelineHandlerSync, t as init_pipeline_notifier } from "./pipeline-notifier-yfbLZ66F.js";
33
+ import { c as init_review_status_sync, i as getMergeBlockerReconcileCandidates, r as getAllReviewStatusesFromDb } from "./review-status-sync-DlQCDXOn.js";
34
+ import { a as getCostForIssueAggregateSync, c as getDailyTrendsSync, l as getModelRollupSync, n as getBackgroundCostBySourceSync, r as getCavemanExperimentDataSync, s as getCostsByIssueSync, u as init_cost_sync } from "./cost-sync-zTeJiiWF.js";
35
+ 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-C1ojtBCQ.js";
36
+ import { _ as setReviewStatus, a as fixStuckCommentedReviews, c as getReviewStatusSync, l as init_review_status, n as clearReviewStatus, o as fixStuckReadyForMerge, r as clearStuckMergeStatuses, s as getReviewStatus, u as loadReviewStatuses } from "./review-status-DlZpTmJb.js";
37
+ import { A as PAN_CONTINUE_FILENAME, I as init_types, _ as findSpecByIssue, a as init_io, j as PAN_DIRNAME, l as readWorkspacePlan, n as findPlan, s as readPlan, y as init_specs } from "./io-Z4rUYasM.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-DSHuOfBZ.js";
39
+ import { f as init_config, m as loadConfigSync$1, s as getDashboardApiUrlSync } from "./config-CxoqEywB.js";
40
40
  import { n as require_auth_errors, r as require_token_error, t as require_token_util } from "./token-util-M91UIf3c.js";
41
- import { A as listConversations, B as setConversationClaudeSessionId, C as getStuckForks, E as init_conversations, G as unarchiveConversation, H as setConversationModel, I as recordConversationHandoff, J as updateConversationForkFallbackReason, K as updateConversationCost, L as removeFavorite, M as markConversationActive, N as markConversationEnded, O as listActiveConversations, P as markConversationRunning, S as getConversationByTmuxSession, T as incrementForkRetryCount, U as setFavorite, V as setConversationHarness, W as setForkRequest, X as updateForkStatus, Y as updateConversationTitle, Z as updateLastAttached, _ as canReplaceTitle, b as getConversationById, g as backfillConversationModel, h as archiveConversation, j as listFavoritedIds, k as listArchivedConversationsWithEnrichment, q as updateConversationDeliveryMethod, v as createConversation, w as hasOtherActiveConversationOnTmuxSession, x as getConversationByName, y as getConversationByClaudeSessionId, z as setClearedToConvId } from "./conversations-BOXFUrBX.js";
42
- import { a as init_conversation_service, c as summarizeConversationActivity, d as isPiSessionFile, f as parsePiConversationMessages, i as gateSnapshotEmission, l as watchConversation, m as parseCodexConversationMessages, n as contextUsageFromParseResult, o as parseConversationMessages, p as init_codex_conversation_parser, s as parseFromLastCompactBoundary, t as computeContextUsage, u as init_pi_conversation_parser } from "./conversation-service-Bzn1HWcE.js";
43
- import { i as getClaudePermissionFlagsStringSync, o as init_claude_permissions, s as resolvePermissionModeSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-Ql4bavTE.js";
44
- import { f as writeThreadId, l as init_codex, s as findRolloutPath } from "./codex-CDAqRWlw.js";
45
- import { $t as init_openai_auth, A as getAgentStateFilePath, At as init_operator_interventions, B as init_agents$1, Bt as init_bridge_token, C as dismissDevChannelsDialog, D as getAgentRuntimeState, Dt as captureTranscriptUserRecordSnapshot, E as getAgentRuntimeBaseCommand, Et as writeChannelsBridgeMcpConfig, F as getProviderEnvForModel, Ft as init_pi_fifo, G as listAgentStates, I as getProviderExportsForModel, It as piFifoPaths, Jt as init_agent_rollback_state, K as listRunningAgents, O as getAgentRuntimeStateSync, Ot as init_transcript_landing, P as getProviderAuthMode, Qt as getOpenAIAuthStatus, Rt as init_pty_token, St as waitForReadySignal, T as getAgentDir, V as injectPiConversationMemory, Vt as writeBridgeTokenSync, Xt as getClaudeAuthStatus, Yt as readRollbackAgentHarnessFromDir, Z as messageAgent, Zt as init_claude_auth, h as clearReadySignal, j as getAgentStateSync, jt as operatorInterventionEvent, k as getAgentState, kt as probeTranscriptSince, q as listRunningAgentsSync, v as deliverAgentMessage, zt as writePtyToken } from "./agents-CoZumDXi.js";
46
- import { a as getBuiltInDefaultHarness, c as getProviderForModelSync, l as init_providers, o as getKimiAnthropicBaseUrl, u as piProviderForModel } from "./providers-Bal6r0b4.js";
47
- import { n as isBackgroundFeatureEnabled, t as init_features } from "./features-DLrA0guP.js";
48
- import { i as init_settings, r as getMemoryWorkerConcurrency, t as areMemoryObservationsEnabled } from "./settings-D2e30bjI.js";
49
- import { t as init_pan_dir } from "./pan-dir-CF3eJjQG.js";
50
- import { n as init_launcher_generator, t as generateLauncherScriptSync } from "./launcher-generator-D2eL0ybn.js";
51
- 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-YEs5QEfF.js";
52
- import { i as init_harness_policy, n as resolveHarness, r as canUseHarnessSync, t as init_harness_resolve } from "./harness-resolve-DWUovlI5.js";
53
- import { c as init_docker_stats } from "./stack-health-DFXzDbx-.js";
54
- import { i as updateMemoryHealth, n as extractWithProviderPolicy, r as init_health, t as init_providers$1 } from "./providers-m2RBUHqD.js";
55
- import { a as getSetting, b as setSetting, c as isDeaconGloballyPausedSync, d as isFlywheelRequireUatBeforeMerge, f as isMergeTrainEnabled, g as setFlywheelAutoPickupBacklog, l as isFlywheelAutoPickupBacklog, m as setDeaconGloballyPausedSync, o as init_control_settings, u as isFlywheelGloballyPaused$1, v as setFlywheelRequireUatBeforeMerge, y as setMergeTrainEnabled } from "./control-settings-Dt8ZP3y4.js";
56
- import { a as parseRelativeTime, n as CostThresholdError, s as embed } from "./scanner-07yNZxQN.js";
57
- import { n as deduplicateEventsSync, o as readEventsSync, s as tailEventsSync } from "./events-B9JAl2Vn.js";
58
- import { n as recordBackgroundAiCost, t as init_cost } from "./cost-CjhAEexd.js";
59
- import { a as startUatTrainReconciler, c as openFlywheelRunReportForDashboard, d as resumeFlywheelRunForDashboard, f as startFlywheelRunForDashboard, l as pauseFlywheelRunForDashboard, o as stopUatTrainReconciler, p as runDashboardDbJob, s as abortFlywheelRunForDashboard, u as readCurrentFlywheelStatusForDashboard } from "./uat-train-Co7Vdqcb.js";
60
- import { c as buildClassifyLookups, d as getFlywheelRunDetail, g as readCurrentLatestFlywheelStatus, m as listFlywheelRuns, p as isFlywheelRunId, v as resolveLiveFlywheelRunId, x as writeLatestFlywheelStatus, y as subscribeLatestFlywheelStatus } from "./flywheel-Dh-BJ0wD.js";
61
- import { f as workspacesRouteLayer, g as getWorkspacePathForIssue, h as processPendingFeedbackDeliveries, m as resumeQueuedMerges, p as enrichReviewStatus } from "./workspaces-Bm5IvBNs.js";
62
- import { g as init_platform, h as detectPlatform } from "./workspace-manager-BkZBOkYD.js";
63
- import { a as getShadowState, s as init_shadow_state } from "./shadow-state-C3a-XR8T.js";
64
- import { S as rejectUnsafeDashboardMutationRequest, _ as dashboardCsrfToken, b as rejectUnauthorizedDashboardRequest, f as issuesRouteLayer, g as sanitizeResourceAllocatedIssues, h as groupResourceAllocatedIssuesByProject, m as getCachedResourceAllocatedIssues, v as dashboardSessionCookieHeader, x as rejectUnauthorizedDashboardSessionMintRequest, y as hasDashboardInternalToken } from "./issues-C-kal-Se.js";
65
- import { a as saveCloisterConfigSync, i as loadCloisterConfigSync, o as shouldAutoStart, r as loadCloisterConfig } from "./config-OYvCWYs5.js";
66
- import { O as init_specialists, b as getReviewerSessionName } from "./specialists-CxKFRxKR.js";
67
- import { at as setMergeReadyNotifier, dt as withConcurrencyLimit, ft as emergencyBrake, it as setAgentStoppedNotifier, rt as setAgentStatusChangedNotifier } from "./deacon-BgdWPMqm.js";
68
- import { t as getNoResumeMode } from "./no-resume-mode-CK1zTSyc.js";
69
- import { o as findPrdAtStatusSync } from "./archive-planning-CIamwvkR.js";
70
- import { a as writeFeedbackFile, i as init_feedback_writer } from "./feedback-writer-BUY2cbmq.js";
71
- import { n as writeSequenceMd, t as parseSequenceMd } from "./sequence-io-D5N1X-nD.js";
72
- import { c as PARKED_LABEL, d as classifyIssue, f as computeCohort, g as normalizeGate, h as computeWaves, l as READY_LABEL, m as computeStats, p as computeLanes, s as BLOCKS_MAIN_LABEL, u as VETOED_LABEL } from "./flywheel-merge-order-CEygIYNw.js";
73
- 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-CS94rsth.js";
74
- import { h as listGitOperationsSync } from "./merge-agent-CGNzsX7x.js";
41
+ import { $ as updateSpawnError, A as listConversations, B as setClearedToConvId, C as getStuckForks, E as init_conversations, G as setForkRequest, H as setConversationHarness, J as updateConversationDeliveryMethod, K as unarchiveConversation, L as recordConversationHandoff, M as markConversationActive, N as markConversationEnded, O as listActiveConversations, P as markConversationRunning, Q as updateLastAttached, R as removeFavorite, S as getConversationByTmuxSession, T as incrementForkRetryCount, U as setConversationModel, V as setConversationClaudeSessionId, W as setFavorite, X as updateConversationTitle, Y as updateConversationForkFallbackReason, Z as updateForkStatus, _ as canReplaceTitle, b as getConversationById, g as backfillConversationModel, h as archiveConversation, j as listFavoritedIds, k as listArchivedConversationsWithEnrichment, q as updateConversationCost, v as createConversation, w as hasOtherActiveConversationOnTmuxSession, x as getConversationByName, y as getConversationByClaudeSessionId } from "./conversations-DLvWHK2i.js";
42
+ import { _ as init_codex_conversation_parser, a as init_conversation_service, c as parseFromLastCompactBoundary, d as init_ohmypi_conversation_parser, f as isOhmypiSessionFile, g as parsePiConversationMessages, h as isPiSessionFile, i as gateSnapshotEmission, l as summarizeConversationActivity, m as init_pi_conversation_parser, n as contextUsageFromParseResult, o as parseConversationMessages, p as parseOhmypiConversationMessages, t as computeContextUsage, u as watchConversation, v as parseCodexConversationMessages } from "./conversation-service-BLnGgpGK.js";
43
+ import { i as getClaudePermissionFlagsStringSync, o as init_claude_permissions, s as resolvePermissionModeSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-BHhkuJLN.js";
44
+ import { l as init_codex, s as findRolloutPath } from "./codex-CAqD-NbA.js";
45
+ import { $t as init_claude_auth, A as getAgentStateFilePath, At as init_operator_interventions, B as init_agents$2, Bt as init_pty_token, C as dismissDevChannelsDialog, D as getAgentRuntimeState, Dt as captureTranscriptUserRecordSnapshot, E as getAgentRuntimeBaseCommand, Et as writeChannelsBridgeMcpConfig, F as getProviderEnvForModel, G as listAgentStates, Ht as init_bridge_token, I as getProviderExportsForModel, K as listRunningAgents, O as getAgentRuntimeStateSync, Ot as init_transcript_landing, P as getProviderAuthMode, Qt as getClaudeAuthStatus, Rt as init_pi_fifo, St as waitForReadySignal, T as getAgentDir, Ut as writeBridgeTokenSync, V as injectPiConversationMemory, Vt as writePtyToken, Z as messageAgent, en as getOpenAIAuthStatus, h as clearReadySignal, j as getAgentStateSync, jt as operatorInterventionEvent, k as getAgentState, kt as probeTranscriptSince, tn as init_openai_auth, v as deliverAgentMessage, zt as piFifoPaths } from "./agents-hLt-Rr4P.js";
46
+ import { a as getBuiltInDefaultHarness, c as getProviderForModelSync, l as init_providers, o as getKimiAnthropicBaseUrl, u as piProviderForModel } from "./providers-CA5H_tiu.js";
47
+ import { n as isBackgroundFeatureEnabled, t as init_features } from "./features-C69va1rT.js";
48
+ import { i as init_settings, r as getMemoryWorkerConcurrency, t as areMemoryObservationsEnabled } from "./settings-D_0oOxVa.js";
49
+ import { t as init_pan_dir } from "./pan-dir-8hycoLIn.js";
50
+ import { n as init_launcher_generator, t as generateLauncherScriptSync } from "./launcher-generator-CQI4Wi8N.js";
51
+ 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-BhmZ9Xjw.js";
52
+ import { i as init_harness_policy, n as resolveHarness, r as canUseHarnessSync, t as init_harness_resolve } from "./harness-resolve-zjwwqUyX.js";
53
+ import { c as init_docker_stats } from "./stack-health-qc0DMiOU.js";
54
+ import { i as updateMemoryHealth, n as extractWithProviderPolicy, r as init_health$1, t as init_providers$1 } from "./providers-BVXfagAf.js";
55
+ import { a as getSetting, b as setSetting, c as isDeaconGloballyPausedSync, d as isFlywheelRequireUatBeforeMerge, f as isMergeTrainEnabled, g as setFlywheelAutoPickupBacklog, l as isFlywheelAutoPickupBacklog, m as setDeaconGloballyPausedSync, o as init_control_settings, u as isFlywheelGloballyPaused$1, v as setFlywheelRequireUatBeforeMerge, y as setMergeTrainEnabled } from "./control-settings-nrCd7a15.js";
56
+ import { a as parseRelativeTime, n as CostThresholdError, s as embed } from "./scanner-nBZUh3Z-.js";
57
+ import { n as deduplicateEventsSync, o as readEventsSync, s as tailEventsSync } from "./events-DlidSJfB.js";
58
+ import { n as recordBackgroundAiCost, t as init_cost$1 } from "./cost-CshfLVLS.js";
59
+ import { a as startUatTrainReconciler, c as openFlywheelRunReportForDashboard, d as resumeFlywheelRunForDashboard, f as startFlywheelRunForDashboard, l as pauseFlywheelRunForDashboard, o as stopUatTrainReconciler, p as runDashboardDbJob, s as abortFlywheelRunForDashboard, u as readCurrentFlywheelStatusForDashboard } from "./uat-train-iwQ7DuuA.js";
60
+ import { S as subscribeLatestFlywheelStatus, _ as listFlywheelRuns, g as isFlywheelRunId, h as init_flywheel_run_state, l as buildClassifyLookups, p as getFlywheelRunDetail, u as init_lookups, w as writeLatestFlywheelStatus, x as resolveLiveFlywheelRunId, y as readCurrentLatestFlywheelStatus } from "./flywheel-B5jy_sgt.js";
61
+ import { f as workspacesRouteLayer, g as getWorkspacePathForIssue, h as processPendingFeedbackDeliveries, m as resumeQueuedMerges, p as enrichReviewStatus } from "./workspaces-CXng3UsG.js";
62
+ import { _ as init_platform, g as detectPlatform } from "./workspace-manager-CDuomoTM.js";
63
+ import { a as getShadowState, s as init_shadow_state } from "./shadow-state-CVp1QwER.js";
64
+ import { C as init_dashboard_auth, D as init_review_status$1, E as rejectUnsafeDashboardMutationRequest, S as hasDashboardInternalToken, T as rejectUnauthorizedDashboardSessionMintRequest, _ as groupResourceAllocatedIssuesByProject, b as dashboardCsrfToken, g as getCachedResourceAllocatedIssues, m as issuesRouteLayer, p as init_issues, v as init_resource_discovery, w as rejectUnauthorizedDashboardRequest, x as dashboardSessionCookieHeader, y as sanitizeResourceAllocatedIssues } from "./issues-CaZ8H94W.js";
65
+ import { a as loadCloisterConfigSync, i as loadCloisterConfig, o as saveCloisterConfigSync, r as init_config$1, s as shouldAutoStart } from "./config-DHYPHDxW.js";
66
+ import { O as init_specialists, b as getReviewerSessionName } from "./specialists-B3b3zCCR.js";
67
+ import { H as init_deacon, _t as init_concurrency, ct as setMergeReadyNotifier, gt as emergencyBrake, ht as withConcurrencyLimit, i as autoResumeStoppedWorkAgents, mt as init_concurrency$1, ot as setAgentStatusChangedNotifier, st as setAgentStoppedNotifier } from "./deacon-BE8F3tlB.js";
68
+ import { n as getNoResumeMode, r as init_no_resume_mode, t as disableNoResumeMode } from "./no-resume-mode-DeRX_RrX.js";
69
+ import { c as init_prd_locations, s as findPrdAtStatusSync } from "./archive-planning-Bn8NdLWB.js";
70
+ import { a as writeFeedbackFile, i as init_feedback_writer } from "./feedback-writer-M6ArcFFB.js";
71
+ import { n as parseSequenceMd, r as writeSequenceMd, t as init_sequence_io } from "./sequence-io-JIDLRyzK.js";
72
+ import { _ as init_pickup, c as BLOCKS_MAIN_LABEL, d as VETOED_LABEL, f as classifyIssue, g as computeWaves, h as computeStats, l as PARKED_LABEL, m as computeLanes, p as computeCohort, u as READY_LABEL, v as normalizeGate } from "./flywheel-merge-order-CEIqRtni.js";
73
+ 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-CxrbmI9_.js";
74
+ import { _ as init_git_activity, v as listGitOperationsSync } from "./merge-agent-Dpkezdnp.js";
75
75
  import { n as getTldrDaemonServiceSync, r as init_tldr_daemon } from "./tldr-daemon-G1HK5zsr.js";
76
- import { i as runMemoryFtsTransaction, n as init_fts_db, r as runMemoryFtsStatement, t as closeMemoryFtsDatabases } from "./fts-db-eiIUjmRQ.js";
77
- import { a as readRecentObservations, c as writePendingTurn, s as maybeTriggerStatusRollup } from "./rollup-DxdMEBML.js";
78
- import { r as specialistsRouteLayer } from "./specialists-CyVAp3cB.js";
79
- import { t as getAgentCommandSync } from "./settings-Dp9vKSf9.js";
80
- import { n as init_review_agent, r as isExtendedReviewEnabled } from "./review-agent-CgDjIOGw.js";
81
- import { r as isRemoteAvailable, t as createFlyProviderFromConfig } from "./remote-K7NPj_ah.js";
76
+ import { i as runMemoryFtsTransaction, n as init_fts_db, r as runMemoryFtsStatement, t as closeMemoryFtsDatabases } from "./fts-db-DYsLK5RG.js";
77
+ import { c as init_pending, l as maybeTriggerStatusRollup, o as readRecentObservations, r as init_rollup, u as writePendingTurn } from "./rollup-D8AxgJpA.js";
78
+ import { r as specialistsRouteLayer } from "./specialists-q3ihCaib.js";
79
+ import { t as getAgentCommandSync } from "./settings-BAejBGHF.js";
80
+ import { n as init_review_agent, r as isExtendedReviewEnabled } from "./review-agent-B_OxVPgn.js";
81
+ import { i as isRemoteAvailable, r as init_remote, t as createFlyProviderFromConfig } from "./remote-DD99dxJA.js";
82
82
  import { n as init_claude_settings_overlay, t as detectProviderEnvConflicts } from "./claude-settings-overlay-DdI3J1rj.js";
83
- 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-GDIgqCF3.js";
84
- 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-C149zUUo.js";
85
- import { a as init_conversation_compaction, c as shouldInterceptManualCompact, n as compactConversationNative, o as isCompacting, s as maybeCompactBeforeRespawn } from "./conversation-compaction-Dbv7FC4s.js";
86
- 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-BpuXeHhe.js";
83
+ 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-DEXsy0T8.js";
84
+ 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-CRstXkJ_.js";
85
+ import { a as init_conversation_compaction, c as shouldInterceptManualCompact, n as compactConversationNative, o as isCompacting } from "./conversation-compaction-BMeD-rqy.js";
86
+ 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-CDdkuSCl.js";
87
87
  import { c as init_github_app, s as getPullRequestState } from "./github-app-DAbOi7t-.js";
88
- import { r as parseArtifactRef } from "./forge-i30UYoWv.js";
89
- import { n as spawnSequencerAgent, t as SEQUENCER_AGENT_ID } from "./sequencer-agent-nsW6G44K.js";
90
- import { i as listTranscriptCheckpoints, r as getTranscriptCheckpoint$1 } from "./checkpoints-BpQ8wIW1.js";
88
+ import { i as parseArtifactRef, r as init_forge } from "./forge-DGbYPb6g.js";
89
+ import { a as spawnSequencerAgent, i as init_sequencer_agent, n as clearFinishedSequencerRun, r as getSequencerRunStatus, t as SEQUENCER_AGENT_ID } from "./sequencer-agent-JLfhLLC6.js";
90
+ import { i as listTranscriptCheckpoints, r as getTranscriptCheckpoint$1 } from "./checkpoints-CHIwn3hr.js";
91
91
  import { createRequire } from "node:module";
92
92
  import { Cause, Context, Data, Effect, Exit, Fiber, FileSystem, Layer, Option, Path, Queue, Result, Schedule, Schema, Stream } from "effect";
93
93
  import { createReadStream, createWriteStream, existsSync, fstatSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
@@ -303,6 +303,8 @@ const ServerConfigLayer = Layer.effect(ServerConfig, Effect.sync(() => {
303
303
  }));
304
304
  //#endregion
305
305
  //#region services/terminal-service.ts
306
+ init_http_helpers();
307
+ init_activity_logger();
306
308
  init_paths$1();
307
309
  init_driver();
308
310
  var import_dist = /* @__PURE__ */ __toESM(require_dist$1(), 1);
@@ -312,7 +314,13 @@ init_registry();
312
314
  init_config_yaml();
313
315
  init_ui_theme();
314
316
  init_infra();
315
- init_agents();
317
+ init_event_store();
318
+ init_agents$1();
319
+ init_read_model();
320
+ init_checkpoint_manager();
321
+ init_domain_services();
322
+ init_cost();
323
+ init_agent_state_service();
316
324
  /**
317
325
  * TerminalService — dual-runtime PTY terminal streaming (PAN-428 B20)
318
326
  *
@@ -325,6 +333,7 @@ init_agents();
325
333
  * toggle dimensions to force SIGWINCH + full repaint.
326
334
  * - On stream/close: do NOT kill the PTY — just remove from tracking.
327
335
  */
336
+ init_src();
328
337
  init_tmux();
329
338
  init_child_env();
330
339
  function isBun() {
@@ -592,12 +601,23 @@ const TerminalServiceLive = Layer.effect(TerminalService, Effect.sync(() => {
592
601
  }));
593
602
  //#endregion
594
603
  //#region services/agent-spawner.ts
604
+ init_issue_id();
605
+ init_projects();
606
+ init_tracker_config();
607
+ init_linear_client();
608
+ init_github_client();
609
+ init_rally_client();
610
+ init_tracker_utils();
611
+ init_cache_service();
612
+ init_issue_service_singleton();
613
+ init_issue_lifecycle();
595
614
  /**
596
615
  * AgentSpawner Effect service (PAN-449)
597
616
  *
598
617
  * Wraps agents.ts in an Effect service with typed errors.
599
618
  * Route handlers use this instead of importing from lib/agents.ts directly.
600
619
  */
620
+ init_typed_errors();
601
621
  var AgentSpawner = class extends Context.Service()("overdeck/dashboard/AgentSpawner") {};
602
622
  const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
603
623
  startWork: (issueId, opts) => Effect.tryPromise({
@@ -611,7 +631,7 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
611
631
  const beadsDir = join$1(workspacePath, ".beads");
612
632
  const projectBeadsDir = join$1(workspacePath, "..", "..", ".beads");
613
633
  if (!(existsSync$1(beadsDir) || existsSync$1(projectBeadsDir))) throw new BeadsNotInitialized({ workspace: workspacePath });
614
- const { getAgentState, spawnAgent, normalizeAgentId } = await import("./agents-DD8lFXJe2.js");
634
+ const { getAgentState, spawnAgent, normalizeAgentId } = await import("./agents-DdvrDsH42.js");
615
635
  const normalizedId = normalizeAgentId(issueId);
616
636
  if ((await Effect.runPromise(getAgentState(normalizedId)))?.status === "running") throw new AgentAlreadyRunning({ id: issueId });
617
637
  return {
@@ -642,7 +662,7 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
642
662
  const { workspacePath, projectPath, issue } = opts;
643
663
  if (!existsSync$1(workspacePath)) throw new WorkspaceNotFound({ id: issueId });
644
664
  const sessionName = opts.sessionName ?? `planning-${issueId.toLowerCase()}`;
645
- const { spawnPlanningSession } = await import("./spawn-planning-session-BAdlPinE.js");
665
+ const { spawnPlanningSession } = await import("./spawn-planning-session-caXjx8yu.js");
646
666
  const result = await spawnPlanningSession({
647
667
  issue: {
648
668
  id: issue.id,
@@ -675,14 +695,14 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
675
695
  }),
676
696
  kill: (agentId) => Effect.tryPromise({
677
697
  try: async () => {
678
- const { stopAgent } = await import("./agents-DD8lFXJe2.js");
698
+ const { stopAgent } = await import("./agents-DdvrDsH42.js");
679
699
  await Effect.runPromise(stopAgent(agentId));
680
700
  },
681
701
  catch: () => void 0
682
702
  }).pipe(Effect.ignore),
683
703
  message: (agentId, msg) => Effect.tryPromise({
684
704
  try: async () => {
685
- const { messageAgent } = await import("./agents-DD8lFXJe2.js");
705
+ const { messageAgent } = await import("./agents-DdvrDsH42.js");
686
706
  await messageAgent(agentId, msg);
687
707
  },
688
708
  catch: (err) => new AgentStartError({
@@ -693,8 +713,8 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
693
713
  }),
694
714
  deepWipe: (issueId, opts) => Effect.tryPromise({
695
715
  try: async () => {
696
- const { deepWipe } = await import("./workflows-DW4lNeVW.js");
697
- const { resolveProjectFromIssueSync } = await import("./projects-BBiAZIGj.js");
716
+ const { deepWipe } = await import("./workflows-D6j_6rZF.js");
717
+ const { resolveProjectFromIssueSync } = await import("./projects-LkdoL5F9.js");
698
718
  const project = resolveProjectFromIssueSync(issueId);
699
719
  const projectPath = project?.projectPath ?? process.cwd();
700
720
  await Effect.runPromise(deepWipe({
@@ -722,7 +742,7 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
722
742
  * Wraps workspace-manager.ts in an Effect service with typed errors.
723
743
  * Route handlers and AgentSpawner use this instead of calling workspace-manager directly.
724
744
  */
725
- init_projects();
745
+ init_typed_errors();
726
746
  const execAsync$8 = promisify(exec);
727
747
  var WorkspaceService = class extends Context.Service()("overdeck/dashboard/WorkspaceService") {};
728
748
  const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
@@ -750,8 +770,8 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
750
770
  const { workspacePath, branch } = getWorkspacePath(issueId);
751
771
  const issueLower = issueId.toLowerCase();
752
772
  if (existsSync$1(workspacePath)) return workspacePath;
753
- const { createWorkspace } = await import("./workspace-manager-D6cUCwLt.js");
754
- const { loadProjectsConfigSync } = await import("./projects-BBiAZIGj.js");
773
+ const { createWorkspace } = await import("./workspace-manager-C1Ocyoqn.js");
774
+ const { loadProjectsConfigSync } = await import("./projects-LkdoL5F9.js");
755
775
  const { projects } = loadProjectsConfigSync();
756
776
  const project = resolveProjectFromIssueSync(issueId);
757
777
  if (!project) throw new WorkspaceCreateError({
@@ -786,7 +806,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
786
806
  const { workspacePath } = getWorkspacePath(issueId);
787
807
  const issueLower = issueId.toLowerCase();
788
808
  if (!existsSync$1(workspacePath)) throw new WorkspaceNotFound({ id: issueId });
789
- const { removeWorkspace } = await import("./workspace-manager-D6cUCwLt.js");
809
+ const { removeWorkspace } = await import("./workspace-manager-C1Ocyoqn.js");
790
810
  const project = resolveProjectFromIssueSync(issueId);
791
811
  if (!project) throw new WorkspaceCreateError({
792
812
  id: issueId,
@@ -817,7 +837,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
817
837
  try: async () => {
818
838
  const { workspacePath } = getWorkspacePath(issueId);
819
839
  const issueLower = issueId.toLowerCase();
820
- const { stopWorkspaceDocker } = await import("./workspace-manager-D6cUCwLt.js");
840
+ const { stopWorkspaceDocker } = await import("./workspace-manager-C1Ocyoqn.js");
821
841
  await Effect.runPromise(stopWorkspaceDocker(workspacePath, issueLower));
822
842
  },
823
843
  catch: () => void 0
@@ -886,7 +906,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
886
906
  join$1(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml")
887
907
  ];
888
908
  if (!composePaths.find((p) => existsSync$1(p))) {
889
- const { ensureDevcontainerSync } = await import("./ensure-devcontainer-CA6HkbfR.js");
909
+ const { ensureDevcontainerSync } = await import("./ensure-devcontainer-C1oOwQUK.js");
890
910
  const ensure = ensureDevcontainerSync({
891
911
  workspacePath,
892
912
  issueId
@@ -915,7 +935,6 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
915
935
  }));
916
936
  //#endregion
917
937
  //#region services/openrouter-service.ts
918
- init_issue_id();
919
938
  /**
920
939
  * OpenRouter Service
921
940
  *
@@ -1060,6 +1079,7 @@ function getDefaultCwd() {
1060
1079
  }
1061
1080
  //#endregion
1062
1081
  //#region services/open.ts
1082
+ init_src();
1063
1083
  const execAsync$7 = promisify(exec);
1064
1084
  function getFileManagerCommand() {
1065
1085
  switch (process.platform) {
@@ -4766,9 +4786,12 @@ function removeClientFromHub(hubs, sessionName, ws) {
4766
4786
  }
4767
4787
  //#endregion
4768
4788
  //#region routes/codex-auth.ts
4789
+ init_http_handler();
4790
+ init_codex_auth();
4769
4791
  init_cliproxy();
4770
4792
  init_tmux();
4771
4793
  init_config();
4794
+ init_origin_validation();
4772
4795
  const reauthSessions = /* @__PURE__ */ new Map();
4773
4796
  const SESSION_MAX_AGE_MS = 3600 * 1e3;
4774
4797
  const readJsonBody$13 = Effect.gen(function* () {
@@ -4973,6 +4996,7 @@ async function waitForSessionRespawn(sessionName, timeoutMs) {
4973
4996
  //#endregion
4974
4997
  //#region ws-terminal.ts
4975
4998
  init_tmux();
4999
+ init_origin_validation();
4976
5000
  init_child_env();
4977
5001
  const RESPAWN_WAIT_MS = 35e3;
4978
5002
  const ATTACH_TIMEOUT_MS = 5e3;
@@ -28470,6 +28494,9 @@ function createTurnQueue(emitter, onTurn) {
28470
28494
  //#endregion
28471
28495
  //#region routes/voice.ts
28472
28496
  init_paths();
28497
+ init_http_helpers();
28498
+ init_http_handler();
28499
+ init_origin_validation();
28473
28500
  const MAX_VOICE_SETTINGS_BODY_BYTES = 16384;
28474
28501
  const DEFAULT_VOICE_SETTINGS = {
28475
28502
  stt: {
@@ -28621,6 +28648,7 @@ const putVoiceSettingsRoute = HttpRouter.add("PUT", "/api/voice/settings", httpH
28621
28648
  const voiceRouteLayer = Layer.mergeAll(getVoiceSettingsRoute, putVoiceSettingsRoute);
28622
28649
  //#endregion
28623
28650
  //#region ws-voice.ts
28651
+ init_origin_validation();
28624
28652
  function sendJson$1(ws, payload) {
28625
28653
  if (ws.readyState === import_websocket.default.OPEN) ws.send(JSON.stringify(payload));
28626
28654
  }
@@ -28836,6 +28864,7 @@ function boundedAutoPresoElements(elements) {
28836
28864
  }
28837
28865
  //#endregion
28838
28866
  //#region ws-autopreso.ts
28867
+ init_origin_validation();
28839
28868
  function sendJson(ws, payload) {
28840
28869
  if (ws.readyState === import_websocket.default.OPEN) ws.send(JSON.stringify(payload));
28841
28870
  }
@@ -28886,220 +28915,17 @@ function setupAutoPresoWebSocket(server) {
28886
28915
  });
28887
28916
  }
28888
28917
  //#endregion
28889
- //#region routes/jsonl-resolver.ts
28918
+ //#region services/read-workspace-file.ts
28919
+ init_briefing_freshness();
28920
+ init_internal_token();
28921
+ init_pipeline_notifier();
28922
+ init_review_status_sync();
28923
+ init_review_status();
28890
28924
  init_conversations();
28891
28925
  init_codex_conversation_parser();
28892
28926
  init_pi_conversation_parser();
28927
+ init_ohmypi_conversation_parser();
28893
28928
  init_conversation_service();
28894
- /**
28895
- * JSONL transcript resolver for the Command Deck (PAN-830).
28896
- *
28897
- * Maps an agent ID (e.g. `agent-pan-830`, `planning-pan-830`, or a canonical
28898
- * specialist tmux session name) to the agent's JSONL transcript file on disk.
28899
- *
28900
- * Codex agents (PAN-1805) write rollout JSONLs under the per-agent
28901
- * `codex-home/sessions/` tree — resolution dispatches on the harness recorded
28902
- * in state.json (thread-id fast path, then latest-rollout fallback).
28903
- *
28904
- * For claude-code agents the JSONL filename is the *Claude session ID* (a UUID
28905
- * written by Claude Code itself), NOT the agent/tmux name. Lookup order:
28906
- * 1. session.id — single UUID written by auto-suspend
28907
- * 2. sessions.json — array of UUIDs the agent has used; the heartbeat hook
28908
- * APPENDS new IDs and dedupes, so once a session has been
28909
- * seen its array index never moves. After a `pan resume`
28910
- * brings an older session back as the live one, the array
28911
- * order no longer reflects time-recency. We disambiguate
28912
- * across all listed IDs by JSONL mtime.
28913
- * 3. runtime state — in-process mirror, populated by hooks
28914
- *
28915
- * Async-only (fs/promises) because this code path runs inside the dashboard
28916
- * server's event loop.
28917
- */
28918
- init_agents$1();
28919
- init_paths();
28920
- init_agent_rollback_state();
28921
- async function pathExists$5(p) {
28922
- return access(p).then(() => true, () => false);
28923
- }
28924
- async function readOptional$3(p) {
28925
- return readFile(p, "utf-8").catch(() => null);
28926
- }
28927
- /**
28928
- * Pick the candidate UUID whose JSONL transcript has the most recent mtime.
28929
- *
28930
- * The heartbeat hook appends to sessions.json and dedupes — so once an ID is
28931
- * recorded, its array index never moves even if a later resume makes it the
28932
- * current session again. Choosing by JSONL mtime is the only reliable signal
28933
- * for "which session is actually live right now."
28934
- *
28935
- * Returns null if no candidate has a corresponding JSONL on disk. Falls back
28936
- * to scanning every project dir if the workspace path can't be resolved
28937
- * (covers specialist sessions whose workspace lives elsewhere).
28938
- */
28939
- async function pickFreshestSessionId(candidates, agentId, opts) {
28940
- if (candidates.length === 0) return null;
28941
- if (candidates.length === 1) return candidates[0];
28942
- const projectsRoot = opts.claudeProjectsDirOverride ?? join$1(homedir$1(), ".claude", "projects");
28943
- let candidatePaths = [];
28944
- try {
28945
- const workspace = await Effect.runPromise(getAgentWorkspace(agentId));
28946
- if (workspace) {
28947
- const projectDir = join$1(projectsRoot, encodeClaudeProjectDir(workspace));
28948
- candidatePaths = candidates.map((id) => ({
28949
- id,
28950
- path: join$1(projectDir, `${id}.jsonl`)
28951
- }));
28952
- }
28953
- } catch {}
28954
- if (candidatePaths.length === 0) try {
28955
- const dirs = await readdir(projectsRoot);
28956
- const SAFE_DIR = /^[a-zA-Z0-9_.-]+$/;
28957
- for (const id of candidates) for (const dir of dirs) {
28958
- if (!SAFE_DIR.test(dir)) continue;
28959
- candidatePaths.push({
28960
- id,
28961
- path: join$1(projectsRoot, dir, `${id}.jsonl`)
28962
- });
28963
- }
28964
- } catch {}
28965
- let best = null;
28966
- for (const { id, path } of candidatePaths) try {
28967
- const s = await stat$1(path);
28968
- if (!best || s.mtimeMs > best.mtimeMs) best = {
28969
- id,
28970
- mtimeMs: s.mtimeMs
28971
- };
28972
- } catch {}
28973
- return best ? best.id : candidates[candidates.length - 1] ?? null;
28974
- }
28975
- /** Async equivalent of getLatestSessionId from lib/agents.ts. */
28976
- async function resolveClaudeSessionId(agentId, opts = {}) {
28977
- const agentDir = join$1(opts.agentsDirOverride ?? join$1(getOverdeckHome(), "agents"), agentId);
28978
- const sessionIdTrimmed = (await readOptional$3(join$1(agentDir, "session.id")))?.trim();
28979
- if (sessionIdTrimmed) return sessionIdTrimmed;
28980
- const sessionsRaw = await readOptional$3(join$1(agentDir, "sessions.json"));
28981
- if (sessionsRaw) try {
28982
- const parsed = JSON.parse(sessionsRaw);
28983
- if (Array.isArray(parsed)) {
28984
- const fresh = await pickFreshestSessionId(parsed.filter((v) => typeof v === "string" && v.trim().length > 0), agentId, opts);
28985
- if (fresh) return fresh;
28986
- }
28987
- } catch {}
28988
- try {
28989
- const runtimeState = await (opts.getRuntimeStateAsync ?? ((id) => Effect.runPromise(getAgentRuntimeState(id))))(agentId);
28990
- if (runtimeState?.claudeSessionId) return runtimeState.claudeSessionId;
28991
- } catch {}
28992
- return null;
28993
- }
28994
- /**
28995
- * Read the Claude session id PINNED into an agent/conversation launcher.sh.
28996
- *
28997
- * The launcher is what spawns the live tmux pane: it runs
28998
- * `claude … --session-id <uuid>` (or `--resume <uuid>`), so the pinned id is the
28999
- * EXACT session the Terminal tab attaches to — the only deterministic ground
29000
- * truth for "which session is live right now." Resolving the transcript panel
29001
- * from this id makes the Conversation tab match the Terminal tab by construction,
29002
- * instead of guessing via JSONL mtime (racy: an older session's file gets touched
29003
- * by a compaction summary write-back or a transient relaunch, its mtime jumps
29004
- * ahead of the live session's, and the panel renders the wrong transcript).
29005
- *
29006
- * Checks the conversation launcher dir first, then the agent launcher dir.
29007
- * Returns the uuid, or null when no launcher exists or it pins no session id.
29008
- */
29009
- const LAUNCHER_UUID = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
29010
- const LAUNCHER_SESSION_ID_RE = new RegExp(`--session-id\\s+'?(${LAUNCHER_UUID})'?`);
29011
- const LAUNCHER_RESUME_RE = new RegExp(`--resume\\s+'?(${LAUNCHER_UUID})'?`);
29012
- async function readLauncherPinnedSessionId(tmuxSession, opts = {}) {
29013
- const home = opts.overdeckHomeOverride ?? getOverdeckHome();
29014
- const candidates = [join$1(home, "conversations", tmuxSession, "launcher.sh"), join$1(home, "agents", tmuxSession, "launcher.sh")];
29015
- for (const path of candidates) {
29016
- const text = await readOptional$3(path);
29017
- if (!text) continue;
29018
- const match = LAUNCHER_SESSION_ID_RE.exec(text) ?? LAUNCHER_RESUME_RE.exec(text);
29019
- if (match) return match[1];
29020
- }
29021
- return null;
29022
- }
29023
- /** Read the harness recorded in the agent's state.json, or null when absent. */
29024
- async function resolveAgentHarness(agentId, opts = {}) {
29025
- if (opts.agentsDirOverride) return readRollbackAgentHarnessFromDir(opts.agentsDirOverride, agentId);
29026
- return getAgentStateSync(agentId)?.harness ?? null;
29027
- }
29028
- /**
29029
- * Resolve the Codex rollout JSONL for a codex-harness agent (PAN-1805).
29030
- *
29031
- * Fast path: the persisted codex-thread-id maps directly to its rollout file.
29032
- * Lazy fallback (same shape as the conversation panel's PAN-1690 fix): codex
29033
- * writes the rollout only on the first turn, so a spawn-time thread-id capture
29034
- * can miss it — the per-agent CODEX_HOME holds only this agent's rollouts, so
29035
- * the newest one is its current thread.
29036
- */
29037
- async function resolveCodexRolloutPath(agentId, opts = {}) {
29038
- const agentDir = join$1(opts.agentsDirOverride ?? join$1(getOverdeckHome(), "agents"), agentId);
29039
- const codexHome = join$1(agentDir, "codex-home");
29040
- if (!await pathExists$5(codexHome)) return null;
29041
- const { findRolloutPath, findLatestRollout } = await import("./codex-D3QWVTal.js");
29042
- const threadId = (await readOptional$3(join$1(agentDir, "codex-thread-id")))?.trim();
29043
- if (threadId) {
29044
- const rollout = findRolloutPath(codexHome, threadId);
29045
- if (rollout) return rollout;
29046
- }
29047
- return findLatestRollout(codexHome);
29048
- }
29049
- /**
29050
- * Resolve the pi/kimi session JSONL (PAN-1908). Pi writes its session transcript
29051
- * as `<iso-ts>_<session-id>.jsonl` either in the agent dir's `sessions/` subdir
29052
- * (conversations) OR in the agent dir root (work agents) — so check both. The dir
29053
- * also holds `cost-events.jsonl` / `activity.jsonl`, which are NOT transcripts.
29054
- * Return the freshest transcript by mtime, or null if pi hasn't written one yet.
29055
- */
29056
- async function resolvePiSessionPath(agentId, opts = {}) {
29057
- const agentDir = join$1(opts.agentsDirOverride ?? join$1(getOverdeckHome(), "agents"), agentId);
29058
- const NON_TRANSCRIPT = new Set(["cost-events.jsonl", "activity.jsonl"]);
29059
- let best = null;
29060
- for (const dir of [join$1(agentDir, "sessions"), agentDir]) {
29061
- let entries;
29062
- try {
29063
- entries = await readdir(dir);
29064
- } catch {
29065
- continue;
29066
- }
29067
- for (const name of entries) {
29068
- if (!name.endsWith(".jsonl") || NON_TRANSCRIPT.has(name)) continue;
29069
- const p = join$1(dir, name);
29070
- try {
29071
- const m = (await stat$1(p)).mtimeMs;
29072
- if (!best || m > best.mtime) best = {
29073
- path: p,
29074
- mtime: m
29075
- };
29076
- } catch {}
29077
- }
29078
- }
29079
- return best?.path ?? null;
29080
- }
29081
- /**
29082
- * Resolve the JSONL transcript for an agent.
29083
- *
29084
- * Codex agents resolve to their rollout JSONL (PAN-1805). For claude-code
29085
- * agents, returns the absolute path if both the claudeSessionId is known AND
29086
- * the corresponding JSONL file exists; otherwise null.
29087
- */
29088
- async function resolveJsonlPath(agentId, workspacePath, opts = {}) {
29089
- const harness = await resolveAgentHarness(agentId, opts);
29090
- if (harness === "codex") return resolveCodexRolloutPath(agentId, opts);
29091
- if (harness === "pi") return resolvePiSessionPath(agentId, opts);
29092
- const claudeSessionId = await resolveClaudeSessionId(agentId, opts);
29093
- if (!claudeSessionId) return null;
29094
- const jsonlPath = join$1(opts.claudeProjectsDirOverride ?? join$1(homedir$1(), ".claude", "projects"), encodeClaudeProjectDir(workspacePath), `${claudeSessionId}.jsonl`);
29095
- if (await pathExists$5(jsonlPath)) return jsonlPath;
29096
- return null;
29097
- }
29098
- //#endregion
29099
- //#region services/read-workspace-file.ts
29100
- init_activity_logger();
29101
- init_event_store();
29102
- init_briefing_freshness();
29103
28929
  init_claude_permissions();
29104
28930
  init_codex();
29105
28931
  init_providers();
@@ -29115,6 +28941,12 @@ init_pi_fifo();
29115
28941
  init_control_settings();
29116
28942
  init_operator_interventions();
29117
28943
  init_transcript_landing();
28944
+ init_agents$2();
28945
+ init_agent_input_detection();
28946
+ init_agent_enrichment();
28947
+ init_jsonl_resolver();
28948
+ init_flywheel_run_state();
28949
+ init_src();
29118
28950
  init_projects();
29119
28951
  const MAX_WORKSPACE_FILE_BYTES = 256 * 1024;
29120
28952
  const DEFAULT_CONTEXT_LINES = 10;
@@ -29283,6 +29115,7 @@ function readWorkspaceFileEffect(input) {
29283
29115
  * out of cwd is no broader than what the caller could query directly
29284
29116
  * with an absolute path.
29285
29117
  */
29118
+ init_src();
29286
29119
  const MAX_PATH_LENGTH = 4096;
29287
29120
  async function resolveFilePathExists(input) {
29288
29121
  const { cwd, path } = input;
@@ -29334,9 +29167,12 @@ function resolveFilePathExistsEffect(input) {
29334
29167
  * Terminal RPC methods (subscribeTerminal, terminalOpen/Write/Resize/Close)
29335
29168
  * are implemented via TerminalService (dual-runtime PTY, B20).
29336
29169
  */
29170
+ init_src();
29337
29171
  init_paths();
29338
29172
  init_tmux();
29339
29173
  init_projects();
29174
+ init_origin_validation();
29175
+ init_http_helpers();
29340
29176
  function storedToDomainEvent(stored) {
29341
29177
  return {
29342
29178
  type: stored.type,
@@ -29359,7 +29195,7 @@ function normalizedIssueId(value) {
29359
29195
  function conversationDiscoveringStream() {
29360
29196
  return Stream.succeed({ kind: "discovering" }).pipe(Stream.repeat(Schedule.fixed("2 seconds")));
29361
29197
  }
29362
- function streamResolvedFullParseSnapshots(resolve, parse, model) {
29198
+ function streamResolvedFullParseSnapshots(resolve, parse, model, unresolvedMeansEmpty = false) {
29363
29199
  return Stream.callback((queue) => Effect.acquireRelease(Effect.promise(async () => {
29364
29200
  let stopped = false;
29365
29201
  let resolving = false;
@@ -29369,6 +29205,14 @@ function streamResolvedFullParseSnapshots(resolve, parse, model) {
29369
29205
  let parsing = false;
29370
29206
  let pendingReparse = false;
29371
29207
  let sessionFile = null;
29208
+ let announcedEmpty = false;
29209
+ let hasContent = false;
29210
+ const stopDiscovery = () => {
29211
+ if (discoveryTimer) {
29212
+ clearInterval(discoveryTimer);
29213
+ discoveryTimer = null;
29214
+ }
29215
+ };
29372
29216
  const offer = (event) => {
29373
29217
  try {
29374
29218
  Queue.offerUnsafe(queue, event);
@@ -29383,6 +29227,10 @@ function streamResolvedFullParseSnapshots(resolve, parse, model) {
29383
29227
  parsing = true;
29384
29228
  try {
29385
29229
  const result = await parse(sessionFile);
29230
+ if (result.messages.length > 0 && !hasContent) {
29231
+ hasContent = true;
29232
+ stopDiscovery();
29233
+ }
29386
29234
  offer({
29387
29235
  kind: "messages",
29388
29236
  messages: result.messages,
@@ -29401,36 +29249,56 @@ function streamResolvedFullParseSnapshots(resolve, parse, model) {
29401
29249
  }
29402
29250
  }
29403
29251
  };
29252
+ const watchFile = (file) => {
29253
+ try {
29254
+ watcher = watch(file, () => {
29255
+ if (debounce) return;
29256
+ debounce = setTimeout(() => {
29257
+ debounce = null;
29258
+ emit();
29259
+ }, 300);
29260
+ });
29261
+ } catch {}
29262
+ };
29404
29263
  const tryResolve = async () => {
29405
- if (stopped || resolving || sessionFile) return;
29264
+ if (stopped || resolving || hasContent) return;
29406
29265
  resolving = true;
29407
29266
  try {
29408
29267
  const resolved = await resolve();
29409
29268
  if (!resolved) {
29410
- offer({ kind: "discovering" });
29269
+ if (unresolvedMeansEmpty) {
29270
+ if (!sessionFile && !announcedEmpty) {
29271
+ announcedEmpty = true;
29272
+ offer({
29273
+ kind: "messages",
29274
+ messages: [],
29275
+ workLog: [],
29276
+ streaming: false,
29277
+ snapshot: true
29278
+ });
29279
+ }
29280
+ return;
29281
+ }
29282
+ if (!sessionFile) offer({ kind: "discovering" });
29411
29283
  return;
29412
29284
  }
29413
- sessionFile = resolved;
29414
- if (discoveryTimer) {
29415
- clearInterval(discoveryTimer);
29416
- discoveryTimer = null;
29417
- }
29418
- await emit();
29419
- try {
29420
- watcher = watch(resolved, () => {
29421
- if (debounce) return;
29422
- debounce = setTimeout(() => {
29423
- debounce = null;
29424
- emit();
29425
- }, 300);
29426
- });
29427
- } catch {}
29285
+ if (resolved !== sessionFile) {
29286
+ if (watcher) {
29287
+ try {
29288
+ watcher.close();
29289
+ } catch {}
29290
+ watcher = null;
29291
+ }
29292
+ sessionFile = resolved;
29293
+ await emit();
29294
+ if (!stopped) watchFile(resolved);
29295
+ } else await emit();
29428
29296
  } finally {
29429
29297
  resolving = false;
29430
29298
  }
29431
29299
  };
29432
29300
  await tryResolve();
29433
- if (!sessionFile) discoveryTimer = setInterval(() => {
29301
+ if (!hasContent) discoveryTimer = setInterval(() => {
29434
29302
  tryResolve();
29435
29303
  }, 2e3);
29436
29304
  return { stop: () => {
@@ -29808,13 +29676,13 @@ const PanRpcLayer = PanRpcGroup.toLayer(Effect.gen(function* () {
29808
29676
  const conv = getConversationByName(input.conversationName);
29809
29677
  if (!conv && /^(agent-|planning-|specialist-)/.test(input.conversationName)) {
29810
29678
  const harness = yield* Effect.promise(() => resolveAgentHarness(input.conversationName));
29811
- if (harness === "pi") return streamResolvedFullParseSnapshots(() => resolvePiSessionPath(input.conversationName), parsePiConversationMessages, null);
29679
+ if (harness === "ohmypi") return streamResolvedFullParseSnapshots(() => resolvePiSessionPath(input.conversationName), parseOhmypiConversationMessages, null);
29812
29680
  if (harness === "codex") return streamResolvedFullParseSnapshots(() => resolveCodexRolloutPath(input.conversationName), parseCodexConversationMessages, null);
29813
29681
  return conversationDiscoveringStream();
29814
29682
  }
29815
29683
  if (!conv) return conversationDiscoveringStream();
29816
- if (conv.harness === "pi") return streamResolvedFullParseSnapshots(() => resolvePiSessionPath(conv.tmuxSession), parsePiConversationMessages, conv.model ?? null);
29817
- if (conv.harness === "codex") return streamResolvedFullParseSnapshots(() => resolveCodexRolloutPath(conv.tmuxSession), parseCodexConversationMessages, conv.model ?? null);
29684
+ if (conv.harness === "ohmypi") return streamResolvedFullParseSnapshots(() => resolvePiSessionPath(conv.tmuxSession), parseOhmypiConversationMessages, conv.model ?? null, true);
29685
+ if (conv.harness === "codex") return streamResolvedFullParseSnapshots(() => resolveCodexRolloutPath(conv.tmuxSession), parseCodexConversationMessages, conv.model ?? null, true);
29818
29686
  if (conv.harness !== "claude-code" && conv.harness != null) return conversationDiscoveringStream();
29819
29687
  const launcherTmuxSession = conv.tmuxSession;
29820
29688
  const resolvedSessionId = (launcherTmuxSession ? yield* Effect.promise(() => readLauncherPinnedSessionId(launcherTmuxSession)) : null) ?? conv.claudeSessionId;
@@ -30077,6 +29945,32 @@ const websocketRpcRouteLayer = Layer.unwrap(Effect.gen(function* () {
30077
29945
  }));
30078
29946
  //#endregion
30079
29947
  //#region routes/costs.ts
29948
+ init_types();
29949
+ init_specs();
29950
+ init_io();
29951
+ init_pan_dir();
29952
+ init_docker_stats();
29953
+ init_review_status$1();
29954
+ init_dashboard_auth();
29955
+ init_reconciler();
29956
+ init_issue_cost_resolver();
29957
+ init_running_agents_cache();
29958
+ init_config$1();
29959
+ init_health();
29960
+ init_runtimes();
29961
+ init_specialists();
29962
+ init_concurrency();
29963
+ init_no_resume_mode();
29964
+ init_concurrency$1();
29965
+ init_deacon();
29966
+ init_service();
29967
+ init_system_health_service();
29968
+ init_git_info();
29969
+ init_agent_projection();
29970
+ init_agents();
29971
+ init_remote_agents();
29972
+ init_resource_discovery();
29973
+ init_issues();
30080
29974
  /**
30081
29975
  * Costs route module — Effect HttpRouter.Layer (PAN-428 B10)
30082
29976
  *
@@ -30093,7 +29987,11 @@ const websocketRpcRouteLayer = Layer.unwrap(Effect.gen(function* () {
30093
29987
  * POST /api/costs/sync-wal
30094
29988
  * POST /api/costs/reconcile
30095
29989
  */
29990
+ init_http_helpers();
29991
+ init_costs();
30096
29992
  init_cost_sync();
29993
+ init_sync_wal();
29994
+ init_http_handler();
30097
29995
  const getCostsSummaryRoute = HttpRouter.add("GET", "/api/costs/summary", httpHandler(Effect.gen(function* () {
30098
29996
  const request = yield* HttpServerRequest.HttpServerRequest;
30099
29997
  const urlOpt = HttpServerRequest.toURL(request);
@@ -30298,7 +30196,7 @@ const postCostsSyncWalRoute = HttpRouter.add("POST", "/api/costs/sync-wal", http
30298
30196
  })));
30299
30197
  const postCostsReconcileRoute = HttpRouter.add("POST", "/api/costs/reconcile", httpHandler(Effect.gen(function* () {
30300
30198
  const result = yield* Effect.tryPromise({
30301
- try: () => reconcile(),
30199
+ try: () => Effect.runPromise(reconcile()),
30302
30200
  catch: (err) => new Error(err instanceof Error ? err.message : String(err))
30303
30201
  });
30304
30202
  console.log(`[reconciler] Sweep complete: ${result.eventsImported ?? 0} imported`);
@@ -30338,7 +30236,8 @@ const costsRouteLayer = Layer.mergeAll(getCostsSummaryRoute, getCostsByIssueRout
30338
30236
  * PUT /api/cloister/config
30339
30237
  * GET /api/cloister/agents/health
30340
30238
  */
30341
- init_agents$1();
30239
+ init_http_helpers();
30240
+ init_http_handler();
30342
30241
  const readJsonBody$11 = Effect.gen(function* () {
30343
30242
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
30344
30243
  try {
@@ -30475,8 +30374,10 @@ const cloisterRouteLayer = Layer.mergeAll(getCloisterStatusRoute, postCloisterSt
30475
30374
  * DELETE /api/resources/docker/volume/:name
30476
30375
  * POST /api/resources/docker/prune-volumes
30477
30376
  */
30478
- init_docker_stats();
30479
- init_agents$1();
30377
+ init_http_helpers();
30378
+ init_domain_services();
30379
+ init_http_handler();
30380
+ init_agents$2();
30480
30381
  init_tmux();
30481
30382
  const execAsync$6 = promisify(exec);
30482
30383
  function formatUptime(startedAt) {
@@ -30729,6 +30630,7 @@ async function deriveSessionPresence(agentId, rtState, tmuxSessionNames) {
30729
30630
  }
30730
30631
  //#endregion
30731
30632
  //#region ../../lib/settings-api.ts
30633
+ init_prd_locations();
30732
30634
  /**
30733
30635
  * Settings API Adapter
30734
30636
  *
@@ -30790,19 +30692,7 @@ function sanitizeApiTtsConfig(tts) {
30790
30692
  function getDefaultConversationModelApi() {
30791
30693
  const { config } = loadConfigSync();
30792
30694
  if (config.defaultConversationModel) return resolveModelIdSync(config.defaultConversationModel);
30793
- if (config.enabledProviders.has("openai")) return resolveModelIdSync("gpt-5.5");
30794
- if (config.enabledProviders.has("minimax")) return resolveModelIdSync("minimax-m2.7-highspeed");
30795
- if (config.enabledProviders.has("google")) return resolveModelIdSync("gemini-3.1-pro-preview");
30796
- if (config.enabledProviders.has("kimi")) return resolveModelIdSync("kimi-k2.5");
30797
- if (config.enabledProviders.has("zai")) return resolveModelIdSync("glm-5.2");
30798
- if (config.enabledProviders.has("mimo")) return resolveModelIdSync("mimo-v2.5-pro");
30799
- if (config.enabledProviders.has("nous")) return resolveModelIdSync("qwen/qwen3.6-plus");
30800
- if (config.enabledProviders.has("dashscope")) return resolveModelIdSync("qwen3-coder-plus");
30801
- if (config.enabledProviders.has("openrouter")) {
30802
- const fav = config.openrouterFavorites[0];
30803
- if (fav) return resolveModelIdSync(fav);
30804
- }
30805
- return resolveModelIdSync("claude-sonnet-4-6");
30695
+ throw new Error("No default model configured — set models.default_conversation_model");
30806
30696
  }
30807
30697
  const ROLE_NAMES = [
30808
30698
  "plan",
@@ -30957,7 +30847,7 @@ function validateModelRef(fieldPath, ref, effectiveWorkhorses, errors, warnings,
30957
30847
  }
30958
30848
  function validateRoleFields(fieldPath, roleConfig, errors) {
30959
30849
  const harness = roleConfig.harness;
30960
- if (harness !== void 0 && harness !== null && harness !== "" && harness !== "claude-code" && harness !== "pi" && harness !== "codex") errors.push(`${fieldPath}.harness must be claude-code, pi, codex, null, or empty string`);
30850
+ if (harness !== void 0 && harness !== null && harness !== "" && harness !== "claude-code" && harness !== "ohmypi" && harness !== "codex") errors.push(`${fieldPath}.harness must be claude-code, ohmypi, codex, null, or empty string`);
30961
30851
  const effort = roleConfig.effort;
30962
30852
  if (effort !== void 0 && !ROLE_EFFORTS.includes(effort)) errors.push(`${fieldPath}.effort must be one of ${ROLE_EFFORTS.join(", ")}`);
30963
30853
  const maxAgents = roleConfig.maxAgents;
@@ -31392,7 +31282,7 @@ function validateSettingsApi(settings) {
31392
31282
  errors.push(`Unknown provider harness entry "${provider}"`);
31393
31283
  continue;
31394
31284
  }
31395
- if (harness !== void 0 && harness !== "" && harness !== "claude-code" && harness !== "pi" && harness !== "codex") errors.push(`models.provider_harnesses.${provider} must be claude-code, pi, codex, or empty string`);
31285
+ if (harness !== void 0 && harness !== "" && harness !== "claude-code" && harness !== "ohmypi" && harness !== "codex") errors.push(`models.provider_harnesses.${provider} must be claude-code, ohmypi, codex, or empty string`);
31396
31286
  }
31397
31287
  if (settings.models?.overrides) {
31398
31288
  const validModelIds = Object.keys(MODEL_CAPABILITIES);
@@ -31631,14 +31521,6 @@ const saveOpenRouterFavorites = (favorites) => Effect.tryPromise({
31631
31521
  });
31632
31522
  //#endregion
31633
31523
  //#region services/agent-status.ts
31634
- init_tracker_config();
31635
- init_cache_service();
31636
- init_internal_token();
31637
- init_pipeline_notifier();
31638
- init_review_status_sync();
31639
- init_review_status();
31640
- init_types();
31641
- init_issue_service_singleton();
31642
31524
  init_review_agent();
31643
31525
  function normalizeAgentStatus(status) {
31644
31526
  switch (status) {
@@ -31681,8 +31563,8 @@ function normalizeAgentStatus(status) {
31681
31563
  *
31682
31564
  * Async-only (fs/promises); safe to call from the dashboard server.
31683
31565
  */
31684
- init_agents$1();
31685
- init_specialists();
31566
+ init_agents$2();
31567
+ init_jsonl_resolver();
31686
31568
  init_tmux();
31687
31569
  const CONVOY_REVIEWER_ROLES = [
31688
31570
  "correctness",
@@ -31878,11 +31760,16 @@ async function buildReviewerNodes(opts) {
31878
31760
  * POST /api/command-deck/planning/:issueId/init
31879
31761
  * GET /api/command-deck/projects
31880
31762
  */
31763
+ init_http_helpers();
31881
31764
  init_child_env();
31882
- init_agents$1();
31765
+ init_domain_services();
31766
+ init_read_model();
31767
+ init_agents$2();
31768
+ init_costs();
31883
31769
  init_tmux();
31884
31770
  init_projects();
31885
- init_io();
31771
+ init_http_handler();
31772
+ init_jsonl_resolver();
31886
31773
  const execAsync$5 = promisify(exec);
31887
31774
  const execFileAsync$4 = promisify(execFile);
31888
31775
  function getIssueDataService$2() {
@@ -32610,8 +32497,8 @@ Be specific: reference actual file names, function names, requirement text, disc
32610
32497
  const { command: cliCmd, args: cliArgs } = getAgentCommandSync(statusModelId);
32611
32498
  const modelFlag = cliArgs.length > 0 ? ` ${cliArgs.join(" ")}` : "";
32612
32499
  const promptFile = join$1(planningDir, ".status-review-prompt.tmp");
32613
- const { getProviderForModelSync, getProviderEnvSync } = await import("./providers-CX4sIKbS.js");
32614
- const { loadConfigSync: loadYamlConfig } = await import("./config-yaml-BPbPSBp2.js");
32500
+ const { getProviderForModelSync, getProviderEnvSync } = await import("./providers-WToYbUwa.js");
32501
+ const { loadConfigSync: loadYamlConfig } = await import("./config-yaml-DR4CnMG4.js");
32615
32502
  let providerEnvStr = "";
32616
32503
  let providerEnv = {};
32617
32504
  const statusProvider = getProviderForModelSync(statusModelId);
@@ -32906,7 +32793,6 @@ async function fetchProjectTree() {
32906
32793
  const commandDeckRouteLayer = Layer.mergeAll(getMissionControlActivityRoute, getMissionControlPlanningRoute, postMissionControlStatusReviewRoute, postMissionControlUploadRoute, postMissionControlSyncDiscussionsRoute, postMissionControlPlanningInitRoute, getMissionControlProjectsRoute);
32907
32794
  //#endregion
32908
32795
  //#region routes/remote.ts
32909
- init_specs();
32910
32796
  /**
32911
32797
  * Remote route module — Effect HttpRouter.Layer (PAN-428 B14)
32912
32798
  *
@@ -32921,7 +32807,11 @@ init_specs();
32921
32807
  * GET /api/remote/workspaces/:issueId/agent/output
32922
32808
  * POST /api/remote/workspaces/:issueId/agent/tell
32923
32809
  */
32810
+ init_http_helpers();
32811
+ init_remote();
32924
32812
  init_config();
32813
+ init_domain_services();
32814
+ init_http_handler();
32925
32815
  async function loadRemoteWorkspaceMetadata(issueId) {
32926
32816
  const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
32927
32817
  const content = await readFile(join$1(homedir$1(), ".overdeck", "workspaces", `${normalizedId}.yaml`), "utf-8").catch(() => null);
@@ -36920,8 +36810,10 @@ function isAbortError(error) {
36920
36810
  * POST /api/settings/validate-api-key
36921
36811
  * PUT /api/settings
36922
36812
  */
36813
+ init_http_helpers();
36923
36814
  init_providers();
36924
- init_agents$1();
36815
+ init_http_handler();
36816
+ init_agents$2();
36925
36817
  init_config_yaml();
36926
36818
  const REINDEX_CONFIRM_THRESHOLD_USD = 1;
36927
36819
  const REINDEX_CONFIRM_TTL_MS = 600 * 1e3;
@@ -37727,6 +37619,9 @@ const getProviderEnvConflictsRoute = HttpRouter.add("GET", "/api/settings/provid
37727
37619
  const settingsRouteLayer = Layer.mergeAll(getSettingsRoute, getAvailableModelsRoute, getOptimalDefaultsRoute, getMiniMaxDefaultsRoute, getClaudeAuthRoute, getOpenAIAuthRoute, postTestApiKeyRoute, postValidateApiKeyRoute, getConversationSearchStatusRoute, getConversationSearchReindexEstimateRoute, postConversationSearchReindexRoute, getConversationSearchReindexProgressRoute, putSettingsRoute, putUiThemeRoute, getOpenRouterModelsRoute, putOpenRouterFavoritesRoute, putOpenRouterApiKeyRoute, postOpenRouterTestKeyRoute, getHarnessPolicyRoute, getProviderEnvConflictsRoute);
37728
37620
  //#endregion
37729
37621
  //#region routes/autopreso.ts
37622
+ init_http_helpers();
37623
+ init_http_handler();
37624
+ init_origin_validation();
37730
37625
  const readJsonBody$7 = Effect.gen(function* () {
37731
37626
  const request = yield* HttpServerRequest.HttpServerRequest;
37732
37627
  const contentLength = Number(request.headers["content-length"] ?? 0);
@@ -37790,7 +37685,12 @@ const autopresoRouteLayer = Layer.mergeAll(startRoute, backToStagingRoute, reset
37790
37685
  * GET /api/activity
37791
37686
  * GET /api/activity/:id
37792
37687
  */
37793
- init_agents$1();
37688
+ init_http_helpers();
37689
+ init_domain_services();
37690
+ init_service();
37691
+ init_agents$2();
37692
+ init_git_activity();
37693
+ init_http_handler();
37794
37694
  let _cachedReviewStatuses = null;
37795
37695
  let _cachedReviewStatusesAt = 0;
37796
37696
  const REVIEW_STATUS_CACHE_TTL_MS = 5e3;
@@ -37951,6 +37851,7 @@ const metricsRouteLayer = Layer.mergeAll(getMetricsSummaryRoute, getMetricsCosts
37951
37851
  * Health filtering logic for dashboard health API
37952
37852
  * Determines which agents should be visible in health checks
37953
37853
  */
37854
+ init_config$1();
37954
37855
  init_tmux();
37955
37856
  const KICKOFF_STALL_GRACE_MS = 300 * 1e3;
37956
37857
  /**
@@ -38071,6 +37972,8 @@ const determineHealthStatus = (agentId, stateFile, liveSessions) => Effect.gen(f
38071
37972
  * POST /api/dev/rebuild
38072
37973
  * POST /api/system/restart-dashboard
38073
37974
  */
37975
+ init_http_helpers();
37976
+ init_service();
38074
37977
  init_tmux();
38075
37978
  init_projects();
38076
37979
  init_tracker_config();
@@ -38078,9 +37981,11 @@ init_config_yaml();
38078
37981
  init_config();
38079
37982
  init_issue_id();
38080
37983
  init_io();
37984
+ init_domain_services();
37985
+ init_read_model();
37986
+ init_http_handler();
38081
37987
  init_types();
38082
- init_agents$1();
38083
- init_remote_agents();
37988
+ init_agents$2();
38084
37989
  const execAsync$4 = promisify(exec);
38085
37990
  async function readPackageVersion() {
38086
37991
  let dir = dirname$1(fileURLToPath(import.meta.url));
@@ -38467,6 +38372,33 @@ const postRallyValidateRoute = HttpRouter.add("POST", "/api/rally/validate", Eff
38467
38372
  });
38468
38373
  }));
38469
38374
  const getNoResumeModeRoute = HttpRouter.add("GET", "/api/no-resume-mode", Effect.sync(() => jsonResponse(getNoResumeMode())));
38375
+ /**
38376
+ * Operator "Resume all" — the call-to-action behind the no-resume banner.
38377
+ * PAN-1963 makes agent auto-resume OFF by default at every boot, so after the
38378
+ * dashboard loads, stopped agents wait for the operator to put them back to
38379
+ * work. This clears no-resume mode for the running process (so the Deacon's
38380
+ * patrols and lifecycle-event handlers resume agents again) and immediately
38381
+ * sweeps the stopped work agents rather than waiting for the next patrol tick.
38382
+ */
38383
+ const postResumeAllRoute = HttpRouter.add("POST", "/api/resume-all", Effect.promise(async () => {
38384
+ try {
38385
+ disableNoResumeMode();
38386
+ const resumed = await autoResumeStoppedWorkAgents();
38387
+ console.log(`[resume-all] No-resume mode cleared; resumed ${resumed.length} work agent(s)${resumed.length ? `: ${resumed.join(", ")}` : ""}`);
38388
+ return jsonResponse({
38389
+ ok: true,
38390
+ resumed,
38391
+ count: resumed.length
38392
+ });
38393
+ } catch (error) {
38394
+ const msg = error instanceof Error ? error.message : String(error);
38395
+ console.error("Error resuming all agents:", error);
38396
+ return jsonResponse({
38397
+ ok: false,
38398
+ error: "Failed to resume agents: " + msg
38399
+ }, { status: 500 });
38400
+ }
38401
+ }));
38470
38402
  const getDeaconStatusRoute = HttpRouter.add("GET", "/api/deacon/status", Effect.try({
38471
38403
  try: () => {
38472
38404
  const service = getCloisterService();
@@ -38537,7 +38469,7 @@ const getVersionRoute = HttpRouter.add("GET", "/api/version", Effect.promise(asy
38537
38469
  const version = await getOverdeckVersion();
38538
38470
  let supervisorUrl = null;
38539
38471
  try {
38540
- const { getSupervisorUrlSync } = await import("./supervisor-sXlJGJUM.js");
38472
+ const { getSupervisorUrlSync } = await import("./supervisor-DHuyC90H.js");
38541
38473
  supervisorUrl = getSupervisorUrlSync();
38542
38474
  } catch {}
38543
38475
  return jsonResponse({
@@ -38633,7 +38565,7 @@ const getPlanningStatusRoute = HttpRouter.add("GET", "/api/planning/:issueId/sta
38633
38565
  const remoteState = loadRemoteAgentState(sessionName);
38634
38566
  const isRemote = !!remoteState;
38635
38567
  const vmName = remoteState?.vmName ?? "";
38636
- const { getAgentStateSync } = await import("./agents-DD8lFXJe2.js");
38568
+ const { getAgentStateSync } = await import("./agents-DdvrDsH42.js");
38637
38569
  const agentStarting = getAgentStateSync(sessionName)?.status === "starting";
38638
38570
  let tmuxSessionAlive = false;
38639
38571
  if (!isRemote) try {
@@ -38776,10 +38708,10 @@ Continue the PLANNING session. Do NOT implement anything.
38776
38708
  `);
38777
38709
  const agentCwd = workspacePath;
38778
38710
  if (existsSync$1(outputFile)) await rename(outputFile, join$1(planningDir, `output-${Date.now()}.jsonl`));
38779
- const { getAgentCommandSync } = await import("./settings-Ce28-uYS.js");
38711
+ const { getAgentCommandSync } = await import("./settings-2rcV0DoQ.js");
38780
38712
  let msgPlanningModel = "claude-sonnet-4-6";
38781
38713
  try {
38782
- const { loadConfigSync, resolveModel } = await import("./config-yaml-BPbPSBp2.js");
38714
+ const { loadConfigSync, resolveModel } = await import("./config-yaml-DR4CnMG4.js");
38783
38715
  msgPlanningModel = resolveModel("plan", void 0, loadConfigSync().config);
38784
38716
  } catch {}
38785
38717
  const msgAgentCmd = getAgentCommandSync(msgPlanningModel);
@@ -39043,7 +38975,7 @@ const postShadowMonitorRoute = HttpRouter.add("POST", "/api/shadow/:issueId/moni
39043
38975
  const projectPath = await getProjectPath(issuePrefix);
39044
38976
  const workspacePath = join$1(projectPath, "workspaces", `feature-${issueLower}`);
39045
38977
  if (!existsSync$1(workspacePath)) return jsonResponse({ error: "Workspace not found" }, { status: 404 });
39046
- const { gatherArtifacts, generateBasicInference, updateInferenceDocumentSync } = await import("./shadow-engineering-Ci8In91q.js");
38978
+ const { gatherArtifacts, generateBasicInference, updateInferenceDocumentSync } = await import("./shadow-engineering-DcZh0oHj.js");
39047
38979
  const config = {
39048
38980
  issueId,
39049
38981
  workspacePath,
@@ -39073,7 +39005,7 @@ const postShadowObserveRoute = HttpRouter.add("POST", "/api/shadow/:issueId/obse
39073
39005
  if (!existsSync$1(workspacePath)) return jsonResponse({ error: "Workspace not found" }, { status: 404 });
39074
39006
  const ghConfig = getGitHubConfig();
39075
39007
  if (!ghConfig) return jsonResponse({ error: "GitHub not configured - Observer requires GitHub" }, { status: 400 });
39076
- const { runObserverCycle } = await import("./shadow-engineering-Ci8In91q.js");
39008
+ const { runObserverCycle } = await import("./shadow-engineering-DcZh0oHj.js");
39077
39009
  const firstRepo = ghConfig.repos[0];
39078
39010
  const config = {
39079
39011
  issueId,
@@ -39137,7 +39069,7 @@ const postRestartDashboardRoute = HttpRouter.add("POST", "/api/system/restart-da
39137
39069
  return jsonResponse({ error: "Restart failed: " + (error instanceof Error ? error.message : String(error)) }, { status: 500 });
39138
39070
  }
39139
39071
  }));
39140
- const miscRouteLayer = Layer.mergeAll(postTrackersRefreshRoute, getProjectMappingsRoute, putProjectMappingsRoute, postProjectMappingsRoute, getSystemHealthRoute, getGodviewSystemHealthRoute, getHealthAgentsRoute, postHealthAgentPingRoute, getTrackerStatusRoute, postRallyValidateRoute, getNoResumeModeRoute, getDeaconStatusRoute, getDeaconLogsRoute, postDeaconPatrolRoute, getDeaconPauseRoute, postDeaconPauseRoute, getVersionRoute, getRegisteredProjectsRoute, getConfirmationsRoute, postConfirmationRespondRoute, getSkillsRoute, getPlanningStatusRoute, postPlanningMessageRoute, deletePlanningSessionRoute, getTldrStatusRoute, postTldrStartRoute, postTldrStopRoute, postTldrReloadRoute, getCacheStatusRoute, clearCacheRoute, getMetricsRuntimesRoute, getMetricsTasksRoute, postShadowMonitorRoute, postShadowObserveRoute, postDevRebuildRoute, postRestartDashboardRoute);
39072
+ const miscRouteLayer = Layer.mergeAll(postTrackersRefreshRoute, getProjectMappingsRoute, putProjectMappingsRoute, postProjectMappingsRoute, getSystemHealthRoute, getGodviewSystemHealthRoute, getHealthAgentsRoute, postHealthAgentPingRoute, getTrackerStatusRoute, postRallyValidateRoute, getNoResumeModeRoute, postResumeAllRoute, getDeaconStatusRoute, getDeaconLogsRoute, postDeaconPatrolRoute, getDeaconPauseRoute, postDeaconPauseRoute, getVersionRoute, getRegisteredProjectsRoute, getConfirmationsRoute, postConfirmationRespondRoute, getSkillsRoute, getPlanningStatusRoute, postPlanningMessageRoute, deletePlanningSessionRoute, getTldrStatusRoute, postTldrStartRoute, postTldrStopRoute, postTldrReloadRoute, getCacheStatusRoute, clearCacheRoute, getMetricsRuntimesRoute, getMetricsTasksRoute, postShadowMonitorRoute, postShadowObserveRoute, postDevRebuildRoute, postRestartDashboardRoute);
39141
39073
  //#endregion
39142
39074
  //#region ../../lib/conversation-search/ranker.ts
39143
39075
  init_fts_db();
@@ -39315,8 +39247,11 @@ async function searchConversationChunks(input) {
39315
39247
  * own embedding pipeline and config (see linked GitHub issue at end of
39316
39248
  * commit).
39317
39249
  */
39250
+ init_http_helpers();
39318
39251
  init_projects();
39319
39252
  init_conversations();
39253
+ init_dashboard_auth();
39254
+ init_origin_validation();
39320
39255
  const PAN_COMMANDS = [
39321
39256
  {
39322
39257
  name: "pan up",
@@ -39891,66 +39826,32 @@ const getPaletteSearchRoute = HttpRouter.add("GET", "/api/palette/search", Effec
39891
39826
  }));
39892
39827
  const paletteRouteLayer = Layer.mergeAll(getPaletteCommandsRoute, getPaletteSearchRoute);
39893
39828
  //#endregion
39894
- //#region ../../lib/conversations/switch-strategy.ts
39895
- init_model_capabilities();
39896
- const EXTENDED_CONTEXT_WINDOW_FLOOR = 1e6;
39897
- function getEffectiveTargetWindow({ registryWindow, currentObservedCeiling, cliproxyRouted, providerRoutingChanged }) {
39898
- if (cliproxyRouted) return CLIPROXY_CODEX_CONTEXT_WINDOW;
39899
- if (!providerRoutingChanged && currentObservedCeiling > registryWindow) return Math.max(EXTENDED_CONTEXT_WINDOW_FLOOR, registryWindow);
39900
- return registryWindow;
39901
- }
39902
- function decideSwitchStrategy({ harnessChanged, providerRoutingChanged, contextTokens, effectiveTargetWindow }) {
39903
- if (harnessChanged) return {
39904
- tier: 4,
39905
- compact: false,
39906
- useModelCommand: false
39907
- };
39908
- if (contextTokens > effectiveTargetWindow * .8) return {
39909
- tier: 3,
39910
- compact: true,
39911
- useModelCommand: false
39912
- };
39913
- if (!providerRoutingChanged) return {
39914
- tier: 1,
39915
- compact: false,
39916
- useModelCommand: true
39917
- };
39918
- return {
39919
- tier: 2,
39920
- compact: false,
39921
- useModelCommand: false
39922
- };
39923
- }
39924
- //#endregion
39925
- //#region ../../lib/pi-codex-auth.ts
39829
+ //#region ../../lib/ohmypi-codex-auth.ts
39926
39830
  init_features();
39927
39831
  /**
39928
- * Pi (ChatGPT / Codex subscription) OAuth helpers — PAN-1520.
39832
+ * oh-my-pi (omp) OAuth helpers for the openai-codex (ChatGPT subscription) provider.
39833
+ * Forked from pi-codex-auth.ts (PAN-1520) for the omp migration (PAN-1989).
39929
39834
  *
39930
- * Pi conversations on a GPT-5.x model authenticate with the user's ChatGPT
39931
- * subscription through an OAuth credential under the `openai-codex` provider,
39932
- * stored at ~/.pi/agent/auth.json. When that access token expires AND its
39933
- * refresh token can no longer renew it, Pi fails with the opaque
39934
- * "No API key for provider: openai-codex".
39835
+ * omp stores its general credentials in ~/.omp/agent/agent.db (SQLite). The
39836
+ * openai-codex OAuth credential is persisted separately at ~/.omp/agent/auth.json
39837
+ * (same JSON format as Pi's ~/.pi/agent/auth.json) so that this helper's
39838
+ * read/write logic is unchanged from the Pi side.
39935
39839
  *
39936
39840
  * This module:
39937
- * - reads/writes that credential in Pi's exact on-disk shape,
39841
+ * - reads/writes the openai-codex credential under ~/.omp/agent/auth.json,
39938
39842
  * - reports auth status (for `pan doctor` and the conversation spawn gate),
39939
39843
  * - refreshes an expired-but-recoverable token, and
39940
- * - drives Pi's headless device-code login to re-authenticate.
39844
+ * - drives omp's headless device-code login to re-authenticate.
39941
39845
  *
39942
- * Refresh and login reuse Pi's OWN OAuth implementation, dynamically loaded
39943
- * from the installed `pi` package (the lightweight oauth submodule only — it
39944
- * imports no provider SDKs), so the credential format and OAuth endpoints
39945
- * never drift from whatever the installed Pi expects. Every reuse is wrapped
39946
- * so a missing/relocated Pi degrades gracefully rather than throwing.
39846
+ * The OAuth module is loaded from the installed `omp` package dynamically.
39847
+ * A missing or relocated omp OAuth module degrades to 'unavailable' — never throws.
39947
39848
  */
39948
39849
  const PI_CODEX_PROVIDER = "openai-codex";
39949
- function getPiAuthPath() {
39950
- return join$1(homedir$1(), ".pi", "agent", "auth.json");
39850
+ function getOhmypiAuthPath() {
39851
+ return join$1(homedir$1(), ".omp", "agent", "auth.json");
39951
39852
  }
39952
39853
  function readAuthFile() {
39953
- const path = getPiAuthPath();
39854
+ const path = getOhmypiAuthPath();
39954
39855
  if (!existsSync$1(path)) return {};
39955
39856
  try {
39956
39857
  const parsed = JSON.parse(readFileSync$1(path, "utf-8"));
@@ -39959,14 +39860,14 @@ function readAuthFile() {
39959
39860
  return {};
39960
39861
  }
39961
39862
  }
39962
- function readPiCodexCredential() {
39863
+ function readOhmypiCodexCredential() {
39963
39864
  const cred = readAuthFile()[PI_CODEX_PROVIDER];
39964
39865
  if (cred && typeof cred === "object" && typeof cred["access"] === "string" && typeof cred["refresh"] === "string") return cred;
39965
39866
  return null;
39966
39867
  }
39967
39868
  /** Atomically merge a fresh codex credential into auth.json (mode 0600). */
39968
- function writePiCodexCredential(tokens) {
39969
- const path = getPiAuthPath();
39869
+ function writeOhmypiCodexCredential(tokens) {
39870
+ const path = getOhmypiAuthPath();
39970
39871
  mkdirSync$1(dirname$1(path), { recursive: true });
39971
39872
  const auth = readAuthFile();
39972
39873
  const stored = {
@@ -39982,27 +39883,26 @@ function writePiCodexCredential(tokens) {
39982
39883
  renameSync$1(tmp, path);
39983
39884
  return stored;
39984
39885
  }
39985
- /** Locate the `pi` executable on PATH (no subprocess — event-loop safe). */
39986
- function findPiBinary() {
39886
+ /** Locate the `omp` executable on PATH (no subprocess — event-loop safe). */
39887
+ function findOmpBinary() {
39987
39888
  for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
39988
39889
  if (!dir) continue;
39989
- const candidate = join$1(dir, "pi");
39890
+ const candidate = join$1(dir, "omp");
39990
39891
  if (existsSync$1(candidate)) return candidate;
39991
39892
  }
39992
39893
  return null;
39993
39894
  }
39994
- /** pi-ai exports ESM-only, so the CJS resolver rejects it. */
39995
- const PI_AI_OAUTH_SUBPATH = join$1("@earendil-works", "pi-ai", "dist", "utils", "oauth", "openai-codex.js");
39895
+ /** omp is @oh-my-pi/pi-coding-agent; the oauth submodule path mirrors pi-ai's layout. */
39896
+ const OMP_AI_OAUTH_SUBPATH = join$1("@oh-my-pi", "pi-ai", "dist", "utils", "oauth", "openai-codex.js");
39996
39897
  /**
39997
- * Resolve Pi's openai-codex OAuth submodule by walking node_modules upward
39998
- * from the real pi binary — covering both a nested install (pi-ai under
39999
- * pi-coding-agent/node_modules) and a hoisted one. Avoids createRequire,
40000
- * which can't resolve pi-ai's ESM-only `exports` map.
39898
+ * Resolve omp's openai-codex OAuth submodule by walking node_modules upward
39899
+ * from the real omp binary. Returns null when not found — the caller degrades
39900
+ * gracefully to 'unavailable' status.
40001
39901
  */
40002
- function resolvePiCodexOAuthFile(piBin) {
40003
- let dir = dirname$1(realpathSync$1(piBin));
39902
+ function resolveOmpCodexOAuthFile(ompBin) {
39903
+ let dir = dirname$1(realpathSync$1(ompBin));
40004
39904
  for (let i = 0; i < 10; i++) {
40005
- const candidate = join$1(dir, "node_modules", PI_AI_OAUTH_SUBPATH);
39905
+ const candidate = join$1(dir, "node_modules", OMP_AI_OAUTH_SUBPATH);
40006
39906
  if (existsSync$1(candidate)) return candidate;
40007
39907
  const parent = dirname$1(dir);
40008
39908
  if (parent === dir) break;
@@ -40012,18 +39912,16 @@ function resolvePiCodexOAuthFile(piBin) {
40012
39912
  }
40013
39913
  let cachedModule = null;
40014
39914
  /**
40015
- * Dynamically load Pi's openai-codex OAuth submodule from the installed `pi`
40016
- * package. The submodule imports only local helpers (pkce, device-code
40017
- * polling), never the heavy provider SDKs. Returns null on any failure so
40018
- * callers can degrade gracefully.
39915
+ * Dynamically load omp's openai-codex OAuth submodule from the installed `omp`
39916
+ * package. Returns null on any failure so callers can degrade gracefully.
40019
39917
  */
40020
- async function loadPiCodexOAuth() {
39918
+ async function loadOhmypiCodexOAuth() {
40021
39919
  if (cachedModule) return cachedModule;
40022
39920
  cachedModule = (async () => {
40023
39921
  try {
40024
- const piBin = findPiBinary();
40025
- if (!piBin) return null;
40026
- const oauthFile = resolvePiCodexOAuthFile(piBin);
39922
+ const ompBin = findOmpBinary();
39923
+ if (!ompBin) return null;
39924
+ const oauthFile = resolveOmpCodexOAuthFile(ompBin);
40027
39925
  if (!oauthFile) return null;
40028
39926
  const mod = await import(pathToFileURL(oauthFile).href);
40029
39927
  if (typeof mod.refreshOpenAICodexToken !== "function" || typeof mod.loginOpenAICodexDeviceCode !== "function") return null;
@@ -40034,22 +39932,16 @@ async function loadPiCodexOAuth() {
40034
39932
  })();
40035
39933
  return cachedModule;
40036
39934
  }
40037
- /** Treat a token expiring within this window as already expired. */
40038
39935
  const EXPIRY_MARGIN_MS = 6e4;
40039
- /**
40040
- * Report Pi openai-codex auth status. With `refreshIfExpired`, an expired
40041
- * token triggers one refresh attempt (writing the renewed token back on
40042
- * success) so the caller learns whether the credential is actually dead.
40043
- */
40044
- async function getPiCodexAuthStatus(opts) {
40045
- const cred = readPiCodexCredential();
39936
+ async function getOhmypiCodexAuthStatus(opts) {
39937
+ const cred = readOhmypiCodexCredential();
40046
39938
  if (!cred) return { status: "missing" };
40047
39939
  if (cred.expires > Date.now() + EXPIRY_MARGIN_MS) return {
40048
39940
  status: "ok",
40049
39941
  expiresAt: cred.expires
40050
39942
  };
40051
39943
  if (opts?.refreshIfExpired) {
40052
- const refreshed = await refreshPiCodexAuth();
39944
+ const refreshed = await refreshOhmypiCodexAuth();
40053
39945
  if (refreshed) return {
40054
39946
  status: "ok",
40055
39947
  expiresAt: refreshed.expires
@@ -40066,19 +39958,13 @@ async function getPiCodexAuthStatus(opts) {
40066
39958
  refreshFailed: false
40067
39959
  };
40068
39960
  }
40069
- /**
40070
- * Refresh an expired openai-codex token using its refresh token. Persists and
40071
- * returns the renewed credential on success, or null if there is no refresh
40072
- * token, Pi's OAuth module is unavailable, or the refresh was rejected (the
40073
- * refresh token itself is dead → a full re-login is required).
40074
- */
40075
- async function refreshPiCodexAuth() {
40076
- const cred = readPiCodexCredential();
39961
+ async function refreshOhmypiCodexAuth() {
39962
+ const cred = readOhmypiCodexCredential();
40077
39963
  if (!cred?.refresh) return null;
40078
- const mod = await loadPiCodexOAuth();
39964
+ const mod = await loadOhmypiCodexOAuth();
40079
39965
  if (!mod) return null;
40080
39966
  try {
40081
- return writePiCodexCredential(await mod.refreshOpenAICodexToken(cred.refresh));
39967
+ return writeOhmypiCodexCredential(await mod.refreshOpenAICodexToken(cred.refresh));
40082
39968
  } catch {
40083
39969
  return null;
40084
39970
  }
@@ -40155,7 +40041,7 @@ function isNonEmptyJsonlLine(buffer) {
40155
40041
  }
40156
40042
  //#endregion
40157
40043
  //#region services/conversation-eaten-message-watcher.ts
40158
- init_agents$1();
40044
+ init_agents$2();
40159
40045
  const WATCH_TIMEOUT_MS = 5 * 6e4;
40160
40046
  const WATCH_INTERVAL_MS = 3e3;
40161
40047
  const REDELIVERY_GRACE_MS = 1e4;
@@ -40191,299 +40077,11 @@ async function watchForEatenConversationMessage(args) {
40191
40077
  return "unverified";
40192
40078
  }
40193
40079
  //#endregion
40194
- //#region ../../lib/session-format-converter.ts
40080
+ //#region ../../lib/conversations/transcript-summary.ts
40195
40081
  init_transcript_adapter();
40196
40082
  init_summary_fork();
40197
40083
  init_conversation_compaction();
40198
40084
  /**
40199
- * Session-format converter (P0, 2026-05-14).
40200
- *
40201
- * When a conversation's harness changes (Claude Code <-> Pi Agent), the two
40202
- * runtimes store transcripts in incompatible JSONL formats and in different
40203
- * locations:
40204
- *
40205
- * - Claude Code: ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
40206
- * - Pi Agent: ~/.overdeck/agents/<tmux-session>/sessions/<iso>_<id>.jsonl
40207
- *
40208
- * Historically the orchestrator would silently flip the harness and leave the
40209
- * old transcript orphaned (the DB kept pointing at a session id with no file),
40210
- * and the compaction path would even append Claude-format records into a Pi
40211
- * JSONL. The chosen fix is to *convert* on an explicit harness change rather
40212
- * than guard against it or build a multi-format reader.
40213
- *
40214
- * The conversion is deliberately lossless-of-content but lossy-of-structure:
40215
- * we extract a readable transcript from the source format and seed a fresh
40216
- * session in the target format with that transcript carried in as a single
40217
- * continuation/summary message — the same "faux compaction boundary" mechanism
40218
- * Overdeck already uses for native compaction. Tool-call structure, thinking
40219
- * blocks, and per-message token accounting do not round-trip; the conversation
40220
- * *content* does. This keeps the converter robust as both harness formats
40221
- * continue to evolve.
40222
- */
40223
- init_paths();
40224
- /** Pull plain text out of a Claude/Pi `message.content` (string or block array). */
40225
- function extractContentText(content) {
40226
- if (typeof content === "string") return content;
40227
- if (!Array.isArray(content)) return "";
40228
- const parts = [];
40229
- for (const block of content) {
40230
- if (!block || typeof block !== "object") continue;
40231
- const b = block;
40232
- if (typeof b["text"] === "string") parts.push(b["text"]);
40233
- else if (typeof b["thinking"] === "string") parts.push(`[thinking] ${b["thinking"]}`);
40234
- else if ((b["type"] === "tool_use" || b["type"] === "toolCall") && typeof b["name"] === "string") parts.push(`[tool_use: ${b["name"]}]`);
40235
- else if (b["type"] === "tool_result" || b["type"] === "toolResult") {
40236
- const inner = extractContentText(b["content"]);
40237
- if (inner) parts.push(`[tool_result] ${inner}`);
40238
- }
40239
- }
40240
- return parts.join("\n").trim();
40241
- }
40242
- /** Read a Claude Code JSONL transcript into ordered role/text turns. */
40243
- function extractClaudeTranscript(raw) {
40244
- const turns = [];
40245
- for (const line of raw.split("\n")) {
40246
- const trimmed = line.trim();
40247
- if (!trimmed) continue;
40248
- let entry;
40249
- try {
40250
- entry = JSON.parse(trimmed);
40251
- } catch {
40252
- continue;
40253
- }
40254
- const type = entry["type"];
40255
- if (type !== "user" && type !== "assistant") continue;
40256
- const message = entry["message"];
40257
- if (!message || typeof message !== "object") continue;
40258
- const m = message;
40259
- const role = typeof m["role"] === "string" ? m["role"] : type;
40260
- const text = extractContentText(m["content"]);
40261
- if (text) turns.push({
40262
- role,
40263
- text
40264
- });
40265
- }
40266
- return turns;
40267
- }
40268
- /** Read a Pi Agent JSONL transcript into ordered role/text turns. */
40269
- function extractPiTranscript(raw) {
40270
- const turns = [];
40271
- for (const line of raw.split("\n")) {
40272
- const trimmed = line.trim();
40273
- if (!trimmed) continue;
40274
- let entry;
40275
- try {
40276
- entry = JSON.parse(trimmed);
40277
- } catch {
40278
- continue;
40279
- }
40280
- if (entry["type"] !== "message") continue;
40281
- const message = entry["message"];
40282
- if (!message || typeof message !== "object") continue;
40283
- const m = message;
40284
- const role = typeof m["role"] === "string" ? m["role"] : "assistant";
40285
- const text = extractContentText(m["content"]);
40286
- if (text) turns.push({
40287
- role,
40288
- text
40289
- });
40290
- }
40291
- return turns;
40292
- }
40293
- /**
40294
- * Read a Codex rollout JSONL transcript into ordered role/text turns.
40295
- *
40296
- * Rollout files use a different schema from --json stdout:
40297
- * - task_started: the initial task (user role)
40298
- * - agent_message: a model turn (assistant role)
40299
- * - token_count: metadata — skipped
40300
- *
40301
- * We extract task_started as the user prompt and agent_message events as
40302
- * assistant turns to produce a readable transcript.
40303
- */
40304
- function extractCodexTranscript(raw) {
40305
- const turns = [];
40306
- for (const line of raw.split("\n")) {
40307
- const trimmed = line.trim();
40308
- if (!trimmed) continue;
40309
- let entry;
40310
- try {
40311
- entry = JSON.parse(trimmed);
40312
- } catch {
40313
- continue;
40314
- }
40315
- const type = entry["type"];
40316
- if (type === "task_started") {
40317
- const task = typeof entry["task"] === "string" ? entry["task"] : "";
40318
- if (task) turns.push({
40319
- role: "user",
40320
- text: task
40321
- });
40322
- } else if (type === "agent_message") {
40323
- const content = typeof entry["content"] === "string" ? entry["content"] : extractContentText(entry["content"]);
40324
- if (content) turns.push({
40325
- role: "assistant",
40326
- text: content
40327
- });
40328
- }
40329
- }
40330
- return turns;
40331
- }
40332
- /** Render ordered turns into a single readable transcript block. */
40333
- function renderTranscript(turns) {
40334
- return turns.map((t) => `### ${t.role}\n${t.text}`).join("\n\n");
40335
- }
40336
- function buildContinuation(fromHarness, transcript) {
40337
- return [
40338
- `This session is being continued from a previous conversation that ran under the ${fromHarness} harness.`,
40339
- "The runtime was switched, so the transcript below is carried forward as context rather than as a native session.",
40340
- "Continue from this history without redoing already-completed work.",
40341
- "",
40342
- "--- PRIOR TRANSCRIPT ---",
40343
- transcript || "(the prior transcript was empty)",
40344
- "--- END PRIOR TRANSCRIPT ---"
40345
- ].join("\n");
40346
- }
40347
- /** Short opaque id in Pi's `<8-hex>` style. */
40348
- function shortId() {
40349
- return randomBytes$1(4).toString("hex");
40350
- }
40351
- async function convertConversationTranscriptPromise(opts) {
40352
- if (opts.fromHarness === opts.toHarness) throw new Error(`convertConversationTranscript called with no harness change (${opts.fromHarness})`);
40353
- const raw = await readFile(opts.sourceSessionFile, "utf-8");
40354
- let turns;
40355
- switch (opts.fromHarness) {
40356
- case "pi":
40357
- turns = extractPiTranscript(raw);
40358
- break;
40359
- case "codex":
40360
- turns = extractCodexTranscript(raw);
40361
- break;
40362
- default: turns = extractClaudeTranscript(raw);
40363
- }
40364
- const continuation = buildContinuation(opts.fromHarness, renderTranscript(turns));
40365
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
40366
- if (opts.toHarness === "codex") {
40367
- const threadId = randomUUID$1().replace(/-/g, "").slice(0, 16);
40368
- const codexAgentDir = join$1(homedir$1(), ".overdeck", "agents", opts.tmuxSession, "codex-home");
40369
- const sessionDate = /* @__PURE__ */ new Date();
40370
- const sessionsDir = join$1(codexAgentDir, "sessions", [
40371
- sessionDate.getFullYear().toString(),
40372
- String(sessionDate.getMonth() + 1).padStart(2, "0"),
40373
- String(sessionDate.getDate()).padStart(2, "0")
40374
- ].join("/"));
40375
- await mkdir(sessionsDir, {
40376
- recursive: true,
40377
- mode: 448
40378
- });
40379
- const targetSessionFile = join$1(sessionsDir, `rollout-${randomUUID$1()}-${threadId}.jsonl`);
40380
- await writeFile(targetSessionFile, `${[JSON.stringify({
40381
- type: "task_started",
40382
- model: "codex",
40383
- task: continuation,
40384
- thread_id: threadId,
40385
- timestamp
40386
- })].join("\n")}\n`, "utf-8");
40387
- await mkdir(join$1(homedir$1(), ".overdeck", "agents", opts.tmuxSession), {
40388
- recursive: true,
40389
- mode: 448
40390
- });
40391
- writeThreadId(opts.tmuxSession, threadId);
40392
- return {
40393
- sessionId: threadId,
40394
- targetSessionFile
40395
- };
40396
- }
40397
- if (opts.toHarness === "pi") {
40398
- const sessionId = randomUUID$1();
40399
- const agentDir = join$1(homedir$1(), ".overdeck", "agents", opts.tmuxSession);
40400
- const sessionDir = join$1(agentDir, "sessions");
40401
- await mkdir(sessionDir, {
40402
- recursive: true,
40403
- mode: 448
40404
- });
40405
- const targetSessionFile = join$1(sessionDir, `${timestamp.replace(/[:.]/g, "-")}_${sessionId}.jsonl`);
40406
- await writeFile(targetSessionFile, `${[JSON.stringify({
40407
- type: "session",
40408
- version: 3,
40409
- id: sessionId,
40410
- timestamp,
40411
- cwd: opts.cwd
40412
- }), JSON.stringify({
40413
- type: "message",
40414
- id: shortId(),
40415
- parentId: null,
40416
- timestamp,
40417
- message: {
40418
- role: "user",
40419
- content: [{
40420
- type: "text",
40421
- text: continuation
40422
- }]
40423
- }
40424
- })].join("\n")}\n`, "utf-8");
40425
- await writeFile(join$1(agentDir, "session.id"), `${sessionId}\n`, "utf-8");
40426
- return {
40427
- sessionId,
40428
- targetSessionFile
40429
- };
40430
- }
40431
- const sessionId = randomUUID$1();
40432
- const targetSessionFile = sessionFilePath(opts.cwd, sessionId);
40433
- await mkdir(join$1(targetSessionFile, ".."), { recursive: true });
40434
- const boundaryUuid = randomUUID$1();
40435
- await writeFile(targetSessionFile, `${[JSON.stringify({
40436
- parentUuid: null,
40437
- isSidechain: false,
40438
- type: "system",
40439
- subtype: "compact_boundary",
40440
- content: `Conversation runtime switched from ${opts.fromHarness} to claude-code`,
40441
- isMeta: false,
40442
- timestamp,
40443
- uuid: boundaryUuid,
40444
- sessionId,
40445
- cwd: opts.cwd,
40446
- level: "info",
40447
- compactMetadata: {
40448
- trigger: "overdeck-harness-switch",
40449
- preTokens: 0
40450
- }
40451
- }), JSON.stringify({
40452
- parentUuid: boundaryUuid,
40453
- isSidechain: false,
40454
- type: "user",
40455
- message: {
40456
- role: "user",
40457
- content: continuation
40458
- },
40459
- isVisibleInTranscriptOnly: true,
40460
- isCompactSummary: true,
40461
- uuid: randomUUID$1(),
40462
- sessionId,
40463
- cwd: opts.cwd,
40464
- timestamp
40465
- })].join("\n")}\n`, "utf-8");
40466
- return {
40467
- sessionId,
40468
- targetSessionFile
40469
- };
40470
- }
40471
- /** Tagged error for session-format-converter Effect variants. */
40472
- var SessionConvertError = class extends Data.TaggedError("SessionConvertError") {};
40473
- /** Effect variant of `convertConversationTranscript`. */
40474
- const convertConversationTranscript = (opts) => Effect.tryPromise({
40475
- try: () => convertConversationTranscriptPromise(opts),
40476
- catch: (cause) => new SessionConvertError({
40477
- fromHarness: opts.fromHarness,
40478
- toHarness: opts.toHarness,
40479
- sourceSessionFile: opts.sourceSessionFile,
40480
- message: cause instanceof Error ? cause.message : String(cause),
40481
- cause
40482
- })
40483
- });
40484
- //#endregion
40485
- //#region ../../lib/conversations/transcript-summary.ts
40486
- /**
40487
40085
  * Conversation transcript summarization.
40488
40086
  *
40489
40087
  * Shared helpers for turning a conversation's parsed messages into LLM-generated
@@ -40506,8 +40104,8 @@ const convertConversationTranscript = (opts) => Effect.tryPromise({
40506
40104
  * layering inversion.
40507
40105
  */
40508
40106
  init_child_env();
40509
- init_agents$1();
40510
- init_cost();
40107
+ init_agents$2();
40108
+ init_cost$1();
40511
40109
  /** Haiku — fast/cheap, used for every conversation title and about-summary. */
40512
40110
  const CONVERSATION_TITLE_MODEL = "claude-haiku-4-5-20251001";
40513
40111
  /** Total transcript characters allowed into a single prompt. */
@@ -40553,6 +40151,17 @@ function sanitizeTitle(raw) {
40553
40151
  return raw.trim().split(/\r?\n/)[0]?.trim().replace(/^['"`]+|['"`]+$/g, "").trim().replace(/\s+/g, " ") ?? "";
40554
40152
  }
40555
40153
  /**
40154
+ * Generate a deterministic title from a serialized transcript when the model
40155
+ * path is unavailable. Prefer the latest user request because explicit retitle
40156
+ * should describe where the conversation currently landed.
40157
+ */
40158
+ function fallbackTranscriptTitle(transcript) {
40159
+ const lines = transcript.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("[…"));
40160
+ 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();
40161
+ if (!compact) return "";
40162
+ return sanitizeTitle(compact.split(/\s+/).slice(0, 8).join(" ").replace(/\s+(?:and|or|to|for|with)$/i, "").replace(/[.,:;!?]+$/g, ""));
40163
+ }
40164
+ /**
40556
40165
  * Invoke `claude -p` with a JSON schema and return the structured output.
40557
40166
  * Throws on non-zero exit, timeout, spawn error, or unparseable output.
40558
40167
  */
@@ -40736,7 +40345,7 @@ async function cleanupConversationAttachments(name) {
40736
40345
  * sessions that the lifecycle poller missed. */
40737
40346
  async function cleanupOrphanedConversationAttachments() {
40738
40347
  try {
40739
- const { listConversations } = await import("./conversations-ZEtsnn7V.js");
40348
+ const { listConversations } = await import("./conversations-BC6lrn4U.js");
40740
40349
  const convNames = new Set(listConversations().map((c) => c.name));
40741
40350
  const root = getConversationAttachmentsRoot();
40742
40351
  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, {
@@ -40931,22 +40540,27 @@ async function removeConversationAttachment(name, attachmentPath) {
40931
40540
  *
40932
40541
  * Conversations are NEVER deleted from the database, and JSONL transcript files are never removed.
40933
40542
  */
40543
+ init_http_helpers();
40934
40544
  init_child_env();
40935
40545
  init_claude_permissions();
40546
+ init_jsonl_resolver();
40547
+ init_origin_validation();
40936
40548
  init_projects();
40937
40549
  init_model_capabilities();
40938
40550
  init_conversations();
40939
40551
  init_tmux();
40940
- init_agents$1();
40552
+ init_agents$2();
40941
40553
  init_config_yaml();
40942
40554
  init_harness_policy();
40943
40555
  init_providers();
40556
+ init_agent_input_detection();
40944
40557
  init_launcher_generator();
40945
40558
  init_layers();
40946
40559
  init_briefing_freshness();
40947
40560
  init_conversation_service();
40948
40561
  init_transcript_landing();
40949
40562
  init_pi_conversation_parser();
40563
+ init_ohmypi_conversation_parser();
40950
40564
  init_codex_conversation_parser();
40951
40565
  init_paths();
40952
40566
  init_event_store();
@@ -41055,66 +40669,11 @@ function shellQuote(str) {
41055
40669
  return "'" + str.replace(/'/g, "'\"'\"'") + "'";
41056
40670
  }
41057
40671
  const SAFE_MODEL_PATTERN = /^[a-zA-Z0-9_.:\/-]+$/;
41058
- const DEFAULT_SWITCH_MODEL_CONTEXT_WINDOW = 2e5;
41059
- const SWITCH_MODEL_STATUSLINE_TIMEOUT_MS = 5e3;
41060
- const SWITCH_MODEL_STATUSLINE_POLL_MS = 250;
41061
40672
  const SAFE_EFFORT_PATTERN = /^(low|medium|high)$/;
41062
- function modelCapabilityForModel(model) {
41063
- if (!model) return void 0;
41064
- const resolvedModel = resolveModelIdSync(model);
41065
- return Object.prototype.hasOwnProperty.call(MODEL_CAPABILITIES, resolvedModel) ? MODEL_CAPABILITIES[resolvedModel] : void 0;
41066
- }
41067
- function registryContextWindowForModel(model) {
41068
- return modelCapabilityForModel(model)?.contextWindow ?? DEFAULT_SWITCH_MODEL_CONTEXT_WINDOW;
41069
- }
41070
- function displayNameForModel(model) {
41071
- return modelCapabilityForModel(model)?.displayName ?? model;
41072
- }
41073
- async function isCliproxyRoutedModel(model) {
41074
- if (!model) return false;
41075
- return getProviderForModelSync(model).name === "openai" && await getProviderAuthMode(model) === "subscription";
41076
- }
41077
- function providerRoutingSignature(exports) {
41078
- return exports.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("unset ")).join("\n");
41079
- }
41080
- async function hasProviderRoutingChanged(currentModel, targetModel) {
41081
- if (!targetModel || currentModel === targetModel) return false;
41082
- const targetSignature = providerRoutingSignature(await getProviderExportsForModel(targetModel));
41083
- if (!currentModel) return targetSignature !== "";
41084
- return providerRoutingSignature(await getProviderExportsForModel(currentModel)) !== targetSignature;
41085
- }
41086
- const ANSI_ESCAPE_PATTERN = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
41087
- function normalizePaneLine(line) {
41088
- return line.replace(ANSI_ESCAPE_PATTERN, "").trim().toLowerCase();
41089
- }
41090
- function modelStatuslinePrefix(line) {
41091
- return line.startsWith("statusline:") ? line.slice(11).trimStart() : line;
41092
- }
41093
- function hasStatuslineModelConfirmation(pane, targetModel) {
41094
- const targetDisplayName = displayNameForModel(targetModel).toLowerCase();
41095
- const targetModelId = targetModel.toLowerCase();
41096
- const expectedWithId = `${targetDisplayName} (${targetModelId})`;
41097
- for (const rawLine of pane.split(/\r?\n/)) {
41098
- const line = normalizePaneLine(rawLine);
41099
- if (!line || line.includes("/model")) continue;
41100
- const modelLine = modelStatuslinePrefix(line);
41101
- if (modelLine === targetModelId || modelLine.startsWith(`${targetModelId} `)) return true;
41102
- if (modelLine === expectedWithId || modelLine.startsWith(`${expectedWithId} `)) return true;
41103
- }
41104
- return false;
41105
- }
41106
- async function waitForModelStatusline(tmuxSession, targetModel) {
41107
- const start = Date.now();
41108
- while (Date.now() - start < SWITCH_MODEL_STATUSLINE_TIMEOUT_MS) {
41109
- if (hasStatuslineModelConfirmation(await Effect.runPromise(capturePane(tmuxSession, 10).pipe(Effect.catch(() => Effect.succeed("")))), targetModel)) return true;
41110
- await new Promise((r) => setTimeout(r, SWITCH_MODEL_STATUSLINE_POLL_MS));
41111
- }
41112
- return false;
41113
- }
41114
40673
  const SAFE_ISSUE_ID_PATTERN = /^[A-Z0-9]+-[0-9]+$/;
41115
40674
  async function resolveAllowedHarness(requested, model) {
41116
40675
  if (!model) return "claude-code";
41117
- const explicit = requested === "pi" || requested === "claude-code" || requested === "codex" ? requested : void 0;
40676
+ const explicit = requested === "ohmypi" || requested === "claude-code" || requested === "codex" ? requested : void 0;
41118
40677
  try {
41119
40678
  return await resolveHarness({
41120
40679
  model,
@@ -41178,6 +40737,7 @@ async function getCachedMessages(sessionFile, isSpecialist) {
41178
40737
  if (cached && cached.mtimeMs === fileStats.mtimeMs && cached.size === fileStats.size && cached.byteOffset >= fileStats.size) return cached.result;
41179
40738
  let result;
41180
40739
  if (isCodexSessionFile(sessionFile)) result = await parseCodexConversationMessages(sessionFile);
40740
+ else if (isOhmypiSessionFile(sessionFile)) result = await parseOhmypiConversationMessages(sessionFile);
41181
40741
  else if (isPiSessionFile(sessionFile)) result = await parsePiConversationMessages(sessionFile);
41182
40742
  else if (isSpecialist) result = await parseFromLastCompactBoundary(sessionFile);
41183
40743
  else if (cached && cached.parseState && cached.byteOffset <= fileStats.size && cached.size <= fileStats.size) {
@@ -41332,8 +40892,14 @@ async function waitForPiTuiReady(tmuxSession, timeoutMs = 3e4) {
41332
40892
  return false;
41333
40893
  }
41334
40894
  async function resolveSessionFile(conv) {
41335
- if (conv.harness === "pi") return resolvePiSessionPath(conv.tmuxSession);
41336
- if (conv.harness === "codex") return resolveCodexRolloutPath(conv.tmuxSession);
40895
+ if (conv.harness === "ohmypi") {
40896
+ const piPath = await resolvePiSessionPath(conv.tmuxSession);
40897
+ if (piPath) return piPath;
40898
+ }
40899
+ if (conv.harness === "codex") {
40900
+ const codexPath = await resolveCodexRolloutPath(conv.tmuxSession);
40901
+ if (codexPath) return codexPath;
40902
+ }
41337
40903
  const sessionId = await readLauncherPinnedSessionId(conv.tmuxSession) ?? conv.claudeSessionId;
41338
40904
  if (sessionId) return sessionFilePath(conv.cwd, sessionId);
41339
40905
  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.`);
@@ -41524,7 +41090,7 @@ async function handleConversationMessage(name, body) {
41524
41090
  if (!outboundMessage) return jsonResponse({ error: `${conv.model ?? "This model"} can't read images. Switch to a vision-capable model (e.g. mimo-v2.5) to send images.` }, { status: 422 });
41525
41091
  }
41526
41092
  let deliveredMessage = transformMessageForHarness(outboundMessage, harness, effectiveAttachmentPaths);
41527
- if (harness === "pi") deliveredMessage = await injectPiConversationMemory({
41093
+ if (harness === "ohmypi") deliveredMessage = await injectPiConversationMemory({
41528
41094
  cwd: conv.cwd,
41529
41095
  issueId: conv.issueId,
41530
41096
  conversationName: conv.name
@@ -41585,7 +41151,7 @@ function shouldUseSupervisorForConversation(harness) {
41585
41151
  }
41586
41152
  function resolveConversationDeliveryMethod(conv) {
41587
41153
  const harness = conv.harness ?? "claude-code";
41588
- return conv.deliveryMethod ?? (harness === "pi" ? "tmux" : "auto");
41154
+ return conv.deliveryMethod ?? (harness === "ohmypi" ? "tmux" : "auto");
41589
41155
  }
41590
41156
  /** Synthetic toolUseId prefix marking a Codex pane-detected approval (PAN-1690). */
41591
41157
  const CODEX_APPROVAL_TOOL_PREFIX = "codex-approval:";
@@ -41801,10 +41367,10 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
41801
41367
  if (!runtimeCommand.includes("--permission-mode")) runtimeCommand = `${runtimeCommand} --permission-mode ${mode === "auto" ? "auto" : BYPASS_PERMISSION_MODE}`;
41802
41368
  providerExportsStr = (await getProviderExportsForModel(model)).trim();
41803
41369
  await getProviderEnvForModel(model);
41804
- if (harness === "pi") {
41370
+ if (harness === "ohmypi") {
41805
41371
  if (getProviderForModelSync(model).name === "openai") {
41806
- const auth = await getPiCodexAuthStatus({ refreshIfExpired: true });
41807
- if (auth.status === "missing" || auth.status === "expired") throw new Error("Pi ChatGPT/Codex login (openai-codex) has expired and could not be refreshed. Re-authenticate with `pan pi-auth login`, then retry.");
41372
+ const auth = await getOhmypiCodexAuthStatus({ refreshIfExpired: true });
41373
+ if (auth.status === "missing" || auth.status === "expired") throw new Error("ohmypi ChatGPT/Codex login (openai-codex) has expired and could not be refreshed. Re-authenticate with `pan pi-auth login`, then retry.");
41808
41374
  }
41809
41375
  const paths = piFifoPaths(tmuxSession);
41810
41376
  const piSessionDir = join$1(paths.agentDir, "sessions");
@@ -41818,9 +41384,9 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
41818
41384
  });
41819
41385
  const storedPiSessionId = resume ? await readFile(join$1(paths.agentDir, "session.id"), "utf-8").then((s) => s.trim()).catch(() => void 0) : void 0;
41820
41386
  piFields = {
41821
- harness: "pi",
41387
+ harness: "ohmypi",
41822
41388
  piMode: "tui",
41823
- piExtensionPath: resolve$1(process.cwd(), "packages/pi-extension/dist/index.js"),
41389
+ piExtensionPath: resolveOhmypiExtensionPath() ?? resolve$1(process.cwd(), "packages/ohmypi-extension/dist/index.js"),
41824
41390
  piSessionDir,
41825
41391
  resumeSessionId: storedPiSessionId || void 0
41826
41392
  };
@@ -41830,7 +41396,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
41830
41396
  const codexApprovalPolicy = codexPermMode === "full-access" ? "never" : "on-request";
41831
41397
  const codexSandboxMode = codexPermMode === "full-access" ? "danger-full-access" : codexPermMode === "read-only" ? "read-only" : "workspace-write";
41832
41398
  const codexApprovalsReviewer = codexPermMode === "auto-review" ? "auto_review" : void 0;
41833
- const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-D3QWVTal.js");
41399
+ const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-Bt9WjVrk.js");
41834
41400
  initCodexHome(codexHome, {
41835
41401
  trustedDir: cwd,
41836
41402
  approvalPolicy: codexApprovalPolicy,
@@ -41848,7 +41414,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
41848
41414
  }
41849
41415
  }
41850
41416
  let launcherModel = model;
41851
- if (harness === "pi" && model) {
41417
+ if (harness === "ohmypi" && model) {
41852
41418
  const piProvider = piProviderForModel(model);
41853
41419
  if (piProvider) launcherModel = `${piProvider}/${model}`;
41854
41420
  }
@@ -41900,7 +41466,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
41900
41466
  } catch {}
41901
41467
  console.log(`[claude-invoke] purpose=conversation-session | model=${model || "default"} | source=conversations.ts:spawnConversationSession | session=${tmuxSession} | resume=${resume} | command="${runtimeCommand}"`);
41902
41468
  try {
41903
- const { preTrustDirectory } = await import("./workspace-manager-D6cUCwLt.js");
41469
+ const { preTrustDirectory } = await import("./workspace-manager-C1Ocyoqn.js");
41904
41470
  preTrustDirectory(cwd);
41905
41471
  } catch {}
41906
41472
  try {
@@ -41917,7 +41483,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
41917
41483
  const codexHomeDir = codexFields.codexHome;
41918
41484
  (async () => {
41919
41485
  try {
41920
- const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-D3QWVTal.js");
41486
+ const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-Bt9WjVrk.js");
41921
41487
  const rollout = await waitForCodexRollout(codexHomeDir, 12e4);
41922
41488
  if (rollout) {
41923
41489
  const threadId = extractThreadIdFromRollout(rollout);
@@ -42051,6 +41617,9 @@ function scheduleTitleRefinement(conversationName) {
42051
41617
  /** Conversations with a retitle currently running — guards against double-clicks. */
42052
41618
  const retitleInFlight = /* @__PURE__ */ new Set();
42053
41619
  const EXPLICIT_RETITLE_TIMEOUT_MS = 9e4;
41620
+ function isClaudeInvocationTimeout(error) {
41621
+ return error instanceof Error && /claude invocation timed out after \d+ms/.test(error.message);
41622
+ }
42054
41623
  /** transcript-size-keyed cache so re-opening the About drawer doesn't re-summarize. */
42055
41624
  const aboutSummaryCache = /* @__PURE__ */ new Map();
42056
41625
  const ABOUT_SUMMARY_CACHE_MAX = 100;
@@ -42452,7 +42021,7 @@ const postConversationRoute = HttpRouter.add("POST", "/api/conversations", Effec
42452
42021
  try {
42453
42022
  await spawnConversationSession(tmuxSession, cwd, claudeSessionId, model, effort, issueId, false, harness);
42454
42023
  console.log(`[conversations] tmux session ${tmuxSession} spawned, sessionId: ${claudeSessionId}`);
42455
- if (harness === "pi") await waitForPiTuiReady(tmuxSession);
42024
+ if (harness === "ohmypi") await waitForPiTuiReady(tmuxSession);
42456
42025
  else if (harness !== "codex") {
42457
42026
  await waitForClaudeReady(tmuxSession);
42458
42027
  console.log(`[conversations] Claude ready in ${tmuxSession}`);
@@ -42461,6 +42030,7 @@ const postConversationRoute = HttpRouter.add("POST", "/api/conversations", Effec
42461
42030
  } catch (spawnErr) {
42462
42031
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
42463
42032
  console.error(`[conversations] background spawn failed for ${tmuxSession}: ${msg}`);
42033
+ updateSpawnError(name, msg);
42464
42034
  getEventStore().emitOnly({
42465
42035
  type: "conversation.created",
42466
42036
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -42546,7 +42116,7 @@ const postConversationResumeRoute = HttpRouter.add("POST", "/api/conversations/:
42546
42116
  try {
42547
42117
  await spawnConversationSession(conv.tmuxSession, conv.cwd, oldSessionId ?? randomUUID$1(), model, effort, conv.issueId ?? void 0, canResume, harness);
42548
42118
  await waitForTmuxSession(conv.tmuxSession);
42549
- if (harness === "pi") await waitForPiTuiReady(conv.tmuxSession);
42119
+ if (harness === "ohmypi") await waitForPiTuiReady(conv.tmuxSession);
42550
42120
  else if (harness !== "codex") await waitForReadySignal(conv.tmuxSession, 30);
42551
42121
  markConversationActive(name);
42552
42122
  return jsonResponse({
@@ -42576,11 +42146,12 @@ const postConversationSwitchModelRoute = HttpRouter.add("POST", "/api/conversati
42576
42146
  try {
42577
42147
  const conv = getConversationByName(name);
42578
42148
  if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
42149
+ if (conv.claudeSessionId) return jsonResponse({ error: "Conversation model is locked once a conversation has started" }, { status: 409 });
42579
42150
  const model = typeof body["model"] === "string" && body["model"].trim() ? body["model"].trim() : conv.model ?? void 0;
42580
42151
  const currentHarness = conv.harness ?? "claude-code";
42581
42152
  const requestedHarness = body["harness"];
42582
42153
  let harness = currentHarness;
42583
- if (requestedHarness === "pi" || requestedHarness === "claude-code" || requestedHarness === "codex") {
42154
+ if (requestedHarness === "ohmypi" || requestedHarness === "claude-code" || requestedHarness === "codex") {
42584
42155
  if (requestedHarness !== currentHarness) {
42585
42156
  const policyModel = model ?? conv.model ?? "";
42586
42157
  const decision = canUseHarnessSync(requestedHarness, policyModel, await getProviderAuthMode(policyModel));
@@ -42591,101 +42162,15 @@ const postConversationSwitchModelRoute = HttpRouter.add("POST", "/api/conversati
42591
42162
  const harnessChanged = harness !== currentHarness;
42592
42163
  if (!await validateCwdContainment(conv.cwd)) return jsonResponse({ error: "Invalid cwd" }, { status: 400 });
42593
42164
  if (model && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
42594
- const oldSessionId = conv.claudeSessionId;
42595
- const sessionFile = await resolveSessionFile(conv);
42596
- const cwd = conv.cwd;
42597
- const tmuxSession = conv.tmuxSession;
42598
- const effort = conv.effort ?? void 0;
42599
- const issueId = conv.issueId ?? void 0;
42600
- const targetModel = model ?? conv.model ?? void 0;
42601
- const providerRoutingChanged = await hasProviderRoutingChanged(conv.model, targetModel);
42602
- const contextUsage = sessionFile && existsSync$1(sessionFile) ? await computeContextUsage(sessionFile, conv.model ?? targetModel ?? null) : null;
42603
- const contextTokens = contextUsage?.estimatedTokens ?? 0;
42604
- const currentObservedCeiling = Math.max(contextUsage?.maxObservedInputTokens ?? 0, contextTokens);
42605
- const switchStrategy = decideSwitchStrategy({
42606
- harnessChanged,
42607
- providerRoutingChanged,
42608
- contextTokens,
42609
- effectiveTargetWindow: getEffectiveTargetWindow({
42610
- registryWindow: registryContextWindowForModel(targetModel),
42611
- currentObservedCeiling,
42612
- cliproxyRouted: await isCliproxyRoutedModel(targetModel),
42613
- providerRoutingChanged
42614
- })
42165
+ if (model) setConversationModel(name, model);
42166
+ if (harnessChanged) setConversationHarness(name, harness);
42167
+ const updated = getConversationByName(name) ?? conv;
42168
+ return jsonResponse({
42169
+ ...updated,
42170
+ model: model ?? updated.model,
42171
+ harness,
42172
+ sessionAlive: false
42615
42173
  });
42616
- if (harness === "claude-code" && switchStrategy.useModelCommand && targetModel) {
42617
- if (model) setConversationModel(name, model);
42618
- try {
42619
- await deliverAgentMessage(tmuxSession, `/model ${targetModel}`, "conversation-switch-model", resolveConversationDeliveryMethod(conv));
42620
- if (await waitForModelStatusline(tmuxSession, targetModel)) {
42621
- markConversationActive(name);
42622
- return jsonResponse({
42623
- ...conv,
42624
- status: "active",
42625
- model: targetModel,
42626
- harness,
42627
- reattached: false,
42628
- sessionAlive: true
42629
- });
42630
- }
42631
- console.warn(`[conversations] switch-model Tier 1 statusline verification timed out for ${name}; falling back to respawn`);
42632
- } catch (deliveryErr) {
42633
- const msg = deliveryErr instanceof Error ? deliveryErr.message : String(deliveryErr);
42634
- console.warn(`[conversations] switch-model Tier 1 delivery failed for ${name}; falling back to respawn: ${msg}`);
42635
- }
42636
- }
42637
- const respawn = markRespawnPending(tmuxSession);
42638
- try {
42639
- await Effect.runPromise(killSession(tmuxSession).pipe(Effect.catch(() => Effect.succeed(void 0))));
42640
- if (model) setConversationModel(name, model);
42641
- if (harnessChanged) setConversationHarness(name, harness);
42642
- let resumeSessionId = oldSessionId ?? randomUUID$1();
42643
- let canResume = !!oldSessionId && !!sessionFile && existsSync$1(sessionFile);
42644
- if (oldSessionId && !canResume) console.error(`[conversations] SESSION-LOST ${name} harness=${currentHarness} claudeSessionId=${oldSessionId} resolved=${sessionFile ?? "null"} — switch-model will start a fresh session`);
42645
- if (harnessChanged) if (canResume && sessionFile) try {
42646
- const result = await Effect.runPromise(convertConversationTranscript({
42647
- fromHarness: currentHarness,
42648
- toHarness: harness,
42649
- sourceSessionFile: sessionFile,
42650
- cwd,
42651
- tmuxSession
42652
- }));
42653
- resumeSessionId = result.sessionId;
42654
- canResume = true;
42655
- if (harness === "claude-code") setConversationClaudeSessionId(name, result.sessionId);
42656
- } catch (convErr) {
42657
- const cm = convErr instanceof Error ? convErr.message : String(convErr);
42658
- console.error(`[conversations] SESSION-CONVERT-FAILED ${name} ${currentHarness}->${harness}: ${cm}`);
42659
- canResume = false;
42660
- }
42661
- else canResume = false;
42662
- else if (harness === "claude-code") {
42663
- const compactionFork = await maybeCompactBeforeRespawn({
42664
- sessionFile,
42665
- cwd,
42666
- shouldCompact: switchStrategy.compact
42667
- });
42668
- if (compactionFork) {
42669
- resumeSessionId = compactionFork.forkedSessionId;
42670
- setConversationClaudeSessionId(name, compactionFork.forkedSessionId);
42671
- }
42672
- }
42673
- await spawnConversationSession(tmuxSession, cwd, resumeSessionId, model, effort, issueId, canResume, harness);
42674
- await waitForTmuxSession(tmuxSession);
42675
- if (harness === "pi") await waitForPiTuiReady(tmuxSession);
42676
- else if (harness !== "codex") await waitForReadySignal(tmuxSession, 30);
42677
- markConversationActive(name);
42678
- return jsonResponse({
42679
- ...conv,
42680
- status: "active",
42681
- model,
42682
- harness,
42683
- reattached: false,
42684
- sessionAlive: true
42685
- });
42686
- } finally {
42687
- respawn.done();
42688
- }
42689
42174
  } catch (error) {
42690
42175
  const msg = error instanceof Error ? error.message : String(error);
42691
42176
  console.error("[conversations] switch model failed:", msg);
@@ -42735,7 +42220,7 @@ const getConversationMessagesRoute = HttpRouter.add("GET", "/api/conversations/:
42735
42220
  sessionFile = rollout;
42736
42221
  setSpecialistSessionCache(name, rollout);
42737
42222
  }
42738
- } else if (agentHarness === "pi") {
42223
+ } else if (agentHarness === "ohmypi") {
42739
42224
  const piSession = await resolvePiSessionPath(name);
42740
42225
  if (piSession) {
42741
42226
  sessionFile = piSession;
@@ -43238,7 +42723,7 @@ async function ensureForkSessionReady(conv, sessionId, resume, plainFork = false
43238
42723
  async function injectForkSummary(conv, summary, caller) {
43239
42724
  updateForkStatus(conv.name, "injecting");
43240
42725
  const method = resolveConversationDeliveryMethod(conv);
43241
- if (conv.harness === "pi") {
42726
+ if (conv.harness === "ohmypi") {
43242
42727
  await waitForPiTuiReady(conv.tmuxSession, 6e4);
43243
42728
  await deliverAgentMessage(conv.tmuxSession, summary, caller, method);
43244
42729
  return;
@@ -43268,7 +42753,7 @@ async function runForkPipeline(convName, parentConv, sessionId, summaryModel, fo
43268
42753
  const parentSessionFile = await resolveForkSourceSessionFile(parentConv);
43269
42754
  if (!parentSessionFile) throw new Error(`Parent has no session file`);
43270
42755
  if (forkMode === "plain") {
43271
- if (conv.harness === "pi" || conv.harness === "codex") throw new Error(`Plain forks cannot launch under the ${conv.harness} harness — it cannot consume Claude session history.`);
42756
+ if (conv.harness === "ohmypi" || conv.harness === "codex") throw new Error(`Plain forks cannot launch under the ${conv.harness} harness — it cannot consume Claude session history.`);
43272
42757
  if (!(await forkSessionExists(conv.tmuxSession) && await forkHarnessProcessAlive(conv.tmuxSession))) {
43273
42758
  const forkSessionFile = await resolveSessionFile(conv);
43274
42759
  if (!forkSessionFile) throw new Error(`Fork conversation ${convName} has no session file`);
@@ -43485,7 +42970,7 @@ const postConversationSummaryForkRoute = HttpRouter.add("POST", "/api/conversati
43485
42970
  const launchHarness = await resolveAllowedHarness(body["harness"], launchModel);
43486
42971
  const summaryHarness = await resolveAllowedHarness(body["summaryHarness"], effectiveSummaryModel);
43487
42972
  const handoffAuthorHarness = body["handoffAuthorHarness"] !== void 0 ? await resolveAllowedHarness(body["handoffAuthorHarness"], handoffAuthorModel || effectiveSummaryModel) : void 0;
43488
- if (forkMode === "plain" && (launchHarness === "pi" || launchHarness === "codex")) return jsonResponse({ error: `Plain forks cannot launch under ${launchHarness} — it cannot consume Claude session history. Use a summary fork instead.` }, { status: 400 });
42973
+ if (forkMode === "plain" && (launchHarness === "ohmypi" || launchHarness === "codex")) return jsonResponse({ error: `Plain forks cannot launch under ${launchHarness} — it cannot consume Claude session history. Use a summary fork instead.` }, { status: 400 });
43489
42974
  const defaultTitle = forkMode === "plain" ? `Fork: ${conv.title || conv.name}` : forkMode === "handoff" ? handoffTitleFromFocus(handoffFocus, conv.title || conv.name) : `Summary Fork: ${conv.title || conv.name}`;
43490
42975
  const newConv = createConversation({
43491
42976
  name: newName,
@@ -43818,7 +43303,15 @@ const postConversationRetitleRoute = HttpRouter.add("POST", "/api/conversations/
43818
43303
  try {
43819
43304
  const model = configuredTitleModel();
43820
43305
  console.log(`[claude-invoke] purpose=conversation-retitle | model=${model} | conversation=${name} | transcriptChars=${transcript.length}`);
43821
- const title = await summarizeTranscriptTitle(transcript, model, EXPLICIT_RETITLE_TIMEOUT_MS);
43306
+ let title;
43307
+ try {
43308
+ title = await summarizeTranscriptTitle(transcript, model, EXPLICIT_RETITLE_TIMEOUT_MS);
43309
+ } catch (error) {
43310
+ if (!isClaudeInvocationTimeout(error)) throw error;
43311
+ title = fallbackTranscriptTitle(transcript);
43312
+ if (!title) throw error;
43313
+ console.warn(`[conversations] retitle timed out for "${name}"; using deterministic fallback title "${title}"`);
43314
+ }
43822
43315
  if (!title) return jsonResponse({ error: "Title model returned an empty result" }, { status: 502 });
43823
43316
  updateConversationTitle(name, title, "ai");
43824
43317
  console.log(`[claude-invoke] SUCCESS purpose=conversation-retitle | conversation=${name} | title="${title}"`);
@@ -43914,6 +43407,8 @@ const conversationsRouteLayer = Layer.mergeAll(getConversationsRoute, getConvers
43914
43407
  * contract. Internal events are not exposed on this route.
43915
43408
  */
43916
43409
  init_event_store();
43410
+ init_http_helpers();
43411
+ init_http_handler();
43917
43412
  const PUBLIC_CATALOG = [
43918
43413
  "activity.entry",
43919
43414
  "activity.detailed",
@@ -44070,6 +43565,8 @@ const eventsRouteLayer = Layer.mergeAll(getEventStreamRoute, getEventVersionRout
44070
43565
  * GET /api/show/:issueId/shadow — shadow state details
44071
43566
  * GET /api/show/:issueId/health — health + heartbeat only
44072
43567
  */
43568
+ init_http_helpers();
43569
+ init_http_handler();
44073
43570
  init_shadow_state();
44074
43571
  const getShowRoute = HttpRouter.add("GET", "/api/show/:issueId", httpHandler(Effect.gen(function* () {
44075
43572
  const issueId = (yield* HttpRouter.params)["issueId"] ?? "";
@@ -44168,7 +43665,7 @@ async function registerProjectFromPath(opts) {
44168
43665
  registerProjectSync(key, projectConfig);
44169
43666
  const seededContextLayer = ensureProjectLayer(fullPath);
44170
43667
  try {
44171
- const { preTrustDirectorySync } = await import("./workspace-manager-D6cUCwLt.js");
43668
+ const { preTrustDirectorySync } = await import("./workspace-manager-C1Ocyoqn.js");
44172
43669
  preTrustDirectorySync(fullPath);
44173
43670
  } catch {}
44174
43671
  let hooksInstalled = 0;
@@ -44183,7 +43680,6 @@ async function registerProjectFromPath(opts) {
44183
43680
  }
44184
43681
  //#endregion
44185
43682
  //#region ../../lib/overdeck/specs.ts
44186
- init_pan_dir();
44187
43683
  init_harness();
44188
43684
  init_render();
44189
43685
  const findSpecByIssueThroughOverdeck = findSpecByIssue;
@@ -44195,14 +43691,22 @@ const findSpecByIssueThroughOverdeck = findSpecByIssue;
44195
43691
  * Implements:
44196
43692
  * GET /api/projects/:projectKey/session-tree
44197
43693
  */
43694
+ init_http_helpers();
43695
+ init_http_handler();
44198
43696
  init_projects();
43697
+ init_dashboard_auth();
44199
43698
  init_issue_id();
44200
43699
  init_tmux();
44201
- init_agents$1();
43700
+ init_concurrency$1();
43701
+ init_read_model();
43702
+ init_agents$2();
43703
+ init_agent_input_detection();
43704
+ init_review_status$1();
43705
+ init_jsonl_resolver();
44202
43706
  init_paths();
44203
43707
  const execAsync$2 = promisify(exec);
44204
43708
  async function getIssueDataService() {
44205
- const { getSharedIssueService } = await import("./issue-service-singleton-HZ65Do8r.js");
43709
+ const { getSharedIssueService } = await import("./issue-service-singleton-p8pVr1YM.js");
44206
43710
  return getSharedIssueService();
44207
43711
  }
44208
43712
  /** Returns true if the path exists (any type). */
@@ -44715,12 +44219,16 @@ const postProjectsRoute = HttpRouter.add("POST", "/api/projects", httpHandler(Ef
44715
44219
  const projectsRouteLayer = Layer.mergeAll(getProjectSessionTreeRoute, getAllSessionTreesRoute, getProjectAutoMergeDefaultRoute, postProjectAutoMergeDefaultRoute, postProjectsRoute);
44716
44220
  //#endregion
44717
44221
  //#region routes/context.ts
44222
+ init_src();
44718
44223
  init_layers();
44719
44224
  init_paths();
44720
44225
  init_projects();
44226
+ init_http_helpers();
44227
+ init_domain_services();
44228
+ init_http_handler();
44721
44229
  const PREVIEW_HARNESSES = [
44722
44230
  "claude-code",
44723
- "pi",
44231
+ "ohmypi",
44724
44232
  "codex"
44725
44233
  ];
44726
44234
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
@@ -44979,9 +44487,9 @@ function fullPromptPreview(previews) {
44979
44487
  "",
44980
44488
  previews["claude-code"] || "(no rendered context)",
44981
44489
  "",
44982
- "## Overdeck-controlled Pi bundle",
44490
+ "## Overdeck-controlled oh-my-pi bundle",
44983
44491
  "",
44984
- previews.pi || "(no rendered context)",
44492
+ previews.ohmypi || "(no rendered context)",
44985
44493
  "",
44986
44494
  "## Overdeck-controlled Codex bundle",
44987
44495
  "",
@@ -45022,7 +44530,7 @@ async function previewContextLayers(projects, requestOrSelectedLayer, maybeDraft
45022
44530
  operation: "preview",
45023
44531
  previews: {
45024
44532
  "claude-code": previews["claude-code"],
45025
- pi: previews.pi,
44533
+ ohmypi: previews.ohmypi,
45026
44534
  codex: previews.codex,
45027
44535
  fullPrompt: fullPromptPreview(previews)
45028
44536
  },
@@ -45155,6 +44663,8 @@ const contextRouteLayer = Layer.mergeAll(getContextLayersRoute, postContextPrevi
45155
44663
  * Implements /api/admin/* endpoints mirroring the `pan admin` CLI namespace:
45156
44664
  * GET /api/admin/tldr/:issueId — TLDR daemon status for a workspace
45157
44665
  */
44666
+ init_http_helpers();
44667
+ init_http_handler();
45158
44668
  init_tldr_daemon();
45159
44669
  init_projects();
45160
44670
  const getAdminTldrRoute = HttpRouter.add("GET", "/api/admin/tldr/:issueId", httpHandler(Effect.gen(function* () {
@@ -45335,6 +44845,8 @@ async function installOx() {
45335
44845
  * GET /api/prereqs/:feature — check which tools are missing for a feature
45336
44846
  * POST /api/prereqs/install — install a specific tool by name
45337
44847
  */
44848
+ init_http_helpers();
44849
+ init_http_handler();
45338
44850
  const readJsonBody$3 = Effect.gen(function* () {
45339
44851
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
45340
44852
  try {
@@ -45381,6 +44893,8 @@ const installToolRoute = HttpRouter.add("POST", "/api/prereqs/install", httpHand
45381
44893
  const prereqsRouteLayer = Layer.mergeAll(listPrereqsRoute, checkFeatureRoute, installToolRoute);
45382
44894
  //#endregion
45383
44895
  //#region routes/cliproxy.ts
44896
+ init_http_helpers();
44897
+ init_http_handler();
45384
44898
  init_cliproxy();
45385
44899
  let lastStatus = null;
45386
44900
  async function refreshStatus() {
@@ -45452,6 +44966,10 @@ const postCliproxyRestartRoute = HttpRouter.add("POST", "/api/cliproxy/restart",
45452
44966
  });
45453
44967
  })));
45454
44968
  const cliproxyRouteLayer = Layer.mergeAll(getCliproxyStatusRoute, postCliproxyRestartRoute);
44969
+ //#endregion
44970
+ //#region routes/tts.ts
44971
+ init_origin_validation();
44972
+ init_http_helpers();
45455
44973
  const TTS_ROUTE_BODY_MAX_BYTES = 64 * 1024;
45456
44974
  async function checkTtsHealth(options = {}) {
45457
44975
  const runtimeConfig = getTtsRuntimeConfig();
@@ -45780,7 +45298,7 @@ const ttsRouteLayer = Layer.mergeAll(getTtsHealthRoute, postTtsStartRoute, getTt
45780
45298
  * - Diff's the PR's failing check names against main's current failing set so
45781
45299
  * inherited main-red failures are labelled as such.
45782
45300
  */
45783
- init_agents$1();
45301
+ init_agents$2();
45784
45302
  init_projects();
45785
45303
  init_feedback_writer();
45786
45304
  function execFilePromise(file, args, options) {
@@ -46090,6 +45608,7 @@ async function refreshMergeStateFromGitHub(issueId, repo, prNumber) {
46090
45608
  encoding: "utf-8",
46091
45609
  timeout: 15e3
46092
45610
  });
45611
+ if (!stdout.trim()) return;
46093
45612
  const pr = JSON.parse(stdout);
46094
45613
  const status = await Effect.runPromise(getReviewStatus(issueId));
46095
45614
  if (!status) return;
@@ -46203,8 +45722,8 @@ async function handlePullRequestPromise(payload) {
46203
45722
  if (!issueId) return;
46204
45723
  const repo = payload.repository.full_name;
46205
45724
  if (payload.action === "closed" && pr.merged === true) try {
46206
- const { postMergeLifecycle } = await import("./merge-agent-Bn54C3oA.js");
46207
- const { resolveProjectFromIssueSync } = await import("./projects-BBiAZIGj.js");
45725
+ const { postMergeLifecycle } = await import("./merge-agent-Dhw22tcB.js");
45726
+ const { resolveProjectFromIssueSync } = await import("./projects-LkdoL5F9.js");
46208
45727
  const project = resolveProjectFromIssueSync(issueId);
46209
45728
  if (project) {
46210
45729
  const branchName = pr.head.ref;
@@ -46472,6 +45991,8 @@ async function reconcileStaleGitHubBlockers() {
46472
45991
  * Verifies X-Hub-Signature-256 HMAC-SHA256 signature.
46473
45992
  * Dispatches to per-event-type handlers.
46474
45993
  */
45994
+ init_http_helpers();
45995
+ init_http_handler();
46475
45996
  const WEBHOOK_SECRET_PATH = join$1(homedir$1(), ".overdeck", "github-app", "webhook-secret");
46476
45997
  let _cachedWebhookSecret = void 0;
46477
45998
  let _secretLoadPromise = null;
@@ -46584,6 +46105,7 @@ const webhooksRouteLayer = HttpRouter.add("POST", "/api/webhooks/github", httpHa
46584
46105
  //#region ../../lib/compliance/advisory-warning.ts
46585
46106
  init_config_yaml();
46586
46107
  init_paths$2();
46108
+ init_rollup();
46587
46109
  const COMPLIANCE_ADVISORY_WARNING = "Last turn included a memory-first trigger but the search wasn't called. Try pan memory search first next time.";
46588
46110
  async function resolveComplianceAdvisoryWarning(input) {
46589
46111
  if (await (input.loadComplianceMode ?? loadComplianceMode)() === "off") return null;
@@ -46625,8 +46147,9 @@ function isCurrentSessionMiss(observation, identity) {
46625
46147
  //#endregion
46626
46148
  //#region ../../lib/memory/checkpoint-client.ts
46627
46149
  init_settings();
46628
- init_health();
46150
+ init_health$1();
46629
46151
  init_providers$1();
46152
+ init_pending();
46630
46153
  init_query_expansion();
46631
46154
  init_injection();
46632
46155
  let worker = null;
@@ -46661,7 +46184,7 @@ function getCheckpointWorker() {
46661
46184
  return worker;
46662
46185
  }
46663
46186
  async function runInline(operation, payload) {
46664
- const { claimTranscriptRange, commitTranscriptRange, releaseTranscriptRange, getTranscriptCheckpoint, listTranscriptCheckpoints } = await import("./checkpoints-BnDRdb3A.js");
46187
+ const { claimTranscriptRange, commitTranscriptRange, releaseTranscriptRange, getTranscriptCheckpoint, listTranscriptCheckpoints } = await import("./checkpoints-xdJ_1zqi.js");
46665
46188
  switch (operation) {
46666
46189
  case "claimTranscriptRange": return claimTranscriptRange(payload);
46667
46190
  case "commitTranscriptRange": return commitTranscriptRange(payload);
@@ -46711,6 +46234,92 @@ const getTranscriptCheckpoint = (sessionId) => Effect.tryPromise({
46711
46234
  catch: toError
46712
46235
  });
46713
46236
  //#endregion
46237
+ //#region ../../lib/session-format-converter.ts
46238
+ init_paths();
46239
+ /** Pull plain text out of a Claude/Pi `message.content` (string or block array). */
46240
+ function extractContentText(content) {
46241
+ if (typeof content === "string") return content;
46242
+ if (!Array.isArray(content)) return "";
46243
+ const parts = [];
46244
+ for (const block of content) {
46245
+ if (!block || typeof block !== "object") continue;
46246
+ const b = block;
46247
+ if (typeof b["text"] === "string") parts.push(b["text"]);
46248
+ else if (typeof b["thinking"] === "string") parts.push(`[thinking] ${b["thinking"]}`);
46249
+ else if ((b["type"] === "tool_use" || b["type"] === "toolCall") && typeof b["name"] === "string") parts.push(`[tool_use: ${b["name"]}]`);
46250
+ else if (b["type"] === "tool_result" || b["type"] === "toolResult") {
46251
+ const inner = extractContentText(b["content"]);
46252
+ if (inner) parts.push(`[tool_result] ${inner}`);
46253
+ }
46254
+ }
46255
+ return parts.join("\n").trim();
46256
+ }
46257
+ /** Read a Pi Agent JSONL transcript into ordered role/text turns. */
46258
+ function extractPiTranscript(raw) {
46259
+ const turns = [];
46260
+ for (const line of raw.split("\n")) {
46261
+ const trimmed = line.trim();
46262
+ if (!trimmed) continue;
46263
+ let entry;
46264
+ try {
46265
+ entry = JSON.parse(trimmed);
46266
+ } catch {
46267
+ continue;
46268
+ }
46269
+ if (entry["type"] !== "message") continue;
46270
+ const message = entry["message"];
46271
+ if (!message || typeof message !== "object") continue;
46272
+ const m = message;
46273
+ const role = typeof m["role"] === "string" ? m["role"] : "assistant";
46274
+ const text = extractContentText(m["content"]);
46275
+ if (text) turns.push({
46276
+ role,
46277
+ text
46278
+ });
46279
+ }
46280
+ return turns;
46281
+ }
46282
+ /**
46283
+ * Read a Codex rollout JSONL transcript into ordered role/text turns.
46284
+ *
46285
+ * Rollout files use a different schema from --json stdout:
46286
+ * - task_started: the initial task (user role)
46287
+ * - agent_message: a model turn (assistant role)
46288
+ * - token_count: metadata — skipped
46289
+ *
46290
+ * We extract task_started as the user prompt and agent_message events as
46291
+ * assistant turns to produce a readable transcript.
46292
+ */
46293
+ function extractCodexTranscript(raw) {
46294
+ const turns = [];
46295
+ for (const line of raw.split("\n")) {
46296
+ const trimmed = line.trim();
46297
+ if (!trimmed) continue;
46298
+ let entry;
46299
+ try {
46300
+ entry = JSON.parse(trimmed);
46301
+ } catch {
46302
+ continue;
46303
+ }
46304
+ const type = entry["type"];
46305
+ if (type === "task_started") {
46306
+ const task = typeof entry["task"] === "string" ? entry["task"] : "";
46307
+ if (task) turns.push({
46308
+ role: "user",
46309
+ text: task
46310
+ });
46311
+ } else if (type === "agent_message") {
46312
+ const content = typeof entry["content"] === "string" ? entry["content"] : extractContentText(entry["content"]);
46313
+ if (content) turns.push({
46314
+ role: "assistant",
46315
+ text: content
46316
+ });
46317
+ }
46318
+ }
46319
+ return turns;
46320
+ }
46321
+ Data.TaggedError("SessionConvertError");
46322
+ //#endregion
46714
46323
  //#region ../../lib/memory/compress.ts
46715
46324
  const MAX_USER_CHARS = 4e4;
46716
46325
  const MAX_TRANSCRIPT_DELTA_BYTES = 1024 * 1024;
@@ -46830,7 +46439,7 @@ function isRecord$1(value) {
46830
46439
  }
46831
46440
  //#endregion
46832
46441
  //#region ../../lib/memory/transcript-source.ts
46833
- init_agents$1();
46442
+ init_agents$2();
46834
46443
  init_paths();
46835
46444
  init_codex();
46836
46445
  var ClaudeCodeTranscriptSource = class {
@@ -46883,7 +46492,7 @@ var ClaudeCodeTranscriptSource = class {
46883
46492
  }
46884
46493
  };
46885
46494
  var PiTranscriptSource = class {
46886
- harness = "pi";
46495
+ harness = "ohmypi";
46887
46496
  listAgents;
46888
46497
  readSessionId;
46889
46498
  resolveTranscriptPath;
@@ -46896,7 +46505,7 @@ var PiTranscriptSource = class {
46896
46505
  }
46897
46506
  async getActiveTranscripts() {
46898
46507
  const agents = await this.listAgents();
46899
- return (await Promise.all(agents.filter((agent) => agent.tmuxActive && agent.status === "running" && agent.role === "work" && agent.harness === "pi").map((agent) => this.resolveAgentTranscript(agent)))).filter((entry) => entry !== null);
46508
+ return (await Promise.all(agents.filter((agent) => agent.tmuxActive && agent.status === "running" && agent.role === "work" && agent.harness === "ohmypi").map((agent) => this.resolveAgentTranscript(agent)))).filter((entry) => entry !== null);
46900
46509
  }
46901
46510
  parseDelta(buffer, fromOffset = 0) {
46902
46511
  const text = Buffer.isBuffer(buffer) ? buffer.toString("utf8") : buffer;
@@ -46927,7 +46536,7 @@ var PiTranscriptSource = class {
46927
46536
  sessionId,
46928
46537
  transcriptPath,
46929
46538
  identity: buildMemoryIdentity(agent, sessionId),
46930
- harness: "pi",
46539
+ harness: "ohmypi",
46931
46540
  size: fileStat.size,
46932
46541
  mtimeMs: fileStat.mtimeMs
46933
46542
  };
@@ -47064,6 +46673,7 @@ async function readCodexThreadId(agent) {
47064
46673
  }
47065
46674
  //#endregion
47066
46675
  //#region ../../lib/memory/extract.ts
46676
+ init_src();
47067
46677
  const MEMORY_DOMAIN_TAGS = [
47068
46678
  "review-blocker",
47069
46679
  "test-failure",
@@ -47377,7 +46987,7 @@ function isRecord(value) {
47377
46987
  }
47378
46988
  //#endregion
47379
46989
  //#region ../../lib/memory/pipeline.ts
47380
- init_health();
46990
+ init_health$1();
47381
46991
  async function extractFromTranscriptDelta(input) {
47382
46992
  if (isSubagentHookPayload(input.hookPayload)) return {
47383
46993
  status: "noop",
@@ -47728,7 +47338,7 @@ function buildPendingTurn(input, fromOffset, toOffset, compressed) {
47728
47338
  };
47729
47339
  }
47730
47340
  async function emitMemoryObservationCreated(observation, timestamp) {
47731
- const { initEventStore } = await import("./event-store-f-xTPhth.js");
47341
+ const { initEventStore } = await import("./event-store-CltcAmHd.js");
47732
47342
  await (await initEventStore()).appendAsync({
47733
47343
  type: "memory.observation_created",
47734
47344
  timestamp,
@@ -47737,7 +47347,7 @@ async function emitMemoryObservationCreated(observation, timestamp) {
47737
47347
  }
47738
47348
  //#endregion
47739
47349
  //#region ../../lib/memory/worker-pool.ts
47740
- init_health();
47350
+ init_health$1();
47741
47351
  init_providers$1();
47742
47352
  function normalizeQueueLimit(value) {
47743
47353
  return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : 500;
@@ -48231,10 +47841,14 @@ function countCompleteLines(text) {
48231
47841
  * `hook_event_name`. We look up the conversation by claude_session_id and
48232
47842
  * emit an in-memory-only event via emitOnly().
48233
47843
  */
47844
+ init_http_helpers();
47845
+ init_http_handler();
48234
47846
  init_event_store();
48235
- init_agents$1();
47847
+ init_agents$2();
48236
47848
  init_paths();
48237
47849
  init_paths$2();
47850
+ init_dashboard_auth();
47851
+ init_read_model();
48238
47852
  init_conversations();
48239
47853
  init_briefing_freshness();
48240
47854
  init_settings();
@@ -48593,7 +48207,10 @@ function isRole(value) {
48593
48207
  * GET /api/agents/:agentId/diffs/:turnId — unified diff for a specific turn
48594
48208
  * GET /api/agents/:agentId/diffs/full — full thread diff (all turns combined)
48595
48209
  */
48210
+ init_http_helpers();
48211
+ init_read_model();
48596
48212
  init_event_store();
48213
+ init_checkpoint_manager();
48597
48214
  function validateOrigin(request) {
48598
48215
  const origin = request.headers["origin"];
48599
48216
  if (origin && typeof origin === "string" && !origin.includes("localhost") && !origin.includes("127.0.0.1")) return {
@@ -48789,7 +48406,11 @@ const diffsRouteLayer = Layer.mergeAll(getDiffsRoute, getTurnDiffRoute, getFullD
48789
48406
  * t3code's `terminal.*` contracts, this create/kill pair is the seam that gets
48790
48407
  * folded into that RPC surface.
48791
48408
  */
48409
+ init_http_helpers();
48410
+ init_http_handler();
48792
48411
  init_tmux();
48412
+ init_origin_validation();
48413
+ init_dashboard_auth();
48793
48414
  const readJsonBody$2 = Effect.gen(function* () {
48794
48415
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
48795
48416
  return text ? JSON.parse(text) : {};
@@ -48850,7 +48471,12 @@ const terminalsRouteLayer = Layer.mergeAll(postTerminalRoute, deleteTerminalRout
48850
48471
  *
48851
48472
  * Zero sync FS calls — lib functions use fs/promises; SQLite work stays in the existing sync DB layer.
48852
48473
  */
48474
+ init_domain_services();
48475
+ init_http_helpers();
48476
+ init_http_handler();
48853
48477
  init_config_yaml();
48478
+ init_origin_validation();
48479
+ init_dashboard_auth();
48854
48480
  function getRequestHeader(request, name) {
48855
48481
  const value = request.headers[name];
48856
48482
  if (Array.isArray(value)) return value[0];
@@ -50143,7 +49769,9 @@ const AUTO_MERGE_COOLDOWN_MS = 3e5;
50143
49769
  //#endregion
50144
49770
  //#region ../../lib/cloister/auto-merge-eligibility.ts
50145
49771
  init_github_app();
49772
+ init_forge();
50146
49773
  init_review_status();
49774
+ init_tracker_utils();
50147
49775
  const execFileAsync = promisify(execFile);
50148
49776
  const BLOCKER_LABELS = [
50149
49777
  "needs-design",
@@ -50324,8 +49952,15 @@ function getProjectAutoMergeDefault(issueId) {
50324
49952
  }
50325
49953
  //#endregion
50326
49954
  //#region routes/flywheel.ts
49955
+ init_http_helpers();
49956
+ init_src();
49957
+ init_http_handler();
49958
+ init_origin_validation();
49959
+ init_flywheel_run_state();
49960
+ init_dashboard_auth();
50327
49961
  init_tmux();
50328
49962
  init_control_settings();
49963
+ init_forge();
50329
49964
  init_review_status();
50330
49965
  init_projects();
50331
49966
  init_merge_sync();
@@ -50631,7 +50266,7 @@ async function getFlywheelConversationPayload() {
50631
50266
  };
50632
50267
  }
50633
50268
  async function postFlywheelReportPayload() {
50634
- const { flywheelReportCommand } = await import("./flywheel-BkaWOjhl.js");
50269
+ const { flywheelReportCommand } = await import("./flywheel-B600poCU.js");
50635
50270
  await flywheelReportCommand({ cwd: process.cwd() });
50636
50271
  return {
50637
50272
  status: 200,
@@ -50820,11 +50455,11 @@ const getMergeBlockersRoute = HttpRouter.add("GET", "/api/flywheel/merge-blocker
50820
50455
  async function defaultGetOrderedIssueIds() {
50821
50456
  const status = await readCurrentFlywheelStatusForDashboard();
50822
50457
  if (!status) return [];
50823
- const { computeMergeQueue } = await import("./flywheel-merge-order-B_SKQBbD.js");
50458
+ const { computeMergeQueue } = await import("./flywheel-merge-order-CxtnH0iT.js");
50824
50459
  return (await Effect.runPromise(computeMergeQueue(status.activePipeline, process.cwd()).pipe(Effect.provide(layer)))).map((i) => i.issueId);
50825
50460
  }
50826
50461
  async function defaultMergeOne(issueId) {
50827
- const { triggerMerge } = await import("./workspaces-DTnkVSs7.js");
50462
+ const { triggerMerge } = await import("./workspaces-u5uok4gi.js");
50828
50463
  const r = await triggerMerge(issueId);
50829
50464
  return r.success ? { ok: true } : {
50830
50465
  ok: false,
@@ -51001,7 +50636,7 @@ const getFlywheelMergeQueueRoute = HttpRouter.add("GET", "/api/flywheel/merge-qu
51001
50636
  return yield* Effect.promise(async () => {
51002
50637
  const status = await readCurrentFlywheelStatusForDashboard();
51003
50638
  if (!status) return jsonResponse([]);
51004
- const { computeMergeQueue, resolveMergeQueuePrUrl } = await import("./flywheel-merge-order-B_SKQBbD.js");
50639
+ const { computeMergeQueue, resolveMergeQueuePrUrl } = await import("./flywheel-merge-order-CxtnH0iT.js");
51005
50640
  return jsonResponse(await Effect.runPromise(computeMergeQueue(status.activePipeline, process.cwd(), { getPrUrl: resolveMergeQueuePrUrl }).pipe(Effect.provide(layer))));
51006
50641
  });
51007
50642
  })));
@@ -51015,14 +50650,14 @@ function uatGenerationNameFromParam(param) {
51015
50650
  return decoded.startsWith("uat/") ? decoded : `uat/${decoded}`;
51016
50651
  }
51017
50652
  const getUatGenerationsRoute = HttpRouter.add("GET", "/api/flywheel/uat-generations", httpHandler(Effect.gen(function* () {
51018
- const { getUatGenerationsPayload } = yield* Effect.promise(() => import("./uat-train-B3-shx6M.js"));
50653
+ const { getUatGenerationsPayload } = yield* Effect.promise(() => import("./uat-train-BlWjkHk7.js"));
51019
50654
  return jsonResponse(yield* Effect.promise(() => getUatGenerationsPayload()));
51020
50655
  })));
51021
50656
  const postUatGenerationStackRoute = HttpRouter.add("POST", "/api/flywheel/uat-generations/:name/stack", httpHandler(Effect.gen(function* () {
51022
50657
  const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
51023
50658
  if (authError) return authError;
51024
50659
  const name = uatGenerationNameFromParam((yield* HttpRouter.params)["name"] ?? "");
51025
- const { postUatGenerationStackPayload } = yield* Effect.promise(() => import("./uat-train-B3-shx6M.js"));
50660
+ const { postUatGenerationStackPayload } = yield* Effect.promise(() => import("./uat-train-BlWjkHk7.js"));
51026
50661
  const result = yield* Effect.promise(() => postUatGenerationStackPayload(name));
51027
50662
  if (!result.ok) return jsonResponse({ error: result.error }, { status: result.status });
51028
50663
  return jsonResponse({
@@ -51034,8 +50669,8 @@ const postUatGenerationPromoteRoute = HttpRouter.add("POST", "/api/flywheel/uat-
51034
50669
  const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
51035
50670
  if (authError) return authError;
51036
50671
  const name = uatGenerationNameFromParam((yield* HttpRouter.params)["name"] ?? "");
51037
- const { postUatGenerationPromotePayload } = yield* Effect.promise(() => import("./uat-train-B3-shx6M.js"));
51038
- const { firePostMergeLifecycle } = yield* Effect.promise(() => import("./specialists-CuT8QcV6.js"));
50672
+ const { postUatGenerationPromotePayload } = yield* Effect.promise(() => import("./uat-train-BlWjkHk7.js"));
50673
+ const { firePostMergeLifecycle } = yield* Effect.promise(() => import("./specialists-OyXhszCe.js"));
51039
50674
  const result = yield* Effect.promise(() => postUatGenerationPromotePayload(name, firePostMergeLifecycle));
51040
50675
  if (!result.success) return jsonResponse(result, { status: result.reason === "not-found" ? 404 : result.reason === "merge-failed" ? 500 : 409 });
51041
50676
  return jsonResponse(result);
@@ -51048,7 +50683,7 @@ const postUatGenerationPromoteRoute = HttpRouter.add("POST", "/api/flywheel/uat-
51048
50683
  const postFlywheelAssembleUatRoute = HttpRouter.add("POST", "/api/flywheel/assemble-uat", httpHandler(Effect.gen(function* () {
51049
50684
  const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
51050
50685
  if (authError) return authError;
51051
- const { runUatTrainReconcile } = yield* Effect.promise(() => import("./uat-train-B3-shx6M.js"));
50686
+ const { runUatTrainReconcile } = yield* Effect.promise(() => import("./uat-train-BlWjkHk7.js"));
51052
50687
  return jsonResponse(yield* Effect.promise(() => runUatTrainReconcile({ force: true })));
51053
50688
  })));
51054
50689
  const getFlywheelStateRoute = HttpRouter.add("GET", "/api/flywheel/state", httpHandler(Effect.gen(function* () {
@@ -51156,6 +50791,9 @@ function escapeXml(value) {
51156
50791
  //#endregion
51157
50792
  //#region routes/artifacts.ts
51158
50793
  init_paths();
50794
+ init_http_helpers();
50795
+ init_dashboard_auth();
50796
+ init_http_handler();
51159
50797
  const ARTIFACT_SLUG_PATTERN = /^[A-Za-z0-9_-]{8}$/;
51160
50798
  const WORKSPACE_ARTIFACT_SELECTOR_PATTERN = /^[A-Za-z0-9._:-]+$/;
51161
50799
  const RAW_ARTIFACT_CSP = [
@@ -51469,6 +51107,9 @@ const postArtifactUnshareRoute = HttpRouter.add("POST", "/api/artifacts/:slug/un
51469
51107
  const artifactsRouteLayer = Layer.mergeAll(getRawArtifactRoute, getArtifactDetailRoute, getWorkspaceArtifactsRoute, getArtifactThumbnailRoute, postArtifactUnshareRoute);
51470
51108
  //#endregion
51471
51109
  //#region ../../lib/backlog/label-ops.ts
51110
+ init_sequence_io();
51111
+ init_tracker_utils();
51112
+ init_pickup();
51472
51113
  const execAsync = promisify(exec);
51473
51114
  /**
51474
51115
  * Add or remove a GitHub label on an issue. Non-GitHub issues are silently
@@ -51492,6 +51133,7 @@ const applyIssueReadyLabel = (id) => addIssueLabel(id, READY_LABEL);
51492
51133
  const removeIssueReadyLabel = (id) => removeIssueLabel(id, READY_LABEL);
51493
51134
  //#endregion
51494
51135
  //#region ../../lib/database/agent-mappers.ts
51136
+ init_lookups();
51495
51137
  function agentStateToDbAgent(state) {
51496
51138
  return {
51497
51139
  id: state.id,
@@ -52440,7 +52082,7 @@ function initSchema(db) {
52440
52082
  */
52441
52083
  function runMigrations(db, dbPath) {
52442
52084
  const currentVersion = db.pragma("user_version", { simple: true });
52443
- if (currentVersion === 57) return;
52085
+ if (currentVersion >= 57) return;
52444
52086
  if (currentVersion === 0) {
52445
52087
  initSchema(db);
52446
52088
  return;
@@ -53264,6 +52906,11 @@ function upsertBacklogSequence(projectKey, doc) {
53264
52906
  `);
53265
52907
  db.transaction((nodes) => {
53266
52908
  for (const node of nodes) stmt.run(projectKey, node.issue, node.rank, node.size, node.importance, node.score, node.condition, JSON.stringify(node.dependsOn), node.why, node.gate, node.planning, doc.generatedAt);
52909
+ const keep = nodes.map((n) => n.issue);
52910
+ if (keep.length > 0) {
52911
+ const placeholders = keep.map(() => "?").join(",");
52912
+ db.prepare(`DELETE FROM backlog_sequence WHERE project_key = ? AND issue_id NOT IN (${placeholders})`).run(projectKey, ...keep);
52913
+ } else db.prepare("DELETE FROM backlog_sequence WHERE project_key = ?").run(projectKey);
53267
52914
  })(doc.nodes);
53268
52915
  }
53269
52916
  function clearBacklogSequence(projectKey) {
@@ -53313,8 +52960,14 @@ function getBacklogSequence(projectKey) {
53313
52960
  }
53314
52961
  //#endregion
53315
52962
  //#region routes/backlog.ts
52963
+ init_http_handler();
52964
+ init_http_helpers();
52965
+ init_dashboard_auth();
52966
+ init_sequence_io();
52967
+ init_pickup();
53316
52968
  init_review_status();
53317
- init_agents$1();
52969
+ init_sequencer_agent();
52970
+ init_jsonl_resolver();
53318
52971
  const readJsonBody = Effect.gen(function* () {
53319
52972
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
53320
52973
  try {
@@ -53408,6 +53061,7 @@ const postBacklogRegenerateRoute = HttpRouter.add("POST", "/api/backlog/sequence
53408
53061
  const { getSharedIssueService } = (init_issue_service_singleton(), __toCommonJS(issue_service_singleton_exports));
53409
53062
  const issues = getSharedIssueService().getIssues();
53410
53063
  try {
53064
+ await clearFinishedSequencerRun(projectRoot);
53411
53065
  return jsonResponse({
53412
53066
  status: "spawned",
53413
53067
  agentId: (await spawnSequencerAgent(pass, {
@@ -53603,14 +53257,7 @@ const postBacklogClearRoute = HttpRouter.add("POST", "/api/backlog/sequence/clea
53603
53257
  const getSequencerStatusRoute = HttpRouter.add("GET", "/api/backlog/sequencer-status", httpHandler(Effect.gen(function* () {
53604
53258
  return yield* Effect.promise(async () => {
53605
53259
  const projectRoot = process.cwd();
53606
- const alive = listRunningAgentsSync().some((a) => a.id === "sequencer-runner" && a.tmuxActive);
53607
- const startedAt = alive ? getAgentStateSync("sequencer-runner")?.startedAt ?? null : null;
53608
- const seqPath = join$1(projectRoot, ".pan", "backlog", "sequence.md");
53609
- let freshSequence = false;
53610
- if (alive && startedAt && existsSync$1(seqPath)) try {
53611
- freshSequence = statSync$1(seqPath).mtimeMs >= new Date(startedAt).getTime();
53612
- } catch {}
53613
- const running = alive && !freshSequence;
53260
+ const { running, startedAt } = getSequencerRunStatus(projectRoot);
53614
53261
  let total = 0;
53615
53262
  const manifestPath = join$1(projectRoot, ".pan", "backlog", "manifest.json");
53616
53263
  if (existsSync$1(manifestPath)) try {
@@ -53652,6 +53299,8 @@ function listFeatureRegistryForDashboard(filter = {}) {
53652
53299
  }
53653
53300
  //#endregion
53654
53301
  //#region routes/feature-registry.ts
53302
+ init_http_helpers();
53303
+ init_http_handler();
53655
53304
  async function getFeatureRegistryListPayload(options = {}) {
53656
53305
  return { entries: await (options.listEntries ?? listFeatureRegistryForDashboard)({ limit: 100 }) };
53657
53306
  }
@@ -53666,6 +53315,9 @@ const featureRegistryRouteLayer = Layer.provideMerge(listFeatureRegistryRoute, L
53666
53315
  *
53667
53316
  * GET /api/fs/list-dirs?path=<abs> — list immediate subdirectories, home-clamped + auth-gated
53668
53317
  */
53318
+ init_http_helpers();
53319
+ init_http_handler();
53320
+ init_dashboard_auth();
53669
53321
  const listDirsRoute = HttpRouter.add("GET", "/api/fs/list-dirs", httpHandler(Effect.gen(function* () {
53670
53322
  const req = yield* HttpServerRequest.HttpServerRequest;
53671
53323
  const authError = rejectUnauthorizedDashboardRequest(req);
@@ -53716,7 +53368,14 @@ const fsRouteLayer = Layer.mergeAll(listDirsRoute);
53716
53368
  * GET /ws/terminal → Raw WebSocket terminal (bypasses Effect RPC)
53717
53369
  * GET * → static files from OVERDECK_FRONTEND_DIR
53718
53370
  */
53371
+ init_http_helpers();
53372
+ init_domain_services();
53373
+ init_read_model();
53374
+ init_cost();
53719
53375
  init_infra();
53376
+ init_linear_client();
53377
+ init_dashboard_auth();
53378
+ init_origin_validation();
53720
53379
  init_activity_logger();
53721
53380
  const HttpServerLive = Layer.unwrap(Effect.gen(function* () {
53722
53381
  const config = yield* ServerConfig;
@@ -53853,10 +53512,14 @@ const runServer = Layer.launch(makeServerLayer);
53853
53512
  * These fields were dropped in the Effect server migration (PAN-428) and are
53854
53513
  * restored here via the event-driven projection pipeline.
53855
53514
  */
53856
- init_agents$1();
53515
+ init_agents$2();
53516
+ init_agent_enrichment();
53857
53517
  init_review_status();
53518
+ init_concurrency$1();
53858
53519
  init_event_store();
53520
+ init_agent_projection();
53859
53521
  init_activity_logger();
53522
+ init_read_model();
53860
53523
  function enrichmentChanged(prev, next) {
53861
53524
  if (!prev) return true;
53862
53525
  return prev.role !== next.role || prev.hasPendingQuestion !== next.hasPendingQuestion || prev.pendingQuestionCount !== next.pendingQuestionCount || prev.pendingQuestionPrompt !== next.pendingQuestionPrompt || prev.pendingQuestionReason !== next.pendingQuestionReason || prev.pendingInputCount !== next.pendingInputCount || prev.pendingInputKinds.join(",") !== next.pendingInputKinds.join(",") || prev.pendingAskUserQuestion?.toolUseId !== next.pendingAskUserQuestion?.toolUseId || prev.resolution !== next.resolution || prev.resolutionCount !== next.resolutionCount;
@@ -54135,8 +53798,9 @@ function drainRefreshQueue(state) {
54135
53798
  * expects — the reducer and WebSocket subscription already exist, but no
54136
53799
  * server code was emitting the events.
54137
53800
  */
54138
- init_agents$1();
53801
+ init_agents$2();
54139
53802
  init_tmux();
53803
+ init_concurrency$1();
54140
53804
  init_event_store();
54141
53805
  function splitLines(text) {
54142
53806
  const lines = text.split("\n");
@@ -54174,7 +53838,7 @@ async function pollOnce(state) {
54174
53838
  try {
54175
53839
  const remoteState = await readFile(join$1(homedir$1(), ".overdeck", "agents", agentId, "remote-state.json"), "utf-8").then((text) => JSON.parse(text)).catch(() => null);
54176
53840
  if (remoteState?.location === "remote" && remoteState?.vmName) {
54177
- const { getRemoteAgentOutput } = await import("./remote-agents-BiAcNt37.js");
53841
+ const { getRemoteAgentOutput } = await import("./remote-agents-B_xysqhd.js");
54178
53842
  stdout = await getRemoteAgentOutput(agentId, remoteState.vmName, 50);
54179
53843
  } else stdout = await Effect.runPromise(capturePane(agentId, 50));
54180
53844
  } catch {
@@ -54377,7 +54041,7 @@ async function detectOrphanedClaudeCodeSessions(activeConvs) {
54377
54041
  for (const conv of activeConvs) {
54378
54042
  if (!conv.cwd) continue;
54379
54043
  if (!conv.claudeSessionId) continue;
54380
- if (conv.harness === "pi" || conv.harness === "codex") continue;
54044
+ if (conv.harness === "ohmypi" || conv.harness === "codex") continue;
54381
54045
  const list = cwdGroups.get(conv.cwd) ?? [];
54382
54046
  list.push(conv);
54383
54047
  cwdGroups.set(conv.cwd, list);
@@ -54698,7 +54362,7 @@ async function announceNewRestart(deps = {}) {
54698
54362
  setLastAnnounced(status.ts);
54699
54363
  return true;
54700
54364
  }
54701
- let timer$1 = null;
54365
+ let timer$2 = null;
54702
54366
  let running = false;
54703
54367
  let runToken = 0;
54704
54368
  function startRestartAnnouncer(deps = {}) {
@@ -54708,8 +54372,8 @@ function startRestartAnnouncer(deps = {}) {
54708
54372
  let remainingBootstrapPolls = BOOTSTRAP_FAST_POLL_COUNT;
54709
54373
  const schedule = (delayMs) => {
54710
54374
  if (!running || token !== runToken) return;
54711
- timer$1 = setTimeout(pass, delayMs);
54712
- timer$1.unref?.();
54375
+ timer$2 = setTimeout(pass, delayMs);
54376
+ timer$2.unref?.();
54713
54377
  };
54714
54378
  const nextDelay = (emitted) => {
54715
54379
  if (emitted) return POLL_MS;
@@ -54727,8 +54391,8 @@ function startRestartAnnouncer(deps = {}) {
54727
54391
  function stopRestartAnnouncer() {
54728
54392
  running = false;
54729
54393
  runToken += 1;
54730
- if (timer$1) clearTimeout(timer$1);
54731
- timer$1 = null;
54394
+ if (timer$2) clearTimeout(timer$2);
54395
+ timer$2 = null;
54732
54396
  }
54733
54397
  //#endregion
54734
54398
  //#region services/substrate-bug-poller.ts
@@ -54957,7 +54621,7 @@ init_config_yaml();
54957
54621
  init_event_store();
54958
54622
  init_activity_logger();
54959
54623
  init_features();
54960
- init_cost();
54624
+ init_cost$1();
54961
54625
  const SYSTEM_PROMPT = `You are a concise voice narrator for a software development dashboard.
54962
54626
  Your job: review recent activity and produce a SINGLE brief utterance suitable for text-to-speech.
54963
54627
 
@@ -55123,7 +54787,7 @@ const RESTART_MARKER = join(homedir(), ".overdeck", "dashboard-restarting.json")
55123
54787
  * loads new content-hashed chunk filenames (no ERR_MODULE_NOT_FOUND after rebuild).
55124
54788
  */
55125
54789
  async function defaultLifecycleRunner(pending) {
55126
- const { postMergeLifecycle, notifyTldrDaemon } = await import("./merge-agent-Bn54C3oA.js");
54790
+ const { postMergeLifecycle, notifyTldrDaemon } = await import("./merge-agent-Dhw22tcB.js");
55127
54791
  await postMergeLifecycle(pending.issueId, pending.projectPath, pending.sourceBranch, { skipDeploy: true });
55128
54792
  if (pending.sourceBranch) await notifyTldrDaemon(pending.projectPath, pending.sourceBranch);
55129
54793
  }
@@ -55191,7 +54855,7 @@ async function processPendingLifecycle(options) {
55191
54855
  }
55192
54856
  //#endregion
55193
54857
  //#region ../../lib/memory/reconciliation.ts
55194
- init_agents$1();
54858
+ init_agents$2();
55195
54859
  async function reconcileStaleTranscriptCheckpoints(options = {}) {
55196
54860
  const limit = options.limit ?? 100;
55197
54861
  const checkpoints = (options.listCheckpoints ?? listTranscriptCheckpoints)(limit);
@@ -55255,7 +54919,7 @@ function checkpointIdentity(checkpoint) {
55255
54919
  };
55256
54920
  }
55257
54921
  function inferCheckpointHarness(transcriptPath) {
55258
- if (transcriptPath.includes("/.overdeck/agents/") && transcriptPath.includes("/sessions/")) return "pi";
54922
+ if (transcriptPath.includes("/.overdeck/agents/") && transcriptPath.includes("/sessions/")) return "ohmypi";
55259
54923
  if (transcriptPath.includes("/codex-home/sessions/") && transcriptPath.includes("rollout-")) return "codex";
55260
54924
  return "claude-code";
55261
54925
  }
@@ -55291,7 +54955,7 @@ init_paths();
55291
54955
  init_tmux();
55292
54956
  init_issue_id();
55293
54957
  init_errors();
55294
- init_agents$1();
54958
+ init_agents$2();
55295
54959
  function getAgentDirectoryIssueId(name) {
55296
54960
  const match = name.match(/^(?:agent|planning)-(.+)$/);
55297
54961
  if (!match) return null;
@@ -55434,7 +55098,7 @@ init_activity_logger();
55434
55098
  init_merge_sync();
55435
55099
  const AUTO_MERGE_EXECUTOR_INTERVAL_MS = 3e4;
55436
55100
  const REQUEUE_BACKOFF_MS = 6e4;
55437
- let timer = null;
55101
+ let timer$1 = null;
55438
55102
  let activeTick = null;
55439
55103
  function errorMessage(error) {
55440
55104
  if (error instanceof Error && error.message) return error.message;
@@ -55444,7 +55108,7 @@ function failureReason(result) {
55444
55108
  return result.error ?? result.message ?? `merge returned status ${result.statusCode ?? "unknown"}`;
55445
55109
  }
55446
55110
  async function defaultMergeIssue(issueId) {
55447
- const { triggerMerge } = await import("./workspaces-DTnkVSs7.js");
55111
+ const { triggerMerge } = await import("./workspaces-u5uok4gi.js");
55448
55112
  return triggerMerge(issueId);
55449
55113
  }
55450
55114
  function defaultAnnounceFailure(issueId, reason) {
@@ -55514,14 +55178,48 @@ function runTick(deps) {
55514
55178
  }
55515
55179
  function startAutoMergeExecutor(deps = {}) {
55516
55180
  if (process.env.OVERDECK_DISABLE_AUTO_MERGE === "1") return false;
55517
- if (timer) return false;
55518
- timer = setInterval(() => runTick(deps), AUTO_MERGE_EXECUTOR_INTERVAL_MS);
55181
+ if (timer$1) return false;
55182
+ timer$1 = setInterval(() => runTick(deps), AUTO_MERGE_EXECUTOR_INTERVAL_MS);
55519
55183
  return true;
55520
55184
  }
55521
55185
  function stopAutoMergeExecutor() {
55522
- if (!timer) return;
55523
- clearInterval(timer);
55524
- timer = null;
55186
+ if (!timer$1) return;
55187
+ clearInterval(timer$1);
55188
+ timer$1 = null;
55189
+ }
55190
+ //#endregion
55191
+ //#region services/cost-reconcile-service.ts
55192
+ const RECONCILE_INTERVAL_MS = 5 * 6e4;
55193
+ let timer = null;
55194
+ let inFlight = null;
55195
+ async function runCostReconcileOnce(reason) {
55196
+ if (inFlight) return inFlight;
55197
+ inFlight = (async () => {
55198
+ const result = await reconcilePiTranscripts();
55199
+ 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)`);
55200
+ if (result.errors.length > 0) for (const err of result.errors.slice(0, 5)) console.warn(`[cost-reconciler] ${err.path}: ${err.error}`);
55201
+ })().finally(() => {
55202
+ inFlight = null;
55203
+ });
55204
+ return inFlight;
55205
+ }
55206
+ function startCostReconcileService() {
55207
+ if (timer) return;
55208
+ timer = setInterval(() => {
55209
+ runCostReconcileOnce("interval").catch((err) => {
55210
+ console.warn("[cost-reconciler] interval sweep failed:", err instanceof Error ? err.message : err);
55211
+ });
55212
+ }, RECONCILE_INTERVAL_MS);
55213
+ timer.unref?.();
55214
+ runCostReconcileOnce("startup").catch((err) => {
55215
+ console.warn("[cost-reconciler] startup sweep failed:", err instanceof Error ? err.message : err);
55216
+ });
55217
+ }
55218
+ function stopCostReconcileService() {
55219
+ if (timer) {
55220
+ clearInterval(timer);
55221
+ timer = null;
55222
+ }
55525
55223
  }
55526
55224
  //#endregion
55527
55225
  //#region ../../lib/boot-gates.ts
@@ -55617,14 +55315,14 @@ function createOverdeckDatabase(options = {}) {
55617
55315
  try {
55618
55316
  applyMigration(db, migrationPath);
55619
55317
  const tables = userTableNames(db);
55620
- if (tables.length !== 32) throw new Error(`Expected 32 overdeck tables, found ${tables.length}: ${tables.join(", ")}`);
55318
+ if (tables.length !== 30) throw new Error(`Expected 30 overdeck tables, found ${tables.length}: ${tables.join(", ")}`);
55621
55319
  assertEmptyTables(db, tables);
55622
55320
  } finally {
55623
55321
  db.close();
55624
55322
  }
55625
55323
  return {
55626
55324
  dbPath,
55627
- tableCount: 32
55325
+ tableCount: 30
55628
55326
  };
55629
55327
  }
55630
55328
  //#endregion
@@ -55640,7 +55338,11 @@ init_tracker_config();
55640
55338
  init_review_status();
55641
55339
  init_event_store();
55642
55340
  init_activity_logger();
55643
- init_agents$1();
55341
+ init_service();
55342
+ init_config$1();
55343
+ init_deacon();
55344
+ init_agents$2();
55345
+ init_agent_projection();
55644
55346
  init_paths();
55645
55347
  init_tmux();
55646
55348
  init_fts_db();
@@ -55916,6 +55618,8 @@ syncTranscriptPollerRegistry().catch((err) => console.warn("[memory-poller] init
55916
55618
  reconcileStaleTranscriptCheckpoints({ log: (message) => console.log(message) }).catch((err) => console.warn("[memory-reconciliation] startup sweep failed:", err?.message ?? err));
55917
55619
  startTranscriptPoller();
55918
55620
  console.log("[overdeck] Memory transcript poller started");
55621
+ startCostReconcileService();
55622
+ console.log("[overdeck] Cost reconciler started");
55919
55623
  const conversationSearchWatcher = startConversationSearchWatcher();
55920
55624
  console.log(conversationSearchWatcher ? "[overdeck] Conversation search watcher started" : "[overdeck] Conversation search watcher skipped (conversationSearch.enabled=false)");
55921
55625
  (async () => {
@@ -55965,6 +55669,7 @@ const handleShutdownSignal = async (signal) => {
55965
55669
  stopTtsPlayback();
55966
55670
  stopAutoMergeExecutor();
55967
55671
  stopTranscriptPoller();
55672
+ stopCostReconcileService();
55968
55673
  stopRestartAnnouncer();
55969
55674
  await stopConversationSearchWatcher().catch((err) => console.warn("[conversation-search] watcher shutdown failed:", err));
55970
55675
  closeConversationSearchService();
@@ -56014,7 +55719,7 @@ reconcileStaleGitHubBlockers().then((n) => {
56014
55719
  }
56015
55720
  }).catch((err) => console.warn(`[overdeck] Startup blocker reconciliation failed: ${err.message}`));
56016
55721
  try {
56017
- const { resetProcessingToQueued } = await import("./merge-sync-BcXooyxk.js");
55722
+ const { resetProcessingToQueued } = await import("./merge-sync-N6oGrZi7.js");
56018
55723
  const resetCount = resetProcessingToQueued();
56019
55724
  if (resetCount > 0) {
56020
55725
  console.log(`[overdeck] Reset ${resetCount} stuck merge queue entries to queued`);