Skip to content

Commit f58a364

Browse files
authored
Merge pull request #20 from levelcodeai/fix/autopilot-and-working-indicator
fix(ai): live autopilot toggle + persistent working indicator (v0.7.2)
2 parents cdf2549 + d4266ab commit f58a364

2 files changed

Lines changed: 26 additions & 9 deletions

File tree

extensions/levelcode-ai/extension.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,10 @@ async function agentFlow(text) {
959959
maxSteps: Math.max(1, cfg.get('agent.maxSteps', 25)),
960960
maxTokens: Math.max(1024, cfg.get('agent.maxTokens', 8192)), // per-turn output cap; continued across turns if hit
961961
post, dbg,
962-
autopilot: autopilot, // run commands without asking, except the danger set (commandSafety.js)
962+
// LIVE, not a snapshot: agent.js reads ctx.autopilot at each run_command, so flipping the
963+
// Autopilot toggle mid-run takes effect on the very next command (run commands without asking,
964+
// except the danger set — commandSafety.js).
965+
get autopilot() { return autopilot; },
963966
approve: requestApproval, // run_command only
964967
ask: requestQuestions, // ask_user — clickable clarifying questions
965968
// Gateway-mode token refresh for the agent loop: on a mid-run 401 (expired access token)

extensions/levelcode-ai/media/chat.html

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@
152152
.agentstatus { font-size: 12px; margin: 5px 2px; opacity: .9; display: flex; gap: 7px; align-items: center; font-style: italic; }
153153
.agentstatus .spin { display: inline-block; font-style: normal; animation: spin .8s linear infinite; }
154154
@keyframes spin { to { transform: rotate(360deg); } }
155+
/* persistent working indicator above the composer — visible the whole time the agent runs */
156+
#workbar { display: flex; align-items: center; gap: 8px; padding: 5px 4px 7px; }
157+
#workbar[hidden] { display: none; }
158+
#workbar .wspin { display: inline-flex; flex: 0 0 auto; }
159+
#workbar .wspin .lc-spin { width: 18px; height: 18px; }
160+
#workbar .wtext { font-size: 12px; }
155161
/* shimmering "glance" text while the agent is working (Cursor/Claude-Code style sweep) */
156162
.shimmer {
157163
background: linear-gradient(90deg, var(--muted) 0%, var(--muted) 38%, var(--vscode-foreground) 50%, var(--muted) 62%, var(--muted) 100%);
@@ -891,6 +897,10 @@
891897
<button class="undoall">Undo all</button>
892898
</div>
893899

900+
<!-- persistent "agent is working" indicator: shown for the WHOLE run (tied to streaming), so there's
901+
never a gap between actions where nothing shows. Its label tracks the current activity. -->
902+
<div id="workbar" hidden role="status" aria-live="polite"><span class="wspin" aria-hidden="true"></span><span class="wtext shimmer">Working…</span></div>
903+
894904
<div id="composer">
895905
<div id="chips"></div>
896906
<textarea id="input" rows="2" placeholder="Give the agent a goal…"></textarea>
@@ -1060,6 +1070,12 @@
10601070
+ '<path class="c1" d="M29,92 L60,70 L91,92"/>'
10611071
+ '<path class="c2" d="M29,71 L60,49 L91,71"/>'
10621072
+ '<path class="c3" d="M29,50 L60,28 L91,50"/></svg>';
1073+
// The persistent working bar (above the composer) reuses the same spinner. Its visibility is tied to
1074+
// `streaming` (setStreaming), so it can never fall into a gap between actions; setWork() updates its label.
1075+
const workbarEl = document.getElementById('workbar');
1076+
const workTextEl = workbarEl && workbarEl.querySelector('.wtext');
1077+
{ const s = workbarEl && workbarEl.querySelector('.wspin'); if (s) { s.innerHTML = LC_SPIN_SVG; } }
1078+
function setWork(text){ if (workTextEl) { workTextEl.textContent = text || 'Working…'; } }
10631079

10641080
function esc(s){ return String(s == null ? '' : s).replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])); }
10651081
// Inline VS Code codicons (MIT, @vscode/codicons) — replaces UI emojis, CSP/font-free.
@@ -1381,13 +1397,9 @@
13811397
+ '<div class="tl-body"><span class="toolt">' + esc(text || '') + '</span></div>';
13821398
log.appendChild(d); scrollIfStuck();
13831399
}
1384-
function setAgentStatus(text){
1385-
clearStatus();
1386-
const d = document.createElement('div'); d.className = 'agentstatus';
1387-
const t = (!text || text === 'thinking…') ? 'Thinking…' : text; // shimmering label while the agent works
1388-
d.innerHTML = LC_SPIN_SVG + '<span class="shimmer">' + esc(t) + '</span>';
1389-
log.appendChild(d); agentStatusEl = d; scrollIfStuck();
1390-
}
1400+
// Drives the persistent working bar (not an inline log element), so the indicator can't be cleared into
1401+
// a gap by an intervening tool card or bubble. The bar's visibility is owned by setStreaming.
1402+
function setAgentStatus(text){ setWork((!text || text === 'thinking…') ? 'Thinking…' : text); }
13911403
function applyMode(agent){
13921404
agentMode = agent;
13931405
document.getElementById('modeLabel').textContent = agent ? 'Agent' : 'Chat';
@@ -2094,6 +2106,8 @@
20942106
sendBtn.classList.toggle('stop', on);
20952107
sendBtn.innerHTML = on ? '&#9632;' : '&#8629;'; // ■ while streaming, ↵ otherwise
20962108
sendBtn.title = on ? 'Stop' : 'Send (Enter)';
2109+
if (workbarEl){ workbarEl.hidden = !on; } // the working bar is visible for the ENTIRE run
2110+
if (on){ setWork('Working…'); }
20972111
}
20982112
// Shell-style history recall in the composer: ↑ = previous sent message, ↓ = newer (back to your draft).
20992113
// Session-only. In multiline text, ↑/↓ recall only when the caret is on the first/last line (arrows still move otherwise).
@@ -2456,7 +2470,7 @@
24562470
else if (m.type === 'autopilot'){ applyAutopilot(!!m.on); }
24572471
else if (m.type === 'agentStart'){ setStreaming(true); agentBubble = null; agentRaw = ''; renderPlan([]); setAgentStatus('thinking…'); }
24582472
else if (m.type === 'agentStatus'){ finishAgentBubble(); setAgentStatus(m.text || 'working…'); }
2459-
else if (m.type === 'agentDelta'){ clearStatus(); if (!agentBubble){ agentBubble = makeStream(add('assistant', '')); agentRaw = ''; } agentRaw += m.text; streamFeed(agentBubble, agentRaw); }
2473+
else if (m.type === 'agentDelta'){ clearStatus(); setWork('Responding…'); if (!agentBubble){ agentBubble = makeStream(add('assistant', '')); agentRaw = ''; } agentRaw += m.text; streamFeed(agentBubble, agentRaw); }
24602474
else if (m.type === 'agentTurnEnd'){ finishAgentBubble(); }
24612475
else if (m.type === 'agentTool'){ finishAgentBubble(); addAgentLine(m.icon, m.text); }
24622476
else if (m.type === 'agentApproval'){ addApproval(m); }

0 commit comments

Comments
 (0)