@co0ontty/wand 1.21.15 → 1.21.16

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.
@@ -328,29 +328,81 @@
328
328
  // 先驱动跨视图的运行指示器(顶部进度条/徽章计时/气泡呼吸条)
329
329
  updateRunningIndicators(session);
330
330
 
331
- // 旧的 .structured-status-bar(在 composer-top-row 里独立的小条)
332
- // 已经合并进 #todo-progress 顶部状态条。这里只清理可能残留的旧节点 +
333
- // 维护 composer in-flight 的发光态 + 同步 _statusBarStartTime 给
334
- // 顶部徽章计时复用。
335
- var legacy = document.querySelector(".structured-status-bar");
336
- if (legacy) legacy.remove();
337
-
331
+ // Status bar now lives in .composer-top-row alongside the todo-progress collapse bar
332
+ var topRow = document.querySelector(".composer-top-row");
333
+ var existing = document.querySelector(".structured-status-bar");
338
334
  var composer = document.querySelector(".input-composer");
339
335
  if (!session || !isStructuredSession(session)) {
336
+ if (existing) existing.remove();
340
337
  if (composer) composer.classList.remove("in-flight");
341
- if (_statusBarTimerId) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; }
342
- _statusBarStartTime = 0;
338
+ clearInterval(_statusBarTimerId);
339
+ _statusBarTimerId = null;
343
340
  return;
344
341
  }
345
342
 
346
- var isInFlight = !!(session.structuredState && session.structuredState.inFlight);
343
+ var isInFlight = session.structuredState && session.structuredState.inFlight;
344
+
347
345
  if (isInFlight) {
348
- if (!_statusBarStartTime) _statusBarStartTime = Date.now();
346
+ // Start timer if not already running
347
+ if (!_statusBarTimerId) {
348
+ _statusBarStartTime = Date.now();
349
+ }
350
+
351
+ // Add glow to input composer
349
352
  if (composer) composer.classList.add("in-flight");
353
+
354
+ if (!existing && topRow) {
355
+ var bar = document.createElement("div");
356
+ bar.className = "structured-status-bar";
357
+ bar.innerHTML =
358
+ '<span class="status-bar-dot"></span>' +
359
+ '<span class="status-bar-label">回复中</span>' +
360
+ '<span class="status-bar-timer">0.0s</span>';
361
+ // Append as last child of the top row so it sits to the right of the todo bar
362
+ topRow.appendChild(bar);
363
+ existing = bar;
364
+ } else if (existing && existing.classList.contains("completed")) {
365
+ // Was completed, now in-flight again — reset
366
+ existing.classList.remove("completed");
367
+ existing.style.animation = "none";
368
+ existing.querySelector(".status-bar-label").textContent = "回复中";
369
+ var dot = existing.querySelector(".status-bar-dot");
370
+ if (dot) dot.style.display = "";
371
+ _statusBarStartTime = Date.now();
372
+ }
373
+
374
+ // Start interval to update timer
375
+ if (!_statusBarTimerId) {
376
+ _statusBarTimerId = setInterval(function() {
377
+ var bar = document.querySelector(".structured-status-bar:not(.completed)");
378
+ if (!bar) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; return; }
379
+ var elapsed = ((Date.now() - _statusBarStartTime) / 1000).toFixed(1);
380
+ var timerEl = bar.querySelector(".status-bar-timer");
381
+ if (timerEl) timerEl.textContent = elapsed + "s";
382
+ }, 100);
383
+ }
350
384
  } else {
385
+ // Not in-flight: show completion or remove
386
+ clearInterval(_statusBarTimerId);
387
+ _statusBarTimerId = null;
388
+
389
+ // Remove glow from input composer
351
390
  if (composer) composer.classList.remove("in-flight");
352
- _statusBarStartTime = 0;
353
- if (_statusBarTimerId) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; }
391
+
392
+ if (existing && !existing.classList.contains("completed")) {
393
+ // Just finished — transition to completed state
394
+ var elapsed = _statusBarStartTime ? ((Date.now() - _statusBarStartTime) / 1000).toFixed(1) : "0.0";
395
+ existing.classList.add("completed");
396
+ existing.querySelector(".status-bar-label").textContent = "完成";
397
+ existing.querySelector(".status-bar-timer").textContent = elapsed + "s";
398
+ var dot = existing.querySelector(".status-bar-dot");
399
+ if (dot) dot.style.display = "none";
400
+ _statusBarStartTime = 0;
401
+ // Remove after animation ends
402
+ setTimeout(function() {
403
+ if (existing.parentNode) existing.remove();
404
+ }, 3000);
405
+ }
354
406
  }
