botmux 2.87.0 → 2.88.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/README.en.md +1 -0
- package/README.md +1 -0
- package/dist/adapters/backend/liveness-gate.d.ts +38 -0
- package/dist/adapters/backend/liveness-gate.d.ts.map +1 -0
- package/dist/adapters/backend/liveness-gate.js +53 -0
- package/dist/adapters/backend/liveness-gate.js.map +1 -0
- package/dist/adapters/backend/tmux-backend.d.ts +21 -0
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +102 -27
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +20 -0
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +85 -19
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/backend/zellij-observe-backend.d.ts +13 -0
- package/dist/adapters/backend/zellij-observe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-observe-backend.js +51 -12
- package/dist/adapters/backend/zellij-observe-backend.js.map +1 -1
- package/dist/adapters/cli/traex.d.ts.map +1 -1
- package/dist/adapters/cli/traex.js +4 -0
- package/dist/adapters/cli/traex.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +6 -0
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/bot-registry.d.ts +13 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +3 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.js +1 -1
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +165 -5
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts +3 -0
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js +4 -1
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/session-activity.d.ts +1 -0
- package/dist/core/session-activity.d.ts.map +1 -1
- package/dist/core/session-activity.js +7 -4
- package/dist/core/session-activity.js.map +1 -1
- package/dist/core/session-create.d.ts +62 -0
- package/dist/core/session-create.d.ts.map +1 -0
- package/dist/core/session-create.js +131 -0
- package/dist/core/session-create.js.map +1 -0
- package/dist/core/session-manager.d.ts +40 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +236 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +78 -4
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +11 -5
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +1 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +1 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/session-cleanup.d.ts +30 -0
- package/dist/dashboard/session-cleanup.d.ts.map +1 -0
- package/dist/dashboard/session-cleanup.js +47 -0
- package/dist/dashboard/session-cleanup.js.map +1 -0
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +20 -5
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +184 -2
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/insights.d.ts.map +1 -1
- package/dist/dashboard/web/insights.js +355 -30
- package/dist/dashboard/web/insights.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts +2 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +457 -30
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard-web/app.js +767 -640
- package/dist/dashboard-web/style.css +257 -7
- package/dist/dashboard.js +213 -2
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +21 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +21 -1
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +4 -5
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +113 -11
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +3 -2
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +167 -11
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +19 -5
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/grant-pending.d.ts.map +1 -1
- package/dist/im/lark/grant-pending.js +9 -2
- package/dist/im/lark/grant-pending.js.map +1 -1
- package/dist/index-daemon.js +5 -0
- package/dist/index-daemon.js.map +1 -1
- package/dist/platform/bind.d.ts +2 -0
- package/dist/platform/bind.d.ts.map +1 -0
- package/dist/platform/bind.js +91 -0
- package/dist/platform/bind.js.map +1 -0
- package/dist/platform/binding.d.ts +22 -0
- package/dist/platform/binding.d.ts.map +1 -0
- package/dist/platform/binding.js +33 -0
- package/dist/platform/binding.js.map +1 -0
- package/dist/platform/tunnel-client.d.ts +15 -0
- package/dist/platform/tunnel-client.d.ts.map +1 -0
- package/dist/platform/tunnel-client.js +193 -0
- package/dist/platform/tunnel-client.js.map +1 -0
- package/dist/services/bot-config-store.d.ts.map +1 -1
- package/dist/services/bot-config-store.js +1 -0
- package/dist/services/bot-config-store.js.map +1 -1
- package/dist/services/git-worktree.d.ts +10 -0
- package/dist/services/git-worktree.d.ts.map +1 -1
- package/dist/services/git-worktree.js +71 -26
- package/dist/services/git-worktree.js.map +1 -1
- package/dist/services/grant-prefs-store.d.ts +3 -0
- package/dist/services/grant-prefs-store.d.ts.map +1 -1
- package/dist/services/grant-prefs-store.js +17 -2
- package/dist/services/grant-prefs-store.js.map +1 -1
- package/dist/services/insight/claude-span-reader.d.ts.map +1 -1
- package/dist/services/insight/claude-span-reader.js +18 -2
- package/dist/services/insight/claude-span-reader.js.map +1 -1
- package/dist/services/insight/codex-span-reader.d.ts.map +1 -1
- package/dist/services/insight/codex-span-reader.js +17 -1
- package/dist/services/insight/codex-span-reader.js.map +1 -1
- package/dist/services/insight/report.d.ts.map +1 -1
- package/dist/services/insight/report.js +48 -6
- package/dist/services/insight/report.js.map +1 -1
- package/dist/services/insight/types.d.ts +9 -0
- package/dist/services/insight/types.d.ts.map +1 -1
- package/dist/services/insight/types.js.map +1 -1
- package/dist/services/worktree-slug-ai.d.ts.map +1 -1
- package/dist/services/worktree-slug-ai.js +3 -1
- package/dist/services/worktree-slug-ai.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/crash-log.d.ts +22 -0
- package/dist/utils/crash-log.d.ts.map +1 -0
- package/dist/utils/crash-log.js +37 -0
- package/dist/utils/crash-log.js.map +1 -0
- package/dist/utils/input-gate.d.ts +10 -0
- package/dist/utils/input-gate.d.ts.map +1 -1
- package/dist/utils/input-gate.js +7 -0
- package/dist/utils/input-gate.js.map +1 -1
- package/dist/utils/stdio-epipe-guard.d.ts +39 -0
- package/dist/utils/stdio-epipe-guard.d.ts.map +1 -0
- package/dist/utils/stdio-epipe-guard.js +67 -0
- package/dist/utils/stdio-epipe-guard.js.map +1 -0
- package/dist/worker.js +210 -26
- package/dist/worker.js.map +1 -1
- package/dist/workflows/daemon-spawn.d.ts +2 -0
- package/dist/workflows/daemon-spawn.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -2070,6 +2070,161 @@ fieldset legend {
|
|
|
2070
2070
|
|
|
2071
2071
|
.bulk-bar[hidden] { display: none; }
|
|
2072
2072
|
.bulk-bar #bulk-count { font-weight: 800; margin-right: 4px; }
|
|
2073
|
+
.idle-cleanup-bar {
|
|
2074
|
+
display: grid;
|
|
2075
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
2076
|
+
align-items: center;
|
|
2077
|
+
gap: 8px 12px;
|
|
2078
|
+
margin: 10px 0 2px;
|
|
2079
|
+
padding: 8px;
|
|
2080
|
+
border-radius: var(--radius);
|
|
2081
|
+
border: 1px solid var(--border-soft);
|
|
2082
|
+
background: color-mix(in srgb, var(--surface) 92%, var(--bg));
|
|
2083
|
+
box-shadow: 0 10px 26px rgba(15, 23, 42, 0.06);
|
|
2084
|
+
}
|
|
2085
|
+
.idle-cleanup-summary,
|
|
2086
|
+
.idle-cleanup-controls {
|
|
2087
|
+
display: flex;
|
|
2088
|
+
align-items: center;
|
|
2089
|
+
min-width: 0;
|
|
2090
|
+
gap: 8px;
|
|
2091
|
+
}
|
|
2092
|
+
.idle-cleanup-controls {
|
|
2093
|
+
justify-content: flex-end;
|
|
2094
|
+
flex-wrap: wrap;
|
|
2095
|
+
}
|
|
2096
|
+
.idle-cleanup-dot {
|
|
2097
|
+
width: 8px;
|
|
2098
|
+
height: 8px;
|
|
2099
|
+
flex: 0 0 auto;
|
|
2100
|
+
border-radius: 50%;
|
|
2101
|
+
background: var(--danger);
|
|
2102
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 12%, transparent);
|
|
2103
|
+
}
|
|
2104
|
+
.idle-cleanup-bar.is-empty .idle-cleanup-dot {
|
|
2105
|
+
background: var(--muted);
|
|
2106
|
+
box-shadow: none;
|
|
2107
|
+
}
|
|
2108
|
+
.idle-cleanup-count,
|
|
2109
|
+
.idle-cleanup-status,
|
|
2110
|
+
.idle-cleanup-label {
|
|
2111
|
+
min-height: 32px;
|
|
2112
|
+
display: inline-flex;
|
|
2113
|
+
align-items: center;
|
|
2114
|
+
white-space: nowrap;
|
|
2115
|
+
}
|
|
2116
|
+
.idle-cleanup-count {
|
|
2117
|
+
padding: 0 12px;
|
|
2118
|
+
border-radius: 999px;
|
|
2119
|
+
background: var(--surface-muted);
|
|
2120
|
+
border: 1px solid var(--border-soft);
|
|
2121
|
+
color: var(--fg);
|
|
2122
|
+
font-size: 12.5px;
|
|
2123
|
+
font-weight: 800;
|
|
2124
|
+
font-variant-numeric: tabular-nums;
|
|
2125
|
+
}
|
|
2126
|
+
.idle-cleanup-label {
|
|
2127
|
+
color: var(--muted);
|
|
2128
|
+
font-size: 12.5px;
|
|
2129
|
+
font-weight: 700;
|
|
2130
|
+
}
|
|
2131
|
+
.idle-cleanup-thresholds {
|
|
2132
|
+
display: inline-grid;
|
|
2133
|
+
grid-auto-flow: column;
|
|
2134
|
+
gap: 2px;
|
|
2135
|
+
min-height: 32px;
|
|
2136
|
+
padding: 2px;
|
|
2137
|
+
border-radius: 999px;
|
|
2138
|
+
border: 1px solid var(--border-soft);
|
|
2139
|
+
background: var(--surface-muted);
|
|
2140
|
+
}
|
|
2141
|
+
.idle-cleanup-thresholds button {
|
|
2142
|
+
min-height: 26px;
|
|
2143
|
+
padding: 0 10px;
|
|
2144
|
+
border: 0;
|
|
2145
|
+
border-radius: 999px;
|
|
2146
|
+
background: transparent;
|
|
2147
|
+
box-shadow: none;
|
|
2148
|
+
color: var(--muted);
|
|
2149
|
+
font-size: 12px;
|
|
2150
|
+
font-weight: 800;
|
|
2151
|
+
font-variant-numeric: tabular-nums;
|
|
2152
|
+
}
|
|
2153
|
+
.idle-cleanup-thresholds button:hover:not(.active) {
|
|
2154
|
+
color: var(--fg);
|
|
2155
|
+
background: color-mix(in srgb, var(--border-soft) 60%, transparent);
|
|
2156
|
+
}
|
|
2157
|
+
.idle-cleanup-thresholds button.active {
|
|
2158
|
+
background: var(--surface);
|
|
2159
|
+
color: var(--fg);
|
|
2160
|
+
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.08);
|
|
2161
|
+
}
|
|
2162
|
+
.idle-cleanup-run {
|
|
2163
|
+
min-height: 32px;
|
|
2164
|
+
padding: 0 15px;
|
|
2165
|
+
font-weight: 800;
|
|
2166
|
+
transition: transform .16s ease, background-color .16s ease, border-color .16s ease, color .16s ease;
|
|
2167
|
+
}
|
|
2168
|
+
.idle-cleanup-run:hover:not(:disabled) {
|
|
2169
|
+
transform: translateY(-1px);
|
|
2170
|
+
}
|
|
2171
|
+
.idle-cleanup-run:active:not(:disabled) {
|
|
2172
|
+
transform: scale(.98);
|
|
2173
|
+
}
|
|
2174
|
+
.idle-cleanup-status {
|
|
2175
|
+
grid-column: 1 / -1;
|
|
2176
|
+
justify-self: end;
|
|
2177
|
+
padding: 0 11px;
|
|
2178
|
+
border-radius: 999px;
|
|
2179
|
+
border: 1px solid color-mix(in srgb, var(--success) 24%, var(--border-soft));
|
|
2180
|
+
background: color-mix(in srgb, var(--success) 8%, var(--surface));
|
|
2181
|
+
color: var(--success);
|
|
2182
|
+
font-size: 12px;
|
|
2183
|
+
font-weight: 800;
|
|
2184
|
+
}
|
|
2185
|
+
.idle-cleanup-status:empty {
|
|
2186
|
+
display: none;
|
|
2187
|
+
}
|
|
2188
|
+
:root[data-theme="dark"] .idle-cleanup-bar {
|
|
2189
|
+
background: rgba(255, 255, 255, 0.035);
|
|
2190
|
+
border-color: rgba(255, 255, 255, 0.08);
|
|
2191
|
+
box-shadow: none;
|
|
2192
|
+
}
|
|
2193
|
+
:root[data-theme="dark"] .idle-cleanup-count,
|
|
2194
|
+
:root[data-theme="dark"] .idle-cleanup-thresholds button.active {
|
|
2195
|
+
background: rgba(255, 255, 255, 0.06);
|
|
2196
|
+
}
|
|
2197
|
+
:root[data-theme="dark"] .idle-cleanup-thresholds {
|
|
2198
|
+
background: rgba(255, 255, 255, 0.035);
|
|
2199
|
+
border-color: rgba(255, 255, 255, 0.09);
|
|
2200
|
+
}
|
|
2201
|
+
:root[data-theme="dark"] .idle-cleanup-status {
|
|
2202
|
+
background: color-mix(in srgb, var(--success) 12%, transparent);
|
|
2203
|
+
}
|
|
2204
|
+
@media (max-width: 720px) {
|
|
2205
|
+
.idle-cleanup-bar {
|
|
2206
|
+
grid-template-columns: 1fr;
|
|
2207
|
+
align-items: stretch;
|
|
2208
|
+
}
|
|
2209
|
+
.idle-cleanup-controls {
|
|
2210
|
+
justify-content: flex-start;
|
|
2211
|
+
}
|
|
2212
|
+
.idle-cleanup-thresholds {
|
|
2213
|
+
flex: 1 1 180px;
|
|
2214
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
2215
|
+
grid-auto-flow: column;
|
|
2216
|
+
}
|
|
2217
|
+
.idle-cleanup-thresholds button {
|
|
2218
|
+
min-width: 0;
|
|
2219
|
+
padding: 0 8px;
|
|
2220
|
+
}
|
|
2221
|
+
.idle-cleanup-run {
|
|
2222
|
+
flex: 1 1 118px;
|
|
2223
|
+
}
|
|
2224
|
+
.idle-cleanup-status {
|
|
2225
|
+
justify-self: start;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2073
2228
|
input.row-select,
|
|
2074
2229
|
#select-all { cursor: pointer; }
|
|
2075
2230
|
|
|
@@ -6850,12 +7005,66 @@ body.cp-fx-slice main { animation: cp-fx-slice 0.32s steps(2, end); }
|
|
|
6850
7005
|
.insights-page .hbtrack { background: var(--ins-inner); border-radius: 6px; height: 14px; overflow: hidden; }
|
|
6851
7006
|
.insights-page .hbfill { height: 100%; background: linear-gradient(90deg, var(--danger-soft), var(--bad)); border-radius: 6px; min-width: 3px; transform-origin: left; animation: insight-bar-grow .55s ease both; }
|
|
6852
7007
|
.insights-page .hbval { font-size: 12px; color: var(--mut); text-align: right; white-space: nowrap; }
|
|
7008
|
+
.insights-page .hbval small { color: var(--ins-faint); font-size: 10px; margin-left: 5px; }
|
|
6853
7009
|
|
|
6854
|
-
/*
|
|
6855
|
-
.insight-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
7010
|
+
/* top-level tabs (总览 / 会话 / 分布 / 热点) — mirrors .detailtabbar */
|
|
7011
|
+
.insights-page .insight-tabs { display: flex; gap: 6px; border-bottom: 1px solid var(--line); margin: 4px 0 16px; }
|
|
7012
|
+
.insights-page .itab {
|
|
7013
|
+
background: transparent; color: var(--mut); border: 1px solid transparent; border-bottom: 0;
|
|
7014
|
+
border-radius: 8px 8px 0 0; padding: 8px 16px; font: inherit; font-size: 13px; cursor: pointer; margin-bottom: -1px;
|
|
7015
|
+
transition: color .12s ease, background .12s ease;
|
|
7016
|
+
}
|
|
7017
|
+
.insights-page .itab:hover { color: var(--fg); }
|
|
7018
|
+
.insights-page .itab.on { color: var(--fg); background: var(--panel); border-color: var(--line); font-weight: 600; }
|
|
7019
|
+
.insights-page .insight-panel[hidden] { display: none; }
|
|
7020
|
+
|
|
7021
|
+
/* filter-bar additions: project/time selects, analyzable toggle, clear */
|
|
7022
|
+
.insights-page .ins-select {
|
|
7023
|
+
background: var(--ins-inner); color: var(--fg); border: 1px solid var(--line); border-radius: 8px;
|
|
7024
|
+
padding: 6px 10px; font: inherit; font-size: 12px; max-width: 220px; cursor: pointer;
|
|
7025
|
+
}
|
|
7026
|
+
.insights-page .ins-toggle { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--mut); cursor: pointer; white-space: nowrap; }
|
|
7027
|
+
.insights-page .ins-toggle input { cursor: pointer; }
|
|
7028
|
+
.insights-page .ins-clear {
|
|
7029
|
+
background: transparent; color: var(--mut); border: 1px solid var(--line); border-radius: 8px;
|
|
7030
|
+
padding: 6px 12px; font: inherit; font-size: 12px; cursor: pointer;
|
|
7031
|
+
}
|
|
7032
|
+
.insights-page .ins-clear:hover { color: var(--fg); border-color: var(--acc); }
|
|
7033
|
+
|
|
7034
|
+
/* distribution histograms + hotspots grids */
|
|
7035
|
+
.insights-page .ins-hint { margin: 0 0 12px; font-size: 12px; }
|
|
7036
|
+
.insights-page .ihist-grid,
|
|
7037
|
+
.insights-page .hot-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 14px; align-items: start; }
|
|
7038
|
+
.insights-page .ihist-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
|
|
7039
|
+
.insights-page .ihist-head h3 { margin: 0; }
|
|
7040
|
+
.insights-page .hot-sessions, .insights-page .hot-note { grid-column: 1 / -1; }
|
|
7041
|
+
.insights-page .hot-note { margin-top: 2px; }
|
|
7042
|
+
|
|
7043
|
+
/* Sessions tab: full-width list ⇆ full-width detail (mirrors ASI 会话, replaces
|
|
7044
|
+
the cramped master-detail side panel) */
|
|
7045
|
+
.insights-page #insight-list-view .insight-list-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
7046
|
+
.insights-page .sesssort { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
|
|
7047
|
+
.insights-page .sesssort-label { font-size: 11px; margin-right: 2px; }
|
|
7048
|
+
.insights-page .ins-back {
|
|
7049
|
+
background: transparent; color: var(--mut); border: 1px solid var(--line); border-radius: 8px;
|
|
7050
|
+
padding: 6px 14px; font: inherit; font-size: 12px; cursor: pointer; margin-bottom: 12px;
|
|
7051
|
+
}
|
|
7052
|
+
.insights-page .ins-back:hover { color: var(--fg); border-color: var(--acc); }
|
|
7053
|
+
.insights-page #insight-detail-view .insight-detail { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; }
|
|
7054
|
+
/* full-width rich rows */
|
|
7055
|
+
.insights-page .slist.wide { max-height: none; overflow: visible; gap: 10px; }
|
|
7056
|
+
.insights-page .slist.wide .srow { grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 18px; padding: 13px 16px; }
|
|
7057
|
+
.insights-page .slist.wide .srmain strong { -webkit-line-clamp: 1; font-size: 14px; }
|
|
7058
|
+
.insights-page .slist.wide .mph { max-width: 440px; height: 8px; }
|
|
7059
|
+
.insights-page .slist.wide .srstats { flex-wrap: nowrap; justify-content: flex-end; align-items: center; }
|
|
7060
|
+
.insights-page .slist.wide .srmsg { justify-self: end; }
|
|
7061
|
+
@media (max-width: 760px) {
|
|
7062
|
+
.insights-page .slist.wide .srow { grid-template-columns: 1fr; }
|
|
7063
|
+
.insights-page .slist.wide .srstats { justify-content: flex-start; flex-wrap: wrap; }
|
|
7064
|
+
}
|
|
7065
|
+
|
|
7066
|
+
/* session detail panel (full-width detail view) */
|
|
7067
|
+
.insight-detail { min-width: 0; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; }
|
|
6859
7068
|
.insight-list-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
|
|
6860
7069
|
.insight-list-head h3 { font-size: 13px; margin: 0; }
|
|
6861
7070
|
.insight-list-head span { font-size: 11px; color: var(--mut); }
|
|
@@ -7274,6 +7483,10 @@ body.cp-fx-slice main { animation: cp-fx-slice 0.32s steps(2, end); }
|
|
|
7274
7483
|
.insights-page .cbub-body .md-body { font-size: 13px; }
|
|
7275
7484
|
.insights-page .cbub-foot { display: flex; justify-content: flex-end; }
|
|
7276
7485
|
.insights-page .cbub-ops { background: var(--ins-recess); max-width: 88%; }
|
|
7486
|
+
/* agent narration ("说了什么") inside the agent bubble, above its operations */
|
|
7487
|
+
.insights-page .cbub-saywrap { display: flex; flex-direction: column; gap: 6px; margin: 2px 0 8px; }
|
|
7488
|
+
.insights-page .cbub-say { font-size: 13px; color: var(--fg); border-left: 2px solid var(--acc); padding-left: 9px; overflow-wrap: anywhere; word-break: break-word; }
|
|
7489
|
+
.insights-page .cbub-say .md-body { font-size: 13px; }
|
|
7277
7490
|
.insights-page .cbub-ops-list { display: flex; flex-direction: column; gap: 3px; }
|
|
7278
7491
|
.insights-page .cop-line { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 12px; padding: 3px 5px; border-radius: 6px; }
|
|
7279
7492
|
.insights-page .cop-line.clickable { cursor: pointer; }
|
|
@@ -7360,12 +7573,49 @@ body.cp-fx-slice main { animation: cp-fx-slice 0.32s steps(2, end); }
|
|
|
7360
7573
|
@keyframes insight-bar-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
|
|
7361
7574
|
.insights-page .insights-metrics { animation: insight-fade-up .35s ease both; }
|
|
7362
7575
|
.insights-page .insights-overview-grid { animation: insight-fade-up .35s ease both; animation-delay: .05s; }
|
|
7363
|
-
.insights-page .insight-
|
|
7576
|
+
.insights-page .insight-panel { animation: insight-fade-up .35s ease both; animation-delay: .1s; }
|
|
7364
7577
|
@media (prefers-reduced-motion: reduce) {
|
|
7365
7578
|
.insights-page .insights-metrics,
|
|
7366
7579
|
.insights-page .insights-overview-grid,
|
|
7367
|
-
.insights-page .insight-
|
|
7580
|
+
.insights-page .insight-panel,
|
|
7368
7581
|
.insights-page .hbfill { animation: none; }
|
|
7369
7582
|
.insights-page .srow { transition: none; }
|
|
7370
7583
|
.insights-page .srow:hover { transform: none; }
|
|
7371
7584
|
}
|
|
7585
|
+
|
|
7586
|
+
/* ─── 创建会话 modal ─────────────────────────────────────────────────────── */
|
|
7587
|
+
.create-session-btn { white-space: nowrap; }
|
|
7588
|
+
.create-session-modal .cs-card { padding: 20px; }
|
|
7589
|
+
.create-session-modal textarea {
|
|
7590
|
+
width: 100%;
|
|
7591
|
+
box-sizing: border-box;
|
|
7592
|
+
resize: vertical;
|
|
7593
|
+
font: inherit;
|
|
7594
|
+
padding: 8px 10px;
|
|
7595
|
+
border: 1px solid var(--border);
|
|
7596
|
+
border-radius: 6px;
|
|
7597
|
+
background: var(--surface-muted);
|
|
7598
|
+
color: var(--fg);
|
|
7599
|
+
}
|
|
7600
|
+
.create-session-modal fieldset {
|
|
7601
|
+
border: 1px solid var(--border);
|
|
7602
|
+
border-radius: 6px;
|
|
7603
|
+
margin: 12px 0;
|
|
7604
|
+
padding: 10px 12px;
|
|
7605
|
+
}
|
|
7606
|
+
.create-session-modal legend { padding: 0 6px; color: var(--muted); font-size: 13px; }
|
|
7607
|
+
.create-session-modal .cs-bots {
|
|
7608
|
+
display: grid;
|
|
7609
|
+
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
|
7610
|
+
gap: 4px 12px;
|
|
7611
|
+
}
|
|
7612
|
+
.create-session-modal .cs-bot { display: flex; align-items: center; gap: 6px; cursor: pointer; }
|
|
7613
|
+
.create-session-modal .cs-mode label,
|
|
7614
|
+
.create-session-modal .cs-column label { display: inline-flex; align-items: center; gap: 6px; margin-right: 16px; cursor: pointer; }
|
|
7615
|
+
.create-session-modal fieldset small { display: block; margin-top: 6px; color: var(--muted); font-size: 12px; }
|
|
7616
|
+
.create-session-modal .cs-lead-row { margin: 12px 0; }
|
|
7617
|
+
.create-session-modal .cs-lead-row select { margin: 4px 0; padding: 6px 8px; }
|
|
7618
|
+
.create-session-modal .cs-advanced { margin: 12px 0; }
|
|
7619
|
+
.create-session-modal .cs-advanced summary { cursor: pointer; color: var(--muted); }
|
|
7620
|
+
.create-session-modal .cs-empty { color: var(--muted); margin: 0; }
|
|
7621
|
+
.create-session-modal .cs-warn { color: var(--warn, #c60); }
|
package/dist/dashboard.js
CHANGED
|
@@ -48,6 +48,9 @@ import { installDashboardSkill, parseDashboardSkillInstallRequest, parseInstallL
|
|
|
48
48
|
import { botDefaultsPayload, botSummaryPayload } from './dashboard/bot-payload.js';
|
|
49
49
|
import { isValidRoleProfileId } from './services/role-profile-store.js';
|
|
50
50
|
import { mergeSafeInsightOverviews } from './services/insight/report.js';
|
|
51
|
+
import { readPlatformBinding } from './platform/binding.js';
|
|
52
|
+
import { startPlatformTunnelClient } from './platform/tunnel-client.js';
|
|
53
|
+
import { cleanupIdleSessions, parseIdleCleanupHours } from './dashboard/session-cleanup.js';
|
|
51
54
|
const SECRET_PATH = join(homedir(), '.botmux', '.dashboard-secret');
|
|
52
55
|
const TOKEN_PATH = join(homedir(), '.botmux', '.dashboard-token');
|
|
53
56
|
/** Per-daemon budget for the cross-daemon insight overview fan-out — bounds
|
|
@@ -792,6 +795,54 @@ const server = createServer(async (req, res) => {
|
|
|
792
795
|
});
|
|
793
796
|
return jsonRes(res, 200, { sessions });
|
|
794
797
|
}
|
|
798
|
+
if (req.method === 'POST' && url.pathname === '/api/sessions/cleanup-idle') {
|
|
799
|
+
let body;
|
|
800
|
+
try {
|
|
801
|
+
body = await readJsonBody(req);
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
805
|
+
}
|
|
806
|
+
const olderThanHours = parseIdleCleanupHours(body?.olderThanHours);
|
|
807
|
+
if (!olderThanHours)
|
|
808
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_threshold' });
|
|
809
|
+
// WYSIWYG: the UI scopes cleanup to the rows currently visible under the
|
|
810
|
+
// page filters and sends their sessionIds, so the closed set matches the
|
|
811
|
+
// confirmed count. We still hand the scoped rows to cleanupIdleSessions,
|
|
812
|
+
// which re-validates each is a genuine idle candidate — a stale/forged id
|
|
813
|
+
// can never close a non-idle session. Omitting sessionIds (e.g. an older
|
|
814
|
+
// client) falls back to a deployment-wide sweep. Cap the id set so a giant
|
|
815
|
+
// body can't blow up the Set build.
|
|
816
|
+
const idScope = Array.isArray(body?.sessionIds)
|
|
817
|
+
? new Set(body.sessionIds.slice(0, 10000).map(String))
|
|
818
|
+
: null;
|
|
819
|
+
const rows = aggregator.getSessions();
|
|
820
|
+
const scoped = idScope ? rows.filter(s => idScope.has(s.sessionId)) : rows;
|
|
821
|
+
const result = await cleanupIdleSessions(scoped, olderThanHours, async (s) => {
|
|
822
|
+
try {
|
|
823
|
+
const upstream = await proxyToDaemon(s.larkAppId, `/api/sessions/${encodeURIComponent(s.sessionId)}/close`, { method: 'POST' });
|
|
824
|
+
const text = await upstream.text();
|
|
825
|
+
let parsed = null;
|
|
826
|
+
try {
|
|
827
|
+
parsed = JSON.parse(text);
|
|
828
|
+
}
|
|
829
|
+
catch { /* tolerate */ }
|
|
830
|
+
// The daemon close route always replies 200 {ok:true}; treat anything
|
|
831
|
+
// else (incl. an unparseable/missing body) as a failure rather than a
|
|
832
|
+
// silent success.
|
|
833
|
+
const ok = upstream.ok && parsed?.ok === true;
|
|
834
|
+
return {
|
|
835
|
+
sessionId: s.sessionId,
|
|
836
|
+
ok,
|
|
837
|
+
error: ok ? undefined : (parsed?.error ?? `http_${upstream.status}`),
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
catch (e) {
|
|
841
|
+
return { sessionId: s.sessionId, ok: false, error: e?.message ?? String(e) };
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
return jsonRes(res, 200, result);
|
|
845
|
+
}
|
|
795
846
|
if (req.method === 'GET' && url.pathname === '/api/insights/summary') {
|
|
796
847
|
const limit = Math.min(Math.max(parseInt(url.searchParams.get('limit') ?? '200', 10) || 200, 1), 500);
|
|
797
848
|
// Per-daemon timeout + isolate failures: an upstream insight parse can be
|
|
@@ -1249,7 +1300,7 @@ const server = createServer(async (req, res) => {
|
|
|
1249
1300
|
return jsonRes(res, 200, { job });
|
|
1250
1301
|
}
|
|
1251
1302
|
let m;
|
|
1252
|
-
if (req.method === 'POST' && (m = url.pathname.match(/^\/api\/sessions\/([^/]+)\/(close|locate|resume)$/))) {
|
|
1303
|
+
if (req.method === 'POST' && (m = url.pathname.match(/^\/api\/sessions\/([^/]+)\/(close|locate|resume|restart|start)$/))) {
|
|
1253
1304
|
const sid = decodeURIComponent(m[1]);
|
|
1254
1305
|
const op = m[2];
|
|
1255
1306
|
const owner = aggregator.ownerOf(sid);
|
|
@@ -1969,7 +2020,8 @@ const server = createServer(async (req, res) => {
|
|
|
1969
2020
|
return;
|
|
1970
2021
|
}
|
|
1971
2022
|
// PUT /api/bots/:appId/grant-prefs — proxy to that bot's daemon. Body carries
|
|
1972
|
-
// any subset of `{ restrictGrantCommands?: boolean,
|
|
2023
|
+
// any subset of `{ restrictGrantCommands?: boolean, autoGrantRequestCards?: boolean,
|
|
2024
|
+
// messageQuotaDefaultLimit?: number|null }`.
|
|
1973
2025
|
let mBotGrantPrefs;
|
|
1974
2026
|
if (req.method === 'PUT' && (mBotGrantPrefs = url.pathname.match(/^\/api\/bots\/([^/]+)\/grant-prefs$/))) {
|
|
1975
2027
|
const appId = decodeURIComponent(mBotGrantPrefs[1]);
|
|
@@ -2097,6 +2149,133 @@ const server = createServer(async (req, res) => {
|
|
|
2097
2149
|
res.end(upstreamJson ? JSON.stringify(upstreamJson) : upstreamText);
|
|
2098
2150
|
return;
|
|
2099
2151
|
}
|
|
2152
|
+
// Dashboard「创建会话」:建飞书群 + 拉选中的 bot,然后按协作模式给各 bot 拉起/暂存
|
|
2153
|
+
// 一条 chat-scope 会话。一起开工=每个被选 bot 各起一条;lead 分配=只起 lead,由它
|
|
2154
|
+
// 在群里 @ 拉起 sub bot。in_progress=立即开跑;backlog=入待办池(parked,等激活)。
|
|
2155
|
+
if (req.method === 'POST' && url.pathname === '/api/sessions/create') {
|
|
2156
|
+
let parsed;
|
|
2157
|
+
try {
|
|
2158
|
+
const chunks = [];
|
|
2159
|
+
for await (const c of req)
|
|
2160
|
+
chunks.push(c);
|
|
2161
|
+
parsed = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
|
|
2162
|
+
}
|
|
2163
|
+
catch {
|
|
2164
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
2165
|
+
}
|
|
2166
|
+
const content = typeof parsed.content === 'string' ? parsed.content.replace(/\s+$/u, '') : '';
|
|
2167
|
+
if (!content.trim())
|
|
2168
|
+
return jsonRes(res, 400, { ok: false, error: 'empty_content' });
|
|
2169
|
+
if (content.length > 8000)
|
|
2170
|
+
return jsonRes(res, 400, { ok: false, error: 'content_too_long' });
|
|
2171
|
+
const selectedIds = Array.isArray(parsed.larkAppIds)
|
|
2172
|
+
? Array.from(new Set(parsed.larkAppIds.filter((x) => typeof x === 'string')))
|
|
2173
|
+
: [];
|
|
2174
|
+
if (selectedIds.length === 0)
|
|
2175
|
+
return jsonRes(res, 400, { ok: false, error: 'larkAppIds_required' });
|
|
2176
|
+
const mode = parsed.mode === 'lead' ? 'lead' : parsed.mode === 'all' ? 'all' : null;
|
|
2177
|
+
if (!mode)
|
|
2178
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_mode' });
|
|
2179
|
+
const column = parsed.column === 'backlog' ? 'backlog' : parsed.column === 'in_progress' ? 'in_progress' : null;
|
|
2180
|
+
if (!column)
|
|
2181
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_column' });
|
|
2182
|
+
const bindWorkingDir = typeof parsed.bindWorkingDir === 'string' && parsed.bindWorkingDir.trim()
|
|
2183
|
+
? parsed.bindWorkingDir.trim() : undefined;
|
|
2184
|
+
const name = typeof parsed.name === 'string' && parsed.name.trim() ? parsed.name.trim().slice(0, 60) : undefined;
|
|
2185
|
+
// 解析 creator:lead 模式 = lead bot;一起开工 = pickCreatorForGroup 在选中里挑一个在线的。
|
|
2186
|
+
let creatorLarkAppId;
|
|
2187
|
+
if (mode === 'lead') {
|
|
2188
|
+
const leadLarkAppId = typeof parsed.leadLarkAppId === 'string' ? parsed.leadLarkAppId : '';
|
|
2189
|
+
if (!leadLarkAppId || !selectedIds.includes(leadLarkAppId)) {
|
|
2190
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_lead' });
|
|
2191
|
+
}
|
|
2192
|
+
if (!registry.getByAppId(leadLarkAppId))
|
|
2193
|
+
return jsonRes(res, 503, { ok: false, error: 'lead_offline' });
|
|
2194
|
+
creatorLarkAppId = leadLarkAppId;
|
|
2195
|
+
}
|
|
2196
|
+
else {
|
|
2197
|
+
const pick = pickCreatorForGroup(selectedIds, (id) => {
|
|
2198
|
+
const d = registry.getByAppId(id);
|
|
2199
|
+
return d ? { larkAppId: d.larkAppId, resolvedAllowedUsers: d.resolvedAllowedUsers ?? [] } : undefined;
|
|
2200
|
+
});
|
|
2201
|
+
if (!pick)
|
|
2202
|
+
return jsonRes(res, 503, { ok: false, error: 'no_online_daemon' });
|
|
2203
|
+
creatorLarkAppId = pick.creatorLarkAppId;
|
|
2204
|
+
}
|
|
2205
|
+
// creator 作用域里的操作者 open_id(首个 ou_ allowedUser)——用于邀请进群 + 转群主 + @通知。
|
|
2206
|
+
// 同时取 on_(union_id,租户内跨 app 稳定)做兜底邀请:lead 模式强制 creator=lead,
|
|
2207
|
+
// 万一 lead 的 allowlist 没有 ou_ 条目,open_id 解析不到、操作者就进不了群——union_id
|
|
2208
|
+
// 不受 app 作用域影响,仍能把人拉进来(createGroupWithBots 走 ownerUnionIds 通道)。
|
|
2209
|
+
const creatorDesc = registry.getByAppId(creatorLarkAppId);
|
|
2210
|
+
const allowed = creatorDesc.resolvedAllowedUsers ?? [];
|
|
2211
|
+
const userOpenId = allowed.find(u => u.startsWith('ou_'));
|
|
2212
|
+
const ownerUnionIds = allowed.filter(u => u.startsWith('on_'));
|
|
2213
|
+
// 建群(拉所有选中 bot + 邀请操作者 + 转群主 + @通知 + 可选绑 oncall 工作目录)。
|
|
2214
|
+
let groupResp = null;
|
|
2215
|
+
try {
|
|
2216
|
+
const groupUpstream = await proxyToDaemon(creatorLarkAppId, '/api/groups/create', {
|
|
2217
|
+
method: 'POST',
|
|
2218
|
+
headers: { 'content-type': 'application/json' },
|
|
2219
|
+
body: JSON.stringify({
|
|
2220
|
+
name,
|
|
2221
|
+
larkAppIds: selectedIds,
|
|
2222
|
+
userOpenIds: userOpenId ? [userOpenId] : [],
|
|
2223
|
+
ownerUnionIds,
|
|
2224
|
+
transferOwnerTo: userOpenId,
|
|
2225
|
+
notifyOwnerOpenId: userOpenId,
|
|
2226
|
+
bindWorkingDir,
|
|
2227
|
+
}),
|
|
2228
|
+
});
|
|
2229
|
+
groupResp = await groupUpstream.json().catch(() => null);
|
|
2230
|
+
if (!groupUpstream.ok || !groupResp?.ok || typeof groupResp.chatId !== 'string') {
|
|
2231
|
+
return jsonRes(res, 502, { ok: false, error: groupResp?.error ?? `group_create_http_${groupUpstream.status}` });
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
catch {
|
|
2235
|
+
return jsonRes(res, 502, { ok: false, error: 'group_create_proxy_failed' });
|
|
2236
|
+
}
|
|
2237
|
+
const chatId = groupResp.chatId;
|
|
2238
|
+
const invalidBotIds = Array.isArray(groupResp.invalidBotIds) ? groupResp.invalidBotIds : [];
|
|
2239
|
+
// spawn 目标:lead 模式只有 lead;一起开工是所有成功入群的选中 bot。
|
|
2240
|
+
const joinedIds = selectedIds.filter(id => !invalidBotIds.includes(id) && !!registry.getByAppId(id));
|
|
2241
|
+
const targets = mode === 'lead'
|
|
2242
|
+
? (joinedIds.includes(creatorLarkAppId) ? [creatorLarkAppId] : [])
|
|
2243
|
+
: joinedIds;
|
|
2244
|
+
if (targets.length === 0) {
|
|
2245
|
+
return jsonRes(res, 200, { ok: true, chatId, shareLink: groupResp.shareLink, spawned: [], failed: [], warning: 'no_spawn_target' });
|
|
2246
|
+
}
|
|
2247
|
+
const bots = liveBots();
|
|
2248
|
+
const nameOf = (id) => bots.find(b => b.larkAppId === id)?.botName ?? id;
|
|
2249
|
+
const spawned = [];
|
|
2250
|
+
const failed = [];
|
|
2251
|
+
await Promise.all(targets.map(async (appId) => {
|
|
2252
|
+
const role = mode === 'lead' ? 'lead' : (targets.length > 1 ? 'collab' : 'solo');
|
|
2253
|
+
// lead 的 coworker = 所有 sub(除自己);collab 的 coworker = 其它并列 bot(除自己)。
|
|
2254
|
+
const coworkerIds = (mode === 'lead' ? selectedIds : targets).filter(id => id !== appId);
|
|
2255
|
+
const coworkers = coworkerIds.map(id => ({ name: nameOf(id) }));
|
|
2256
|
+
try {
|
|
2257
|
+
const up = await proxyToDaemon(appId, '/api/sessions/spawn', {
|
|
2258
|
+
method: 'POST',
|
|
2259
|
+
headers: { 'content-type': 'application/json' },
|
|
2260
|
+
body: JSON.stringify({
|
|
2261
|
+
chatId, content, column, role, coworkers,
|
|
2262
|
+
postBanner: appId === creatorLarkAppId,
|
|
2263
|
+
}),
|
|
2264
|
+
});
|
|
2265
|
+
const b = await up.json().catch(() => null);
|
|
2266
|
+
if (up.ok && b?.ok)
|
|
2267
|
+
spawned.push(appId);
|
|
2268
|
+
else
|
|
2269
|
+
failed.push({ larkAppId: appId, error: b?.error ?? `http_${up.status}` });
|
|
2270
|
+
}
|
|
2271
|
+
catch (e) {
|
|
2272
|
+
failed.push({ larkAppId: appId, error: e?.message ?? String(e) });
|
|
2273
|
+
}
|
|
2274
|
+
}));
|
|
2275
|
+
return jsonRes(res, 200, {
|
|
2276
|
+
ok: true, chatId, shareLink: groupResp.shareLink, mode, column, spawned, failed,
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2100
2279
|
// Public SSE — relays aggregator's listener events
|
|
2101
2280
|
if (req.method === 'GET' && url.pathname === '/events') {
|
|
2102
2281
|
res.writeHead(200, {
|
|
@@ -2155,6 +2334,7 @@ listenWithProbe({
|
|
|
2155
2334
|
logger.warn(`[dashboard] Failed to persist port to ${PORT_PATH}: ${e.message}`);
|
|
2156
2335
|
}
|
|
2157
2336
|
logger.info(`[dashboard] listening on ${config.dashboard.host}:${port}`);
|
|
2337
|
+
startPlatformTunnelIfBound();
|
|
2158
2338
|
}).catch((err) => {
|
|
2159
2339
|
logger.error(`[dashboard] could not bind near ${config.dashboard.host}:${config.dashboard.port} after probing — set BOTMUX_DASHBOARD_PORT to a free port. ${err.message}`);
|
|
2160
2340
|
process.exit(1);
|
|
@@ -2169,12 +2349,43 @@ const federationSync = setInterval(() => {
|
|
|
2169
2349
|
.catch(() => { });
|
|
2170
2350
|
}, 2 * 60 * 1000);
|
|
2171
2351
|
federationSync.unref();
|
|
2352
|
+
// 中心化平台隧道(已绑定才启动;每台机器一个,跑在 dashboard 进程里)
|
|
2353
|
+
let platformTunnel = null;
|
|
2354
|
+
function readBotmuxVersion() {
|
|
2355
|
+
try {
|
|
2356
|
+
const pkg = JSON.parse(readFileSync(join(dirname(__dirname), 'package.json'), 'utf8'));
|
|
2357
|
+
return pkg.version || 'unknown';
|
|
2358
|
+
}
|
|
2359
|
+
catch {
|
|
2360
|
+
return 'unknown';
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
function startPlatformTunnelIfBound() {
|
|
2364
|
+
try {
|
|
2365
|
+
const binding = readPlatformBinding();
|
|
2366
|
+
if (!binding)
|
|
2367
|
+
return;
|
|
2368
|
+
const version = readBotmuxVersion();
|
|
2369
|
+
platformTunnel = startPlatformTunnelClient({
|
|
2370
|
+
binding,
|
|
2371
|
+
getDashboardPort: () => boundDashboardPort,
|
|
2372
|
+
getDashboardToken: () => activeToken,
|
|
2373
|
+
getVersion: () => version,
|
|
2374
|
+
log: (msg, extra) => logger.info(`[platform-tunnel] ${msg}${extra ? ' ' + JSON.stringify(extra) : ''}`),
|
|
2375
|
+
});
|
|
2376
|
+
logger.info(`[platform-tunnel] 绑定到 ${binding.platformUrl},启动隧道`);
|
|
2377
|
+
}
|
|
2378
|
+
catch (e) {
|
|
2379
|
+
logger.warn(`[platform-tunnel] 启动失败: ${e.message}`);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2172
2382
|
// Graceful shutdown
|
|
2173
2383
|
function shutdown() {
|
|
2174
2384
|
for (const off of subs.values())
|
|
2175
2385
|
off();
|
|
2176
2386
|
subs.clear();
|
|
2177
2387
|
registry.stop();
|
|
2388
|
+
platformTunnel?.stop();
|
|
2178
2389
|
server.close(() => process.exit(0));
|
|
2179
2390
|
// Hard-exit fallback after 5s
|
|
2180
2391
|
setTimeout(() => process.exit(0), 5_000).unref();
|