botmux 2.95.0 → 2.96.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 +4 -4
- package/README.md +3 -3
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +1 -10
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/cli/kimi.d.ts +4 -0
- package/dist/adapters/cli/kimi.d.ts.map +1 -0
- package/dist/adapters/cli/kimi.js +54 -0
- package/dist/adapters/cli/kimi.js.map +1 -0
- package/dist/adapters/cli/registry.d.ts +2 -1
- package/dist/adapters/cli/registry.d.ts.map +1 -1
- package/dist/adapters/cli/registry.js +6 -20
- package/dist/adapters/cli/registry.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +1 -1
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/adapters/hook-installer.d.ts.map +1 -1
- package/dist/adapters/hook-installer.js +138 -28
- package/dist/adapters/hook-installer.js.map +1 -1
- package/dist/bot-registry.d.ts +61 -4
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +164 -3
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/stdin-encoding.d.ts +21 -0
- package/dist/cli/stdin-encoding.d.ts.map +1 -1
- package/dist/cli/stdin-encoding.js +37 -0
- package/dist/cli/stdin-encoding.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -4
- package/dist/cli.js.map +1 -1
- package/dist/core/ask-hook/opencode.d.ts +15 -17
- package/dist/core/ask-hook/opencode.d.ts.map +1 -1
- package/dist/core/ask-hook/opencode.js +17 -19
- package/dist/core/ask-hook/opencode.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +1 -0
- 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 +193 -5
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts +2 -0
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js +2 -0
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +99 -7
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/types.d.ts +16 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +1 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +68 -11
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +7 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +7 -1
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/session-cleanup.d.ts +1 -0
- package/dist/dashboard/session-cleanup.d.ts.map +1 -1
- package/dist/dashboard/session-cleanup.js +2 -0
- package/dist/dashboard/session-cleanup.js.map +1 -1
- package/dist/dashboard/trigger-api.d.ts +4 -0
- package/dist/dashboard/trigger-api.d.ts.map +1 -1
- package/dist/dashboard/trigger-api.js +20 -0
- package/dist/dashboard/trigger-api.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +1 -0
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +247 -4
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/connectors.d.ts +11 -0
- package/dist/dashboard/web/connectors.d.ts.map +1 -1
- package/dist/dashboard/web/connectors.js +45 -1
- package/dist/dashboard/web/connectors.js.map +1 -1
- package/dist/dashboard/web/groups.d.ts +7 -0
- package/dist/dashboard/web/groups.d.ts.map +1 -1
- package/dist/dashboard/web/groups.js +62 -15
- package/dist/dashboard/web/groups.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +52 -2
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +149 -5
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/webhook-routes.d.ts.map +1 -1
- package/dist/dashboard/webhook-routes.js +126 -6
- package/dist/dashboard/webhook-routes.js.map +1 -1
- package/dist/dashboard-web/app.js +818 -751
- package/dist/dashboard-web/style.css +47 -0
- package/dist/dashboard.js +84 -6
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +1 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +1 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +1 -0
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/client.d.ts +12 -0
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +45 -9
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +13 -0
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +180 -11
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/message-parser.d.ts +11 -0
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +2 -1
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/im/lark/summary-command.d.ts +19 -0
- package/dist/im/lark/summary-command.d.ts.map +1 -0
- package/dist/im/lark/summary-command.js +276 -0
- package/dist/im/lark/summary-command.js.map +1 -0
- package/dist/mir-local-runtime.d.ts +40 -0
- package/dist/mir-local-runtime.d.ts.map +1 -1
- package/dist/mir-local-runtime.js +246 -52
- package/dist/mir-local-runtime.js.map +1 -1
- package/dist/mir-runner.js +26 -1
- package/dist/mir-runner.js.map +1 -1
- package/dist/services/card-prefs-store.d.ts +2 -2
- package/dist/services/card-prefs-store.d.ts.map +1 -1
- package/dist/services/card-prefs-store.js +5 -5
- package/dist/services/card-prefs-store.js.map +1 -1
- package/dist/services/chat-reply-mode-store.d.ts +6 -4
- package/dist/services/chat-reply-mode-store.d.ts.map +1 -1
- package/dist/services/chat-reply-mode-store.js +6 -4
- package/dist/services/chat-reply-mode-store.js.map +1 -1
- package/dist/services/pi-transcript.d.ts +19 -0
- package/dist/services/pi-transcript.d.ts.map +1 -0
- package/dist/services/pi-transcript.js +222 -0
- package/dist/services/pi-transcript.js.map +1 -0
- package/dist/services/summary-range-store.d.ts +21 -0
- package/dist/services/summary-range-store.d.ts.map +1 -0
- package/dist/services/summary-range-store.js +89 -0
- package/dist/services/summary-range-store.js.map +1 -0
- package/dist/services/trigger-types.d.ts +14 -2
- package/dist/services/trigger-types.d.ts.map +1 -1
- package/dist/services/trigger-types.js +15 -1
- package/dist/services/trigger-types.js.map +1 -1
- package/dist/setup/bot-config-editor.d.ts.map +1 -1
- package/dist/setup/bot-config-editor.js +2 -0
- package/dist/setup/bot-config-editor.js.map +1 -1
- package/dist/types.d.ts +20 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/executable.d.ts +3 -0
- package/dist/utils/executable.d.ts.map +1 -0
- package/dist/utils/executable.js +24 -0
- package/dist/utils/executable.js.map +1 -0
- package/dist/worker.js +69 -5
- package/dist/worker.js.map +1 -1
- package/dist/workflows/definition.d.ts +8 -8
- package/dist/workflows/events/payloads.d.ts +10 -10
- package/dist/workflows/events/schema.d.ts +28 -28
- package/package.json +1 -1
|
@@ -98,6 +98,8 @@ const ICON = {
|
|
|
98
98
|
details: '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2.2" y="2.9" width="11.6" height="10.2" rx="1.8"/><path d="M9.9 2.9v10.2"/></svg>',
|
|
99
99
|
terminal: '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="1.7" y="2.7" width="12.6" height="10.6" rx="2"/><path d="M4.4 6.3 6.4 8.1 4.4 9.9"/><path d="M8.2 10.2h3.4"/></svg>',
|
|
100
100
|
key: '<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="6" cy="6.1" r="3"/><path d="M8.1 8.2 13 13.1"/><path d="M11.3 11.4 12.6 10.1"/><path d="M12.7 12.8 13.7 11.8"/></svg>',
|
|
101
|
+
lock: '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="3.2" y="7" width="9.6" height="6.2" rx="1.4"/><path d="M5.2 7V5.2a2.8 2.8 0 0 1 5.6 0V7"/></svg>',
|
|
102
|
+
unlock: '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="3.2" y="7" width="9.6" height="6.2" rx="1.4"/><path d="M5.2 7V5.2a2.8 2.8 0 0 1 5.2-1.4"/></svg>',
|
|
101
103
|
close: '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4.2 4.2 11.8 11.8"/><path d="M11.8 4.2 4.2 11.8"/></svg>',
|
|
102
104
|
edit: '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M10.7 3.3 12.7 5.3 6.3 11.7 3.7 12.3 4.3 9.7 10.7 3.3z"/></svg>',
|
|
103
105
|
history: '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.2 4.8a2 2 0 0 1 2-2h7.6a2 2 0 0 1 2 2v4.6a2 2 0 0 1-2 2H6.6l-2.9 2.4v-2.4h-.5a2 2 0 0 1-2-2z"/><path d="M5.2 6.2h5.6M5.2 8.4h3.6"/></svg>',
|
|
@@ -110,6 +112,14 @@ const ICON = {
|
|
|
110
112
|
function cardActBtn(action, icon, label, kind = '') {
|
|
111
113
|
return `<button type="button" class="card-act${kind ? ' ' + kind : ''}" data-action="${action}" title="${escapeHtml(label)}" aria-label="${escapeHtml(label)}">${icon}</button>`;
|
|
112
114
|
}
|
|
115
|
+
function lockActionLabel(s) {
|
|
116
|
+
return s.locked ? t('sessions.unlock') : t('sessions.lock');
|
|
117
|
+
}
|
|
118
|
+
function lockChipHtml(s) {
|
|
119
|
+
return s.locked
|
|
120
|
+
? `<span class="session-lock-badge" title="${escapeHtml(t('sessions.locked'))}">${escapeHtml(t('sessions.locked'))}</span>`
|
|
121
|
+
: '';
|
|
122
|
+
}
|
|
113
123
|
// Terminal access pill for a live session: a read-only "open" segment (always
|
|
114
124
|
// shown) plus — for authenticated users — a "key" segment that mints the
|
|
115
125
|
// writable (token-bearing) link on demand. Icon-only + tooltip keeps it compact;
|
|
@@ -254,6 +264,8 @@ function pageHtml() {
|
|
|
254
264
|
</div>
|
|
255
265
|
<div id="bulk-bar" class="bulk-bar" hidden>
|
|
256
266
|
<span id="bulk-count"></span>
|
|
267
|
+
<button type="button" id="bulk-lock">${t('sessions.lockSelected')}</button>
|
|
268
|
+
<button type="button" id="bulk-unlock">${t('sessions.unlockSelected')}</button>
|
|
257
269
|
<button type="button" id="bulk-close" class="contrast">${t('sessions.closeSelected')}</button>
|
|
258
270
|
<button type="button" id="bulk-clear">${t('sessions.clearSelection')}</button>
|
|
259
271
|
</div>
|
|
@@ -489,6 +501,8 @@ export function renderSessionsPage(root) {
|
|
|
489
501
|
const selectAllBox = root.querySelector('#select-all');
|
|
490
502
|
const bulkBar = root.querySelector('#bulk-bar');
|
|
491
503
|
const bulkCountSpan = root.querySelector('#bulk-count');
|
|
504
|
+
const bulkLockBtn = root.querySelector('#bulk-lock');
|
|
505
|
+
const bulkUnlockBtn = root.querySelector('#bulk-unlock');
|
|
492
506
|
const bulkCloseBtn = root.querySelector('#bulk-close');
|
|
493
507
|
const bulkClearBtn = root.querySelector('#bulk-clear');
|
|
494
508
|
const idleCleanupBar = root.querySelector('#idle-cleanup-bar');
|
|
@@ -828,7 +842,7 @@ export function renderSessionsPage(root) {
|
|
|
828
842
|
<td><input type="checkbox" class="row-select" ${checked} ${closed ? 'disabled' : ''}></td>
|
|
829
843
|
<td>${escapeHtml(botDisplayName(s))}</td>
|
|
830
844
|
<td><span class="badge cli-${cssToken(s.cliId)}">${escapeHtml(s.cliId ?? 'unknown')}</span></td>
|
|
831
|
-
<td><span class="status status-${escapeHtml(s.status ?? 'unknown')}">${escapeHtml(s.status ?? 'unknown')}</span
|
|
845
|
+
<td><span class="status status-${escapeHtml(s.status ?? 'unknown')}">${escapeHtml(s.status ?? 'unknown')}</span>${lockChipHtml(s)}</td>
|
|
832
846
|
<td class="token-cell">${formatTokenCount(s.tokenUsage?.in)}</td>
|
|
833
847
|
<td class="token-cell">${formatTokenCount(s.tokenUsage?.out)}</td>
|
|
834
848
|
<td title="${escapeHtml(String(s.title ?? ''))}">${escapeHtml(stripMentionPrefix(s.title ?? '').slice(0, 48))}</td>
|
|
@@ -872,14 +886,17 @@ export function renderSessionsPage(root) {
|
|
|
872
886
|
const terminal = terminalHref(s);
|
|
873
887
|
const signal = boardSignalLabel(s);
|
|
874
888
|
const repo = repoBasename(s.workingDir);
|
|
875
|
-
return `<article class="session-card${isSelected ? ' selected' : ''}" data-id="${escapeHtml(s.sessionId)}" aria-pressed="${isSelected}">
|
|
889
|
+
return `<article class="session-card${isSelected ? ' selected' : ''}${s.locked ? ' locked' : ''}" data-id="${escapeHtml(s.sessionId)}" aria-pressed="${isSelected}">
|
|
876
890
|
<div class="session-card-top">
|
|
877
891
|
${botAvatarHtml({ name: botName, larkAppId: s.larkAppId, size: 'sm' })}
|
|
878
892
|
<div class="session-card-title">
|
|
879
893
|
<strong title="${escapeHtml(String(s.title ?? title))}">${escapeHtml(String(title).slice(0, 72))}</strong>
|
|
880
894
|
<span>${escapeHtml(botName)} · ${escapeHtml(chatTitle ?? s.cliId ?? 'unknown')}</span>
|
|
881
895
|
</div>
|
|
882
|
-
<span class="status
|
|
896
|
+
<span class="session-card-status-group">
|
|
897
|
+
<span class="status status-${escapeHtml(s.status ?? 'unknown')}">${escapeHtml(s.status ?? 'unknown')}</span>
|
|
898
|
+
${lockChipHtml(s)}
|
|
899
|
+
</span>
|
|
883
900
|
</div>
|
|
884
901
|
${repo !== '-' || s.adopt ? `<div class="session-card-meta">
|
|
885
902
|
${repo !== '-' ? `<span title="${escapeHtml(s.workingDir ?? '')}">${escapeHtml(repo)}</span>` : ''}
|
|
@@ -896,6 +913,7 @@ export function renderSessionsPage(root) {
|
|
|
896
913
|
${cardActBtn('details', ICON.details, t('sessions.details'))}
|
|
897
914
|
${canRestartSession(s) ? cardActBtn('restart', ICON.restart, t('sessions.restart')) : ''}
|
|
898
915
|
${terminalControlsHtml(terminal)}
|
|
916
|
+
${cardActBtn('lock', s.locked ? ICON.unlock : ICON.lock, lockActionLabel(s), s.locked ? 'locked' : '')}
|
|
899
917
|
${cardActBtn('close', ICON.close, t('sessions.close'), 'danger')}
|
|
900
918
|
</div>
|
|
901
919
|
</article>`;
|
|
@@ -963,15 +981,17 @@ export function renderSessionsPage(root) {
|
|
|
963
981
|
// 对方部署的会话:数据是 host 快照,终端/历史/改名都在对方机器上做不了——
|
|
964
982
|
// 只保留状态点与部署来源徽章,卡片仍可拖(团队共享编排)。
|
|
965
983
|
const remote = typeof s.remoteDeployment === 'string' ? s.remoteDeployment : '';
|
|
966
|
-
return `<article class="kanban-card${remote ? ' kanban-card-remote' : ''}" data-id="${escapeHtml(s.sessionId)}" tabindex="0" role="button" draggable="true">
|
|
984
|
+
return `<article class="kanban-card${remote ? ' kanban-card-remote' : ''}${s.locked ? ' locked' : ''}" data-id="${escapeHtml(s.sessionId)}" tabindex="0" role="button" draggable="true">
|
|
967
985
|
<div class="kanban-card-top">
|
|
968
986
|
<span class="badge cli-${cssToken(s.cliId)}">${escapeHtml(s.cliId ?? 'unknown')}</span>
|
|
969
987
|
${s.adopt ? '<span class="badge">adopt</span>' : ''}
|
|
988
|
+
${lockChipHtml(s)}
|
|
970
989
|
${remote ? `<span class="badge kanban-remote-badge" title="${escapeHtml(t('sessions.kanban.remoteHint', { name: remote }))}">${escapeHtml(remote)}</span>` : ''}
|
|
971
990
|
<span class="kanban-card-top-right">
|
|
972
991
|
<span class="kanban-card-dot" data-status="${escapeHtml(status)}" title="${escapeHtml(status)}"></span>
|
|
973
992
|
${remote ? '' : `<button type="button" class="card-act kanban-card-act" data-action="history" title="${escapeHtml(t('sessions.history.title'))}" aria-label="${escapeHtml(t('sessions.history.title'))}">${ICON.history}</button>
|
|
974
993
|
${s.feishuChatLink ? `<a class="card-act kanban-card-act" href="${escapeHtml(s.feishuChatLink)}" target="_blank" rel="noopener" title="${escapeHtml(t('sessions.kanban.openFeishu'))}" aria-label="${escapeHtml(t('sessions.kanban.openFeishu'))}">${ICON.feishu}</a>` : ''}
|
|
994
|
+
<button type="button" class="card-act kanban-card-act${s.locked ? ' locked' : ''}" data-action="lock" title="${escapeHtml(lockActionLabel(s))}" aria-label="${escapeHtml(lockActionLabel(s))}">${s.locked ? ICON.unlock : ICON.lock}</button>
|
|
975
995
|
<button type="button" class="card-act kanban-card-act" data-action="details" title="${escapeHtml(t('sessions.details'))}" aria-label="${escapeHtml(t('sessions.details'))}">${ICON.details}</button>
|
|
976
996
|
${canRestartSession(s) ? `<button type="button" class="card-act kanban-card-act" data-action="restart" title="${escapeHtml(t('sessions.restart'))}" aria-label="${escapeHtml(t('sessions.restart'))}">${ICON.restart}</button>` : ''}`}
|
|
977
997
|
</span>
|
|
@@ -1516,6 +1536,11 @@ export function renderSessionsPage(root) {
|
|
|
1516
1536
|
function syncBulkUi(rows) {
|
|
1517
1537
|
bulkBar.hidden = selected.size === 0;
|
|
1518
1538
|
bulkCountSpan.textContent = t('sessions.selectedCount', { count: selected.size });
|
|
1539
|
+
const selectedRows = [...selected]
|
|
1540
|
+
.map(id => store.sessions.get(id))
|
|
1541
|
+
.filter((r) => !!r && r.status !== 'closed');
|
|
1542
|
+
bulkLockBtn.disabled = !selectedRows.some(r => !r.locked);
|
|
1543
|
+
bulkUnlockBtn.disabled = !selectedRows.some(r => !!r.locked);
|
|
1519
1544
|
const selectable = rows.filter(r => r.status !== 'closed');
|
|
1520
1545
|
if (selectable.length === 0) {
|
|
1521
1546
|
selectAllBox.checked = false;
|
|
@@ -1664,6 +1689,52 @@ export function renderSessionsPage(root) {
|
|
|
1664
1689
|
closeBtn.disabled = false;
|
|
1665
1690
|
}
|
|
1666
1691
|
}
|
|
1692
|
+
function invalidateSessionViews() {
|
|
1693
|
+
lastBoardHtml = '';
|
|
1694
|
+
lastTableHtml = '';
|
|
1695
|
+
lastKanbanHtml = '';
|
|
1696
|
+
}
|
|
1697
|
+
async function setSessionLocked(s, locked, btn) {
|
|
1698
|
+
const prev = !!s.locked;
|
|
1699
|
+
if (prev === locked)
|
|
1700
|
+
return true;
|
|
1701
|
+
s.locked = locked;
|
|
1702
|
+
invalidateSessionViews();
|
|
1703
|
+
rerender();
|
|
1704
|
+
if (btn)
|
|
1705
|
+
btn.disabled = true;
|
|
1706
|
+
try {
|
|
1707
|
+
const r = await fetch(`/api/sessions/${encodeURIComponent(s.sessionId)}/lock`, {
|
|
1708
|
+
method: 'POST',
|
|
1709
|
+
headers: { 'content-type': 'application/json' },
|
|
1710
|
+
body: JSON.stringify({ locked }),
|
|
1711
|
+
});
|
|
1712
|
+
const body = await r.json().catch(() => ({}));
|
|
1713
|
+
if (!r.ok || body?.ok === false) {
|
|
1714
|
+
s.locked = prev;
|
|
1715
|
+
invalidateSessionViews();
|
|
1716
|
+
rerender();
|
|
1717
|
+
if (r.status !== 401)
|
|
1718
|
+
alert(`${t('sessions.lockFailed')}: ${body?.error ?? r.status}`);
|
|
1719
|
+
return false;
|
|
1720
|
+
}
|
|
1721
|
+
s.locked = !!body.locked;
|
|
1722
|
+
invalidateSessionViews();
|
|
1723
|
+
rerender();
|
|
1724
|
+
return true;
|
|
1725
|
+
}
|
|
1726
|
+
catch (e) {
|
|
1727
|
+
s.locked = prev;
|
|
1728
|
+
invalidateSessionViews();
|
|
1729
|
+
rerender();
|
|
1730
|
+
alert(`${t('sessions.lockFailed')}: ${e}`);
|
|
1731
|
+
return false;
|
|
1732
|
+
}
|
|
1733
|
+
finally {
|
|
1734
|
+
if (btn)
|
|
1735
|
+
btn.disabled = false;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1667
1738
|
// Per-session restart cooldown. The route returns 200 the instant the IPC is
|
|
1668
1739
|
// sent — long before the worker's ~500ms+ respawn — so re-enabling the button
|
|
1669
1740
|
// immediately let a second restart land inside the respawn window, which trips
|
|
@@ -1764,7 +1835,10 @@ export function renderSessionsPage(root) {
|
|
|
1764
1835
|
drawer.innerHTML = `<article>
|
|
1765
1836
|
<header>
|
|
1766
1837
|
<h3>${escapeHtml(stripMentionPrefix(s.title) || s.sessionId)}</h3>
|
|
1767
|
-
<span class="status
|
|
1838
|
+
<span class="drawer-status-line">
|
|
1839
|
+
<span class="status status-${escapeHtml(s.status ?? 'unknown')}">${escapeHtml(s.status ?? 'unknown')}</span>
|
|
1840
|
+
${lockChipHtml(s)}
|
|
1841
|
+
</span>
|
|
1768
1842
|
<p><code>${escapeHtml(s.sessionId)}</code> <button data-copy="${escapeHtml(s.sessionId)}">${t('sessions.copy')}</button></p>
|
|
1769
1843
|
</header>
|
|
1770
1844
|
<p><b>${t('sessions.bot')}:</b> ${escapeHtml(botDisplayName(s))} · <b>${t('sessions.cli')}:</b> ${escapeHtml(s.cliId ?? '?')}</p>
|
|
@@ -1778,6 +1852,7 @@ export function renderSessionsPage(root) {
|
|
|
1778
1852
|
<button id="history-drawer-btn" type="button">${t('sessions.history.title')}</button>
|
|
1779
1853
|
${terminalControlsHtml(terminal)}
|
|
1780
1854
|
${canRestartSession(s) ? `<button id="restart-btn" type="button">${t('sessions.restart')}</button>` : ''}
|
|
1855
|
+
${!closed ? `<button id="lock-btn" type="button">${escapeHtml(lockActionLabel(s))}</button>` : ''}
|
|
1781
1856
|
${s.queued && !closed ? `<button id="start-btn" type="button" class="primary">${t('sessions.create.start')}</button>` : ''}
|
|
1782
1857
|
${closed ? `<button id="resume-btn" type="button" class="primary">${t('sessions.resume')}</button>` : ''}
|
|
1783
1858
|
${!closed ? `<button id="close-btn" type="button" class="contrast">${t('sessions.close')}</button>` : ''}
|
|
@@ -1843,6 +1918,19 @@ export function renderSessionsPage(root) {
|
|
|
1843
1918
|
drawer.close();
|
|
1844
1919
|
};
|
|
1845
1920
|
}
|
|
1921
|
+
const lockBtn = drawer.querySelector('#lock-btn');
|
|
1922
|
+
if (lockBtn) {
|
|
1923
|
+
lockBtn.onclick = async () => {
|
|
1924
|
+
const next = !s.locked;
|
|
1925
|
+
if (await setSessionLocked(s, next, lockBtn)) {
|
|
1926
|
+
lockBtn.textContent = lockActionLabel(s);
|
|
1927
|
+
const line = drawer.querySelector('.drawer-status-line');
|
|
1928
|
+
if (line) {
|
|
1929
|
+
line.innerHTML = `<span class="status status-${escapeHtml(s.status ?? 'unknown')}">${escapeHtml(s.status ?? 'unknown')}</span>${lockChipHtml(s)}`;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1846
1934
|
// 待办池会话「开始」:激活 parked 会话(发首轮、起 CLI)。
|
|
1847
1935
|
const startBtn = drawer.querySelector('#start-btn');
|
|
1848
1936
|
if (startBtn) {
|
|
@@ -1991,6 +2079,8 @@ export function renderSessionsPage(root) {
|
|
|
1991
2079
|
void locateSession(s, actionButton);
|
|
1992
2080
|
else if (action === 'restart')
|
|
1993
2081
|
void restartSession(s, actionButton);
|
|
2082
|
+
else if (action === 'lock')
|
|
2083
|
+
void setSessionLocked(s, !s.locked, actionButton);
|
|
1994
2084
|
else if (action === 'close')
|
|
1995
2085
|
void closeSession(s, actionButton).then(ok => {
|
|
1996
2086
|
if (ok) {
|
|
@@ -2107,6 +2197,8 @@ export function renderSessionsPage(root) {
|
|
|
2107
2197
|
void openHistoryModal(s);
|
|
2108
2198
|
else if (actionButton.dataset.action === 'restart')
|
|
2109
2199
|
void restartSession(s, actionButton);
|
|
2200
|
+
else if (actionButton.dataset.action === 'lock')
|
|
2201
|
+
void setSessionLocked(s, !s.locked, actionButton);
|
|
2110
2202
|
return;
|
|
2111
2203
|
}
|
|
2112
2204
|
if (target.closest('a, button, input, label'))
|
|
@@ -2340,6 +2432,58 @@ export function renderSessionsPage(root) {
|
|
|
2340
2432
|
if (failed > 0)
|
|
2341
2433
|
alert(`Failed: ${failed}/${ids.length}`);
|
|
2342
2434
|
});
|
|
2435
|
+
async function setSelectedLocked(locked) {
|
|
2436
|
+
const rows = [...selected]
|
|
2437
|
+
.map(id => store.sessions.get(id))
|
|
2438
|
+
.filter((s) => !!s && s.status !== 'closed' && !!s.locked !== locked);
|
|
2439
|
+
if (rows.length === 0)
|
|
2440
|
+
return;
|
|
2441
|
+
bulkLockBtn.disabled = true;
|
|
2442
|
+
bulkUnlockBtn.disabled = true;
|
|
2443
|
+
const original = locked ? bulkLockBtn.textContent : bulkUnlockBtn.textContent;
|
|
2444
|
+
const activeBtn = locked ? bulkLockBtn : bulkUnlockBtn;
|
|
2445
|
+
let done = 0;
|
|
2446
|
+
let failed = 0;
|
|
2447
|
+
const queue = [...rows];
|
|
2448
|
+
activeBtn.textContent = `0/${rows.length}`;
|
|
2449
|
+
async function worker() {
|
|
2450
|
+
while (queue.length) {
|
|
2451
|
+
const s = queue.shift();
|
|
2452
|
+
const prev = !!s.locked;
|
|
2453
|
+
try {
|
|
2454
|
+
const r = await fetch(`/api/sessions/${encodeURIComponent(s.sessionId)}/lock`, {
|
|
2455
|
+
method: 'POST',
|
|
2456
|
+
headers: { 'content-type': 'application/json' },
|
|
2457
|
+
body: JSON.stringify({ locked }),
|
|
2458
|
+
});
|
|
2459
|
+
const body = await r.json().catch(() => ({}));
|
|
2460
|
+
if (!r.ok || body?.ok === false) {
|
|
2461
|
+
failed += 1;
|
|
2462
|
+
s.locked = prev;
|
|
2463
|
+
}
|
|
2464
|
+
else {
|
|
2465
|
+
s.locked = !!body.locked;
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
catch {
|
|
2469
|
+
failed += 1;
|
|
2470
|
+
s.locked = prev;
|
|
2471
|
+
}
|
|
2472
|
+
finally {
|
|
2473
|
+
done += 1;
|
|
2474
|
+
activeBtn.textContent = `${done}/${rows.length}`;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
await Promise.all(Array.from({ length: Math.min(6, rows.length) }, () => worker()));
|
|
2479
|
+
activeBtn.textContent = original;
|
|
2480
|
+
invalidateSessionViews();
|
|
2481
|
+
rerender();
|
|
2482
|
+
if (failed > 0)
|
|
2483
|
+
alert(`${t('sessions.lockFailed')}: ${failed}/${rows.length}`);
|
|
2484
|
+
}
|
|
2485
|
+
bulkLockBtn.addEventListener('click', () => void setSelectedLocked(true));
|
|
2486
|
+
bulkUnlockBtn.addEventListener('click', () => void setSelectedLocked(false));
|
|
2343
2487
|
idleCleanupThreshold.addEventListener('click', e => {
|
|
2344
2488
|
// Don't let a threshold switch mid-cleanup repaint the count/pill against a
|
|
2345
2489
|
// different threshold than the request that's already running.
|