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