355
407
  }
356
408
 
@@ -1373,12 +1425,10 @@
1373
1425
  '<div class="todo-progress-header" id="todo-progress-toggle">' +
1374
1426
  '<div class="todo-progress-left">' +
1375
1427
  '<span class="todo-progress-spinner"></span>' +
1376
- '<span class="todo-progress-message" id="todo-progress-message"></span>' +
1377
- '</div>' +
1378
- '<div class="todo-progress-right">' +
1379
- '<span class="todo-progress-status" id="todo-progress-status">运行中</span>' +
1380
- '<svg class="todo-progress-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>' +
1428
+ '<span class="todo-progress-counter" id="todo-progress-counter">0/0</span>' +
1429
+ '<span class="todo-progress-task" id="todo-progress-task"></span>' +
1381
1430
  '</div>' +
1431
+ '<svg class="todo-progress-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>' +
1382
1432
  '</div>' +
1383
1433
  '<div class="todo-progress-body hidden" id="todo-progress-body">' +
1384
1434
  '<ul class="todo-progress-list" id="todo-progress-list"></ul>' +
@@ -13981,122 +14031,77 @@
13981
14031
  });
13982
14032
 
13983
14033
  function updateTodoProgress(messages) {
13984
- var container = document.getElementById("todo-progress");
13985
- if (!container) return;
13986
-
13987
- var session = state.sessions.find(function(s) { return s.id === state.selectedId; });
13988
- if (!session) {
13989
- container.classList.add("hidden");
13990
- return;
13991
- }
13992
-
13993
- // ── 左侧:用户最新发出的一条消息 ──
13994
- var lastUserText = "";
13995
- if (Array.isArray(messages)) {
13996
- for (var i = messages.length - 1; i >= 0; i--) {
13997
- var umsg = messages[i];
13998
- if (!umsg || umsg.role !== "user" || !Array.isArray(umsg.content)) continue;
13999
- for (var uj = 0; uj < umsg.content.length; uj++) {
14000
- var ublock = umsg.content[uj];
14001
- if (ublock && ublock.type === "text" && ublock.text && ublock.text.trim()) {
14002
- lastUserText = ublock.text.trim().replace(/\s+/g, " ");
14003
- break;
14004
- }
14034
+ var todos = null;
14035
+ // Scan all messages for latest TodoWrite tool_use
14036
+ for (var i = messages.length - 1; i >= 0; i--) {
14037
+ var msg = messages[i];
14038
+ if (!msg.content || !Array.isArray(msg.content)) continue;
14039
+ for (var j = msg.content.length - 1; j >= 0; j--) {
14040
+ var block = msg.content[j];
14041
+ if (block.type === "tool_use" && block.name === "TodoWrite" && block.input && block.input.todos) {
14042
+ todos = block.input.todos;
14043
+ break;
14005
14044
  }
14006
- if (lastUserText) break;
14007
14045
  }
14046
+ if (todos) break;
14008
14047
  }
14009
14048
 
14010
- // 没有任何用户消息(例如刚新建的空会话)才隐藏
14011
- if (!lastUserText) {
14049
+ var container = document.getElementById("todo-progress");
14050
+ if (!container) return;
14051
+
14052
+ if (!todos || todos.length === 0) {
14012
14053
  container.classList.add("hidden");
14013
14054
  return;
14014
14055
  }
14015
- container.classList.remove("hidden");
14016
14056
 
14017
- // ── 右侧:当前运行状态 ──
14018
- var sig = computeRunningSignal(session);
14019
- var isStructured = isStructuredSession(session);
14020
- var isRunning = isStructured ? !!sig.inFlight : !!sig.ptyRunning;
14021
- var statusText = isRunning
14022
- ? (isStructured ? "运行中" : "正在运行")
14023
- : "已完成";
14057
+ container.classList.remove("hidden");
14024
14058
 
14025
- // ── 步骤数:来自最近一次 TodoWrite ──
14026
- var todos = null;
14027
- if (Array.isArray(messages)) {
14028
- for (var ti = messages.length - 1; ti >= 0; ti--) {
14029
- var tmsg = messages[ti];
14030
- if (!tmsg.content || !Array.isArray(tmsg.content)) continue;
14031
- for (var tj = tmsg.content.length - 1; tj >= 0; tj--) {
14032
- var tblock = tmsg.content[tj];
14033
- if (tblock && tblock.type === "tool_use" && tblock.name === "TodoWrite" && tblock.input && tblock.input.todos) {
14034
- todos = tblock.input.todos;
14035
- break;
14036
- }
14059
+ var completed = 0;
14060
+ var inProgress = 0;
14061
+ var activeTask = "";
14062
+ for (var k = 0; k < todos.length; k++) {
14063
+ if (todos[k].status === "completed") completed++;
14064
+ if (todos[k].status === "in_progress") {
14065
+ inProgress++;
14066
+ if (!activeTask) {
14067
+ activeTask = todos[k].activeForm || todos[k].content || "";
14037
14068
  }
14038
- if (todos) break;
14039
14069
  }
14040
14070
  }
14041
14071
 
14042
- var hasTodos = !!(todos && todos.length > 0);
14043
- var allTodosDone = false;
14044
- var stepLabel = "";
14045
- if (hasTodos) {
14046
- var completedCount = 0;
14047
- var inProgressCount = 0;
14048
- for (var k = 0; k < todos.length; k++) {
14049
- if (todos[k].status === "completed") completedCount++;
14050
- if (todos[k].status === "in_progress") inProgressCount++;
14051
- }
14052
- allTodosDone = completedCount === todos.length;
14053
- if (isRunning && !allTodosDone) {
14054
- var stepIdx = Math.max(1, completedCount + (inProgressCount > 0 ? 1 : 0));
14055
- stepLabel = " · 第 " + stepIdx + "/" + todos.length + " 步";
14056
- }
14072
+ // 显示当前执行步骤 = 已完成 + 正在进行(如果有)
14073
+ var currentStep = completed + inProgress;
14074
+ var allDone = completed === todos.length;
14075
+ if (allDone) {
14076
+ // Hide todo when all tasks are completed
14077
+ container.classList.add("hidden");
14078
+ return;
14079
+ } else {
14080
+ container.classList.remove("all-done");
14057
14081
  }
14058
14082
 
14059
- // ── 应用状态类 ──
14060
- container.classList.toggle("is-running", isRunning);
14061
- // 复用已有 .all-done 样式(隐藏 spinner + 显示绿色 ✓)
14062
- container.classList.toggle("all-done", !isRunning);
14063
- container.classList.toggle("has-todos", hasTodos);
14064
-
14065
- // ── 写入 DOM ──
14066
- var msgEl = document.getElementById("todo-progress-message");
14067
- if (msgEl) msgEl.textContent = lastUserText;
14083
+ var counter = document.getElementById("todo-progress-counter");
14084
+ if (counter) counter.textContent = currentStep + "/" + todos.length;
14068
14085
 
14069
- var statusEl = document.getElementById("todo-progress-status");
14070
- if (statusEl) statusEl.textContent = statusText + stepLabel;
14086
+ var task = document.getElementById("todo-progress-task");
14087
+ if (task) task.textContent = activeTask;
14071
14088
 
14072
- // ── 渲染展开后的 todo 列表(仅当有 todos 时) ──
14089
+ // Render expanded list
14073
14090
  var list = document.getElementById("todo-progress-list");
14074
14091
  if (list) {
14075
- if (!hasTodos) {
14076
- list.innerHTML = "";
14077
- } else {
14078
- var html = "";
14079
- for (var m = 0; m < todos.length; m++) {
14080
- var t = todos[m];
14081
- var st = t.status || "pending";
14082
- var itemClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "";
14083
- var iconClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "pending";
14084
- var icon = st === "completed" ? "✓" : st === "in_progress" ? "›" : "○";
14085
- html += '<li class="todo-progress-item ' + itemClass + '">' +
14086
- '<span class="todo-item-icon ' + iconClass + '">' + icon + '</span>' +
14087
- '<span>' + escapeHtml(t.content || "") + '</span>' +
14088
- '</li>';
14089
- }
14090
- list.innerHTML = html;
14092
+ var html = "";
14093
+ for (var m = 0; m < todos.length; m++) {
14094
+ var t = todos[m];
14095
+ var st = t.status || "pending";
14096
+ var itemClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "";
14097
+ var iconClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "pending";
14098
+ var icon = st === "completed" ? "✓" : st === "in_progress" ? "›" : "○";
14099
+ html += '<li class="todo-progress-item ' + itemClass + '">' +
14100
+ '<span class="todo-item-icon ' + iconClass + '">' + icon + '</span>' +
14101
+ '<span>' + escapeHtml(t.content || "") + '</span>' +
14102
+ '</li>';
14091
14103
  }
14092
- }
14093
-
14094
- // 没有 todos 时强制收起 body(没东西可展开)
14095
- if (!hasTodos) {
14096
- todoExpanded = false;
14097
- container.classList.remove("expanded");
14098
- var body = document.getElementById("todo-progress-body");
14099
- if (body) body.classList.add("hidden");
14104
+ list.innerHTML = html;
14100
14105
  }
14101
14106
 
14102
14107
  // Sync todo progress to native notification
@@ -16479,9 +16484,8 @@
16479
16484
  var sessionLabel = session.summary || session.command || sessionId;
16480
16485
  var sessionStatus = session.status || "running";
16481
16486
 
16482
- // 真正消亡的会话才清掉通知;idle / exited 仍然要把"已完成"推上去,
16483
- // 让流体云 / 锁屏卡片显示与 web 通知条一致的最终状态。
16484
- if (sessionStatus === "archived") {
16487
+ // Clear notification for inactive sessions
16488
+ if (sessionStatus === "idle" || sessionStatus === "archived" || sessionStatus === "exited") {
16485
16489
  clearSessionProgressNative(sessionId);
16486
16490
  return;
16487
16491
  }
@@ -16548,37 +16552,9 @@
16548
16552
  currentTask = state.currentTask.title;
16549
16553
  }
16550
16554
 
16551
- // ── 与 web 通知条同源的 statusLabel / stepLabel / isRunning ──
16552
- // 复用 computeRunningSignal & isStructuredSession,确保胶囊文字和顶部
16553
- // 通知条永远保持一致。
16554
- var sig = computeRunningSignal(session);
16555
- var isStructured = isStructuredSession(session);
16556
- var isRunning = isStructured ? !!sig.inFlight : !!sig.ptyRunning;
16557
- var statusLabel = isRunning
16558
- ? (isStructured ? "运行中" : "正在运行")
16559
- : "已完成";
16560
-
16561
- var stepLabel = "";
16562
- if (todos && todos.length > 0) {
16563
- var completedCount = 0;
16564
- var inProgressCount = 0;
16565
- for (var sk = 0; sk < todos.length; sk++) {
16566
- if (todos[sk].status === "completed") completedCount++;
16567
- if (todos[sk].status === "in_progress") inProgressCount++;
16568
- }
16569
- var allTodosDone = completedCount === todos.length;
16570
- if (isRunning && !allTodosDone) {
16571
- var stepIdx = Math.max(1, completedCount + (inProgressCount > 0 ? 1 : 0));
16572
- stepLabel = "第 " + stepIdx + "/" + todos.length + " 步";
16573
- }
16574
- }
16575
-
16576
16555
  var data = {
16577
16556
  sessionLabel: sessionLabel,
16578
16557
  status: sessionStatus,
16579
- isRunning: isRunning,
16580
- statusLabel: statusLabel,
16581
- stepLabel: stepLabel,
16582
16558
  currentTask: currentTask,
16583
16559
  latestUserText: latestUserText,
16584
16560
  latestAssistantText: latestAssistantText,
@@ -5390,38 +5390,6 @@
5390
5390
  text-overflow: ellipsis;
5391
5391
  white-space: nowrap;
5392
5392
  }
5393
- /* 左侧:用户最新发出的消息 */
5394
- .todo-progress-message {
5395
- flex: 1;
5396
- min-width: 0;
5397
- font-size: 0.75rem;
5398
- font-weight: 500;
5399
- color: var(--text-primary);
5400
- overflow: hidden;
5401
- text-overflow: ellipsis;
5402
- white-space: nowrap;
5403
- }
5404
- /* 右侧:状态文字 + 折叠箭头 */
5405
- .todo-progress-right {
5406
- display: flex;
5407
- align-items: center;
5408
- gap: 6px;
5409
- flex-shrink: 0;
5410
- min-width: 0;
5411
- }
5412
- .todo-progress-status {
5413
- font-size: 0.75rem;
5414
- font-weight: 500;
5415
- color: var(--text-secondary);
5416
- white-space: nowrap;
5417
- letter-spacing: 0.01em;
5418
- }
5419
- .todo-progress.is-running .todo-progress-status {
5420
- color: var(--accent);
5421
- }
5422
- .todo-progress.all-done .todo-progress-status {
5423
- color: #4a7a4f;
5424
- }
5425
5393
  .todo-progress-chevron {
5426
5394
  color: var(--text-muted);
5427
5395
  flex-shrink: 0;
@@ -5430,16 +5398,6 @@
5430
5398
  .todo-progress.expanded .todo-progress-chevron {
5431
5399
  transform: rotate(180deg);
5432
5400
  }
5433
- /* 没有 todo 列表时,没什么可展开的——隐藏箭头并去掉指针手势 */
5434
- .todo-progress:not(.has-todos) .todo-progress-chevron {
5435
- display: none;
5436
- }
5437
- .todo-progress:not(.has-todos) .todo-progress-header {
5438
- cursor: default;
5439
- }
5440
- .todo-progress:not(.has-todos) .todo-progress-header:hover {
5441
- background: transparent;
5442
- }
5443
5401
  .todo-progress-body {
5444
5402
  border-top: 1px solid var(--border-subtle);
5445
5403
  padding: 6px 10px 8px;
@@ -8580,9 +8538,6 @@
8580
8538
  .todo-progress-header { padding: 3px 6px; gap: 4px; }
8581
8539
  .todo-progress-counter { font-size: 0.5625rem; }
8582
8540
  .todo-progress-task { font-size: 0.5625rem; }
8583
- .todo-progress-message { font-size: 0.625rem; }
8584
- .todo-progress-status { font-size: 0.5625rem; }
8585
- .todo-progress-right { gap: 4px; }
8586
8541
  .todo-progress-chevron { width: 12px; height: 12px; }
8587
8542
  .todo-progress-body { padding: 4px 6px; }
8588
8543
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.21.15",
3
+ "version": "1.21.16",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {