@co0ontty/wand 1.21.13 → 1.21.15

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.
@@ -332,7 +332,11 @@ export class StructuredSessionManager {
332
332
  sessionKind: "structured",
333
333
  provider,
334
334
  runner,
335
- command: provider === "codex" ? "codex exec --json" : "claude -p --output-format stream-json",
335
+ command: provider === "codex"
336
+ ? "codex exec --json"
337
+ : runner === "claude-sdk"
338
+ ? "claude-agent-sdk (stream-json)"
339
+ : "claude -p --output-format stream-json",
336
340
  cwd: worktreeSetup?.cwd ?? options.cwd,
337
341
  mode: options.mode,
338
342
  worktreeEnabled: Boolean(worktreeSetup),
@@ -328,81 +328,29 @@
328
328
  // 先驱动跨视图的运行指示器(顶部进度条/徽章计时/气泡呼吸条)
329
329
  updateRunningIndicators(session);
330
330
 
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");
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
+
334
338
  var composer = document.querySelector(".input-composer");
335
339
  if (!session || !isStructuredSession(session)) {
336
- if (existing) existing.remove();
337
340
  if (composer) composer.classList.remove("in-flight");
338
- clearInterval(_statusBarTimerId);
339
- _statusBarTimerId = null;
341
+ if (_statusBarTimerId) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; }
342
+ _statusBarStartTime = 0;
340
343
  return;
341
344
  }
342
345
 
343
- var isInFlight = session.structuredState && session.structuredState.inFlight;
344
-
346
+ var isInFlight = !!(session.structuredState && session.structuredState.inFlight);
345
347
  if (isInFlight) {
346
- // Start timer if not already running
347
- if (!_statusBarTimerId) {
348
- _statusBarStartTime = Date.now();
349
- }
350
-
351
- // Add glow to input composer
348
+ if (!_statusBarStartTime) _statusBarStartTime = Date.now();
352
349
  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
- }
384
350
  } else {
385
- // Not in-flight: show completion or remove
386
- clearInterval(_statusBarTimerId);
387
- _statusBarTimerId = null;
388
-
389
- // Remove glow from input composer
390
351
  if (composer) composer.classList.remove("in-flight");
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
- }
352
+ _statusBarStartTime = 0;
353
+ if (_statusBarTimerId) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; }
406
354
  }
407
355
  }
408
356
 
@@ -1425,10 +1373,12 @@
1425
1373
  '<div class="todo-progress-header" id="todo-progress-toggle">' +
1426
1374
  '<div class="todo-progress-left">' +
1427
1375
  '<span class="todo-progress-spinner"></span>' +
1428
- '<span class="todo-progress-counter" id="todo-progress-counter">0/0</span>' +
1429
- '<span class="todo-progress-task" id="todo-progress-task"></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>' +
1430
1381
  '</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>' +
1432
1382
  '</div>' +
1433
1383
  '<div class="todo-progress-body hidden" id="todo-progress-body">' +
1434
1384
  '<ul class="todo-progress-list" id="todo-progress-list"></ul>' +
@@ -13796,6 +13746,17 @@
13796
13746
  }
13797
13747
 
13798
13748
  chatMessages.innerHTML = html;
13749
+ // 会话切换 / 首次渲染后,浏览器会把旧的 scrollTop 钳制到新内容
13750
+ // 的最大值——column-reverse 下这意味着视觉上跳到最上面(最旧消息),
13751
+ // 也就是用户反馈的"退出再回来时被重定向到最上面"。这里在
13752
+ // prevMsgCount === 0(resetChatRenderCache 后或刚从空状态进入)
13753
+ // 强制 scrollTop=0(视觉底部 = 最新消息),避免错位。
13754
+ // 注意:仅在"换会话/初次渲染"时强制重置;同一会话内的全量重渲染
13755
+ // (prevMsgCount > 0,比如 forceFullRender 或消息数减少)继续走
13756
+ // smartScrollToBottom,尊重用户的 chatAutoFollow 选择。
13757
+ if (prevMsgCount === 0) {
13758
+ chatMessages.scrollTop = 0;
13759
+ }
13799
13760
  attachAllCopyHandlers(chatMessages);
13800
13761
  bindChatScrollListener();
13801
13762
  applyPersistedExpandState(chatMessages);
@@ -14020,77 +13981,122 @@
14020
13981
  });
14021
13982
 
