botmux 2.86.0 → 2.87.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 +26 -0
- package/README.md +26 -0
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +12 -1
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/pty-backend.d.ts.map +1 -1
- package/dist/adapters/backend/pty-backend.js +4 -1
- package/dist/adapters/backend/pty-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +22 -9
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/backend/types.d.ts +9 -0
- package/dist/adapters/backend/types.d.ts.map +1 -1
- package/dist/adapters/backend/types.js.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +1 -1
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/adapters/cli/codex.d.ts.map +1 -1
- package/dist/adapters/cli/codex.js +8 -5
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts +2 -1
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +25 -3
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/bot-registry.d.ts +10 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +6 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +362 -16
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +6 -4
- 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 +45 -0
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/maintenance.d.ts +18 -0
- package/dist/core/maintenance.d.ts.map +1 -1
- package/dist/core/maintenance.js +58 -30
- package/dist/core/maintenance.js.map +1 -1
- package/dist/core/per-bot-env.d.ts +38 -0
- package/dist/core/per-bot-env.d.ts.map +1 -0
- package/dist/core/per-bot-env.js +79 -0
- package/dist/core/per-bot-env.js.map +1 -0
- package/dist/core/session-manager.d.ts +3 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +70 -6
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/skills/discovery.d.ts +7 -0
- package/dist/core/skills/discovery.d.ts.map +1 -1
- package/dist/core/skills/discovery.js +51 -0
- package/dist/core/skills/discovery.js.map +1 -1
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +5 -2
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/update-check.d.ts +67 -0
- package/dist/core/update-check.d.ts.map +1 -0
- package/dist/core/update-check.js +159 -0
- package/dist/core/update-check.js.map +1 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +4 -0
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +29 -6
- 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/skill-install-request.d.ts +10 -0
- package/dist/dashboard/skill-install-request.d.ts.map +1 -1
- package/dist/dashboard/skill-install-request.js +17 -0
- package/dist/dashboard/skill-install-request.js.map +1 -1
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +29 -1
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +62 -1
- 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 +128 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/settings.d.ts.map +1 -1
- package/dist/dashboard/web/settings.js +289 -0
- package/dist/dashboard/web/settings.js.map +1 -1
- package/dist/dashboard/web/skills.d.ts.map +1 -1
- package/dist/dashboard/web/skills.js +183 -10
- package/dist/dashboard/web/skills.js.map +1 -1
- package/dist/dashboard/web/whiteboards.d.ts +2 -0
- package/dist/dashboard/web/whiteboards.d.ts.map +1 -0
- package/dist/dashboard/web/whiteboards.js +267 -0
- package/dist/dashboard/web/whiteboards.js.map +1 -0
- package/dist/dashboard-web/app.js +774 -613
- package/dist/dashboard-web/index.html +1 -0
- package/dist/dashboard-web/style.css +271 -1
- package/dist/dashboard.js +259 -3
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +6 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +12 -0
- package/dist/global-config.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +7 -3
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/services/bot-config-store.d.ts.map +1 -1
- package/dist/services/bot-config-store.js +19 -0
- package/dist/services/bot-config-store.js.map +1 -1
- package/dist/services/resumable-session-discovery.d.ts.map +1 -1
- package/dist/services/resumable-session-discovery.js +21 -4
- package/dist/services/resumable-session-discovery.js.map +1 -1
- package/dist/services/skill-registry-store.d.ts +1 -0
- package/dist/services/skill-registry-store.d.ts.map +1 -1
- package/dist/services/skill-registry-store.js +32 -0
- package/dist/services/skill-registry-store.js.map +1 -1
- package/dist/services/whiteboard-store.d.ts +67 -0
- package/dist/services/whiteboard-store.d.ts.map +1 -0
- package/dist/services/whiteboard-store.js +450 -0
- package/dist/services/whiteboard-store.js.map +1 -0
- package/dist/setup/bot-config-editor.d.ts +8 -0
- package/dist/setup/bot-config-editor.d.ts.map +1 -1
- package/dist/setup/bot-config-editor.js +9 -0
- package/dist/setup/bot-config-editor.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +118 -0
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/install-diagnostics.d.ts +52 -0
- package/dist/utils/install-diagnostics.d.ts.map +1 -0
- package/dist/utils/install-diagnostics.js +155 -0
- package/dist/utils/install-diagnostics.js.map +1 -0
- package/dist/utils/install-info.d.ts +4 -0
- package/dist/utils/install-info.d.ts.map +1 -1
- package/dist/utils/install-info.js +6 -0
- package/dist/utils/install-info.js.map +1 -1
- package/dist/worker.js +12 -0
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
<a href="#/workflows" data-route="workflows"><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="3.4" cy="8" r="1.9"/><circle cx="12.6" cy="3.6" r="1.9"/><circle cx="12.6" cy="12.4" r="1.9"/><path d="M5.2 7.2l5.6-2.8M5.2 8.8l5.6 2.8"/></svg><span data-i18n="nav.workflows">工作流(beta)</span></a>
|
|
26
26
|
<a href="#/groups" data-route="groups"><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="5.6" cy="5.8" r="2.4"/><circle cx="11" cy="6.8" r="1.9"/><path d="M1.8 13.2c.5-2.4 2-3.6 3.8-3.6s3.3 1.2 3.8 3.6M9.8 12.6c.4-1.7 1.5-2.6 2.8-2.6 1 0 1.9.5 2.4 1.6"/></svg><span data-i18n="nav.groups">群组</span></a>
|
|
27
27
|
<a href="#/schedules" data-route="schedules"><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="6.2"/><path d="M8 4.5V8l2.4 1.6"/></svg><span data-i18n="nav.schedules">定时</span></a>
|
|
28
|
+
<a href="#/whiteboards" data-route="whiteboards"><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2.2" y="2.2" width="11.6" height="11.6" rx="2"/><path d="M4.8 5.2h6.4M4.8 8h6.4M4.8 10.8h4"/></svg><span>白板</span></a>
|
|
28
29
|
<a href="#/roles" data-route="roles"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 1.8l5.2 2v3.4c0 3.4-2.2 5.9-5.2 7-3-1.1-5.2-3.6-5.2-7V3.8z"/><path d="M5.8 8l1.6 1.6 2.8-3"/></svg><span data-i18n="nav.roles">角色管理</span></a>
|
|
29
30
|
<a href="#/bot-defaults" data-route="bot-defaults"><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2.5" y="5" width="11" height="8" rx="2"/><circle cx="5.8" cy="9" r="1"/><circle cx="10.2" cy="9" r="1"/><path d="M8 5V2.5M5.5 13v1.2M10.5 13v1.2"/></svg><span data-i18n="nav.botDefaults">Bot 配置</span></a>
|
|
30
31
|
<a href="#/skills" data-route="skills"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 2.5h10v3H3zM3 7h10v6.5H3z"/><path d="M5.4 9.2h5.2M5.4 11.2h3.8"/></svg><span data-i18n="nav.skills">Skills</span></a>
|
|
@@ -157,6 +157,20 @@ a { color: inherit; }
|
|
|
157
157
|
color: var(--accent-strong);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/* Red dot on the Settings nav when a newer botmux version is available. */
|
|
161
|
+
.sidebar-nav a.nav-has-update { position: relative; }
|
|
162
|
+
.sidebar-nav a.nav-has-update::after {
|
|
163
|
+
content: "";
|
|
164
|
+
position: absolute;
|
|
165
|
+
top: 7px;
|
|
166
|
+
right: 8px;
|
|
167
|
+
width: 7px;
|
|
168
|
+
height: 7px;
|
|
169
|
+
border-radius: 50%;
|
|
170
|
+
background: var(--danger);
|
|
171
|
+
box-shadow: 0 0 0 2px var(--surface);
|
|
172
|
+
}
|
|
173
|
+
|
|
160
174
|
.sidebar-foot {
|
|
161
175
|
margin-top: auto;
|
|
162
176
|
padding: 10px 8px 4px;
|
|
@@ -2228,6 +2242,21 @@ input.row-select,
|
|
|
2228
2242
|
}
|
|
2229
2243
|
.bd-section-note { margin: 6px 0 0; color: var(--muted); font-size: 12px; line-height: 1.5; }
|
|
2230
2244
|
|
|
2245
|
+
/* ── Version & update card ── */
|
|
2246
|
+
.update-version { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; }
|
|
2247
|
+
.update-badge { font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 999px; }
|
|
2248
|
+
.update-badge-new { color: var(--accent-strong); background: var(--accent-soft); }
|
|
2249
|
+
.update-badge-ok { color: var(--success); background: var(--success-soft); }
|
|
2250
|
+
.update-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
|
2251
|
+
.update-install-list { margin: 6px 0 0; padding-left: 18px; font-size: 12px; line-height: 1.6; }
|
|
2252
|
+
.update-install-list code { font-size: 11px; }
|
|
2253
|
+
.update-install-list small { color: var(--muted); }
|
|
2254
|
+
.update-changelog { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; }
|
|
2255
|
+
.update-release { border: 1px solid var(--border-soft); border-radius: 8px; padding: 8px 12px; background: var(--surface-muted); }
|
|
2256
|
+
.update-release > summary { cursor: pointer; font-size: 13px; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
|
2257
|
+
.update-release > summary small { color: var(--muted); font-size: 11px; }
|
|
2258
|
+
.update-release-body { margin: 8px 0 0; white-space: pre-wrap; word-break: break-word; font-size: 12px; line-height: 1.5; max-height: 320px; overflow: auto; color: var(--fg); }
|
|
2259
|
+
|
|
2231
2260
|
/* A lighter sub-group inside a bd-section (e.g. 主动开工 lives under 新群 Oncall).
|
|
2232
2261
|
Separated by a soft top rule + indent so it reads as "part of this block". */
|
|
2233
2262
|
.bd-subsection {
|
|
@@ -4179,6 +4208,19 @@ button.contrast:hover { background: var(--danger-soft); border-color: var(--dang
|
|
|
4179
4208
|
min-width: 0;
|
|
4180
4209
|
}
|
|
4181
4210
|
|
|
4211
|
+
.skills-source-control {
|
|
4212
|
+
display: grid;
|
|
4213
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
4214
|
+
gap: 8px;
|
|
4215
|
+
align-items: center;
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
.skills-source-control button {
|
|
4219
|
+
min-height: 36px;
|
|
4220
|
+
padding: 0 13px;
|
|
4221
|
+
white-space: nowrap;
|
|
4222
|
+
}
|
|
4223
|
+
|
|
4182
4224
|
.skills-main-panel,
|
|
4183
4225
|
.skills-bot-grid {
|
|
4184
4226
|
display: grid;
|
|
@@ -4323,7 +4365,27 @@ button.contrast:hover { background: var(--danger-soft); border-color: var(--dang
|
|
|
4323
4365
|
line-height: 1.45;
|
|
4324
4366
|
overflow-wrap: anywhere;
|
|
4325
4367
|
}
|
|
4326
|
-
.skills-row small {
|
|
4368
|
+
.skills-row small {
|
|
4369
|
+
color: var(--muted);
|
|
4370
|
+
font-family: var(--mono);
|
|
4371
|
+
font-size: 11px;
|
|
4372
|
+
overflow-wrap: anywhere;
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4375
|
+
.skills-source-badge {
|
|
4376
|
+
display: inline-flex;
|
|
4377
|
+
align-items: center;
|
|
4378
|
+
width: fit-content;
|
|
4379
|
+
max-width: 100%;
|
|
4380
|
+
min-height: 22px;
|
|
4381
|
+
margin-top: 4px;
|
|
4382
|
+
padding: 2px 7px;
|
|
4383
|
+
border: 1px solid var(--border-soft);
|
|
4384
|
+
border-radius: 999px;
|
|
4385
|
+
background: var(--surface-muted);
|
|
4386
|
+
overflow: hidden;
|
|
4387
|
+
text-overflow: ellipsis;
|
|
4388
|
+
}
|
|
4327
4389
|
|
|
4328
4390
|
.skills-card-actions {
|
|
4329
4391
|
display: grid;
|
|
@@ -4758,6 +4820,213 @@ button.contrast:hover { background: var(--danger-soft); border-color: var(--dang
|
|
|
4758
4820
|
align-self: start;
|
|
4759
4821
|
}
|
|
4760
4822
|
|
|
4823
|
+
.skills-discovery-dialog {
|
|
4824
|
+
width: min(860px, calc(100vw - 44px));
|
|
4825
|
+
max-height: min(760px, calc(100vh - 64px));
|
|
4826
|
+
padding: 0;
|
|
4827
|
+
border: 1px solid var(--border-soft);
|
|
4828
|
+
border-radius: 14px;
|
|
4829
|
+
background: var(--surface);
|
|
4830
|
+
box-shadow: var(--modal-shadow);
|
|
4831
|
+
color: var(--fg);
|
|
4832
|
+
}
|
|
4833
|
+
|
|
4834
|
+
.skills-discovery-dialog::backdrop {
|
|
4835
|
+
background: color-mix(in srgb, #0b1524 42%, transparent);
|
|
4836
|
+
backdrop-filter: blur(4px);
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
.skills-discovery-dialog article {
|
|
4840
|
+
display: grid;
|
|
4841
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
4842
|
+
max-height: inherit;
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4845
|
+
.skills-discovery-dialog header {
|
|
4846
|
+
display: grid;
|
|
4847
|
+
gap: 5px;
|
|
4848
|
+
padding: 18px 20px 12px;
|
|
4849
|
+
border-bottom: 1px solid var(--border-soft);
|
|
4850
|
+
}
|
|
4851
|
+
|
|
4852
|
+
.skills-discovery-dialog h3,
|
|
4853
|
+
.skills-discovery-dialog p {
|
|
4854
|
+
margin: 0;
|
|
4855
|
+
}
|
|
4856
|
+
|
|
4857
|
+
.skills-discovery-dialog h3 {
|
|
4858
|
+
font-size: 17px;
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4861
|
+
.skills-discovery-dialog p {
|
|
4862
|
+
color: var(--muted);
|
|
4863
|
+
font-size: 12.5px;
|
|
4864
|
+
line-height: 1.5;
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4867
|
+
.skills-discovery-body {
|
|
4868
|
+
display: grid;
|
|
4869
|
+
gap: 12px;
|
|
4870
|
+
min-height: 0;
|
|
4871
|
+
max-height: 520px;
|
|
4872
|
+
overflow: auto;
|
|
4873
|
+
padding: 16px 20px;
|
|
4874
|
+
}
|
|
4875
|
+
|
|
4876
|
+
.skills-discovery-tabs {
|
|
4877
|
+
display: flex;
|
|
4878
|
+
gap: 8px;
|
|
4879
|
+
align-items: center;
|
|
4880
|
+
min-height: 40px;
|
|
4881
|
+
overflow-x: auto;
|
|
4882
|
+
overflow-y: hidden;
|
|
4883
|
+
padding-bottom: 2px;
|
|
4884
|
+
scrollbar-width: thin;
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
.skills-discovery-tabs button {
|
|
4888
|
+
display: grid;
|
|
4889
|
+
grid-template-columns: auto auto;
|
|
4890
|
+
gap: 8px;
|
|
4891
|
+
align-items: center;
|
|
4892
|
+
flex: 0 0 auto;
|
|
4893
|
+
min-height: 38px;
|
|
4894
|
+
padding: 0 12px;
|
|
4895
|
+
border-radius: 999px;
|
|
4896
|
+
text-transform: uppercase;
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4899
|
+
.skills-discovery-tabs button.selected {
|
|
4900
|
+
border-color: color-mix(in srgb, var(--accent) 72%, var(--border-soft));
|
|
4901
|
+
background: color-mix(in srgb, var(--accent) 10%, var(--surface));
|
|
4902
|
+
color: var(--fg);
|
|
4903
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);
|
|
4904
|
+
}
|
|
4905
|
+
|
|
4906
|
+
.skills-discovery-tabs strong {
|
|
4907
|
+
font-size: 12px;
|
|
4908
|
+
}
|
|
4909
|
+
|
|
4910
|
+
.skills-discovery-tabs small {
|
|
4911
|
+
color: var(--muted);
|
|
4912
|
+
font-size: 11px;
|
|
4913
|
+
font-weight: 700;
|
|
4914
|
+
text-transform: none;
|
|
4915
|
+
}
|
|
4916
|
+
|
|
4917
|
+
.skills-discovery-path {
|
|
4918
|
+
min-width: 0;
|
|
4919
|
+
padding: 7px 10px;
|
|
4920
|
+
border-radius: 8px;
|
|
4921
|
+
background: color-mix(in srgb, var(--surface-muted) 70%, transparent);
|
|
4922
|
+
}
|
|
4923
|
+
|
|
4924
|
+
.skills-discovery-path code {
|
|
4925
|
+
display: block;
|
|
4926
|
+
overflow: hidden;
|
|
4927
|
+
color: var(--muted);
|
|
4928
|
+
font-size: 12px;
|
|
4929
|
+
text-overflow: ellipsis;
|
|
4930
|
+
white-space: nowrap;
|
|
4931
|
+
}
|
|
4932
|
+
|
|
4933
|
+
.skills-discovery-group {
|
|
4934
|
+
display: grid;
|
|
4935
|
+
gap: 8px;
|
|
4936
|
+
}
|
|
4937
|
+
|
|
4938
|
+
.skills-discovery-group[hidden] {
|
|
4939
|
+
display: none;
|
|
4940
|
+
}
|
|
4941
|
+
|
|
4942
|
+
.skills-discovery-group header {
|
|
4943
|
+
display: grid;
|
|
4944
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
4945
|
+
gap: 8px;
|
|
4946
|
+
align-items: center;
|
|
4947
|
+
padding: 0;
|
|
4948
|
+
border: 0;
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4951
|
+
.skills-discovery-group strong {
|
|
4952
|
+
text-transform: uppercase;
|
|
4953
|
+
color: var(--fg);
|
|
4954
|
+
font-size: 12px;
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
.skills-discovery-group code {
|
|
4958
|
+
overflow: hidden;
|
|
4959
|
+
color: var(--muted);
|
|
4960
|
+
font-size: 11px;
|
|
4961
|
+
text-overflow: ellipsis;
|
|
4962
|
+
white-space: nowrap;
|
|
4963
|
+
}
|
|
4964
|
+
|
|
4965
|
+
.skills-discovery-list {
|
|
4966
|
+
display: grid;
|
|
4967
|
+
gap: 7px;
|
|
4968
|
+
}
|
|
4969
|
+
|
|
4970
|
+
.skills-discovery-row {
|
|
4971
|
+
display: grid;
|
|
4972
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
4973
|
+
gap: 10px;
|
|
4974
|
+
align-items: start;
|
|
4975
|
+
min-height: 54px;
|
|
4976
|
+
padding: 10px 12px;
|
|
4977
|
+
border: 1px solid var(--border-soft);
|
|
4978
|
+
border-radius: 8px;
|
|
4979
|
+
background: var(--surface-muted);
|
|
4980
|
+
cursor: pointer;
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
.skills-discovery-row input {
|
|
4984
|
+
margin-top: 3px;
|
|
4985
|
+
}
|
|
4986
|
+
|
|
4987
|
+
.skills-discovery-row span {
|
|
4988
|
+
display: grid;
|
|
4989
|
+
gap: 3px;
|
|
4990
|
+
min-width: 0;
|
|
4991
|
+
}
|
|
4992
|
+
|
|
4993
|
+
.skills-discovery-row strong {
|
|
4994
|
+
overflow: hidden;
|
|
4995
|
+
color: var(--fg);
|
|
4996
|
+
font-size: 13px;
|
|
4997
|
+
text-overflow: ellipsis;
|
|
4998
|
+
white-space: nowrap;
|
|
4999
|
+
}
|
|
5000
|
+
|
|
5001
|
+
.skills-discovery-row small {
|
|
5002
|
+
display: -webkit-box;
|
|
5003
|
+
-webkit-line-clamp: 2;
|
|
5004
|
+
-webkit-box-orient: vertical;
|
|
5005
|
+
overflow: hidden;
|
|
5006
|
+
color: var(--muted);
|
|
5007
|
+
font-size: 12px;
|
|
5008
|
+
line-height: 1.4;
|
|
5009
|
+
}
|
|
5010
|
+
|
|
5011
|
+
.skills-discovery-row em {
|
|
5012
|
+
align-self: center;
|
|
5013
|
+
color: var(--muted);
|
|
5014
|
+
font-size: 11px;
|
|
5015
|
+
font-style: normal;
|
|
5016
|
+
font-weight: 700;
|
|
5017
|
+
}
|
|
5018
|
+
|
|
5019
|
+
.skills-discovery-row.installed {
|
|
5020
|
+
opacity: 0.68;
|
|
5021
|
+
cursor: default;
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
.skills-discovery-dialog footer {
|
|
5025
|
+
justify-content: end;
|
|
5026
|
+
padding: 12px 20px 16px;
|
|
5027
|
+
border-top: 1px solid var(--border-soft);
|
|
5028
|
+
}
|
|
5029
|
+
|
|
4761
5030
|
.skills-policy-controls [data-bot-status] {
|
|
4762
5031
|
grid-column: 1 / -1;
|
|
4763
5032
|
}
|
|
@@ -4803,6 +5072,7 @@ button.contrast:hover { background: var(--danger-soft); border-color: var(--dang
|
|
|
4803
5072
|
}
|
|
4804
5073
|
.skills-side-rail,
|
|
4805
5074
|
.skills-install-grid,
|
|
5075
|
+
.skills-source-control,
|
|
4806
5076
|
.skills-section-head-row,
|
|
4807
5077
|
.skills-overview,
|
|
4808
5078
|
.skills-metric-strip,
|
package/dist/dashboard.js
CHANGED
|
@@ -27,15 +27,24 @@ import { BotOnboardingManager } from './dashboard/bot-onboarding.js';
|
|
|
27
27
|
import { CLI_SELECT_OPTIONS, resolveCliSelection, isTtadkWrapper, ttadkAcceptsModel, TTADK_DEFAULT_MODEL, TTADK_MODEL_SUGGESTIONS, } from './setup/cli-selection.js';
|
|
28
28
|
import { invalidWorkingDirs } from './utils/working-dir.js';
|
|
29
29
|
import { mergeDashboardConfig, mergeGlobalConfig, mergeMaintenanceConfig, parseMaintenancePatch, readGlobalConfig, setGlobalLocale } from './global-config.js';
|
|
30
|
+
import { deleteWhiteboard, listWhiteboards, readWhiteboard, whiteboardEnabled } from './services/whiteboard-store.js';
|
|
30
31
|
import { isLocale } from './i18n/types.js';
|
|
31
|
-
import { isLocalDevInstall } from './utils/install-info.js';
|
|
32
|
+
import { isLocalDevInstall, botmuxVersion } from './utils/install-info.js';
|
|
33
|
+
import { checkNode, detectBotmuxInstalls, resolveCurrentVersion } from './utils/install-diagnostics.js';
|
|
34
|
+
import { fetchLatestVersion, fetchReleasesSince, isNewerVersion } from './core/update-check.js';
|
|
35
|
+
import { GITHUB_REPO } from './core/restart-report.js';
|
|
36
|
+
import { spawnDetachedRestart, npmGlobalUpdateLockTarget } from './core/maintenance.js';
|
|
37
|
+
import { writeRestartIntent } from './services/restart-intent-store.js';
|
|
38
|
+
import { withFileLock } from './utils/file-lock.js';
|
|
39
|
+
import { spawn } from 'node:child_process';
|
|
32
40
|
import { listTeamReports, readTeamBoard, setTeamBoardEntry } from './services/team-board-store.js';
|
|
33
41
|
import { hd2dAssetPath, hd2dStatus, startHd2dDownload } from './dashboard/hd2d-assets.js';
|
|
34
|
-
import { readSkillRegistry, removeInstalledSkill, updateInstalledSkillAsync, } from './services/skill-registry-store.js';
|
|
42
|
+
import { installLocalSkillLinks, readSkillRegistry, removeInstalledSkill, updateInstalledSkillAsync, } from './services/skill-registry-store.js';
|
|
35
43
|
import { redactGitUrlCredentials } from './core/skills/sources.js';
|
|
36
44
|
import { loadBotConfigs } from './bot-registry.js';
|
|
45
|
+
import { discoverNativeCliSkillGroups } from './core/skills/discovery.js';
|
|
37
46
|
import { analyzeSkillReferences } from './core/skills/references.js';
|
|
38
|
-
import { installDashboardSkill, parseDashboardSkillInstallRequest } from './dashboard/skill-install-request.js';
|
|
47
|
+
import { installDashboardSkill, parseDashboardSkillInstallRequest, parseInstallLocalLinksSources, MAX_LOCAL_LINK_SOURCES } from './dashboard/skill-install-request.js';
|
|
39
48
|
import { botDefaultsPayload, botSummaryPayload } from './dashboard/bot-payload.js';
|
|
40
49
|
import { isValidRoleProfileId } from './services/role-profile-store.js';
|
|
41
50
|
import { mergeSafeInsightOverviews } from './services/insight/report.js';
|
|
@@ -116,6 +125,7 @@ function resolveDashboardSettings() {
|
|
|
116
125
|
repoPickerMode: global.repoPickerMode ?? 'all',
|
|
117
126
|
maintenance: global.maintenance ?? {},
|
|
118
127
|
localDevInstall: isLocalDevInstall(),
|
|
128
|
+
whiteboard: { enabled: global.whiteboard?.enabled === true },
|
|
119
129
|
};
|
|
120
130
|
}
|
|
121
131
|
async function readJsonBody(req) {
|
|
@@ -125,6 +135,67 @@ async function readJsonBody(req) {
|
|
|
125
135
|
const raw = Buffer.concat(chunks).toString('utf8').trim();
|
|
126
136
|
return raw ? JSON.parse(raw) : {};
|
|
127
137
|
}
|
|
138
|
+
/** Fast in-process guard against double-clicks within this dashboard process.
|
|
139
|
+
* Cross-process serialization against the maintenance auto-update (a different
|
|
140
|
+
* process) is handled separately by the shared file lock in the run route. */
|
|
141
|
+
let updateInFlight = false;
|
|
142
|
+
// Cache the upstream version/changelog lookups so the nav-badge check + the
|
|
143
|
+
// Settings card don't hammer the npm registry / GitHub on every page load.
|
|
144
|
+
// GitHub's unauthenticated API is only 60 req/h per IP, so caching the changelog
|
|
145
|
+
// also keeps us from exhausting it. Failures cache briefly so they self-heal.
|
|
146
|
+
const LATEST_TTL_MS = 30 * 60_000;
|
|
147
|
+
const CHANGELOG_TTL_MS = 15 * 60_000;
|
|
148
|
+
const FAILURE_TTL_MS = 60_000;
|
|
149
|
+
let latestVersionCache = null;
|
|
150
|
+
let changelogCache = null;
|
|
151
|
+
async function cachedLatestVersion(now = Date.now()) {
|
|
152
|
+
const ttl = latestVersionCache?.value ? LATEST_TTL_MS : FAILURE_TTL_MS;
|
|
153
|
+
if (latestVersionCache && now - latestVersionCache.at < ttl)
|
|
154
|
+
return latestVersionCache.value;
|
|
155
|
+
const value = await fetchLatestVersion();
|
|
156
|
+
latestVersionCache = { value, at: now };
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
async function cachedChangelog(current, now = Date.now()) {
|
|
160
|
+
const ttl = changelogCache?.value.ok ? CHANGELOG_TTL_MS : FAILURE_TTL_MS;
|
|
161
|
+
if (changelogCache && changelogCache.key === current && now - changelogCache.at < ttl)
|
|
162
|
+
return changelogCache.value;
|
|
163
|
+
const value = await fetchReleasesSince(current);
|
|
164
|
+
changelogCache = { key: current, value, at: now };
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Run `npm install -g botmux@latest` for the manual-update flow WITHOUT blocking
|
|
169
|
+
* the event loop (async spawn, not execSync — the dashboard must keep serving
|
|
170
|
+
* during the ~10-30s install). Resolves on exit 0; rejects with the tail of
|
|
171
|
+
* stdout/stderr on a non-zero exit, spawn error, or 3-minute timeout. Args are
|
|
172
|
+
* a fixed literal — no shell interpolation of untrusted input.
|
|
173
|
+
*/
|
|
174
|
+
function runNpmInstallLatest() {
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
const child = spawn('npm', ['install', '-g', 'botmux@latest'], {
|
|
177
|
+
env: process.env,
|
|
178
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
179
|
+
shell: process.platform === 'win32', // resolve npm.cmd on Windows
|
|
180
|
+
});
|
|
181
|
+
let tail = '';
|
|
182
|
+
const capture = (d) => { tail = (tail + d.toString()).slice(-2000); };
|
|
183
|
+
child.stdout?.on('data', capture);
|
|
184
|
+
child.stderr?.on('data', capture);
|
|
185
|
+
const timer = setTimeout(() => {
|
|
186
|
+
child.kill('SIGKILL');
|
|
187
|
+
reject(new Error('npm install timed out after 180s'));
|
|
188
|
+
}, 180_000);
|
|
189
|
+
child.on('error', (e) => { clearTimeout(timer); reject(e); });
|
|
190
|
+
child.on('exit', (code) => {
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
if (code === 0)
|
|
193
|
+
resolve();
|
|
194
|
+
else
|
|
195
|
+
reject(new Error(`npm exited ${code}: ${tail.trim().slice(-500)}`));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
128
199
|
/**
|
|
129
200
|
* Attach to one daemon: hydrate its sessions/schedules into the aggregator,
|
|
130
201
|
* THEN open the SSE subscription. Order matters — hydrating after subscribe
|
|
@@ -507,12 +578,33 @@ function sanitizeSkillForDashboard(skill) {
|
|
|
507
578
|
source: { ...skill.source, url: redactGitUrlCredentials(skill.source.url) },
|
|
508
579
|
};
|
|
509
580
|
}
|
|
581
|
+
function dashboardSkillCliIds() {
|
|
582
|
+
const ids = new Set();
|
|
583
|
+
try {
|
|
584
|
+
for (const cliId of configuredCliIds().values())
|
|
585
|
+
ids.add(cliId);
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
// Fall back to daemon descriptors below when persistent config is unavailable.
|
|
589
|
+
}
|
|
590
|
+
for (const bot of registry.list()) {
|
|
591
|
+
if (bot.cliId)
|
|
592
|
+
ids.add(bot.cliId);
|
|
593
|
+
}
|
|
594
|
+
return [...ids];
|
|
595
|
+
}
|
|
510
596
|
function dashboardSkillsPayload() {
|
|
511
597
|
const globalSkills = readGlobalConfig().skills ?? {};
|
|
598
|
+
const nativeSkillGroups = discoverNativeCliSkillGroups(dashboardSkillCliIds())
|
|
599
|
+
.map(group => ({
|
|
600
|
+
...group,
|
|
601
|
+
skills: group.skills.map(sanitizeSkillForDashboard),
|
|
602
|
+
}));
|
|
512
603
|
return {
|
|
513
604
|
skills: Object.values(readSkillRegistry().skills)
|
|
514
605
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
515
606
|
.map(sanitizeSkillForDashboard),
|
|
607
|
+
nativeSkillGroups,
|
|
516
608
|
trustProjectSkills: globalSkills.trustProjectSkills ?? 'off',
|
|
517
609
|
delivery: globalSkills.delivery ?? 'auto',
|
|
518
610
|
};
|
|
@@ -774,6 +866,16 @@ const server = createServer(async (req, res) => {
|
|
|
774
866
|
mergeGlobalConfig({ repoPickerMode: v });
|
|
775
867
|
touched = true;
|
|
776
868
|
}
|
|
869
|
+
if ('whiteboard' in body) {
|
|
870
|
+
const raw = body.whiteboard;
|
|
871
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
872
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_whiteboard' });
|
|
873
|
+
const wb = raw;
|
|
874
|
+
if (typeof wb.enabled !== 'boolean')
|
|
875
|
+
return jsonRes(res, 400, { ok: false, error: 'invalid_whiteboard_enabled' });
|
|
876
|
+
mergeGlobalConfig({ whiteboard: { enabled: wb.enabled } });
|
|
877
|
+
touched = true;
|
|
878
|
+
}
|
|
777
879
|
if ('maintenance' in body) {
|
|
778
880
|
const r = parseMaintenancePatch(body.maintenance);
|
|
779
881
|
if (!r.ok)
|
|
@@ -813,6 +915,96 @@ const server = createServer(async (req, res) => {
|
|
|
813
915
|
return jsonRes(res, 400, { ok: false, error: 'empty_patch' });
|
|
814
916
|
return jsonRes(res, 200, { ok: true, settings: resolveDashboardSettings() });
|
|
815
917
|
}
|
|
918
|
+
// ─── Version & manual update ─────────────────────────────────────────────
|
|
919
|
+
// `npm install -g` and a host restart are privileged: none of these paths
|
|
920
|
+
// are on PUBLIC_READ_PATHS, so decideDashboardAuth already 401s an
|
|
921
|
+
// unauthenticated caller (in both normal and public-read mode). The explicit
|
|
922
|
+
// `authed` guards on the two mutations are defense-in-depth for host actions.
|
|
923
|
+
if (req.method === 'GET' && url.pathname === '/api/update/status') {
|
|
924
|
+
const current = resolveCurrentVersion();
|
|
925
|
+
// Compare against the npm `latest` dist-tag (always stable; the update
|
|
926
|
+
// button installs `@latest`). isNewerVersion uses semver precedence, so a
|
|
927
|
+
// canary running AHEAD of the latest stable (e.g. 2.87.0-canary.0 vs
|
|
928
|
+
// 2.86.0) is NOT flagged behind — exactly the canary case we want.
|
|
929
|
+
const latest = await cachedLatestVersion();
|
|
930
|
+
return jsonRes(res, 200, {
|
|
931
|
+
current,
|
|
932
|
+
latest,
|
|
933
|
+
behind: !!latest && isNewerVersion(latest, current),
|
|
934
|
+
localDevInstall: isLocalDevInstall(),
|
|
935
|
+
node: checkNode(),
|
|
936
|
+
installs: detectBotmuxInstalls(),
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
if (req.method === 'GET' && url.pathname === '/api/update/changelog') {
|
|
940
|
+
const current = resolveCurrentVersion();
|
|
941
|
+
const result = await cachedChangelog(current);
|
|
942
|
+
return jsonRes(res, 200, {
|
|
943
|
+
current,
|
|
944
|
+
ok: result.ok,
|
|
945
|
+
rateLimited: result.rateLimited === true,
|
|
946
|
+
releases: result.releases,
|
|
947
|
+
releasesUrl: `https://github.com/${GITHUB_REPO}/releases`,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
if (req.method === 'POST' && url.pathname === '/api/update/run') {
|
|
951
|
+
if (!authed)
|
|
952
|
+
return jsonRes(res, 401, { ok: false, error: 'unauthorized' });
|
|
953
|
+
if (isLocalDevInstall())
|
|
954
|
+
return jsonRes(res, 400, { ok: false, error: 'local_dev_no_update' });
|
|
955
|
+
const node = checkNode();
|
|
956
|
+
if (!node.ok)
|
|
957
|
+
return jsonRes(res, 400, { ok: false, error: 'node_too_old', node });
|
|
958
|
+
if (updateInFlight)
|
|
959
|
+
return jsonRes(res, 409, { ok: false, error: 'update_in_flight' });
|
|
960
|
+
updateInFlight = true;
|
|
961
|
+
const oldVersion = botmuxVersion();
|
|
962
|
+
// Acquire the shared cross-process lock so a scheduled maintenance
|
|
963
|
+
// auto-update (running in the bot-0 daemon) can't `npm install -g` at the
|
|
964
|
+
// same time. `acquired` distinguishes "lock held by maintenance" (409)
|
|
965
|
+
// from "npm itself failed" (500). Short wait: don't block the request on a
|
|
966
|
+
// full in-progress install — report busy fast.
|
|
967
|
+
let acquired = false;
|
|
968
|
+
try {
|
|
969
|
+
await withFileLock(npmGlobalUpdateLockTarget(), async () => {
|
|
970
|
+
acquired = true;
|
|
971
|
+
await runNpmInstallLatest();
|
|
972
|
+
}, { maxWaitMs: 2_000 });
|
|
973
|
+
}
|
|
974
|
+
catch (e) {
|
|
975
|
+
if (!acquired)
|
|
976
|
+
return jsonRes(res, 409, { ok: false, error: 'update_in_flight' });
|
|
977
|
+
return jsonRes(res, 500, { ok: false, error: 'npm_failed', detail: e instanceof Error ? e.message : String(e) });
|
|
978
|
+
}
|
|
979
|
+
finally {
|
|
980
|
+
updateInFlight = false;
|
|
981
|
+
}
|
|
982
|
+
const newVersion = botmuxVersion();
|
|
983
|
+
return jsonRes(res, 200, { ok: true, oldVersion, newVersion, changed: newVersion !== oldVersion });
|
|
984
|
+
}
|
|
985
|
+
if (req.method === 'POST' && url.pathname === '/api/update/restart') {
|
|
986
|
+
if (!authed)
|
|
987
|
+
return jsonRes(res, 401, { ok: false, error: 'unauthorized' });
|
|
988
|
+
let body = {};
|
|
989
|
+
try {
|
|
990
|
+
const parsed = await readJsonBody(req);
|
|
991
|
+
if (parsed && typeof parsed === 'object')
|
|
992
|
+
body = parsed;
|
|
993
|
+
}
|
|
994
|
+
catch { /* empty / bad body → plain restart */ }
|
|
995
|
+
const upd = body.update && typeof body.update === 'object' ? body.update : null;
|
|
996
|
+
// After a manual update, leave an `update` breadcrumb so the fresh daemon
|
|
997
|
+
// DMs the owner the changelog (reuses the restart-report pipeline). A plain
|
|
998
|
+
// restart leaves none here; cmdRestart writes a `manual` breadcrumb itself.
|
|
999
|
+
if (upd && typeof upd.oldVersion === 'string' && typeof upd.newVersion === 'string' && upd.oldVersion !== upd.newVersion) {
|
|
1000
|
+
try {
|
|
1001
|
+
writeRestartIntent({ kind: 'update', oldVersion: upd.oldVersion, newVersion: upd.newVersion, at: new Date().toISOString() });
|
|
1002
|
+
}
|
|
1003
|
+
catch { /* breadcrumb is best-effort */ }
|
|
1004
|
+
}
|
|
1005
|
+
spawnDetachedRestart('dashboard');
|
|
1006
|
+
return jsonRes(res, 200, { ok: true });
|
|
1007
|
+
}
|
|
816
1008
|
if (req.method === 'GET' && url.pathname === '/api/skills') {
|
|
817
1009
|
return jsonRes(res, 200, dashboardSkillsPayload());
|
|
818
1010
|
}
|
|
@@ -865,6 +1057,30 @@ const server = createServer(async (req, res) => {
|
|
|
865
1057
|
return jsonRes(res, 400, { ok: false, error: redactGitUrlCredentials(err?.message ?? String(err)) });
|
|
866
1058
|
}
|
|
867
1059
|
}
|
|
1060
|
+
if (req.method === 'POST' && url.pathname === '/api/skills/install-local-links') {
|
|
1061
|
+
let parsed;
|
|
1062
|
+
try {
|
|
1063
|
+
parsed = await readJsonBody(req);
|
|
1064
|
+
}
|
|
1065
|
+
catch {
|
|
1066
|
+
return jsonRes(res, 400, { ok: false, error: 'bad_json' });
|
|
1067
|
+
}
|
|
1068
|
+
const sources = parseInstallLocalLinksSources(parsed);
|
|
1069
|
+
if (sources.length === 0)
|
|
1070
|
+
return jsonRes(res, 400, { ok: false, error: 'sources_required' });
|
|
1071
|
+
if (sources.length > MAX_LOCAL_LINK_SOURCES)
|
|
1072
|
+
return jsonRes(res, 400, { ok: false, error: 'too_many_sources' });
|
|
1073
|
+
try {
|
|
1074
|
+
const skills = installLocalSkillLinks(sources);
|
|
1075
|
+
// Frontend re-fetches /api/skills (refresh()) after success, so we keep
|
|
1076
|
+
// the response lean — no need to spread a full dashboardSkillsPayload()
|
|
1077
|
+
// (which would re-run the native-skill discovery scan a second time).
|
|
1078
|
+
return jsonRes(res, 200, { ok: true, installed: skills.map(sanitizeSkillForDashboard) });
|
|
1079
|
+
}
|
|
1080
|
+
catch (err) {
|
|
1081
|
+
return jsonRes(res, 400, { ok: false, error: redactGitUrlCredentials(err?.message ?? String(err)) });
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
868
1084
|
let mSkillJob;
|
|
869
1085
|
if (req.method === 'GET' && (mSkillJob = url.pathname.match(/^\/api\/skills\/jobs\/([^/]+)$/))) {
|
|
870
1086
|
const job = skillJobs.get(decodeURIComponent(mSkillJob[1]));
|
|
@@ -908,6 +1124,28 @@ const server = createServer(async (req, res) => {
|
|
|
908
1124
|
...dashboardSkillsPayload(),
|
|
909
1125
|
});
|
|
910
1126
|
}
|
|
1127
|
+
if (req.method === 'GET' && url.pathname === '/api/whiteboards') {
|
|
1128
|
+
return jsonRes(res, 200, { enabled: whiteboardEnabled(), whiteboards: listWhiteboards() });
|
|
1129
|
+
}
|
|
1130
|
+
const mWhiteboard = url.pathname.match(/^\/api\/whiteboards\/([^/]+)$/);
|
|
1131
|
+
if (req.method === 'GET' && mWhiteboard) {
|
|
1132
|
+
try {
|
|
1133
|
+
const id = decodeURIComponent(mWhiteboard[1]);
|
|
1134
|
+
return jsonRes(res, 200, { enabled: whiteboardEnabled(), id, content: readWhiteboard(id, { allowDisabled: true }) });
|
|
1135
|
+
}
|
|
1136
|
+
catch (err) {
|
|
1137
|
+
return jsonRes(res, 404, { ok: false, error: err?.message ?? 'whiteboard_not_found' });
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (req.method === 'DELETE' && mWhiteboard) {
|
|
1141
|
+
try {
|
|
1142
|
+
const id = decodeURIComponent(mWhiteboard[1]);
|
|
1143
|
+
return jsonRes(res, 200, deleteWhiteboard(id));
|
|
1144
|
+
}
|
|
1145
|
+
catch (err) {
|
|
1146
|
+
return jsonRes(res, 400, { ok: false, error: err?.message ?? 'whiteboard_delete_failed' });
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
911
1149
|
if (await handleConnectorApi(req, res, url)) {
|
|
912
1150
|
return;
|
|
913
1151
|
}
|
|
@@ -1658,6 +1896,24 @@ const server = createServer(async (req, res) => {
|
|
|
1658
1896
|
res.end(await upstream.text());
|
|
1659
1897
|
return;
|
|
1660
1898
|
}
|
|
1899
|
+
// PUT /api/bots/:appId/env — proxy to that bot's daemon. Body
|
|
1900
|
+
// `{ env: string }` (raw JSON text; '' = clear).
|
|
1901
|
+
let mBotEnv;
|
|
1902
|
+
if (req.method === 'PUT' && (mBotEnv = url.pathname.match(/^\/api\/bots\/([^/]+)\/env$/))) {
|
|
1903
|
+
const appId = decodeURIComponent(mBotEnv[1]);
|
|
1904
|
+
const chunks = [];
|
|
1905
|
+
for await (const c of req)
|
|
1906
|
+
chunks.push(c);
|
|
1907
|
+
const raw = Buffer.concat(chunks).toString('utf8') || '{}';
|
|
1908
|
+
const upstream = await proxyToDaemon(appId, `/api/bot-env`, {
|
|
1909
|
+
method: 'PUT',
|
|
1910
|
+
headers: { 'content-type': 'application/json' },
|
|
1911
|
+
body: raw,
|
|
1912
|
+
});
|
|
1913
|
+
res.writeHead(upstream.status, { 'content-type': 'application/json' });
|
|
1914
|
+
res.end(await upstream.text());
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1661
1917
|
// PUT /api/bots/:appId/sandbox — proxy to that bot's daemon. Body `{ enabled: boolean }`.
|
|
1662
1918
|
let mBotSandbox;
|
|
1663
1919
|
if (req.method === 'PUT' && (mBotSandbox = url.pathname.match(/^\/api\/bots\/([^/]+)\/sandbox$/))) {
|