@co0ontty/wand 1.5.7 → 1.6.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.
@@ -397,6 +397,24 @@ function getLatestClaudeTaskId(excludeIds) {
397
397
  return null;
398
398
  }
399
399
  }
400
+ /** Derive a short summary for a session from user messages or current task. */
401
+ function deriveSessionSummary(messages, currentTaskTitle) {
402
+ // Prefer first user message as summary
403
+ for (const msg of messages) {
404
+ if (msg.role !== "user")
405
+ continue;
406
+ for (const block of msg.content) {
407
+ if (block.type === "text" && block.text.trim()) {
408
+ return block.text.trim().slice(0, 120);
409
+ }
410
+ }
411
+ break; // only check the first user turn
412
+ }
413
+ // Fallback to current task title
414
+ if (currentTaskTitle)
415
+ return currentTaskTitle.slice(0, 120);
416
+ return undefined;
417
+ }
400
418
  export class ProcessManager extends EventEmitter {
401
419
  config;
402
420
  storage;
@@ -1102,7 +1120,8 @@ export class ProcessManager extends EventEmitter {
1102
1120
  resumedToSessionId: record.resumedToSessionId ?? undefined,
1103
1121
  autoRecovered: record.autoRecovered ?? false,
1104
1122
  autoApprovePermissions: record.autoApprovePermissions || undefined,
1105
- approvalStats: record.approvalStats.total > 0 ? record.approvalStats : undefined
1123
+ approvalStats: record.approvalStats.total > 0 ? record.approvalStats : undefined,
1124
+ summary: deriveSessionSummary(messages, record.currentTask?.title ?? null),
1106
1125
  };
1107
1126
  }