14022
13983
  function updateTodoProgress(messages) {
14023
- var todos = null;
14024
- // Scan all messages for latest TodoWrite tool_use
14025
- for (var i = messages.length - 1; i >= 0; i--) {
14026
- var msg = messages[i];
14027
- if (!msg.content || !Array.isArray(msg.content)) continue;
14028
- for (var j = msg.content.length - 1; j >= 0; j--) {
14029
- var block = msg.content[j];
14030
- if (block.type === "tool_use" && block.name === "TodoWrite" && block.input && block.input.todos) {
14031
- todos = block.input.todos;
14032
- break;
14033
- }
14034
- }
14035
- if (todos) break;
14036
- }
14037
-
14038
13984
  var container = document.getElementById("todo-progress");
14039
13985
  if (!container) return;
14040
13986
 
14041
- if (!todos || todos.length === 0) {
13987
+ var session = state.sessions.find(function(s) { return s.id === state.selectedId; });
13988
+ if (!session) {
14042
13989
  container.classList.add("hidden");
14043
13990
  return;
14044
13991
  }
14045
13992
 
14046
- container.classList.remove("hidden");
14047
-
14048
- var completed = 0;
14049
- var inProgress = 0;
14050
- var activeTask = "";
14051
- for (var k = 0; k < todos.length; k++) {
14052
- if (todos[k].status === "completed") completed++;
14053
- if (todos[k].status === "in_progress") {
14054
- inProgress++;
14055
- if (!activeTask) {
14056
- activeTask = todos[k].activeForm || todos[k].content || "";
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
+ }
14057
14005
  }
14006
+ if (lastUserText) break;
14058
14007
  }
14059
14008
  }
14060
14009
 
14061
- // 显示当前执行步骤 = 已完成 + 正在进行(如果有)
14062
- var currentStep = completed + inProgress;
14063
- var allDone = completed === todos.length;
14064
- if (allDone) {
14065
- // Hide todo when all tasks are completed
14010
+ // 没有任何用户消息(例如刚新建的空会话)才隐藏
14011
+ if (!lastUserText) {
14066
14012
  container.classList.add("hidden");
14067
14013
  return;
14068
- } else {
14069
- container.classList.remove("all-done");
14014
+ }
14015
+ container.classList.remove("hidden");
14016
+
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
+ : "已完成";
14024
+
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
+ }
14037
+ }
14038
+ if (todos) break;
14039
+ }
14070
14040
  }
14071
14041
 
14072
- var counter = document.getElementById("todo-progress-counter");
14073
- if (counter) counter.textContent = currentStep + "/" + todos.length;
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
+ }
14057
+ }
14074
14058
 
14075
- var task = document.getElementById("todo-progress-task");
14076
- if (task) task.textContent = activeTask;
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);
14077
14064
 
14078
- // Render expanded list
14065
+ // ── 写入 DOM ──
14066
+ var msgEl = document.getElementById("todo-progress-message");
14067
+ if (msgEl) msgEl.textContent = lastUserText;
14068
+
14069
+ var statusEl = document.getElementById("todo-progress-status");
14070
+ if (statusEl) statusEl.textContent = statusText + stepLabel;
14071
+
14072
+ // ── 渲染展开后的 todo 列表(仅当有 todos 时) ──
14079
14073
  var list = document.getElementById("todo-progress-list");
14080
14074
  if (list) {
14081
- var html = "";
14082
- for (var m = 0; m < todos.length; m++) {
14083
- var t = todos[m];
14084
- var st = t.status || "pending";
14085
- var itemClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "";
14086
- var iconClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "pending";
14087
- var icon = st === "completed" ? "✓" : st === "in_progress" ? "›" : "○";
14088
- html += '<li class="todo-progress-item ' + itemClass + '">' +
14089
- '<span class="todo-item-icon ' + iconClass + '">' + icon + '</span>' +
14090
- '<span>' + escapeHtml(t.content || "") + '</span>' +
14091
- '</li>';
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
14091
  }
14093
- list.innerHTML = html;
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");
14094
14100
  }
14095
14101
 
14096
14102
  // Sync todo progress to native notification
@@ -16473,8 +16479,9 @@
16473
16479
  var sessionLabel = session.summary || session.command || sessionId;
16474
16480
  var sessionStatus = session.status || "running";
16475
16481
 
16476
- // Clear notification for inactive sessions
16477
- if (sessionStatus === "idle" || sessionStatus === "archived" || sessionStatus === "exited") {
16482
+ // 真正消亡的会话才清掉通知;idle / exited 仍然要把"已完成"推上去,
16483
+ // 让流体云 / 锁屏卡片显示与 web 通知条一致的最终状态。
16484
+ if (sessionStatus === "archived") {
16478
16485
  clearSessionProgressNative(sessionId);
16479
16486
  return;
16480
16487
  }
@@ -16541,9 +16548,37 @@
16541
16548
  currentTask = state.currentTask.title;
16542
16549
  }
16543
16550
 
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
+
16544
16576
  var data = {
16545
16577
  sessionLabel: sessionLabel,
16546
16578
  status: sessionStatus,
16579
+ isRunning: isRunning,
16580
+ statusLabel: statusLabel,
16581
+ stepLabel: stepLabel,
16547
16582
  currentTask: currentTask,
16548
16583
  latestUserText: latestUserText,
16549
16584
  latestAssistantText: latestAssistantText,
@@ -5390,6 +5390,38 @@
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
+ }
5393
5425
  .todo-progress-chevron {
5394
5426
  color: var(--text-muted);
5395
5427
  flex-shrink: 0;
@@ -5398,6 +5430,16 @@
5398
5430
  .todo-progress.expanded .todo-progress-chevron {
5399
5431
  transform: rotate(180deg);
5400
5432
  }
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
+ }
5401
5443
  .todo-progress-body {
5402
5444
  border-top: 1px solid var(--border-subtle);
5403
5445
  padding: 6px 10px 8px;
@@ -8538,6 +8580,9 @@
8538
8580
  .todo-progress-header { padding: 3px 6px; gap: 4px; }
8539
8581
  .todo-progress-counter { font-size: 0.5625rem; }
8540
8582
  .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; }
8541
8586
  .todo-progress-chevron { width: 12px; height: 12px; }
8542
8587
  .todo-progress-body { padding: 4px 6px; }
8543
8588
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.21.13",
3
+ "version": "1.21.15",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {