claude-code-kanban 7.19.0 → 7.21.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/lib/ask.js +215 -0
- package/lib/parent-cache.js +5 -1
- package/package.json +1 -1
- package/plugin/plugins/claude-code-kanban/.claude-plugin/plugin.json +1 -1
- package/plugin/plugins/claude-code-kanban/hooks/ask.ts +163 -0
- package/plugin/plugins/claude-code-kanban/hooks/index.ts +2 -0
- package/plugin/plugins/claude-code-kanban/skills/ask/SKILL.md +44 -0
- package/plugin/plugins/claude-code-kanban/skills/ask/references/playground.md +25 -0
- package/plugin/plugins/claude-code-kanban/tests/ask.test.ts +100 -0
- package/plugin/plugins/claude-code-kanban/tests/show.test.ts +1 -1
- package/public/app.js +257 -45
- package/public/index.html +24 -3
- package/public/style.css +320 -69
- package/public/themes.css +93 -93
- package/server.js +17 -5
package/public/app.js
CHANGED
|
@@ -1939,6 +1939,8 @@ const ICON_AGENT_ACTIVE =
|
|
|
1939
1939
|
const ICON_TERMINAL =
|
|
1940
1940
|
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>';
|
|
1941
1941
|
const TERMINAL_TITLE = 'Running in a terminal here';
|
|
1942
|
+
const ICON_FORK =
|
|
1943
|
+
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9"/><path d="M12 12v3"/></svg>';
|
|
1942
1944
|
const ICON_DISPATCHED =
|
|
1943
1945
|
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>';
|
|
1944
1946
|
const ICON_CHAT =
|
|
@@ -2808,7 +2810,7 @@ function isAnyModalOpen() {
|
|
|
2808
2810
|
|
|
2809
2811
|
// An open modal owns the zoom keys even when it has nothing to scale.
|
|
2810
2812
|
function isZoomableOnScreen() {
|
|
2811
|
-
return isAnyModalOpen() ? isZoomableModalOpen() : isPaneDocOnScreen() || isShowCardFocused();
|
|
2813
|
+
return isAnyModalOpen() ? isZoomableModalOpen() : isAskOnScreen() || isPaneDocOnScreen() || isShowCardFocused();
|
|
2812
2814
|
}
|
|
2813
2815
|
|
|
2814
2816
|
const ZOOM_KEYS = { '+': 0.1, '=': 0.1, NumpadAdd: 0.1, '-': -0.1, _: -0.1, NumpadSubtract: -0.1, 0: 0, Numpad0: 0 };
|
|
@@ -3982,16 +3984,15 @@ function renderSessions() {
|
|
|
3982
3984
|
return out;
|
|
3983
3985
|
};
|
|
3984
3986
|
|
|
3985
|
-
const renderSessionCard = (session) => {
|
|
3986
|
-
if (session.placeholder) return renderPlaceholderCard(session, childDepth.get(session.id));
|
|
3987
|
+
const renderSessionCard = (session, hostProject = null) => {
|
|
3988
|
+
if (session.placeholder) return renderPlaceholderCard(session, childDepth.get(session.id), hostProject);
|
|
3987
3989
|
const total = session.taskCount;
|
|
3988
3990
|
const pct = (n) => Math.round((n / total) * 100);
|
|
3989
3991
|
const isActive = session.id === currentSessionId && viewMode === 'session';
|
|
3990
3992
|
const isLive = isSessionLive(session);
|
|
3991
3993
|
const sessionName = sessionDisplayName(session);
|
|
3992
|
-
const projectHtml = renderProjectIdentity(session);
|
|
3994
|
+
const projectHtml = renderProjectIdentity(session, hostProject);
|
|
3993
3995
|
|
|
3994
|
-
const gitBranch = session.gitBranch ? escapeHtml(session.gitBranch) : null;
|
|
3995
3996
|
const isTeam = session.isTeam;
|
|
3996
3997
|
const memberCount = session.memberCount || 0;
|
|
3997
3998
|
|
|
@@ -4025,8 +4026,7 @@ function renderSessions() {
|
|
|
4025
4026
|
<button onclick="openSession('${sid}')" draggable="true" data-session-id="${escapeHtml(session.id)}" class="session-item ${isActive ? 'active' : ''} ${sessionBarClass(session)} ${tempClass} ${idleClass} ${justFinishedClass}${childDepthClass(childDepth.get(session.id))}">
|
|
4026
4027
|
<span class="session-pin-btn${pinClass}" onclick="event.stopPropagation();toggleSessionPin('${sid}')" title="${pinTitle} session">${pinState === 'sticky' ? SESSION_STAR_SVG : SESSION_PIN_SVG}</span>
|
|
4027
4028
|
<div class="session-name">${escapeHtml(sessionName)}</div>
|
|
4028
|
-
${projectHtml
|
|
4029
|
-
${gitBranch ? `<div class="session-branch">${gitBranch}</div>` : ''}
|
|
4029
|
+
${renderSessionIdentity(projectHtml, session.gitBranch)}
|
|
4030
4030
|
${metricsHtml ? `<div class="session-metrics">${metricsHtml}</div>` : ''}
|
|
4031
4031
|
<div class="session-footer">
|
|
4032
4032
|
<div class="session-time">${formatDate(session.modifiedAt)}</div>
|
|
@@ -4042,6 +4042,7 @@ function renderSessions() {
|
|
|
4042
4042
|
${session.planSourceSessionId ? `<span class="plan-indicator" title="Implements plan — click to reveal plan session" onclick="event.stopPropagation(); revealPlanSession('${escAttrJs(session.planSourceSessionId)}')">${ICON_PLAN}</span>` : ''}
|
|
4043
4043
|
${session.sharedTaskList ? `<span class="shared-tasklist-badge" title="Shared task list: ${escapeHtml(session.sharedTaskList)}">${linkSvg(12)}</span>` : ''}
|
|
4044
4044
|
${session.dispatched ? `<span class="dispatched-badge"${session.dispatched.parent ? ` onclick="event.stopPropagation(); revealSession('${escAttrJs(session.dispatched.parent)}')"` : ''} title="${escapeHtml(dispatchedTitle(session.dispatched))}">${ICON_DISPATCHED}</span>` : ''}
|
|
4045
|
+
${session.forkedFromSessionId ? `<span class="fork-badge" onclick="event.stopPropagation(); revealSession('${escAttrJs(session.forkedFromSessionId)}')" title="Forked from ${escapeHtml(session.forkedFromSessionId)}, click to reveal">${ICON_FORK}</span>` : ''}
|
|
4045
4046
|
${runningTerminals.has(session.id) ? `<span class="terminal-badge" onclick="event.stopPropagation(); showSessionTerminal('${escAttrJs(session.id)}')" title="${TERMINAL_TITLE}">${ICON_TERMINAL}</span>` : ''}
|
|
4046
4047
|
${isWaitingSession(session) ? `<span class="agent-badge agent-badge-waiting" title="Waiting for user">${ICON_AGENT_WAITING}</span>` : ''}
|
|
4047
4048
|
${session.hasRunningAgents && !isWaitingSession(session) ? `<span class="agent-badge agent-badge-active" title="Agents running">${ICON_AGENT_ACTIVE}</span>` : ''}
|
|
@@ -4072,7 +4073,7 @@ function renderSessions() {
|
|
|
4072
4073
|
SECTION_OUTSIDE,
|
|
4073
4074
|
`Outside current filters ${countHtml(outside.rows)}`,
|
|
4074
4075
|
false,
|
|
4075
|
-
collapsedProjectGroups.has(SECTION_OUTSIDE) ? '' : outside.rows.map(renderSessionCard).join(''),
|
|
4076
|
+
collapsedProjectGroups.has(SECTION_OUTSIDE) ? '' : outside.rows.map((s) => renderSessionCard(s)).join(''),
|
|
4076
4077
|
)
|
|
4077
4078
|
: '';
|
|
4078
4079
|
|
|
@@ -4134,10 +4135,11 @@ function renderSessions() {
|
|
|
4134
4135
|
const groupPinned = store.getItem(GROUP_PINNED_KEY) !== 'false';
|
|
4135
4136
|
const pinWeight = (s) => (isPlacedSticky(s.id) ? 2 : isInPinnedGroup(s) ? 1 : 0);
|
|
4136
4137
|
const pinSort = (a, b) => pinWeight(b) - pinWeight(a);
|
|
4137
|
-
const renderGroupSessions = (list, pinKey) => {
|
|
4138
|
-
|
|
4138
|
+
const renderGroupSessions = (list, pinKey, hostProject = null) => {
|
|
4139
|
+
const card = (s) => renderSessionCard(s, hostProject);
|
|
4140
|
+
if (!groupPinned || pinnedSessionIds.size === 0) return nestChildren(list).map(card).join('');
|
|
4139
4141
|
const gPinned = nestChildren(list.filter(isInPinnedGroup));
|
|
4140
|
-
if (gPinned.length === 0) return nestChildren(list).map(
|
|
4142
|
+
if (gPinned.length === 0) return nestChildren(list).map(card).join('');
|
|
4141
4143
|
const gUnpinned = nestChildren(list.filter((s) => !isInPinnedGroup(s)));
|
|
4142
4144
|
const pinCollapsed = collapsedProjectGroups.has(pinKey);
|
|
4143
4145
|
return (
|
|
@@ -4157,10 +4159,10 @@ function renderSessions() {
|
|
|
4157
4159
|
'<div class="pinned-sub-items' +
|
|
4158
4160
|
(pinCollapsed ? ' collapsed' : '') +
|
|
4159
4161
|
'">' +
|
|
4160
|
-
gPinned.map(
|
|
4162
|
+
gPinned.map(card).join('') +
|
|
4161
4163
|
'</div>' +
|
|
4162
4164
|
'</div>' +
|
|
4163
|
-
gUnpinned.map(
|
|
4165
|
+
gUnpinned.map(card).join('')
|
|
4164
4166
|
);
|
|
4165
4167
|
};
|
|
4166
4168
|
|
|
@@ -4192,7 +4194,7 @@ function renderSessions() {
|
|
|
4192
4194
|
</div>
|
|
4193
4195
|
<div class="project-group-breadcrumb${nestedCls}" data-full-path="${escapedPath}" title="Click to copy path">${breadcrumbHtml}</div>
|
|
4194
4196
|
<div class="project-group-sessions${isCollapsed ? ' collapsed' : ''}${nestedCls}" data-project-path="${escapedPath}">
|
|
4195
|
-
${renderGroupSessions(projectSessions, pinKey(projectPath))}
|
|
4197
|
+
${renderGroupSessions(projectSessions, pinKey(projectPath), projectPath)}
|
|
4196
4198
|
</div>
|
|
4197
4199
|
`;
|
|
4198
4200
|
};
|
|
@@ -4255,7 +4257,7 @@ function renderSessions() {
|
|
|
4255
4257
|
let body = '';
|
|
4256
4258
|
if (flat) {
|
|
4257
4259
|
body = nestChildren([...bucket].sort(byMemberOrder))
|
|
4258
|
-
.map(renderSessionCard)
|
|
4260
|
+
.map((s) => renderSessionCard(s))
|
|
4259
4261
|
.join('');
|
|
4260
4262
|
} else {
|
|
4261
4263
|
const loose = [];
|
|
@@ -4361,7 +4363,7 @@ function renderSessions() {
|
|
|
4361
4363
|
</div>
|
|
4362
4364
|
`;
|
|
4363
4365
|
} else {
|
|
4364
|
-
projectsHtml += ungrouped.map(renderSessionCard).join('');
|
|
4366
|
+
projectsHtml += ungrouped.map((s) => renderSessionCard(s)).join('');
|
|
4365
4367
|
}
|
|
4366
4368
|
}
|
|
4367
4369
|
|
|
@@ -4372,7 +4374,7 @@ function renderSessions() {
|
|
|
4372
4374
|
} else {
|
|
4373
4375
|
const sectioned = sessionGroups.length > 0;
|
|
4374
4376
|
const restHtml =
|
|
4375
|
-
sectioned && collapsedProjectGroups.has(SECTION_SESSIONS) ? '' : sgRest.map(renderSessionCard).join('');
|
|
4377
|
+
sectioned && collapsedProjectGroups.has(SECTION_SESSIONS) ? '' : sgRest.map((s) => renderSessionCard(s)).join('');
|
|
4376
4378
|
const tail = sectioned ? sectionHtml(SECTION_SESSIONS, 'Sessions', true, restHtml, sgRest.length === 0) : restHtml;
|
|
4377
4379
|
sessionsList.innerHTML = html + sgSectionHtml(true) + tail + outsideHtml;
|
|
4378
4380
|
}
|
|
@@ -4444,7 +4446,7 @@ function renderSession() {
|
|
|
4444
4446
|
const completed = currentTasks.filter((t) => t.status === 'completed').length;
|
|
4445
4447
|
const percent = currentTasks.length > 0 ? Math.round((completed / currentTasks.length) * 100) : 0;
|
|
4446
4448
|
|
|
4447
|
-
progressPercent.textContent = `${percent}
|
|
4449
|
+
progressPercent.textContent = `${percent}% done`;
|
|
4448
4450
|
progressBar.style.width = `${percent}%`;
|
|
4449
4451
|
const hasInProgress = currentTasks.some((t) => t.status === 'in_progress');
|
|
4450
4452
|
progressBar.classList.toggle('shimmer', hasInProgress && percent < 100);
|
|
@@ -4468,7 +4470,7 @@ function renderProjectView() {
|
|
|
4468
4470
|
const completed = currentTasks.filter((t) => t.status === 'completed').length;
|
|
4469
4471
|
const percent = currentTasks.length > 0 ? Math.round((completed / currentTasks.length) * 100) : 0;
|
|
4470
4472
|
|
|
4471
|
-
progressPercent.textContent = `${percent}
|
|
4473
|
+
progressPercent.textContent = `${percent}% done`;
|
|
4472
4474
|
progressBar.style.width = `${percent}%`;
|
|
4473
4475
|
const hasInProgress = currentTasks.some((t) => t.status === 'in_progress');
|
|
4474
4476
|
progressBar.classList.toggle('shimmer', hasInProgress && percent < 100);
|
|
@@ -9073,6 +9075,7 @@ function reviewOptsOfFrame(frame) {
|
|
|
9073
9075
|
|
|
9074
9076
|
async function sendPreviewAction(frame, msg) {
|
|
9075
9077
|
if (!navigator.userActivation?.isActive) return;
|
|
9078
|
+
if (frame === askState.frame) return answerAsk({ action: msg.action, label: msg.label, fields: msg.data });
|
|
9076
9079
|
const opts = reviewOptsOfFrame(frame);
|
|
9077
9080
|
if (!opts?.sessionId) return;
|
|
9078
9081
|
const key = `${msg.action}\n${JSON.stringify(msg.data)}`;
|
|
@@ -9446,7 +9449,7 @@ async function openPreviewByPath(filePath, base, onUnsupported, sessionId = prev
|
|
|
9446
9449
|
try {
|
|
9447
9450
|
const r = await api('/api/preview', { query: { path: filePath, base: base || null } });
|
|
9448
9451
|
if (!r.ok) {
|
|
9449
|
-
showToast('Preview file unavailable', 'error');
|
|
9452
|
+
showToast('Preview file unavailable', 'error', { label: 'Copy path', onClick: () => copyText(filePath, 'path') });
|
|
9450
9453
|
return;
|
|
9451
9454
|
}
|
|
9452
9455
|
const data = await r.json();
|
|
@@ -10250,6 +10253,8 @@ function setupEventSource() {
|
|
|
10250
10253
|
|
|
10251
10254
|
if (data.type === 'show:cleared') onShowCleared(data);
|
|
10252
10255
|
if (data.type === 'show:removed') onShowRemoved(data);
|
|
10256
|
+
if (data.type === 'ask:opened') onAskOpened(data);
|
|
10257
|
+
if (data.type === 'ask:closed') onAskClosed(data);
|
|
10253
10258
|
|
|
10254
10259
|
if (data.type === 'agent-update') {
|
|
10255
10260
|
if (!data.unreadOnly) pendingAgentSessionIds.add(data.sessionId);
|
|
@@ -10368,6 +10373,8 @@ const CONTEXT_COLORS = {
|
|
|
10368
10373
|
red: 'var(--danger)',
|
|
10369
10374
|
};
|
|
10370
10375
|
const COST_THRESHOLDS = { green: 0.5, yellow: 2, orange: 5 };
|
|
10376
|
+
// Status colors as small text measure 3.7–3.9:1 on light surfaces; bars and dots keep the pure color.
|
|
10377
|
+
const textTone = (color) => `color-mix(in srgb, ${color} 70%, var(--text-primary))`;
|
|
10371
10378
|
const MODEL_THRESHOLDS = [
|
|
10372
10379
|
{ match: /sonnet/i, yellow: 120000, orange: 200000, red: 300000 },
|
|
10373
10380
|
{ match: /haiku/i, yellow: 100000, orange: 130000, red: 150000 },
|
|
@@ -10474,7 +10481,7 @@ function renderContextBar(session) {
|
|
|
10474
10481
|
${renderMarkers(ctx.markers)}
|
|
10475
10482
|
</div>
|
|
10476
10483
|
<div class="context-bar-labels">
|
|
10477
|
-
<span style="color:${color}">${Math.round(ctx.pct)}% (${used})</span>
|
|
10484
|
+
<span>ctx <span style="color:${textTone(color)}">${Math.round(ctx.pct)}% (${used})</span></span>
|
|
10478
10485
|
<span>${Math.round(ctx.remaining)}% free</span>
|
|
10479
10486
|
</div>
|
|
10480
10487
|
</div>`;
|
|
@@ -10515,7 +10522,7 @@ function formatCacheLeft(expiresAt, expiredText) {
|
|
|
10515
10522
|
if (left === 0) return { text: expiredText, color: 'var(--text-muted)' };
|
|
10516
10523
|
const text = `${Math.floor(left / 60)}:${String(left % 60).padStart(2, '0')}`;
|
|
10517
10524
|
const color = left < 120 ? CONTEXT_COLORS.red : left < 300 ? CONTEXT_COLORS.orange : CONTEXT_COLORS.green;
|
|
10518
|
-
return { text, color };
|
|
10525
|
+
return { text, color: textTone(color) };
|
|
10519
10526
|
}
|
|
10520
10527
|
|
|
10521
10528
|
let cacheTimerInterval = null;
|
|
@@ -10584,7 +10591,7 @@ function renderContextDetail(session, { tokens = true } = {}) {
|
|
|
10584
10591
|
</div>
|
|
10585
10592
|
</div>
|
|
10586
10593
|
<div class="detail-context-summary">
|
|
10587
|
-
<span style="color:${color}">${Math.round(ctx.pct)}% used</span>
|
|
10594
|
+
<span style="color:${textTone(color)}">${Math.round(ctx.pct)}% used</span>
|
|
10588
10595
|
<span${ctx.compacts ? ` title="Auto-compact window · model window ${escapeHtml(formatTokens(ctx.modelSize / 1000))}"` : ''}>${formatTokens(ctx.usedTokens / 1000)} / ${formatTokens(ctx.size / 1000)}</span>
|
|
10589
10596
|
</div>`
|
|
10590
10597
|
: '';
|
|
@@ -10596,11 +10603,11 @@ function renderContextDetail(session, { tokens = true } = {}) {
|
|
|
10596
10603
|
<div class="detail-context-stats">
|
|
10597
10604
|
${usageRows}
|
|
10598
10605
|
${totalRows}
|
|
10599
|
-
${ctx ? `<div class="stat-item"><span class="stat-label">Cost</span><span class="stat-value" style="color:${getCostColor(cost.total_cost_usd)}">${formatCost(cost.total_cost_usd)}</span></div>` : ''}
|
|
10606
|
+
${ctx ? `<div class="stat-item"><span class="stat-label">Cost</span><span class="stat-value" style="color:${textTone(getCostColor(cost.total_cost_usd))}">${formatCost(cost.total_cost_usd)}</span></div>` : ''}
|
|
10600
10607
|
${reply ? renderCacheTimer(reply) : ''}
|
|
10601
10608
|
${cost.total_duration_ms != null ? `<div class="stat-item"><span class="stat-label">Duration</span><span class="stat-value">${formatDuration(cost.total_duration_ms)}</span></div>` : ''}
|
|
10602
10609
|
${cost.total_api_duration_ms != null ? `<div class="stat-item"><span class="stat-label">API time</span><span class="stat-value">${formatDuration(cost.total_api_duration_ms)}</span></div>` : ''}
|
|
10603
|
-
${cost.total_lines_added != null ? `<div class="stat-item"><span class="stat-label">Lines</span><span class="stat-value"><span style="color:${CONTEXT_COLORS.green}">+${(cost.total_lines_added || 0).toLocaleString()}</span> / <span style="color:${CONTEXT_COLORS.red}">-${(cost.total_lines_removed || 0).toLocaleString()}</span></span></div>` : ''}
|
|
10610
|
+
${cost.total_lines_added != null ? `<div class="stat-item"><span class="stat-label">Lines</span><span class="stat-value"><span style="color:${textTone(CONTEXT_COLORS.green)}">+${(cost.total_lines_added || 0).toLocaleString()}</span> / <span style="color:${textTone(CONTEXT_COLORS.red)}">-${(cost.total_lines_removed || 0).toLocaleString()}</span></span></div>` : ''}
|
|
10604
10611
|
</div>
|
|
10605
10612
|
</div>`;
|
|
10606
10613
|
}
|
|
@@ -10873,8 +10880,8 @@ function showWaitingDetail() {
|
|
|
10873
10880
|
// With actions on screen the form/buttons are the content — tuck the raw
|
|
10874
10881
|
// JSON behind a toggle, but keep what the user is approving visible: for a
|
|
10875
10882
|
// permission ask surface the command (or description) as a one-line summary
|
|
10883
|
+
let summaryHtml = '';
|
|
10876
10884
|
if ((actionsHtml || footerHtml) && inputHtml) {
|
|
10877
|
-
let summaryHtml = '';
|
|
10878
10885
|
if (currentWaiting.kind === 'plan' && typeof params?.plan === 'string' && params.plan) {
|
|
10879
10886
|
// A plan ask IS the plan — render it in full so it can be reviewed and
|
|
10880
10887
|
// approved right here (follow mode included) instead of a JSON dump. The
|
|
@@ -10897,7 +10904,8 @@ function showWaitingDetail() {
|
|
|
10897
10904
|
autoSizeModal(modal, body);
|
|
10898
10905
|
setMsgDetailFollow(msgDetailFollowLatest);
|
|
10899
10906
|
const overlay = document.getElementById('msg-detail-modal');
|
|
10900
|
-
|
|
10907
|
+
// The permission card's header already shows how long the ask has waited.
|
|
10908
|
+
const meta = summaryHtml ? [] : [formatDate(currentWaiting.timestamp), 'waiting'];
|
|
10901
10909
|
document.getElementById('msg-detail-meta').textContent = meta.join(' · ');
|
|
10902
10910
|
currentPinDetailId = null;
|
|
10903
10911
|
updateMsgDetailPinState();
|
|
@@ -10927,17 +10935,34 @@ function pathBasename(p) {
|
|
|
10927
10935
|
|
|
10928
10936
|
// `session.worktree` is resolved server-side from the `.git` pointer file, so it holds for a
|
|
10929
10937
|
// worktree parked beside its repo as well as one under <repo>/.claude/worktrees/.
|
|
10930
|
-
|
|
10938
|
+
// Under its own project group the card leaves the repo name out: the group header shows it.
|
|
10939
|
+
function renderProjectIdentity(session, hostProject) {
|
|
10931
10940
|
if (!session.project) return null;
|
|
10941
|
+
const underHost = !!hostProject && sessionProjectKey(session) === hostProject;
|
|
10932
10942
|
const wt = session.worktree;
|
|
10933
|
-
if (!wt)
|
|
10943
|
+
if (!wt) {
|
|
10944
|
+
return underHost ? null : `<span class="session-project">${escapeHtml(pathBasename(session.project))}</span>`;
|
|
10945
|
+
}
|
|
10934
10946
|
const repo = pathBasename(wt.repo);
|
|
10935
10947
|
// Worktrees beside the repo are conventionally named `<repo>-<branch>`, and repeating the
|
|
10936
10948
|
// repo inside a chip that already sits next to it wastes the whole width of the card.
|
|
10937
10949
|
const name = wt.name.startsWith(repo) ? wt.name.slice(repo.length).replace(/^[-._]/, '') || wt.name : wt.name;
|
|
10938
|
-
|
|
10950
|
+
const chip = `<span class="wt-chip" title="Worktree of ${escapeHtml(wt.repo)}">⑂ ${escapeHtml(name)}</span>`;
|
|
10951
|
+
return underHost ? chip : `<span class="session-project">${escapeHtml(repo)}</span>${chip}`;
|
|
10939
10952
|
}
|
|
10940
10953
|
|
|
10954
|
+
function renderSessionIdentity(projectHtml, gitBranch) {
|
|
10955
|
+
if (!projectHtml && !gitBranch) return '';
|
|
10956
|
+
const branch = gitBranch
|
|
10957
|
+
? `<span class="session-branch" title="${escapeHtml(gitBranch)}">${escapeHtml(gitBranch)}</span>`
|
|
10958
|
+
: '';
|
|
10959
|
+
const sep = projectHtml && branch ? '<span class="agent-ident-sep">·</span>' : '';
|
|
10960
|
+
return `<div class="session-secondary">${projectHtml || ''}${sep}${branch}</div>`;
|
|
10961
|
+
}
|
|
10962
|
+
|
|
10963
|
+
const SHORT_DATE = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric' });
|
|
10964
|
+
const SHORT_DATE_YEAR = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
|
10965
|
+
|
|
10941
10966
|
function formatDate(dateStr) {
|
|
10942
10967
|
const date = new Date(dateStr);
|
|
10943
10968
|
const now = new Date();
|
|
@@ -10946,7 +10971,8 @@ function formatDate(dateStr) {
|
|
|
10946
10971
|
if (diff < JUST_NOW_MS) return 'just now';
|
|
10947
10972
|
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
|
|
10948
10973
|
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
|
|
10949
|
-
return
|
|
10974
|
+
if (diff < 7 * 86400000) return `${Math.floor(diff / 86400000)}d ago`;
|
|
10975
|
+
return (date.getFullYear() === now.getFullYear() ? SHORT_DATE : SHORT_DATE_YEAR).format(date);
|
|
10950
10976
|
}
|
|
10951
10977
|
|
|
10952
10978
|
// Terminal output reaches the transcript raw. A CSI-only pattern left OSC
|
|
@@ -11272,8 +11298,9 @@ function loadMermaid() {
|
|
|
11272
11298
|
}
|
|
11273
11299
|
|
|
11274
11300
|
let mermaidQueue = Promise.resolve();
|
|
11301
|
+
let mermaidSeq = 0;
|
|
11275
11302
|
|
|
11276
|
-
// mermaid.
|
|
11303
|
+
// mermaid.render is async, and two renders that overlap draw diagrams over each other.
|
|
11277
11304
|
function queueMermaid(work) {
|
|
11278
11305
|
mermaidQueue = mermaidQueue.then(work).catch((err) => console.warn(err.message));
|
|
11279
11306
|
}
|
|
@@ -11300,10 +11327,15 @@ function initMermaidBlocks(container) {
|
|
|
11300
11327
|
return;
|
|
11301
11328
|
}
|
|
11302
11329
|
queueMermaid(async () => {
|
|
11303
|
-
// One block per
|
|
11330
|
+
// One block per render, so a bad diagram shows its own error and the others still render.
|
|
11331
|
+
// render, not run: run measures labels in place, and under a modal's CSS zoom the measured
|
|
11332
|
+
// widths are off, so node boxes clip their text.
|
|
11304
11333
|
for (const b of stampPending()) {
|
|
11305
11334
|
try {
|
|
11306
|
-
await mermaid.
|
|
11335
|
+
const { svg, bindFunctions } = await mermaid.render(`mermaid-${++mermaidSeq}`, b.textContent.trim());
|
|
11336
|
+
b.innerHTML = svg;
|
|
11337
|
+
b.setAttribute('data-processed', 'true');
|
|
11338
|
+
bindFunctions?.(b);
|
|
11307
11339
|
} catch (err) {
|
|
11308
11340
|
showMermaidError(b, err);
|
|
11309
11341
|
}
|
|
@@ -12360,8 +12392,8 @@ function updateDismissBtnState() {
|
|
|
12360
12392
|
const btn = document.getElementById('session-info-dismiss-btn');
|
|
12361
12393
|
if (!btn || !_infoModalSessionId) return;
|
|
12362
12394
|
const isDismissed = dismissedSessionIds.has(_infoModalSessionId);
|
|
12363
|
-
btn.textContent = isDismissed ? '
|
|
12364
|
-
btn.title = isDismissed ? '
|
|
12395
|
+
btn.textContent = isDismissed ? 'Show session' : 'Hide session';
|
|
12396
|
+
btn.title = isDismissed ? 'Show in the active list again' : 'Hide from the active list';
|
|
12365
12397
|
}
|
|
12366
12398
|
|
|
12367
12399
|
let _planSessionId = null;
|
|
@@ -15214,9 +15246,10 @@ function inPageField(e) {
|
|
|
15214
15246
|
function terminalShortcut(e, probe = false) {
|
|
15215
15247
|
const ctrlAlt = hubModDown(e);
|
|
15216
15248
|
const ctrlShift = e.ctrlKey && e.shiftKey && !e.altKey && !e.metaKey;
|
|
15217
|
-
// Ctrl+_ is Claude Code's undo, so the zoom keys leave it to the terminal.
|
|
15249
|
+
// Ctrl+_ is Claude Code's undo, so the zoom keys leave it to the terminal. An open ask card,
|
|
15250
|
+
// which sits in the terminal pane, takes them for its own text zoom.
|
|
15218
15251
|
const zoom = e.key === '_' ? undefined : zoomDelta(e);
|
|
15219
|
-
if (zoom !== undefined && termFrame.inited && (probe || terminalPaneFocused())) {
|
|
15252
|
+
if (zoom !== undefined && termFrame.inited && (probe || (terminalPaneFocused() && !isAskOnScreen()))) {
|
|
15220
15253
|
return () => adjustTerminalFontSize(Math.sign(zoom));
|
|
15221
15254
|
}
|
|
15222
15255
|
if (ctrlAlt && e.code === 'KeyS') return swapToPreviousSession;
|
|
@@ -16417,6 +16450,177 @@ window.addEventListener('message', (e) => {
|
|
|
16417
16450
|
|
|
16418
16451
|
//#endregion
|
|
16419
16452
|
|
|
16453
|
+
//#region ASK
|
|
16454
|
+
// The question the session in the embedded terminal asks with the ask tool; the tool call waits for
|
|
16455
|
+
// the answer. Hiding keeps the frame, so a half-filled card comes back as it was. Only an answer, a
|
|
16456
|
+
// skip or the agent's interrupt ends the question.
|
|
16457
|
+
const ASK_MSG = 'cck-ask:';
|
|
16458
|
+
const askState = { terminalId: null, ask: null, frame: null, frameH: 0, hidden: false, busy: false, seq: 0 };
|
|
16459
|
+
|
|
16460
|
+
// Runs inside the card as source text, ahead of the card's own scripts. The JSON round trip makes
|
|
16461
|
+
// the answer cloneable, and a value that is not JSON throws in the card.
|
|
16462
|
+
function askBridge() {
|
|
16463
|
+
window.cck = {
|
|
16464
|
+
send: (data) => parent.postMessage({ type: 'cck-ask:send', data: JSON.parse(JSON.stringify(data ?? null)) }, '*'),
|
|
16465
|
+
};
|
|
16466
|
+
}
|
|
16467
|
+
|
|
16468
|
+
const ASK_BRIDGE_TAG = `<script>(${askBridge})();</script>`;
|
|
16469
|
+
|
|
16470
|
+
function askEl() {
|
|
16471
|
+
return document.getElementById('ask-modal');
|
|
16472
|
+
}
|
|
16473
|
+
|
|
16474
|
+
function isAskOnScreen() {
|
|
16475
|
+
return !!askState.ask && !askEl().hidden;
|
|
16476
|
+
}
|
|
16477
|
+
|
|
16478
|
+
function syncAsk(tid) {
|
|
16479
|
+
if (tid === askState.terminalId) return;
|
|
16480
|
+
askState.terminalId = tid;
|
|
16481
|
+
setAsk(null);
|
|
16482
|
+
if (tid) loadAsk(tid);
|
|
16483
|
+
}
|
|
16484
|
+
|
|
16485
|
+
async function loadAsk(tid) {
|
|
16486
|
+
const seq = ++askState.seq;
|
|
16487
|
+
const data = await getJson(apiPath`/api/terminals/${tid}/ask`, null, { cache: 'no-store' });
|
|
16488
|
+
if (seq !== askState.seq || tid !== askState.terminalId) return;
|
|
16489
|
+
const a = data?.ask ?? null;
|
|
16490
|
+
if (a?.id !== askState.ask?.id) setAsk(a);
|
|
16491
|
+
}
|
|
16492
|
+
|
|
16493
|
+
function setAsk(a) {
|
|
16494
|
+
const modal = askEl();
|
|
16495
|
+
const body = modal.querySelector('.ask-body');
|
|
16496
|
+
Object.assign(askState, { ask: a, frame: null, frameH: 0, hidden: false, busy: false });
|
|
16497
|
+
body.replaceChildren();
|
|
16498
|
+
if (a) {
|
|
16499
|
+
modal.querySelector('.ask-title').textContent = a.title;
|
|
16500
|
+
modal.querySelector('.ask-box').dataset.size = a.size;
|
|
16501
|
+
if (a.content == null) {
|
|
16502
|
+
body.innerHTML = '<p class="ask-missing">The card file is gone. Skip, so the agent stops waiting.</p>';
|
|
16503
|
+
} else {
|
|
16504
|
+
const frame = createPreviewFrame('ask-frame', showSrcdoc(a.content, 0, ASK_BRIDGE_TAG), SHOW_SANDBOX);
|
|
16505
|
+
frame.addEventListener('load', () => {
|
|
16506
|
+
if (askState.frame === frame && !askState.hidden) frame.focus();
|
|
16507
|
+
});
|
|
16508
|
+
askState.frame = frame;
|
|
16509
|
+
body.append(frame);
|
|
16510
|
+
}
|
|
16511
|
+
}
|
|
16512
|
+
renderAsk();
|
|
16513
|
+
}
|
|
16514
|
+
|
|
16515
|
+
function renderAsk() {
|
|
16516
|
+
const a = askState.ask;
|
|
16517
|
+
askEl().hidden = !a || askState.hidden;
|
|
16518
|
+
const chip = document.getElementById('ask-reopen');
|
|
16519
|
+
chip.hidden = !a || !askState.hidden;
|
|
16520
|
+
if (a) {
|
|
16521
|
+
chip.querySelector('.ask-reopen-n').textContent = a.title;
|
|
16522
|
+
chip.title = `Open the question: ${a.title}`;
|
|
16523
|
+
}
|
|
16524
|
+
layoutAsk();
|
|
16525
|
+
}
|
|
16526
|
+
|
|
16527
|
+
// A full card fills the modal; a smaller one takes its content's height, up to the terminal's.
|
|
16528
|
+
function layoutAsk() {
|
|
16529
|
+
const frame = askState.frame;
|
|
16530
|
+
const box = askEl().querySelector('.ask-box');
|
|
16531
|
+
if (!frame || askEl().hidden || box.dataset.size === 'full') return;
|
|
16532
|
+
const host = document.getElementById('terminal-host');
|
|
16533
|
+
const chrome = box.querySelector('.ask-head').offsetHeight + box.querySelector('.ask-foot').offsetHeight + 2;
|
|
16534
|
+
frame.style.height = `${Math.max(SHOW_MIN_H, Math.min(askState.frameH || SHOW_MIN_H, host.clientHeight - 32 - chrome))}px`;
|
|
16535
|
+
}
|
|
16536
|
+
|
|
16537
|
+
function hideAsk() {
|
|
16538
|
+
askState.hidden = true;
|
|
16539
|
+
renderAsk();
|
|
16540
|
+
focusTerminalPane();
|
|
16541
|
+
}
|
|
16542
|
+
|
|
16543
|
+
function reopenAsk() {
|
|
16544
|
+
askState.hidden = false;
|
|
16545
|
+
renderAsk();
|
|
16546
|
+
askState.frame?.focus();
|
|
16547
|
+
}
|
|
16548
|
+
|
|
16549
|
+
async function endAsk(body) {
|
|
16550
|
+
const a = askState.ask;
|
|
16551
|
+
const tid = askState.terminalId;
|
|
16552
|
+
if (!a || !tid || askState.busy) return;
|
|
16553
|
+
askState.busy = true;
|
|
16554
|
+
try {
|
|
16555
|
+
const res = await terminalFetch(apiPath`/api/terminals/${tid}/ask/${a.id}`, 'POST', body);
|
|
16556
|
+
if (res.status === 404) showToast('The question is no longer open');
|
|
16557
|
+
else if (!res.ok) {
|
|
16558
|
+
const out = await res.json().catch(() => ({}));
|
|
16559
|
+
return showToast(out.error || `Send failed (HTTP ${res.status})`, 'error');
|
|
16560
|
+
}
|
|
16561
|
+
if (askState.ask?.id !== a.id) return;
|
|
16562
|
+
setAsk(null);
|
|
16563
|
+
focusTerminalPane();
|
|
16564
|
+
} catch {
|
|
16565
|
+
showToast('Failed to send the answer', 'error');
|
|
16566
|
+
} finally {
|
|
16567
|
+
if (askState.ask?.id === a.id) askState.busy = false;
|
|
16568
|
+
}
|
|
16569
|
+
}
|
|
16570
|
+
|
|
16571
|
+
function answerAsk(data) {
|
|
16572
|
+
return endAsk({ status: 'answered', data });
|
|
16573
|
+
}
|
|
16574
|
+
|
|
16575
|
+
function onAskOpened(d) {
|
|
16576
|
+
if (typeof d.terminalId !== 'string') return;
|
|
16577
|
+
if (d.terminalId === askState.terminalId) return loadAsk(d.terminalId);
|
|
16578
|
+
if (askState.terminalId || !wantsTerminal()) return;
|
|
16579
|
+
if (!terminalIds.has(currentSessionId)) showState.lookup = null;
|
|
16580
|
+
syncShowCard();
|
|
16581
|
+
}
|
|
16582
|
+
|
|
16583
|
+
function onAskClosed(d) {
|
|
16584
|
+
if (d.terminalId !== askState.terminalId || askState.ask?.id !== d.id) return;
|
|
16585
|
+
if (d.status === 'cancelled') showToast('The agent stopped waiting for the answer');
|
|
16586
|
+
setAsk(null);
|
|
16587
|
+
}
|
|
16588
|
+
|
|
16589
|
+
// A script can send without a click, so a send needs transient user activation, as a card action does.
|
|
16590
|
+
window.addEventListener('message', (e) => {
|
|
16591
|
+
const frame = askState.frame;
|
|
16592
|
+
if (!frame || e.source !== frame.contentWindow || typeof e.data?.type !== 'string') return;
|
|
16593
|
+
if (e.data.type === `${ASK_MSG}send`) {
|
|
16594
|
+
if (navigator.userActivation?.isActive) answerAsk(e.data.data);
|
|
16595
|
+
} else if (e.data.type === `${SHOW_MSG}height`) {
|
|
16596
|
+
askState.frameH = Number(e.data.h) || askState.frameH;
|
|
16597
|
+
layoutAsk();
|
|
16598
|
+
}
|
|
16599
|
+
});
|
|
16600
|
+
|
|
16601
|
+
function initAsk() {
|
|
16602
|
+
const modal = askEl();
|
|
16603
|
+
modal.addEventListener('click', (e) => {
|
|
16604
|
+
const cmd = e.target === modal ? 'hide' : e.target.closest('[data-ask]')?.dataset.ask;
|
|
16605
|
+
if (cmd === 'hide') hideAsk();
|
|
16606
|
+
else if (cmd === 'skip') endAsk({ status: 'dismissed' });
|
|
16607
|
+
});
|
|
16608
|
+
document.getElementById('ask-reopen').addEventListener('click', reopenAsk);
|
|
16609
|
+
// Capture, ahead of the board's own Escape. The card's frame hands Escape back as a key on the frame.
|
|
16610
|
+
window.addEventListener(
|
|
16611
|
+
'keydown',
|
|
16612
|
+
(e) => {
|
|
16613
|
+
if (e.key !== 'Escape' || modal.hidden || !modal.contains(e.target)) return;
|
|
16614
|
+
e.preventDefault();
|
|
16615
|
+
e.stopImmediatePropagation();
|
|
16616
|
+
hideAsk();
|
|
16617
|
+
},
|
|
16618
|
+
true,
|
|
16619
|
+
);
|
|
16620
|
+
window.addEventListener('resize', layoutAsk);
|
|
16621
|
+
}
|
|
16622
|
+
//#endregion
|
|
16623
|
+
|
|
16420
16624
|
//#region SHOW
|
|
16421
16625
|
// The card the session in the embedded terminal posts to with the show tool. It belongs to the
|
|
16422
16626
|
// terminal, not the session: /clear and the --resume picker change the session id, the terminal id
|
|
@@ -16629,6 +16833,7 @@ function syncShowCard() {
|
|
|
16629
16833
|
loadTerminals().catch(() => {});
|
|
16630
16834
|
}
|
|
16631
16835
|
const tid = pty ? (terminalIds.get(pty) ?? null) : null;
|
|
16836
|
+
syncAsk(tid);
|
|
16632
16837
|
if (tid === showState.terminalId) return;
|
|
16633
16838
|
const view = (tid && showViews[tid]) || { collapsed: true };
|
|
16634
16839
|
Object.assign(showState, { terminalId: tid, sessionId: null, idx: 0 });
|
|
@@ -16882,8 +17087,8 @@ function withHeadFirst(content, head) {
|
|
|
16882
17087
|
const FRAME_SCROLLBAR_CSS = ':where(html){scrollbar-width:thin;scrollbar-color:rgba(128,128,128,.45) transparent}';
|
|
16883
17088
|
|
|
16884
17089
|
// A whole document gets the tokens and base styles first in its head, so its own styles win.
|
|
16885
|
-
function showSrcdoc(content, restoreY) {
|
|
16886
|
-
const head = `<meta http-equiv="Content-Security-Policy" content="${SHOW_CSP}"><style>:root{${showTokenCss()}}${SHOW_FRAME_BASE}${FRAME_SCROLLBAR_CSS}</style
|
|
17090
|
+
function showSrcdoc(content, restoreY, extraHead = '') {
|
|
17091
|
+
const head = `<meta http-equiv="Content-Security-Policy" content="${SHOW_CSP}"><style>:root{${showTokenCss()}}${SHOW_FRAME_BASE}${FRAME_SCROLLBAR_CSS}</style>${extraHead}`;
|
|
16887
17092
|
const bridge = `<script>(${showBridge})(${Math.max(0, Math.round(restoreY) || 0)}, ${modalZoom});</script>${REVIEW_BRIDGE_TAG}`;
|
|
16888
17093
|
if (!/^\s*(<!doctype|<html)/i.test(content)) {
|
|
16889
17094
|
return `<!doctype html><html><head><meta charset="utf-8">${head}</head><body>${content}${bridge}</body></html>`;
|
|
@@ -16989,7 +17194,7 @@ new MutationObserver(() => {
|
|
|
16989
17194
|
|
|
16990
17195
|
// Builds the message only when a frame is there, because showTokens forces a style recalc.
|
|
16991
17196
|
function postToShowFrames(makeMsg) {
|
|
16992
|
-
const frames = [showState.frame, ...document.querySelectorAll('.show-pane-frame, .themed-frame')].filter(
|
|
17197
|
+
const frames = [showState.frame, ...document.querySelectorAll('.show-pane-frame, .themed-frame, .ask-frame')].filter(
|
|
16993
17198
|
(f) => f?.contentWindow,
|
|
16994
17199
|
);
|
|
16995
17200
|
if (!frames.length) return;
|
|
@@ -17292,13 +17497,13 @@ function forgetPlaceholder(id) {
|
|
|
17292
17497
|
return true;
|
|
17293
17498
|
}
|
|
17294
17499
|
|
|
17295
|
-
function renderPlaceholderCard(session, depth) {
|
|
17500
|
+
function renderPlaceholderCard(session, depth, hostProject) {
|
|
17296
17501
|
const isActive = session.id === currentSessionId && viewMode === 'session';
|
|
17297
|
-
const projectHtml = renderProjectIdentity(session);
|
|
17502
|
+
const projectHtml = renderProjectIdentity(session, hostProject);
|
|
17298
17503
|
return `
|
|
17299
17504
|
<button onclick="openSession('${escAttrJs(session.id)}')" data-session-id="${escapeHtml(session.id)}" class="session-item session-placeholder ${isActive ? 'active' : ''}${childDepthClass(depth)}" title="${escapeHtml(`${session.id} | ${session.project}`)}">
|
|
17300
17505
|
<div class="session-name">${escapeHtml(session.name)}</div>
|
|
17301
|
-
${projectHtml
|
|
17506
|
+
${renderSessionIdentity(projectHtml, null)}
|
|
17302
17507
|
${
|
|
17303
17508
|
trustWaiting.has(session.id)
|
|
17304
17509
|
? `<div class="session-waiting"><span class="agent-badge agent-badge-waiting" title="Waiting for user">${ICON_AGENT_WAITING}</span>trust this folder?</div>`
|
|
@@ -17929,6 +18134,7 @@ initSessionGroupsDnd();
|
|
|
17929
18134
|
initSessionPicker();
|
|
17930
18135
|
initProjectPicker();
|
|
17931
18136
|
initShowCard();
|
|
18137
|
+
initAsk();
|
|
17932
18138
|
for (const k of readStoredList(ACTIVITY_FILTER_KEY)) activityFilter.add(k);
|
|
17933
18139
|
initSidebarResize();
|
|
17934
18140
|
applyModalZoom();
|
|
@@ -18010,7 +18216,13 @@ function makeLimitCell(label, bucket) {
|
|
|
18010
18216
|
function makeLimitSpan(rl) {
|
|
18011
18217
|
const span = document.createElement('span');
|
|
18012
18218
|
span.className = 'footer-limits';
|
|
18013
|
-
span.
|
|
18219
|
+
span.title = 'Plan usage: 5-hour and 7-day limits, used % (time until reset)';
|
|
18220
|
+
span.append(
|
|
18221
|
+
'Usage ',
|
|
18222
|
+
makeLimitCell('5h', rl?.five_hour),
|
|
18223
|
+
document.createTextNode(' · '),
|
|
18224
|
+
makeLimitCell('7d', rl?.seven_day),
|
|
18225
|
+
);
|
|
18014
18226
|
return span;
|
|
18015
18227
|
}
|
|
18016
18228
|
function renderSidebarFooter(rateLimits) {
|
package/public/index.html
CHANGED
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
<div class="progress-bar">
|
|
244
244
|
<div id="progress-bar" class="progress-fill" style="width: 0%"></div>
|
|
245
245
|
</div>
|
|
246
|
-
<span id="progress-percent" class="progress-text">0
|
|
246
|
+
<span id="progress-percent" class="progress-text">0% done</span>
|
|
247
247
|
</div>
|
|
248
248
|
<button id="terminal-toggle" class="icon-btn" onclick="event.shiftKey ? openTerminalManager() : toggleTerminal()" title="Show / hide terminal (Ctrl+`) · Focus (Alt+`) · End (Alt+Shift+`) · Shift+click or Ctrl+Shift+` for all terminals" aria-label="Toggle terminal" style="display: none;">
|
|
249
249
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -362,6 +362,10 @@
|
|
|
362
362
|
<div id="terminal-pane" class="terminal-pane">
|
|
363
363
|
<div class="terminal-bar">
|
|
364
364
|
<span id="terminal-status" class="terminal-status"></span>
|
|
365
|
+
<button id="ask-reopen" class="show-reopen ask-reopen" type="button" hidden>
|
|
366
|
+
<span class="ask-dot" aria-hidden="true">?</span>
|
|
367
|
+
<span class="ask-reopen-n"></span>
|
|
368
|
+
</button>
|
|
365
369
|
<button id="show-reopen" class="show-reopen" type="button" title="Show the overlay" hidden>
|
|
366
370
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M13 4v7h8"/></svg>
|
|
367
371
|
<span class="show-reopen-n">show</span>
|
|
@@ -375,6 +379,23 @@
|
|
|
375
379
|
</div>
|
|
376
380
|
<div id="terminal-host" class="terminal-host">
|
|
377
381
|
<section id="show-card" class="show-card" aria-label="Show overlay"></section>
|
|
382
|
+
<div id="ask-modal" class="ask-modal" hidden>
|
|
383
|
+
<section class="ask-box" role="dialog" aria-modal="true" aria-labelledby="ask-title">
|
|
384
|
+
<header class="ask-head">
|
|
385
|
+
<span class="ask-dot" aria-hidden="true">?</span>
|
|
386
|
+
<span id="ask-title" class="ask-title"></span>
|
|
387
|
+
<span class="ask-hint">Esc hides</span>
|
|
388
|
+
<button type="button" class="ask-x" data-ask="hide" title="Hide. The question stays open." aria-label="Hide">
|
|
389
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
390
|
+
</button>
|
|
391
|
+
</header>
|
|
392
|
+
<div class="ask-body"></div>
|
|
393
|
+
<footer class="ask-foot">
|
|
394
|
+
<span>The agent waits for your answer.</span>
|
|
395
|
+
<button type="button" data-ask="skip">Skip</button>
|
|
396
|
+
</footer>
|
|
397
|
+
</section>
|
|
398
|
+
</div>
|
|
378
399
|
</div>
|
|
379
400
|
<textarea id="terminal-key-proxy" class="terminal-key-proxy" tabindex="-1" aria-hidden="true"></textarea>
|
|
380
401
|
</div>
|
|
@@ -575,7 +596,7 @@
|
|
|
575
596
|
</div>
|
|
576
597
|
<div id="team-modal-body" class="modal-body modal-zoomable"></div>
|
|
577
598
|
<div class="modal-footer">
|
|
578
|
-
<button id="session-info-dismiss-btn" class="btn btn-secondary" onclick="toggleDismissSession(_infoModalSessionId); closeTeamModal()">
|
|
599
|
+
<button id="session-info-dismiss-btn" class="btn btn-secondary" onclick="toggleDismissSession(_infoModalSessionId); closeTeamModal()">Hide session</button>
|
|
579
600
|
<button class="btn btn-secondary" onclick="showToolStatsModal(_infoModalSessionId)" title="Tool invocation statistics">Tool Stats</button>
|
|
580
601
|
<button class="btn btn-secondary" onclick="closeTeamModal()">Close</button>
|
|
581
602
|
</div>
|
|
@@ -706,7 +727,7 @@
|
|
|
706
727
|
</div>
|
|
707
728
|
<div id="agent-modal-body" class="modal-body modal-zoomable" style="overflow-y: auto; overflow-x: hidden; flex: 0 1 auto; min-height: 0;"></div>
|
|
708
729
|
<div class="modal-footer">
|
|
709
|
-
<button id="agent-modal-dismiss-btn" class="btn
|
|
730
|
+
<button id="agent-modal-dismiss-btn" class="btn btn-secondary" style="display:none" onclick="dismissAgent(currentAgentModalId);closeAgentModal()" title="Mark this agent as stopped">Mark stopped</button>
|
|
710
731
|
<button class="btn btn-secondary" onclick="closeAgentModal()">Close</button>
|
|
711
732
|
</div>
|
|
712
733
|
</div>
|