1108
1127
  isPermissionBlocked(record) {
@@ -4,6 +4,23 @@ const STREAM_EMIT_DEBOUNCE_MS = 16;
4
4
  function isRunningAsRoot() {
5
5
  return process.getuid?.() === 0 || process.geteuid?.() === 0;
6
6
  }
7
+ /** Enrich a snapshot with a derived summary from the first user message. */
8
+ function withSummary(snapshot) {
9
+ if (snapshot.summary)
10
+ return snapshot;
11
+ const messages = snapshot.messages ?? [];
12
+ for (const msg of messages) {
13
+ if (msg.role !== "user")
14
+ continue;
15
+ for (const block of msg.content) {
16
+ if (block.type === "text" && block.text.trim()) {
17
+ return { ...snapshot, summary: block.text.trim().slice(0, 120) };
18
+ }
19
+ }
20
+ break;
21
+ }
22
+ return snapshot;
23
+ }
7
24
  /** Should we auto-approve permissions for this mode? */
8
25
  function shouldAutoApproveForMode(mode) {
9
26
  return mode === "full-access" || mode === "managed" || mode === "auto-edit";
@@ -46,10 +63,13 @@ export class StructuredSessionManager {
46
63
  this.emitEvent = emitEvent;
47
64
  }
48
65
  list() {
49
- return Array.from(this.sessions.values()).sort((a, b) => b.startedAt.localeCompare(a.startedAt));
66
+ return Array.from(this.sessions.values())
67
+ .map(withSummary)
68
+ .sort((a, b) => b.startedAt.localeCompare(a.startedAt));
50
69
  }
51
70
  get(id) {
52
- return this.sessions.get(id) ?? null;
71
+ const s = this.sessions.get(id);
72
+ return s ? withSummary(s) : null;
53
73
  }
54
74
  createSession(options) {
55
75
  const id = randomUUID();
package/dist/types.d.ts CHANGED
@@ -189,6 +189,8 @@ export interface SessionSnapshot {
189
189
  file: number;
190
190
  total: number;
191
191
  };
192
+ /** 会话摘要:从首条用户消息或当前任务提取 */
193
+ summary?: string;
192
194
  }
193
195
  export type SessionLifecycleState = "initializing" | "running" | "idle" | "thinking" | "waiting-input" | "archived";
194
196
  export interface SessionLifecycle {
@@ -678,7 +678,6 @@
678
678
  '</div>' +
679
679
  '<div class="todo-progress-body hidden" id="todo-progress-body">' +
680
680
  '<ul class="todo-progress-list" id="todo-progress-list"></ul>' +
681
- '<div id="recent-actions" class="recent-actions"></div>' +
682
681
  '</div>' +
683
682
  '</div>' +
684
683
  '<div class="input-composer">' +
@@ -1816,7 +1815,9 @@
1816
1815
  '<div class="session-item-row">' +
1817
1816
  checkbox +
1818
1817
  '<div class="session-main">' +
1819
- '<div class="session-command">' + escapeHtml(session.resumedFromSessionId ? session.command.replace(/\s+--resume\s+\S+/, '') : session.command) + '</div>' +
1818
+ (session.summary
1819
+ ? '<div class="session-title">' + escapeHtml(session.summary) + '</div>'
1820
+ : '<div class="session-command">' + escapeHtml(session.resumedFromSessionId ? session.command.replace(/\s+--resume\s+\S+/, '') : session.command) + '</div>') +
1820
1821
  '<div class="session-meta">' +
1821
1822
  modeBadge +
1822
1823
  '<span>' + escapeHtml(modeName) + '</span>' +
@@ -8375,40 +8376,6 @@
8375
8376
  list.innerHTML = html;
8376
8377
  }
8377
8378
 
8378
- // Extract recent important actions for key points summary
8379
- var recentActions = [];
8380
- var actionTools = ["Write", "Edit", "Bash", "WebFetch", "WebSearch"];
8381
- var msgCount = messages.length;
8382
- for (var ai = 0; ai < msgCount && recentActions.length < 5; ai++) {
8383
- var m = messages[ai];
8384
- if (!m.content || !Array.isArray(m.content)) continue;
8385
- for (var bi = 0; bi < m.content.length && recentActions.length < 5; bi++) {
8386
- var blk = m.content[bi];
8387
- if (blk.type !== "tool_use") continue;
8388
- var toolName = blk.name || "";
8389
- if (actionTools.indexOf(toolName) === -1) continue;
8390
- var desc = blk.description || generateInputSummary(toolName, blk.input) || toolName;
8391
- if (desc && desc.length > 50) desc = desc.slice(0, 47) + "...";
8392
- var icon = getToolIcon(toolName);
8393
- recentActions.push({ icon: icon, text: desc });
8394
- }
8395
- }
8396
-
8397
- var actionsEl = document.getElementById("recent-actions");
8398
- if (actionsEl) {
8399
- if (recentActions.length > 0) {
8400
- var actionsHtml = '<div class="recent-actions-label">最近操作</div>';
8401
- actionsHtml += '<div class="recent-actions-list">';
8402
- for (var ri = 0; ri < recentActions.length; ri++) {
8403
- var a = recentActions[ri];
8404
- actionsHtml += '<span class="recent-action-pill">' + a.icon + ' ' + escapeHtml(a.text) + '</span>';
8405
- }
8406
- actionsHtml += '</div>';
8407
- actionsEl.innerHTML = actionsHtml;
8408
- } else {
8409
- actionsEl.innerHTML = '';
8410
- }
8411
- }
8412
8379
  }
8413
8380
 
8414
8381
  function updateQueueCounter() {
@@ -8944,6 +8911,67 @@
8944
8911
  return messages;
8945
8912
  }
8946
8913
 
8914
+ // ── 像素风猫咪头像 ──
8915
+ var PIXEL_AVATAR = (function() {
8916
+ var _ = "transparent";
8917
+ function buildSvg(grid, size) {
8918
+ var s = size || 3;
8919
+ var w = grid[0].length * s;
8920
+ var h = grid.length * s;
8921
+ var rects = "";
8922
+ for (var y = 0; y < grid.length; y++) {
8923
+ for (var x = 0; x < grid[y].length; x++) {
8924
+ if (grid[y][x] !== _) {
8925
+ rects += '<rect x="' + (x * s) + '" y="' + (y * s) + '" width="' + s + '" height="' + s + '" fill="' + grid[y][x] + '"/>';
8926
+ }
8927
+ }
8928
+ }
8929
+ return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + w + ' ' + h + '" class="pixel-avatar-svg">' + rects + '</svg>';
8930
+ }
8931
+ // 加菲猫 (勤劳初二 / AI) — 橙色系
8932
+ var o = "#F0923A", d = "#C46A1A", w = "#FFFFFF", k = "#2D2D2D", p = "#F28B9A", n = "#E87D5A";
8933
+ var garfield = [
8934
+ [_,d,_,_,_,_,_,_,d,_],
8935
+ [d,o,d,_,_,_,_,d,o,d],
8936
+ [d,o,o,o,o,o,o,o,o,d],
8937
+ [o,o,w,k,o,o,w,k,o,o],
8938
+ [o,o,w,w,o,o,w,w,o,o],
8939
+ [o,o,o,o,p,p,o,o,o,o],
8940
+ [o,d,o,n,o,o,n,o,d,o],
8941
+ [_,o,o,o,o,o,o,o,o,_],
8942
+ [_,_,o,d,o,o,d,o,_,_],
8943
+ [_,_,_,o,_,_,o,_,_,_],
8944
+ ];
8945
+ // 美短 (赛博虎妞 / 用户) — 灰色系
8946
+ var g = "#9EAAB8", dg = "#6B7B8D", lg = "#C5CED8", gn = "#7EC88B";
8947
+ var shorthair = [
8948
+ [_,dg,_,_,_,_,_,_,dg,_],
8949
+ [dg,g,dg,_,_,_,_,dg,g,dg],
8950
+ [dg,g,g,g,g,g,g,g,g,dg],
8951
+ [g,g,w,gn,g,g,w,gn,g,g],
8952
+ [g,g,w,w,g,g,w,w,g,g],
8953
+ [g,g,g,g,p,p,g,g,g,g],
8954
+ [g,dg,g,lg,g,g,lg,g,dg,g],
8955
+ [_,g,g,g,g,g,g,g,g,_],
8956
+ [_,_,g,dg,g,g,dg,g,_,_],
8957
+ [_,_,_,g,_,_,g,_,_,_],
8958
+ ];
8959
+ return {
8960
+ assistant: buildSvg(garfield),
8961
+ user: buildSvg(shorthair)
8962
+ };
8963
+ })();
8964
+
8965
+ function chatAvatar(role) {
8966
+ var isUser = role === "user";
8967
+ var svg = isUser ? PIXEL_AVATAR.user : PIXEL_AVATAR.assistant;
8968
+ var name = isUser ? "赛博虎妞" : "勤劳初二";
8969
+ return '<div class="chat-message-avatar ' + role + '">' +
8970
+ '<div class="pixel-avatar">' + svg + '</div>' +
8971
+ '<span class="avatar-name">' + name + '</span>' +
8972
+ '</div>';
8973
+ }
8974
+
8947
8975
  function renderChatMessage(msg, roundUsage) {
8948
8976
  // Thinking card (deep thought) — from PTY parsing
8949
8977
  if (msg.role === "thinking") {
@@ -8972,7 +9000,7 @@
8972
9000
  }
8973
9001
 
8974
9002
  // Legacy string content (from PTY parsing)
8975
- var avatar = msg.role === "assistant" ? '<div class="chat-message-avatar">赛博虎妞</div>' : "";
9003
+ var avatar = chatAvatar(msg.role);
8976
9004
  var bubbleContent = msg.role === "assistant" ? renderMarkdown(msg.content) : escapeHtml(msg.content);
8977
9005
  return '<div class="chat-message ' + msg.role + '">' +
8978
9006
  avatar +
@@ -9117,7 +9145,7 @@
9117
9145
 
9118
9146
  function renderStructuredMessage(msg, roundUsage) {
9119
9147
  var role = msg.role;
9120
- var avatar = role === "assistant" ? '<div class="chat-message-avatar">赛博虎妞</div>' : "";
9148
+ var avatar = chatAvatar(role);
9121
9149
 
9122
9150
  if (!msg.content || msg.content.length === 0) {
9123
9151
  if (role === "assistant") {
@@ -1162,6 +1162,17 @@
1162
1162
  letter-spacing: -0.01em;
1163
1163
  }
1164
1164
 
1165
+ .session-title {
1166
+ font-weight: 600;
1167
+ font-size: 0.8125rem;
1168
+ white-space: nowrap;
1169
+ overflow: hidden;
1170
+ text-overflow: ellipsis;
1171
+ color: var(--text-primary);
1172
+ letter-spacing: -0.01em;
1173
+ line-height: 1.3;
1174
+ }
1175
+
1165
1176
  /* ===== 会话元信息 ===== */
1166
1177
  .session-meta {
1167
1178
  display: flex;
@@ -2292,16 +2303,51 @@
2292
2303
  }
2293
2304
 
2294
2305
  /* ===== 消息头像 ===== */
2295
- .chat-message.user .chat-message-avatar {
2296
- display: none;
2306
+ .chat-message-avatar {
2307
+ display: flex;
2308
+ align-items: center;
2309
+ gap: 6px;
2310
+ padding: 0 2px 4px 2px;
2297
2311
  }
2298
2312
 
2299
- .chat-message.assistant .chat-message-avatar {
2300
- font-size: 0.75rem;
2313
+ .chat-message-avatar.assistant {
2314
+ flex-direction: row;
2315
+ }
2316
+
2317
+ .chat-message-avatar.user {
2318
+ flex-direction: row-reverse;
2319
+ }
2320
+
2321
+ .pixel-avatar {
2322
+ width: 24px;
2323
+ height: 24px;
2324
+ flex-shrink: 0;
2325
+ border-radius: 6px;
2326
+ overflow: hidden;
2327
+ background: var(--bg-tertiary);
2328
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
2329
+ }
2330
+
2331
+ .pixel-avatar-svg {
2332
+ display: block;
2333
+ width: 100%;
2334
+ height: 100%;
2335
+ image-rendering: pixelated;
2336
+ }
2337
+
2338
+ .avatar-name {
2339
+ font-size: 0.7rem;
2301
2340
  font-weight: 600;
2302
- color: var(--accent);
2303
- padding: 0 2px 4px 2px;
2304
2341
  letter-spacing: 0.03em;
2342
+ line-height: 1;
2343
+ }
2344
+
2345
+ .chat-message.assistant .avatar-name {
2346
+ color: var(--accent);
2347
+ }
2348
+
2349
+ .chat-message.user .avatar-name {
2350
+ color: var(--text-tertiary);
2305
2351
  }
2306
2352
 
2307
2353
  /* ===== 消息气泡 ===== */
@@ -3904,39 +3950,6 @@
3904
3950
  .todo-item-icon.active { color: var(--accent); }
3905
3951
  .todo-item-icon.done { color: #4a7a4f; }
3906
3952
 
3907
- /* Recent actions key points section */
3908
- .recent-actions {
3909
- margin-top: 8px;
3910
- padding-top: 8px;
3911
- border-top: 1px solid var(--border-subtle);
3912
- }
3913
- .recent-actions-label {
3914
- font-size: 0.6875rem;
3915
- font-weight: 600;
3916
- color: var(--text-muted);
3917
- text-transform: uppercase;
3918
- letter-spacing: 0.03em;
3919
- margin-bottom: 6px;
3920
- }
3921
- .recent-actions-list {
3922
- display: flex;
3923
- flex-wrap: wrap;
3924
- gap: 4px;
3925
- }
3926
- .recent-action-pill {
3927
- display: inline-flex;
3928
- align-items: center;
3929
- gap: 3px;
3930
- padding: 3px 8px;
3931
- font-size: 0.6875rem;
3932
- color: var(--text-secondary);
3933
- background: rgba(79, 122, 88, 0.08);
3934
- border-radius: 10px;
3935
- white-space: nowrap;
3936
- max-width: 220px;
3937
- overflow: hidden;
3938
- text-overflow: ellipsis;
3939
- }
3940
3953
 
3941
3954
  @keyframes spin {
3942
3955
  to { transform: rotate(360deg); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.5.7",
3
+ "version": "1.6.0",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {