@yolk_vat-y/dsh-project-memory 0.4.3 → 0.5.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/client/client.js CHANGED
@@ -25,6 +25,9 @@ window.__ModuleLoader__.load({
25
25
  "style.mono": "终端",
26
26
  "panel.syncing": "同步中…",
27
27
  "panel.sync-failed": "同步失败",
28
+ "panel.hints-on": "显示提示信息",
29
+ "panel.hints-off": "隐藏提示信息",
30
+ "drag.cancel": "取消拖拽",
28
31
  "panel.empty": "暂无任务",
29
32
  "panel.empty-desc": "让模型开始工作并维护 todo 清单后会自动建档",
30
33
  "panel.no-session": "还没有会话",
@@ -46,7 +49,41 @@ window.__ModuleLoader__.load({
46
49
  "minibar.current": "当前任务",
47
50
  "minibar.click-expand": "点击展开并同步",
48
51
  "minibar.tasks": "个任务",
49
- "minibar.no-task": "暂无任务 · 点击查看"
52
+ "minibar.no-task": "暂无任务 · 点击查看",
53
+ "view.cycle": "切换视图:任务 / 项目记忆 / 全局记忆",
54
+ "view.task": "任务",
55
+ "view.project": "项目记忆",
56
+ "view.global": "全局记忆",
57
+ "mem.scope.task": "本任务草稿",
58
+ "mem.scope.project": "项目记忆",
59
+ "mem.scope.global": "全局记忆",
60
+ "mem.empty": "暂无记忆条目(反思或 save_lesson 后出现)",
61
+ "mem.confirm": "审核",
62
+ "mem.promote": "提升",
63
+ "mem.demote": "降级",
64
+ "mem.archive": "归档",
65
+ "mem.restore": "恢复",
66
+ "mem.delete": "删除",
67
+ "mem.draft": "草稿",
68
+ "mem.archived": "已归档",
69
+ "mem.loading": "加载中…",
70
+ "mem.error": "同步失败",
71
+ "mem.kind.lesson": "坑",
72
+ "mem.kind.decision": "权衡",
73
+ "mem.kind.procedure": "指南",
74
+ "mem.kind.experience": "经验",
75
+ "mem.needs-bound": "需绑定任务后可降级",
76
+ "mem.new": "新建",
77
+ "mem.save": "保存",
78
+ "mem.cancel": "取消",
79
+ "mem.f.title": "标题(教训模式/主题/指南名)",
80
+ "mem.f.kind": "类型",
81
+ "mem.f.body": "做法 / 理由 / 解法",
82
+ "mem.f.steps": "步骤(每行一步)",
83
+ "mem.f.trigger": "作为 Skill:触发关键词(逗号分隔)",
84
+ "mem.saved": "已保存",
85
+ "mem.edit": "编辑",
86
+ "mem.section-label": "任务记忆"
50
87
  };
51
88
  const en = {
52
89
  "panel.title": "Task Flow",
@@ -62,6 +99,9 @@ window.__ModuleLoader__.load({
62
99
  "style.mono": "Mono",
63
100
  "panel.syncing": "Syncing…",
64
101
  "panel.sync-failed": "Sync failed",
102
+ "panel.hints-on": "Show hints",
103
+ "panel.hints-off": "Hide hints",
104
+ "drag.cancel": "Cancel drag",
65
105
  "panel.empty": "No Tasks",
66
106
  "panel.empty-desc": "Tasks are created automatically when the model maintains a todo list",
67
107
  "panel.no-session": "No session yet",
@@ -83,7 +123,41 @@ window.__ModuleLoader__.load({
83
123
  "minibar.current": "Current task",
84
124
  "minibar.click-expand": "Click to expand & sync",
85
125
  "minibar.tasks": "tasks",
86
- "minibar.no-task": "No tasks · click to view"
126
+ "minibar.no-task": "No tasks · click to view",
127
+ "view.cycle": "Switch view: tasks / project memory / global memory",
128
+ "view.task": "Tasks",
129
+ "view.project": "Project Memory",
130
+ "view.global": "Global Memory",
131
+ "mem.scope.task": "Task drafts",
132
+ "mem.scope.project": "Project memory",
133
+ "mem.scope.global": "Global memory",
134
+ "mem.empty": "No memory entries yet (created by reflection or save_lesson)",
135
+ "mem.confirm": "Approve",
136
+ "mem.promote": "Promote",
137
+ "mem.demote": "Demote",
138
+ "mem.archive": "Archive",
139
+ "mem.restore": "Restore",
140
+ "mem.delete": "Delete",
141
+ "mem.draft": "draft",
142
+ "mem.archived": "archived",
143
+ "mem.loading": "Loading…",
144
+ "mem.error": "Sync failed",
145
+ "mem.kind.lesson": "lesson",
146
+ "mem.kind.decision": "decision",
147
+ "mem.kind.procedure": "procedure",
148
+ "mem.kind.experience": "experience",
149
+ "mem.needs-bound": "Bind a task to demote",
150
+ "mem.new": "New",
151
+ "mem.save": "Save",
152
+ "mem.cancel": "Cancel",
153
+ "mem.f.title": "Title (pattern / topic / procedure name)",
154
+ "mem.f.kind": "Kind",
155
+ "mem.f.body": "Fix / reason / solution",
156
+ "mem.f.steps": "Steps (one per line)",
157
+ "mem.f.trigger": "As Skill: trigger keywords (comma separated)",
158
+ "mem.saved": "Saved",
159
+ "mem.edit": "Edit",
160
+ "mem.section-label": "Task memory"
87
161
  };
88
162
  function createTranslate(dict) {
89
163
  return (key, params) => {
@@ -93,13 +167,12 @@ window.__ModuleLoader__.load({
93
167
  };
94
168
  }
95
169
  //#endregion
96
- //#region src/client/task-store.ts
170
+ //#region src/client/task-data-store.ts
97
171
  /**
98
- * Task Panel Client Store(自包含:不依赖宿主 store 包)
99
- * Reactive store for task list, bound task, UI state.
100
- * 数据由 /tasks、/task 命令执行结果(JSON 载荷)写入,localStorage 持久化 UI 状态。
172
+ * Task Data Store — 仅管服务端同步的数据(tasks、boundTaskId、archivedCount)
173
+ * - 单向数据流:命令执行结果 → setTasks → 广播给其他标签页
174
+ * - 无 UI 状态,无 localStorage,纯内存 + BroadcastChannel 同步
101
175
  */
102
- /** 从命令输出文本中解析任务快照 JSON(支持 fenced ```json 块或尾部对象)。 */
103
176
  function parseTaskPayloadText(text) {
104
177
  if (!text) return null;
105
178
  let jsonBlock = null;
@@ -122,7 +195,93 @@ window.__ModuleLoader__.load({
122
195
  return null;
123
196
  }
124
197
  }
125
- const STORAGE_KEY = "dsh-pm-task-panel-state";
198
+ const SYNC_CHANNEL = typeof window !== "undefined" ? new BroadcastChannel("dsh-pm-tasks-data") : null;
199
+ let dataState = {
200
+ tasks: [],
201
+ boundTaskId: null,
202
+ archivedCount: 0,
203
+ lastUpdate: 0
204
+ };
205
+ const dataListeners = /* @__PURE__ */ new Set();
206
+ function setDataState(next) {
207
+ const patch = typeof next === "function" ? next(dataState) : next;
208
+ dataState = {
209
+ tasks: Array.isArray(patch?.tasks) ? patch.tasks : [],
210
+ boundTaskId: patch?.boundTaskId ?? null,
211
+ archivedCount: typeof patch?.archivedCount === "number" ? patch.archivedCount : 0,
212
+ lastUpdate: typeof patch?.lastUpdate === "number" ? patch.lastUpdate : 0
213
+ };
214
+ for (const listener of dataListeners) listener();
215
+ }
216
+ function broadcastDataUpdate(tasks, archivedCount) {
217
+ if (!SYNC_CHANNEL) return;
218
+ SYNC_CHANNEL.postMessage({
219
+ type: "PROJECT_TASKS_UPDATED",
220
+ payload: {
221
+ tasks: Array.isArray(tasks) ? tasks : [],
222
+ archivedCount
223
+ }
224
+ });
225
+ }
226
+ if (SYNC_CHANNEL) SYNC_CHANNEL.onmessage = (event) => {
227
+ const msg = event.data;
228
+ if (msg?.type === "PROJECT_TASKS_UPDATED" && msg.payload) {
229
+ const { tasks, archivedCount } = msg.payload;
230
+ setDataState((prev) => ({
231
+ ...prev,
232
+ tasks: Array.isArray(tasks) ? tasks : [],
233
+ archivedCount: typeof archivedCount === "number" ? archivedCount : prev.archivedCount,
234
+ lastUpdate: Date.now()
235
+ }));
236
+ }
237
+ };
238
+ const taskDataStore = {
239
+ subscribe(listener) {
240
+ dataListeners.add(listener);
241
+ return () => {
242
+ dataListeners.delete(listener);
243
+ };
244
+ },
245
+ getSnapshot() {
246
+ return dataState;
247
+ },
248
+ actions: {
249
+ setTasks(payload) {
250
+ const nextTasks = payload.tasks || [];
251
+ const nextArchived = payload.archivedCount ?? 0;
252
+ setDataState({
253
+ tasks: nextTasks,
254
+ boundTaskId: payload.boundTaskId ?? null,
255
+ archivedCount: nextArchived,
256
+ lastUpdate: Date.now()
257
+ });
258
+ broadcastDataUpdate(nextTasks, nextArchived);
259
+ },
260
+ reset() {
261
+ setDataState({
262
+ tasks: [],
263
+ boundTaskId: null,
264
+ archivedCount: 0,
265
+ lastUpdate: 0
266
+ });
267
+ }
268
+ }
269
+ };
270
+ function useTaskData() {
271
+ return (0, react.useSyncExternalStore)(taskDataStore.subscribe, taskDataStore.getSnapshot, taskDataStore.getSnapshot);
272
+ }
273
+ function useTaskDataActions() {
274
+ return taskDataStore.actions;
275
+ }
276
+ //#endregion
277
+ //#region src/client/task-ui-store.ts
278
+ /**
279
+ * Task UI Store — 仅管本地 UI 状态(closed、minimized、position、expandedIds、theme)
280
+ * - 持久化到 localStorage(key: dsh-pm-task-panel-ui)
281
+ * - 不跨标签页同步(每个标签页独立)
282
+ * - 启动/刷新强制 closed: true,面板默认不显示
283
+ */
284
+ const STORAGE_KEY = "dsh-pm-task-panel-ui";
126
285
  function defaultPosition() {
127
286
  if (typeof window !== "undefined") return {
128
287
  x: Math.max(16, window.innerWidth - 408),
@@ -133,138 +292,276 @@ window.__ModuleLoader__.load({
133
292
  y: 0
134
293
  };
135
294
  }
136
- function getDefaultState() {
295
+ /**
296
+ * 启动时的初始状态:
297
+ * - closed 恒为 true(不读取 localStorage 的 closed),刷新后面板隐藏;
298
+ * - 其余(展开项/位置/主题)可恢复上次会话偏好,minimized 默认折叠成迷你条。
299
+ */
300
+ function getDefaultUIState() {
137
301
  if (typeof window !== "undefined") try {
138
302
  const saved = localStorage.getItem(STORAGE_KEY);
139
303
  if (saved) {
140
304
  const parsed = JSON.parse(saved);
141
305
  return {
142
- tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
143
- boundTaskId: parsed.boundTaskId ?? null,
144
- archivedCount: parsed.archivedCount ?? 0,
145
306
  expandedTaskIds: Array.isArray(parsed.expandedTaskIds) ? parsed.expandedTaskIds : [],
146
307
  panelPosition: parsed.panelPosition ?? defaultPosition(),
147
308
  minimized: parsed.minimized !== false,
148
- theme: typeof parsed.theme === "string" ? parsed.theme : "native",
149
- closed: !!parsed.closed,
150
- lastUpdate: parsed.lastUpdate ?? 0
309
+ closed: true,
310
+ theme: typeof parsed.theme === "string" ? parsed.theme : "native"
151
311
  };
152
312
  }
153
313
  } catch {}
154
314
  return {
155
- tasks: [],
156
- boundTaskId: null,
157
- archivedCount: 0,
158
315
  expandedTaskIds: [],
159
316
  panelPosition: defaultPosition(),
160
317
  minimized: true,
161
- closed: false,
162
- theme: "native",
163
- lastUpdate: 0
318
+ closed: true,
319
+ theme: "native"
164
320
  };
165
321
  }
166
- function persist(state) {
322
+ function persistUI(state) {
167
323
  if (typeof window === "undefined") return;
168
324
  try {
169
325
  localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
170
326
  } catch {}
171
327
  }
172
- let state = getDefaultState();
173
- const listeners = /* @__PURE__ */ new Set();
174
- function setState(next) {
175
- state = next;
176
- persist(next);
177
- for (const listener of listeners) listener();
328
+ let uiState = getDefaultUIState();
329
+ const uiListeners = /* @__PURE__ */ new Set();
330
+ function setUIState(next) {
331
+ uiState = next;
332
+ persistUI(next);
333
+ for (const listener of uiListeners) listener();
178
334
  }
179
- const taskStore = {
335
+ const taskUIStore = {
180
336
  subscribe(listener) {
181
- listeners.add(listener);
337
+ uiListeners.add(listener);
182
338
  return () => {
183
- listeners.delete(listener);
339
+ uiListeners.delete(listener);
184
340
  };
185
341
  },
186
342
  getSnapshot() {
187
- return state;
343
+ return uiState;
188
344
  },
189
345
  actions: {
190
- setTasks(payload) {
191
- setState({
192
- ...state,
193
- tasks: payload.tasks || [],
194
- boundTaskId: payload.boundTaskId ?? null,
195
- archivedCount: payload.archivedCount ?? 0,
196
- lastUpdate: Date.now()
197
- });
198
- },
199
346
  toggleTaskExpanded(taskId) {
200
- const prev = state;
347
+ const prev = uiState;
201
348
  const ids = prev.expandedTaskIds.includes(taskId) ? prev.expandedTaskIds.filter((id) => id !== taskId) : [...prev.expandedTaskIds, taskId];
202
- setState({
349
+ setUIState({
203
350
  ...prev,
204
351
  expandedTaskIds: ids
205
352
  });
206
353
  },
207
- expandAll() {
208
- const prev = state;
209
- setState({
210
- ...prev,
211
- expandedTaskIds: prev.tasks.map((t) => t.id)
354
+ expandAll(taskIds) {
355
+ setUIState({
356
+ ...uiState,
357
+ expandedTaskIds: taskIds
212
358
  });
213
359
  },
214
360
  setPanelPosition(pos) {
215
- setState({
216
- ...state,
361
+ setUIState({
362
+ ...uiState,
217
363
  panelPosition: pos
218
364
  });
219
365
  },
220
- /** 展开面板(同时解除隐藏/折叠)。 */
221
366
  open() {
222
- setState({
223
- ...state,
367
+ setUIState({
368
+ ...uiState,
224
369
  minimized: false,
225
370
  closed: false
226
371
  });
227
372
  },
228
- /** 折叠成顶部迷你条(仍可见)。 */
229
373
  minimize() {
230
- setState({
231
- ...state,
374
+ setUIState({
375
+ ...uiState,
232
376
  minimized: true,
233
377
  closed: false
234
378
  });
235
379
  },
236
- /** 彻底隐藏(无迷你条);仅能通过 /task、/tasks 或重新加载唤起。 */
237
380
  close() {
238
- setState({
239
- ...state,
381
+ setUIState({
382
+ ...uiState,
240
383
  minimized: true,
241
384
  closed: true
242
385
  });
243
386
  },
244
387
  setTheme(theme) {
245
- setState({
246
- ...state,
388
+ setUIState({
389
+ ...uiState,
247
390
  theme
248
391
  });
249
392
  },
250
- setMinimized(minimized) {
251
- setState({
252
- ...state,
253
- minimized,
254
- closed: minimized ? state.closed : false
255
- });
256
- },
257
393
  reset() {
258
- setState(getDefaultState());
394
+ setUIState(getDefaultUIState());
259
395
  }
260
396
  }
261
397
  };
262
- function useTaskStore() {
263
- return (0, react.useSyncExternalStore)(taskStore.subscribe, taskStore.getSnapshot, taskStore.getSnapshot);
398
+ function useTaskUI() {
399
+ return (0, react.useSyncExternalStore)(taskUIStore.subscribe, taskUIStore.getSnapshot, taskUIStore.getSnapshot);
400
+ }
401
+ function useTaskUIActions() {
402
+ return taskUIStore.actions;
403
+ }
404
+ //#endregion
405
+ //#region src/client/task-hooks.ts
406
+ /**
407
+ * Task Panel Hooks — 步骤拖拽交互逻辑(useTaskDrag)。
408
+ * 只依赖 react + task-data-store 类型,不碰数据/UI store。
409
+ */
410
+ /**
411
+ * useTaskDrag — 步骤拖拽重排(绑定任务内,同一任务卡的步骤行间移动)。
412
+ *
413
+ * 交互模型(拖动全程按住鼠标):
414
+ * - 步骤行上按下(左键、非按钮/输入区域)→ 进入预览;
415
+ * - 按住 350ms 不松,或按住后移动超过 6px → 唤起跟随鼠标的浮动幽灵卡片;
416
+ * - 幽灵悬停到本任务卡内的步骤行时显示蓝色插入线(上半=插到该行前,下半=插到该行后);
417
+ * - 松手(mouseup):落在有效位置 → onReorder 落子;落在卡外/别的任务卡/原行
418
+ * (插入自身前后)/按钮输入等控件上 → 取消;
419
+ * - 350ms 内原地松手 = 普通点击,不产生幽灵;Esc → 取消。
420
+ */
421
+ function useTaskDrag(taskId, steps, isBound, onReorder) {
422
+ const [dragState, setDragState] = (0, react.useState)(null);
423
+ const [dropTargetIndex, setDropTargetIndex] = (0, react.useState)(null);
424
+ const dragRef = (0, react.useRef)(dragState);
425
+ dragRef.current = dragState;
426
+ const longPressTimer = (0, react.useRef)(void 0);
427
+ const dragOrigin = (0, react.useRef)(null);
428
+ const clearTimer = (0, react.useCallback)(() => {
429
+ if (longPressTimer.current !== void 0) {
430
+ window.clearTimeout(longPressTimer.current);
431
+ longPressTimer.current = void 0;
432
+ }
433
+ }, []);
434
+ const cancelDrag = (0, react.useCallback)(() => {
435
+ clearTimer();
436
+ dragOrigin.current = null;
437
+ document.body.style.userSelect = "";
438
+ setDropTargetIndex(null);
439
+ setDragState(null);
440
+ }, [clearTimer]);
441
+ const startDrag = (0, react.useCallback)((fromIndex, stepContent, e) => {
442
+ if (!isBound || e.button !== 0) return;
443
+ if (dragRef.current) return;
444
+ e.preventDefault();
445
+ e.stopPropagation();
446
+ document.body.style.userSelect = "none";
447
+ dragOrigin.current = {
448
+ x: e.clientX,
449
+ y: e.clientY
450
+ };
451
+ setDropTargetIndex(null);
452
+ setDragState({
453
+ fromIndex,
454
+ stepContent,
455
+ clientX: e.clientX,
456
+ clientY: e.clientY,
457
+ dragging: false
458
+ });
459
+ clearTimer();
460
+ longPressTimer.current = window.setTimeout(() => {
461
+ longPressTimer.current = void 0;
462
+ setDragState((prev) => prev ? {
463
+ ...prev,
464
+ dragging: true
465
+ } : null);
466
+ }, 350);
467
+ }, [isBound, clearTimer]);
468
+ /** 计算某行上 y 坐标对应的插入位(行数下标:上半=该行前,下半=该行后);非本任务卡返回 null */
469
+ const dropIndexAt = (0, react.useCallback)((row, clientY) => {
470
+ const parentOl = row.closest("ol");
471
+ if (!parentOl || !parentOl.closest(`article[data-task-id="${taskId}"]`)) return null;
472
+ const index = Array.from(parentOl.children).indexOf(row);
473
+ const rect = row.getBoundingClientRect();
474
+ return clientY < rect.top + rect.height / 2 ? index : index + 1;
475
+ }, [taskId]);
476
+ /** 在 (x,y) 处结算:命中有效落点则重排,否则取消;并结束本次拖拽 */
477
+ const commitDrop = (0, react.useCallback)((x, y) => {
478
+ const cur = dragRef.current;
479
+ if (!cur || !cur.dragging) return;
480
+ const el = document.elementFromPoint(x, y);
481
+ if (el && !el.closest("button, input, textarea, a")) {
482
+ const row = el.closest("[data-step-row]");
483
+ if (row) {
484
+ const drop = dropIndexAt(row, y);
485
+ if (drop !== null) {
486
+ if ((drop > cur.fromIndex ? drop - 1 : drop) !== cur.fromIndex) onReorder(cur.fromIndex, drop);
487
+ }
488
+ }
489
+ }
490
+ cancelDrag();
491
+ }, [
492
+ dropIndexAt,
493
+ onReorder,
494
+ cancelDrag
495
+ ]);
496
+ const active = dragState !== null;
497
+ (0, react.useEffect)(() => {
498
+ if (!active) return;
499
+ const onMouseMove = (e) => {
500
+ const cur = dragRef.current;
501
+ if (!cur) return;
502
+ if (!cur.dragging) {
503
+ const origin = dragOrigin.current;
504
+ if (!origin) return;
505
+ if (Math.hypot(e.clientX - origin.x, e.clientY - origin.y) < 6) return;
506
+ clearTimer();
507
+ setDragState((prev) => prev ? {
508
+ ...prev,
509
+ dragging: true,
510
+ clientX: e.clientX,
511
+ clientY: e.clientY
512
+ } : null);
513
+ } else setDragState((prev) => prev ? {
514
+ ...prev,
515
+ clientX: e.clientX,
516
+ clientY: e.clientY
517
+ } : null);
518
+ const row = document.elementFromPoint(e.clientX, e.clientY)?.closest("[data-step-row]");
519
+ setDropTargetIndex(row ? dropIndexAt(row, e.clientY) : null);
520
+ };
521
+ const onMouseUp = (e) => {
522
+ const cur = dragRef.current;
523
+ if (!cur) return;
524
+ if (cur.dragging) commitDrop(e.clientX, e.clientY);
525
+ else cancelDrag();
526
+ };
527
+ const onClick = (e) => {
528
+ const cur = dragRef.current;
529
+ if (!cur || !cur.dragging) return;
530
+ commitDrop(e.clientX, e.clientY);
531
+ };
532
+ const onKeyDown = (e) => {
533
+ if (e.key === "Escape") cancelDrag();
534
+ };
535
+ window.addEventListener("mousemove", onMouseMove);
536
+ window.addEventListener("mouseup", onMouseUp);
537
+ window.addEventListener("click", onClick, true);
538
+ window.addEventListener("keydown", onKeyDown);
539
+ return () => {
540
+ window.removeEventListener("mousemove", onMouseMove);
541
+ window.removeEventListener("mouseup", onMouseUp);
542
+ window.removeEventListener("click", onClick, true);
543
+ window.removeEventListener("keydown", onKeyDown);
544
+ document.body.style.userSelect = "";
545
+ };
546
+ }, [
547
+ active,
548
+ taskId,
549
+ dropIndexAt,
550
+ onReorder,
551
+ cancelDrag,
552
+ commitDrop
553
+ ]);
554
+ (0, react.useEffect)(() => () => clearTimer(), [clearTimer]);
555
+ return {
556
+ dragState,
557
+ dropTargetIndex,
558
+ startDrag,
559
+ cancelDrag
560
+ };
264
561
  }
265
562
  //#endregion
266
563
  //#region \0dsh-css:/home/sxt/project/dsh-project-memory/src/client/TaskPanel.module.css.mjs
267
- const css$1 = ".vZSZnG_panel,.vZSZnG_pill{pointer-events:auto;font-family:inherit}.vZSZnG_panel{background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));border:1px solid var(--dsw-alias-border-l2,#7f7f7f4d);border-radius:14px;flex-direction:column;width:380px;max-width:calc(100vw - 24px);max-height:min(70vh,640px);animation:.18s ease-out vZSZnG_panelIn;display:flex;position:fixed;overflow:hidden;box-shadow:0 12px 32px #00000029,0 2px 8px #00000014}@keyframes vZSZnG_panelIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}.vZSZnG_dragHandle{cursor:grab;z-index:1;justify-content:center;align-items:center;height:12px;display:flex;position:absolute;top:0;left:0;right:0}.vZSZnG_dragHandle:active{cursor:grabbing}.vZSZnG_handleGrip{background:var(--dsw-alias-separator-primary,#7f7f7f59);opacity:.8;border-radius:2px;width:32px;height:3px}.vZSZnG_header{border-bottom:1px solid var(--dsw-alias-border-l1,#7f7f7f2e);-webkit-user-select:none;user-select:none;justify-content:space-between;align-items:center;gap:8px;padding:10px 10px 8px 14px;display:flex}.vZSZnG_headerLeft{align-items:center;gap:6px;min-width:0;display:flex}.vZSZnG_headerIcon{color:var(--dsw-alias-label-secondary);flex:none}.vZSZnG_headerTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:13px;font-weight:600;line-height:20px}.vZSZnG_headerRight{align-items:center;gap:2px;margin-left:auto;display:flex}.vZSZnG_counts{color:var(--dsw-alias-label-tertiary);white-space:nowrap;margin-right:4px;font-size:11px;line-height:16px}.vZSZnG_boundBadge{color:var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-button-primary-fill,#3a6ef5);white-space:nowrap;border-radius:7px;flex:none;padding:0 6px;font-size:10px;line-height:14px}.vZSZnG_softBadge{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1f);white-space:nowrap;border-radius:7px;flex:none;padding:0 6px;font-size:10px;line-height:14px}.vZSZnG_notice{color:var(--dsw-alias-state-warn-label,#b7791f);background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1a);word-break:break-all;border-radius:8px;margin:8px 12px 0;padding:6px 10px;font-size:12px;line-height:18px}.vZSZnG_taskList{scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,transparent) transparent;flex-direction:column;gap:6px;padding:8px;display:flex;overflow-y:auto}.vZSZnG_emptyState{text-align:center;color:var(--dsw-alias-label-secondary);padding:36px 24px 30px}.vZSZnG_emptyTitle{color:var(--dsw-alias-label-primary);margin:0 0 6px;font-size:13px;font-weight:600}.vZSZnG_emptyDesc{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:20px}.vZSZnG_syncHint{color:var(--dsw-alias-label-dimmed);justify-content:center;align-items:center;gap:6px;margin:10px 0 0;font-size:11px;display:flex}.vZSZnG_card{border:1px solid var(--dsw-alias-border-l1,#7f7f7f29);background:var(--dsw-alias-bg-base,transparent);border-radius:10px;transition:border-color .15s;overflow:hidden}.vZSZnG_cardBound{border-color:var(--dsw-alias-border-l3,#7f7f7f59);box-shadow:inset 0 0 0 .5px var(--dsw-alias-border-l3,transparent)}.vZSZnG_cardHead{cursor:pointer;text-align:left;background:0 0;border:none;justify-content:space-between;align-items:center;gap:8px;width:100%;padding:8px 10px 7px;display:flex}.vZSZnG_cardHead:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f14)}.vZSZnG_cardTitleRow{align-items:center;gap:6px;min-width:0;display:flex}.vZSZnG_cardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:500;line-height:20px;overflow:hidden}.vZSZnG_cardMeta{color:var(--dsw-alias-label-tertiary);flex:none;align-items:center;gap:8px;display:flex}.vZSZnG_progressText{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}.vZSZnG_updated{color:var(--dsw-alias-label-dimmed);font-size:10px;line-height:14px}.vZSZnG_chevron{color:var(--dsw-alias-label-tertiary);display:inline-flex}.vZSZnG_track{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1f);border-radius:1px;height:2px;margin:0 10px 6px;overflow:hidden}.vZSZnG_trackFill{background:var(--dsw-alias-label-primary-bluish,var(--dsw-alias-button-primary-fill,#3a6ef5));border-radius:1px;height:100%;transition:width .2s}.vZSZnG_cardBody{border-top:1px solid var(--dsw-alias-border-l1,#7f7f7f24);padding:8px 10px 10px}.vZSZnG_section{margin-bottom:8px}.vZSZnG_section:last-child{margin-bottom:0}.vZSZnG_sectionLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;margin-bottom:4px;font-size:11px;line-height:16px;display:flex}.vZSZnG_sectionCount{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1a);color:var(--dsw-alias-label-tertiary);border-radius:6px;padding:0 5px;font-size:10px;line-height:14px}.vZSZnG_stepsList{flex-direction:column;gap:1px;margin:0;padding:0;list-style:none;display:flex}.vZSZnG_stepRow{align-items:flex-start;gap:7px;padding:3px 2px;display:flex}.vZSZnG_stepRow>svg{flex:none;margin-top:2px}.vZSZnG_stepDone{color:var(--dsw-alias-label-dimmed)}.vZSZnG_stepRun{color:var(--dsw-alias-label-primary-bluish,#3a6ef5)}.vZSZnG_stepPending{background:var(--dsw-alias-border-l3,#7f7f7f80);border-radius:50%;flex:none;width:8px;height:8px;margin:5px 3px}.vZSZnG_stepContent{color:var(--dsw-alias-label-secondary);word-break:break-word;font-size:12px;line-height:20px}.vZSZnG_stepContentDone{color:var(--dsw-alias-label-dimmed);text-decoration:line-through}.vZSZnG_stepContentRun{color:var(--dsw-alias-label-primary)}.vZSZnG_muted{color:var(--dsw-alias-label-dimmed);font-size:11px;line-height:18px}.vZSZnG_filesList{flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.vZSZnG_fileRow{cursor:pointer;text-align:left;background:0 0;border:none;border-radius:6px;align-items:center;gap:6px;width:100%;padding:3px 4px;display:flex}.vZSZnG_fileRow:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f14)}.vZSZnG_fileDot{background:var(--dsw-alias-border-l3,#7f7f7f80);border-radius:50%;flex:none;width:5px;height:5px}.vZSZnG_filePath{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;text-align:left;direction:rtl;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;font-size:11px;line-height:18px;overflow:hidden}.vZSZnG_fileLine{color:var(--dsw-alias-label-dimmed);flex:none;font-size:10px;line-height:18px}.vZSZnG_cardFooter{gap:6px;margin-top:8px;display:flex}.vZSZnG_spinning svg{animation:.9s linear infinite vZSZnG_spin}@keyframes vZSZnG_spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.vZSZnG_panel{animation:none}.vZSZnG_trackFill,.vZSZnG_card,.vZSZnG_pill{transition:none}.vZSZnG_spinning svg{animation-duration:2.4s}}.vZSZnG_miniBar{border:1px solid var(--dsw-alias-border-l3,#7f7f7f4d);background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));max-width:min(46vw,340px);height:34px;color:var(--dsw-alias-label-primary);cursor:grab;-webkit-user-select:none;user-select:none;pointer-events:auto;border-radius:999px;align-items:center;gap:7px;padding:0 12px;font-size:12px;line-height:18px;transition:background .15s,border-color .15s;display:inline-flex;position:fixed;box-shadow:0 6px 20px #00000024,0 1px 4px #00000014}.vZSZnG_miniBar:active{cursor:grabbing}.vZSZnG_miniBar:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f14)}.vZSZnG_miniIcon{color:var(--dsw-alias-label-secondary);flex:none}.vZSZnG_miniText{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.vZSZnG_miniChevron{color:var(--dsw-alias-label-tertiary);flex:none}.vZSZnG_taskList{flex:auto;min-height:0}.vZSZnG_cardBody{overscroll-behavior:contain;max-height:300px;overflow-y:auto}.vZSZnG_card{flex-direction:column;display:flex}.vZSZnG_boundaryFallback{border:1px solid var(--dsw-alias-border-l3,#7f7f7f4d);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary);cursor:pointer;pointer-events:auto;border-radius:999px;padding:4px 10px;font-size:12px;line-height:18px;position:fixed;top:64px;right:16px}.vZSZnG_stepToggle{cursor:pointer;background:0 0;border:none;border-radius:4px;flex:none;align-items:center;margin-top:2px;padding:0;display:inline-flex}.vZSZnG_stepToggle:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1f)}.vZSZnG_stepToggle:disabled{cursor:default;opacity:.5}.vZSZnG_stepEditable{cursor:text;border-radius:3px}.vZSZnG_stepEditable:hover{outline:1px dashed var(--dsw-alias-border-l3,#7f7f7f66);outline-offset:1px}.vZSZnG_stepInput{min-width:0;font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base,transparent);border:1px solid var(--dsw-alias-border-l3,#7f7f7f73);resize:none;box-sizing:border-box;white-space:pre-wrap;word-break:break-word;border-radius:5px;flex:auto;width:auto;min-width:0;padding:0 5px;font-size:12px;line-height:20px;display:block;overflow:hidden}.vZSZnG_cardTitleEditable{cursor:text;border-radius:3px}.vZSZnG_cardTitleEditable:hover{outline:1px dashed var(--dsw-alias-border-l3,#7f7f7f66);outline-offset:1px}.vZSZnG_panel[data-theme=glass],.vZSZnG_miniBar[data-theme=glass]{background:color-mix(in srgb, var(--dsw-alias-bg-layer-2,#fff) 90%, transparent);-webkit-backdrop-filter:blur(18px)saturate(1.2);border:1px solid color-mix(in srgb, var(--dsw-alias-border-l2,#7f7f7f4d) 80%, transparent)}.vZSZnG_panel[data-theme=glass] .vZSZnG_card{background:color-mix(in srgb, var(--dsw-alias-bg-base,transparent) 84%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-border-l1,#7f7f7f29) 60%, transparent)}.vZSZnG_panel[data-theme=glass] .vZSZnG_cardBody{border-top-color:color-mix(in srgb, var(--dsw-alias-border-l1,#7f7f7f24) 60%, transparent)}@media (prefers-reduced-transparency:reduce){.vZSZnG_panel[data-theme=glass],.vZSZnG_miniBar[data-theme=glass]{background:var(--dsw-alias-bg-layer-2,#fff);-webkit-backdrop-filter:none;backdrop-filter:none}.vZSZnG_panel[data-theme=glass] .vZSZnG_card{background:var(--dsw-alias-bg-base,transparent)}}@supports not ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.vZSZnG_panel[data-theme=glass],.vZSZnG_miniBar[data-theme=glass]{background:var(--dsw-alias-bg-layer-2,#fff)}}.vZSZnG_panel[data-theme=brutal]{box-shadow:none;border-width:1px;border-radius:6px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_card{border:1px solid var(--dsw-alias-border-l3,#7f7f7f6b);background:var(--dsw-alias-bg-base,transparent);box-shadow:none;border-radius:3px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_cardHead{padding:7px 10px 6px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_cardTitle{letter-spacing:.01em;font-weight:650}.vZSZnG_panel[data-theme=brutal] .vZSZnG_headerTitle{letter-spacing:.03em}.vZSZnG_panel[data-theme=brutal] .vZSZnG_boundBadge{border-radius:2px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_track{border-radius:0;height:3px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_trackFill{border-radius:0}.vZSZnG_panel[data-theme=brutal] .vZSZnG_cardBody{border-top:1px solid var(--dsw-alias-border-l2,#7f7f7f3d)}.vZSZnG_panel[data-theme=mono] .vZSZnG_headerTitle,.vZSZnG_panel[data-theme=mono] .vZSZnG_counts,.vZSZnG_panel[data-theme=mono] .vZSZnG_cardTitle,.vZSZnG_panel[data-theme=mono] .vZSZnG_progressText,.vZSZnG_panel[data-theme=mono] .vZSZnG_stepContent,.vZSZnG_panel[data-theme=mono] .vZSZnG_filePath{font-family:ui-monospace,SF Mono,Menlo,Consolas,Liberation Mono,monospace}.vZSZnG_panel[data-theme=mono] .vZSZnG_cardTitle{font-size:12px}.vZSZnG_panel[data-theme=mono] .vZSZnG_stepContent{font-size:11.5px}.vZSZnG_panel[data-theme=mono] .vZSZnG_card{border-radius:5px}.vZSZnG_panel[data-theme=mono] .vZSZnG_trackFill{border-radius:0}.vZSZnG_panel[data-theme=mono] .vZSZnG_stepPending{border-radius:1px}.vZSZnG_panel[data-theme=mono] .vZSZnG_updated{letter-spacing:.02em}.vZSZnG_headerIconBtn{color:inherit;cursor:pointer;background:0 0;border:none;border-radius:0;flex:none;justify-content:center;align-items:center;margin:0;padding:0;display:inline-flex}.vZSZnG_headerIcon{color:var(--dsw-alias-label-secondary);flex:none;display:block}.vZSZnG_headerLeft{min-width:0}.vZSZnG_headerTitle{text-overflow:ellipsis;flex:0 auto;min-width:0;overflow:hidden}.vZSZnG_headerRight{min-width:0}.vZSZnG_counts{text-overflow:ellipsis;flex:none;max-width:45%;margin-left:auto;overflow:hidden}";
564
+ const css$1 = ".vZSZnG_panel{pointer-events:auto;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));border:1px solid var(--dsw-alias-border-l2,#7f7f7f4d);border-radius:14px;flex-direction:column;width:380px;max-width:calc(100vw - 24px);max-height:min(70vh,640px);font-family:inherit;animation:.18s ease-out vZSZnG_panelIn;display:flex;position:fixed;overflow:hidden;box-shadow:0 12px 32px #00000029,0 2px 8px #00000014}@keyframes vZSZnG_panelIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}.vZSZnG_dragHandle{cursor:grab;z-index:1;justify-content:center;align-items:center;height:12px;display:flex;position:absolute;top:0;left:0;right:0}.vZSZnG_dragHandle:active{cursor:grabbing}.vZSZnG_handleGrip{background:var(--dsw-alias-separator-primary,#7f7f7f59);opacity:.8;border-radius:2px;width:32px;height:3px}.vZSZnG_header{border-bottom:1px solid var(--dsw-alias-border-l1,#7f7f7f2e);-webkit-user-select:none;user-select:none;justify-content:space-between;align-items:center;gap:8px;padding:10px 10px 8px 14px;display:flex}.vZSZnG_headerLeft{align-items:center;gap:6px;min-width:0;display:flex}.vZSZnG_headerIcon{color:var(--dsw-alias-label-secondary);flex:none}.vZSZnG_headerTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:13px;font-weight:600;line-height:20px}.vZSZnG_headerRight{align-items:center;gap:2px;margin-left:auto;display:flex}.vZSZnG_counts{color:var(--dsw-alias-label-tertiary);white-space:nowrap;margin-right:4px;font-size:11px;line-height:16px}.vZSZnG_boundBadge{color:var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-button-primary-fill,#3a6ef5);white-space:nowrap;border-radius:7px;flex:none;padding:0 6px;font-size:10px;line-height:14px}.vZSZnG_notice{color:var(--dsw-alias-state-warn-label,#b7791f);background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1a);word-break:break-all;border-radius:8px;margin:8px 12px 0;padding:6px 10px;font-size:12px;line-height:18px}.vZSZnG_taskList{scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,transparent) transparent;flex-direction:column;gap:6px;padding:8px;display:flex;overflow-y:auto}.vZSZnG_emptyState{text-align:center;color:var(--dsw-alias-label-secondary);padding:36px 24px 30px}.vZSZnG_emptyTitle{color:var(--dsw-alias-label-primary);margin:0 0 6px;font-size:13px;font-weight:600}.vZSZnG_emptyDesc{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:20px}.vZSZnG_syncHint{color:var(--dsw-alias-label-dimmed);justify-content:center;align-items:center;gap:6px;margin:10px 0 0;font-size:11px;display:flex}.vZSZnG_card{border:1px solid var(--dsw-alias-border-l1,#7f7f7f29);background:var(--dsw-alias-bg-base,transparent);border-radius:10px;transition:border-color .15s;overflow:hidden}.vZSZnG_cardBound{border-color:var(--dsw-alias-border-l3,#7f7f7f59);box-shadow:inset 0 0 0 .5px var(--dsw-alias-border-l3,transparent)}.vZSZnG_cardHead{cursor:pointer;text-align:left;background:0 0;border:none;justify-content:space-between;align-items:center;gap:8px;width:100%;padding:8px 10px 7px;display:flex}.vZSZnG_cardHead:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f14)}.vZSZnG_cardTitleRow{align-items:center;gap:6px;min-width:0;display:flex}.vZSZnG_cardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:500;line-height:20px;overflow:hidden}.vZSZnG_cardMeta{color:var(--dsw-alias-label-tertiary);flex:none;align-items:center;gap:8px;display:flex}.vZSZnG_progressText{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}.vZSZnG_updated{color:var(--dsw-alias-label-dimmed);font-size:10px;line-height:14px}.vZSZnG_chevron{color:var(--dsw-alias-label-tertiary);display:inline-flex}.vZSZnG_track{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1f);border-radius:1px;height:2px;margin:0 10px 6px;overflow:hidden}.vZSZnG_trackFill{background:var(--dsw-alias-label-primary-bluish,var(--dsw-alias-button-primary-fill,#3a6ef5));border-radius:1px;height:100%;transition:width .2s}.vZSZnG_cardBody{border-top:1px solid var(--dsw-alias-border-l1,#7f7f7f24);overscroll-behavior:contain;max-height:300px;padding:8px 10px 10px;overflow-y:auto}.vZSZnG_section{margin-bottom:8px}.vZSZnG_section:last-child{margin-bottom:0}.vZSZnG_sectionLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;margin-bottom:4px;font-size:11px;line-height:16px;display:flex}.vZSZnG_sectionCount{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1a);color:var(--dsw-alias-label-tertiary);border-radius:6px;padding:0 5px;font-size:10px;line-height:14px}.vZSZnG_stepsList{flex-direction:column;gap:1px;margin:0;padding:0;list-style:none;display:flex}.vZSZnG_stepRow{align-items:flex-start;gap:7px;padding:3px 2px;display:flex}.vZSZnG_stepRow>svg{flex:none;margin-top:2px}.vZSZnG_stepDone{color:var(--dsw-alias-label-dimmed)}.vZSZnG_stepRun{color:var(--dsw-alias-label-primary-bluish,#3a6ef5)}.vZSZnG_stepPending{background:var(--dsw-alias-border-l3,#7f7f7f80);border-radius:50%;flex:none;width:8px;height:8px;margin:5px 3px}.vZSZnG_stepContent{color:var(--dsw-alias-label-secondary);word-break:break-word;font-size:12px;line-height:20px}.vZSZnG_stepContentDone{color:var(--dsw-alias-label-dimmed);text-decoration:line-through}.vZSZnG_stepContentRun{color:var(--dsw-alias-label-primary)}.vZSZnG_muted{color:var(--dsw-alias-label-dimmed);font-size:11px;line-height:18px}.vZSZnG_filesList{flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.vZSZnG_fileRow{cursor:pointer;text-align:left;background:0 0;border:none;border-radius:6px;align-items:center;gap:6px;width:100%;padding:3px 4px;display:flex}.vZSZnG_fileRow:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f14)}.vZSZnG_fileDot{background:var(--dsw-alias-border-l3,#7f7f7f80);border-radius:50%;flex:none;width:5px;height:5px}.vZSZnG_filePath{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;text-align:left;direction:rtl;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;font-size:11px;line-height:18px;overflow:hidden}.vZSZnG_fileLine{color:var(--dsw-alias-label-dimmed);flex:none;font-size:10px;line-height:18px}.vZSZnG_cardFooter{gap:6px;margin-top:8px;display:flex}@media (prefers-reduced-motion:reduce){.vZSZnG_panel{animation:none}.vZSZnG_trackFill,.vZSZnG_card{transition:none}}.vZSZnG_miniBar{border:1px solid var(--dsw-alias-border-l3,#7f7f7f4d);background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));max-width:min(46vw,340px);height:34px;color:var(--dsw-alias-label-primary);cursor:grab;-webkit-user-select:none;user-select:none;pointer-events:auto;border-radius:999px;align-items:center;gap:7px;padding:0 12px;font-size:12px;line-height:18px;transition:background .15s,border-color .15s;display:inline-flex;position:fixed;box-shadow:0 6px 20px #00000024,0 1px 4px #00000014}.vZSZnG_miniBar:active{cursor:grabbing}.vZSZnG_miniBar:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f14)}.vZSZnG_miniIcon{color:var(--dsw-alias-label-secondary);flex:none}.vZSZnG_miniText{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.vZSZnG_miniChevron{color:var(--dsw-alias-label-tertiary);flex:none}.vZSZnG_taskList{flex:auto;min-height:0}.vZSZnG_card{flex-direction:column;display:flex}.vZSZnG_boundaryFallback{border:1px solid var(--dsw-alias-border-l3,#7f7f7f4d);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary);cursor:pointer;pointer-events:auto;border-radius:999px;padding:4px 10px;font-size:12px;line-height:18px;position:fixed;top:64px;right:16px}.vZSZnG_stepToggle{cursor:pointer;background:0 0;border:none;border-radius:4px;flex:none;align-items:center;margin-top:2px;padding:0;display:inline-flex}.vZSZnG_stepToggle:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1f)}.vZSZnG_stepToggle:disabled{cursor:default;opacity:.5}.vZSZnG_stepEditable{cursor:text;border-radius:3px}.vZSZnG_stepEditable:hover{outline:1px dashed var(--dsw-alias-border-l3,#7f7f7f66);outline-offset:1px}.vZSZnG_stepInput{box-sizing:border-box;width:auto;min-width:0;font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base,transparent);border:1px solid var(--dsw-alias-border-l3,#7f7f7f73);border-radius:5px;flex:auto;padding:0 5px;font-size:12px;line-height:20px}.vZSZnG_stepDragging{opacity:.3;background:var(--dsw-alias-interactive-bg-hover,#7f7f7f14)}.vZSZnG_cardTitleEditable{cursor:text;border-radius:3px}.vZSZnG_cardTitleEditable:hover{outline:1px dashed var(--dsw-alias-border-l3,#7f7f7f66);outline-offset:1px}.vZSZnG_panel[data-theme=glass],.vZSZnG_miniBar[data-theme=glass]{background:color-mix(in srgb, var(--dsw-alias-bg-layer-2,#fff) 90%, transparent);-webkit-backdrop-filter:blur(18px)saturate(1.2);border:1px solid color-mix(in srgb, var(--dsw-alias-border-l2,#7f7f7f4d) 80%, transparent)}.vZSZnG_panel[data-theme=glass] .vZSZnG_card{background:color-mix(in srgb, var(--dsw-alias-bg-base,transparent) 84%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-border-l1,#7f7f7f29) 60%, transparent)}.vZSZnG_panel[data-theme=glass] .vZSZnG_cardBody{border-top-color:color-mix(in srgb, var(--dsw-alias-border-l1,#7f7f7f24) 60%, transparent)}@media (prefers-reduced-transparency:reduce){.vZSZnG_panel[data-theme=glass],.vZSZnG_miniBar[data-theme=glass]{background:var(--dsw-alias-bg-layer-2,#fff);-webkit-backdrop-filter:none;backdrop-filter:none}.vZSZnG_panel[data-theme=glass] .vZSZnG_card{background:var(--dsw-alias-bg-base,transparent)}}@supports not ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.vZSZnG_panel[data-theme=glass],.vZSZnG_miniBar[data-theme=glass]{background:var(--dsw-alias-bg-layer-2,#fff)}}.vZSZnG_panel[data-theme=brutal]{box-shadow:none;border-width:1px;border-radius:6px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_card{border:1px solid var(--dsw-alias-border-l3,#7f7f7f6b);background:var(--dsw-alias-bg-base,transparent);box-shadow:none;border-radius:3px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_cardHead{padding:7px 10px 6px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_cardTitle{letter-spacing:.01em;font-weight:650}.vZSZnG_panel[data-theme=brutal] .vZSZnG_headerTitle{letter-spacing:.03em}.vZSZnG_panel[data-theme=brutal] .vZSZnG_boundBadge{border-radius:2px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_track{border-radius:0;height:3px}.vZSZnG_panel[data-theme=brutal] .vZSZnG_trackFill{border-radius:0}.vZSZnG_panel[data-theme=brutal] .vZSZnG_cardBody{border-top:1px solid var(--dsw-alias-border-l2,#7f7f7f3d)}.vZSZnG_panel[data-theme=mono] .vZSZnG_headerTitle,.vZSZnG_panel[data-theme=mono] .vZSZnG_counts,.vZSZnG_panel[data-theme=mono] .vZSZnG_cardTitle,.vZSZnG_panel[data-theme=mono] .vZSZnG_progressText,.vZSZnG_panel[data-theme=mono] .vZSZnG_stepContent,.vZSZnG_panel[data-theme=mono] .vZSZnG_filePath{font-family:ui-monospace,SF Mono,Menlo,Consolas,Liberation Mono,monospace}.vZSZnG_panel[data-theme=mono] .vZSZnG_cardTitle{font-size:12px}.vZSZnG_panel[data-theme=mono] .vZSZnG_stepContent{font-size:11.5px}.vZSZnG_panel[data-theme=mono] .vZSZnG_card{border-radius:5px}.vZSZnG_panel[data-theme=mono] .vZSZnG_trackFill{border-radius:0}.vZSZnG_panel[data-theme=mono] .vZSZnG_stepPending{border-radius:1px}.vZSZnG_panel[data-theme=mono] .vZSZnG_updated{letter-spacing:.02em}.vZSZnG_headerIconBtn{color:inherit;cursor:pointer;background:0 0;border:none;border-radius:0;flex:none;justify-content:center;align-items:center;margin:0;padding:0;display:inline-flex}.vZSZnG_headerIcon{color:var(--dsw-alias-label-secondary);flex:none;display:block}.vZSZnG_headerLeft{min-width:0}.vZSZnG_headerTitle{text-overflow:ellipsis;flex:0 auto;min-width:0;overflow:hidden}.vZSZnG_headerRight{min-width:0}.vZSZnG_counts{text-overflow:ellipsis;flex:none;max-width:45%;margin-left:auto;overflow:hidden}.vZSZnG_dragGhost{pointer-events:none;z-index:9999;transition:left 50ms,top 50ms;position:fixed;transform:translate(-50%,-50%)}.vZSZnG_dragGhostCard{background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));border:1px solid var(--dsw-alias-border-l3,#7f7f7f59);color:var(--dsw-alias-label-primary);white-space:nowrap;border-radius:8px;align-items:center;gap:8px;max-width:280px;padding:8px 12px;font-size:12px;line-height:20px;display:inline-flex;transform:scale(.9);box-shadow:0 8px 24px #0000002e,0 2px 8px #0000001a}.vZSZnG_dragGhostContent{text-overflow:ellipsis;word-break:break-word;flex:1;min-width:0;overflow:hidden}.vZSZnG_dragGhostClose{width:20px;height:20px;color:var(--dsw-alias-label-tertiary);cursor:pointer;opacity:.6;background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;margin-left:8px;font-size:16px;line-height:1;transition:opacity .15s,background .15s,color .15s;display:inline-flex}.vZSZnG_dragGhostClose:hover{opacity:1;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1f)}.vZSZnG_dropTargetBefore{position:relative}.vZSZnG_dropTargetBefore:before{content:\"\";background:var(--dsw-alias-button-primary-fill,#3a6ef5);height:2px;box-shadow:0 0 4px var(--dsw-alias-button-primary-fill,#3a6ef5);border-radius:1px;animation:.8s ease-in-out infinite vZSZnG_dropPulse;position:absolute;top:-1px;left:0;right:0}.vZSZnG_dropTargetAfter{position:relative}.vZSZnG_dropTargetAfter:after{content:\"\";background:var(--dsw-alias-button-primary-fill,#3a6ef5);height:2px;box-shadow:0 0 4px var(--dsw-alias-button-primary-fill,#3a6ef5);border-radius:1px;animation:.8s ease-in-out infinite vZSZnG_dropPulse;position:absolute;bottom:-1px;left:0;right:0}@keyframes vZSZnG_dropPulse{0%,to{opacity:.6}50%{opacity:1}}.vZSZnG_memoryPane{flex-direction:column;gap:6px;max-height:480px;padding:8px 10px 10px;display:flex;overflow:auto}.vZSZnG_memToolbar{justify-content:space-between;align-items:center;gap:8px;display:flex}.vZSZnG_memCount{opacity:.75;font-size:12px;font-weight:600}.vZSZnG_memList{flex-direction:column;gap:6px;display:flex}.vZSZnG_memRow{border:1px solid var(--dsw-border-weak,#80808040);background:color-mix(in srgb, var(--dsw-bg-secondary,#f5f5f5) 45%, transparent);border-radius:6px;flex-direction:column;gap:4px;padding:6px 8px;display:flex}.vZSZnG_memRowArchived{opacity:.55}.vZSZnG_memRowMain{flex-direction:column;gap:2px;display:flex}.vZSZnG_memTitle{word-break:break-word;align-items:flex-start;gap:6px;font-size:13px;line-height:1.35;display:flex}.vZSZnG_kindBadge{background:var(--dsw-accent-soft,#5078ff26);color:var(--dsw-accent,#4d6bfe);border-radius:4px;flex:none;padding:0 5px;font-size:11px;line-height:1.5}.vZSZnG_memMeta{flex-wrap:wrap;align-items:center;gap:4px;display:flex}.vZSZnG_badge{background:color-mix(in srgb, var(--dsw-fg,#222) 8%, transparent);border-radius:4px;padding:0 5px;font-size:11px;line-height:1.5}.vZSZnG_badgeDraft{color:#b26a00;background:#ffb40029}.vZSZnG_badgeArchived{opacity:.8}.vZSZnG_memBody{opacity:.8;word-break:break-word;font-size:12px}.vZSZnG_memActs{flex-wrap:wrap;gap:4px;display:flex}.vZSZnG_memToolbarActs{align-items:center;gap:6px;display:flex}.vZSZnG_memNew{border:1px solid var(--dsw-border-weak,#80808040);border-radius:6px;flex-direction:column;gap:6px;padding:8px;display:flex}.vZSZnG_memNewRow{align-items:center;gap:8px;display:flex}.vZSZnG_memLabel{opacity:.8;align-items:center;gap:6px;font-size:12px;display:inline-flex}.vZSZnG_memNew select,.vZSZnG_memNew textarea{box-sizing:border-box;width:100%}";
268
565
  const tagId$1 = "@yolk_vat-y/dsh-project-memory/TaskPanel.module.css";
269
566
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
270
567
  const tag = document.createElement("style");
@@ -274,85 +571,104 @@ window.__ModuleLoader__.load({
274
571
  document.head.appendChild(tag);
275
572
  }
276
573
  var TaskPanel_module_css_default = {
277
- "stepEditable": "vZSZnG_stepEditable",
278
- "stepContent": "vZSZnG_stepContent",
279
- "cardBound": "vZSZnG_cardBound",
280
- "updated": "vZSZnG_updated",
281
- "headerRight": "vZSZnG_headerRight",
282
- "muted": "vZSZnG_muted",
283
- "fileLine": "vZSZnG_fileLine",
284
- "spinning": "vZSZnG_spinning",
285
- "headerTitle": "vZSZnG_headerTitle",
286
- "miniText": "vZSZnG_miniText",
287
- "boundBadge": "vZSZnG_boundBadge",
288
- "card": "vZSZnG_card",
289
- "spin": "vZSZnG_spin",
574
+ "cardTitleRow": "vZSZnG_cardTitleRow",
575
+ "memMeta": "vZSZnG_memMeta",
576
+ "cardTitle": "vZSZnG_cardTitle",
577
+ "dragGhostClose": "vZSZnG_dragGhostClose",
290
578
  "syncHint": "vZSZnG_syncHint",
291
- "emptyDesc": "vZSZnG_emptyDesc",
292
- "stepsList": "vZSZnG_stepsList",
579
+ "updated": "vZSZnG_updated",
580
+ "section": "vZSZnG_section",
581
+ "emptyTitle": "vZSZnG_emptyTitle",
293
582
  "stepRow": "vZSZnG_stepRow",
294
- "trackFill": "vZSZnG_trackFill",
295
- "stepContentDone": "vZSZnG_stepContentDone",
296
- "filesList": "vZSZnG_filesList",
297
- "miniBar": "vZSZnG_miniBar",
583
+ "sectionLabel": "vZSZnG_sectionLabel",
584
+ "emptyDesc": "vZSZnG_emptyDesc",
585
+ "fileLine": "vZSZnG_fileLine",
298
586
  "boundaryFallback": "vZSZnG_boundaryFallback",
299
- "handleGrip": "vZSZnG_handleGrip",
300
- "sectionCount": "vZSZnG_sectionCount",
301
- "miniIcon": "vZSZnG_miniIcon",
302
- "panelIn": "vZSZnG_panelIn",
303
- "stepInput": "vZSZnG_stepInput",
304
- "cardTitleRow": "vZSZnG_cardTitleRow",
305
- "filePath": "vZSZnG_filePath",
306
- "panel": "vZSZnG_panel",
307
- "dragHandle": "vZSZnG_dragHandle",
587
+ "fileDot": "vZSZnG_fileDot",
588
+ "memRow": "vZSZnG_memRow",
589
+ "stepContent": "vZSZnG_stepContent",
590
+ "fileRow": "vZSZnG_fileRow",
308
591
  "cardHead": "vZSZnG_cardHead",
309
- "pill": "vZSZnG_pill",
310
- "chevron": "vZSZnG_chevron",
592
+ "stepContentDone": "vZSZnG_stepContentDone",
593
+ "dropTargetAfter": "vZSZnG_dropTargetAfter",
594
+ "stepDragging": "vZSZnG_stepDragging",
595
+ "memNew": "vZSZnG_memNew",
596
+ "taskList": "vZSZnG_taskList",
597
+ "memCount": "vZSZnG_memCount",
598
+ "stepToggle": "vZSZnG_stepToggle",
599
+ "trackFill": "vZSZnG_trackFill",
600
+ "memToolbar": "vZSZnG_memToolbar",
601
+ "stepPending": "vZSZnG_stepPending",
602
+ "memBody": "vZSZnG_memBody",
603
+ "memRowMain": "vZSZnG_memRowMain",
604
+ "badgeArchived": "vZSZnG_badgeArchived",
311
605
  "header": "vZSZnG_header",
312
- "fileRow": "vZSZnG_fileRow",
606
+ "dragGhost": "vZSZnG_dragGhost",
607
+ "card": "vZSZnG_card",
608
+ "cardTitleEditable": "vZSZnG_cardTitleEditable",
609
+ "panelIn": "vZSZnG_panelIn",
610
+ "headerRight": "vZSZnG_headerRight",
313
611
  "emptyState": "vZSZnG_emptyState",
314
612
  "headerIconBtn": "vZSZnG_headerIconBtn",
613
+ "progressText": "vZSZnG_progressText",
614
+ "boundBadge": "vZSZnG_boundBadge",
315
615
  "headerIcon": "vZSZnG_headerIcon",
316
- "stepToggle": "vZSZnG_stepToggle",
317
- "taskList": "vZSZnG_taskList",
318
- "cardTitleEditable": "vZSZnG_cardTitleEditable",
616
+ "chevron": "vZSZnG_chevron",
617
+ "track": "vZSZnG_track",
618
+ "kindBadge": "vZSZnG_kindBadge",
619
+ "memLabel": "vZSZnG_memLabel",
620
+ "cardBound": "vZSZnG_cardBound",
621
+ "stepsList": "vZSZnG_stepsList",
622
+ "dragGhostContent": "vZSZnG_dragGhostContent",
623
+ "miniIcon": "vZSZnG_miniIcon",
624
+ "stepEditable": "vZSZnG_stepEditable",
625
+ "memNewRow": "vZSZnG_memNewRow",
626
+ "panel": "vZSZnG_panel",
627
+ "filePath": "vZSZnG_filePath",
628
+ "badge": "vZSZnG_badge",
629
+ "memTitle": "vZSZnG_memTitle",
630
+ "cardFooter": "vZSZnG_cardFooter",
631
+ "headerLeft": "vZSZnG_headerLeft",
319
632
  "notice": "vZSZnG_notice",
320
- "softBadge": "vZSZnG_softBadge",
633
+ "headerTitle": "vZSZnG_headerTitle",
634
+ "miniBar": "vZSZnG_miniBar",
321
635
  "stepDone": "vZSZnG_stepDone",
322
- "stepPending": "vZSZnG_stepPending",
323
- "track": "vZSZnG_track",
324
- "cardTitle": "vZSZnG_cardTitle",
325
- "cardMeta": "vZSZnG_cardMeta",
326
- "fileDot": "vZSZnG_fileDot",
327
- "counts": "vZSZnG_counts",
328
636
  "stepContentRun": "vZSZnG_stepContentRun",
329
- "emptyTitle": "vZSZnG_emptyTitle",
330
- "progressText": "vZSZnG_progressText",
331
637
  "cardBody": "vZSZnG_cardBody",
332
- "stepRun": "vZSZnG_stepRun",
333
- "cardFooter": "vZSZnG_cardFooter",
334
- "sectionLabel": "vZSZnG_sectionLabel",
638
+ "filesList": "vZSZnG_filesList",
639
+ "handleGrip": "vZSZnG_handleGrip",
640
+ "memoryPane": "vZSZnG_memoryPane",
641
+ "memRowArchived": "vZSZnG_memRowArchived",
642
+ "dragHandle": "vZSZnG_dragHandle",
335
643
  "miniChevron": "vZSZnG_miniChevron",
336
- "headerLeft": "vZSZnG_headerLeft",
337
- "section": "vZSZnG_section"
644
+ "badgeDraft": "vZSZnG_badgeDraft",
645
+ "memList": "vZSZnG_memList",
646
+ "memActs": "vZSZnG_memActs",
647
+ "memToolbarActs": "vZSZnG_memToolbarActs",
648
+ "cardMeta": "vZSZnG_cardMeta",
649
+ "stepRun": "vZSZnG_stepRun",
650
+ "stepInput": "vZSZnG_stepInput",
651
+ "dragGhostCard": "vZSZnG_dragGhostCard",
652
+ "sectionCount": "vZSZnG_sectionCount",
653
+ "muted": "vZSZnG_muted",
654
+ "counts": "vZSZnG_counts",
655
+ "dropTargetBefore": "vZSZnG_dropTargetBefore",
656
+ "dropPulse": "vZSZnG_dropPulse",
657
+ "miniText": "vZSZnG_miniText"
338
658
  };
339
659
  //#endregion
340
- //#region src/client/TaskPanel.tsx
660
+ //#region src/client/TaskComponents.tsx
341
661
  /**
342
- * Task Panel — dsh web `shell.overlay` 浮动任务面板。
343
- *
344
- * 升级原 `/tasks` 文本输出为可交互卡片:
345
- * - 默认收成右侧浮标(不影响用户操作/不挡对话);
346
- * - 展开时通过宿主 `remote.commands.execute(sessionId, '/tasks')` 拉取最新快照
347
- * (命令只读,走 /tasks 同一数据源 = tasks.json,因此与模型维护的 todo 双向一致);
348
- * - 卡片内「切换 / 归档」执行 `/task switch|archive <id>`,服务端返回新快照 JSON,
349
- * 面板即时刷新,无需再跑一次 /tasks;
350
- * - 可拖拽(仅头部把手),位置持久化 localStorage。
351
- *
352
- * 依赖仅 react + @deepseek-ai/dsh-client-ui-primitives(宿主 seed 模块)。
662
+ * Task Panel Presentational 组件 — MiniBar(折叠迷你条)与 TaskCard(任务卡)。
663
+ * 纯渲染 + 卡片局部行内编辑/拖拽状态,动作经 props 回调交给容器(TaskPanel.tsx)。
353
664
  */
354
- function getT() {
355
- return createTranslate(typeof navigator !== "undefined" && navigator.language.startsWith("zh") ? zh : en);
665
+ function insightKindLabel(kind, t) {
666
+ return {
667
+ lesson: t("mem.kind.lesson"),
668
+ decision: t("mem.kind.decision"),
669
+ procedure: t("mem.kind.procedure"),
670
+ experience: t("mem.kind.experience")
671
+ }[kind] ?? kind;
356
672
  }
357
673
  function timeAgo(iso) {
358
674
  if (!iso) return "";
@@ -369,62 +685,6 @@ window.__ModuleLoader__.load({
369
685
  if (status === "in_progress") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlayOutline16, { className: TaskPanel_module_css_default.stepRun });
370
686
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: TaskPanel_module_css_default.stepPending });
371
687
  }
372
- function useSessionId(ctx) {
373
- const [, force] = (0, react.useState)(0);
374
- (0, react.useEffect)(() => {
375
- const list = ctx?.sessions?.list;
376
- if (!list || typeof list.subscribe !== "function") return;
377
- return list.subscribe(() => force((n) => n + 1));
378
- }, [ctx]);
379
- const snap = ctx?.sessions?.list?.getSnapshot?.();
380
- if (!snap) return null;
381
- if (snap.current) return snap.current;
382
- return (Array.isArray(snap.items) ? snap.items.find((s) => !s.blank) ?? snap.items[0] : void 0)?.sessionId ?? null;
383
- }
384
- /** 行内编辑框:自动按内容增高(最多 160px),Enter 提交、Shift+Enter 换行、Esc 取消、失焦提交。 */
385
- function AutoEdit({ value, onCommit, onCancel, singleLine }) {
386
- const [text, setText] = (0, react.useState)(value);
387
- const ref = (0, react.useRef)(null);
388
- const settled = (0, react.useRef)(false);
389
- const once = (fn) => {
390
- if (settled.current) return;
391
- settled.current = true;
392
- fn();
393
- };
394
- const adjust = () => {
395
- const el = ref.current;
396
- if (!el) return;
397
- el.style.height = "auto";
398
- el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
399
- };
400
- (0, react.useEffect)(() => {
401
- adjust();
402
- const el = ref.current;
403
- el?.focus();
404
- el?.select();
405
- }, []);
406
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
407
- ref,
408
- className: TaskPanel_module_css_default.stepInput,
409
- rows: 1,
410
- value: text,
411
- onChange: (e) => {
412
- setText(e.target.value);
413
- adjust();
414
- },
415
- onBlur: () => once(() => onCommit(text)),
416
- onKeyDown: (e) => {
417
- if (e.key === "Escape") {
418
- e.preventDefault();
419
- once(() => onCancel());
420
- } else if (e.key === "Enter" && (singleLine || !e.shiftKey)) {
421
- e.preventDefault();
422
- once(() => onCommit(text));
423
- }
424
- }
425
- });
426
- }
427
- /** 折叠迷你条:可拖拽(按住拖动,轻点展开)。 */
428
688
  function MiniBar({ label, hint, position, theme, onMove, open }) {
429
689
  const barRef = (0, react.useRef)(null);
430
690
  const down = (0, react.useRef)(null);
@@ -484,18 +744,806 @@ window.__ModuleLoader__.load({
484
744
  ]
485
745
  });
486
746
  }
747
+ function TaskCard({ task, isBound, expanded, onToggleExpand, onSwitch, onUnbind, onArchive, onRename, onEditStep, onCycleStatus, onReorderSteps, onInsightAction, showHints, syncing, t }) {
748
+ const steps = task.steps || [];
749
+ const { dragState, dropTargetIndex, startDrag, cancelDrag } = useTaskDrag(task.id, steps, isBound, onReorderSteps);
750
+ const [stepEdit, setStepEdit] = (0, react.useState)(null);
751
+ const [titleEdit, setTitleEdit] = (0, react.useState)(null);
752
+ const done = steps.filter((s) => s.status === "completed").length;
753
+ const pct = steps.length > 0 ? Math.round(done / steps.length * 100) : 0;
754
+ const commitStepEdit = (index) => {
755
+ if (!stepEdit || stepEdit.index !== index) return;
756
+ const value = stepEdit.value;
757
+ setStepEdit(null);
758
+ onEditStep(index, value);
759
+ };
760
+ const commitTitleEdit = () => {
761
+ if (titleEdit === null) return;
762
+ const value = titleEdit.value;
763
+ setTitleEdit(null);
764
+ onRename(value);
765
+ };
766
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
767
+ "data-task-id": task.id,
768
+ className: `${TaskPanel_module_css_default.card}${isBound ? ` ${TaskPanel_module_css_default.cardBound}` : ""}`,
769
+ children: [
770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
771
+ className: TaskPanel_module_css_default.cardHead,
772
+ onClick: () => {
773
+ if (titleEdit !== null) return;
774
+ onToggleExpand();
775
+ },
776
+ "aria-expanded": expanded,
777
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
778
+ className: TaskPanel_module_css_default.cardTitleRow,
779
+ children: [isBound && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
780
+ className: TaskPanel_module_css_default.boundBadge,
781
+ children: t("task.current")
782
+ }), isBound && titleEdit !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
783
+ className: TaskPanel_module_css_default.stepInput,
784
+ value: titleEdit.value,
785
+ autoFocus: true,
786
+ onChange: (e) => setTitleEdit({ value: e.target.value }),
787
+ onBlur: commitTitleEdit,
788
+ onKeyDown: (e) => {
789
+ if (e.key === "Enter") commitTitleEdit();
790
+ else if (e.key === "Escape") setTitleEdit(null);
791
+ }
792
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
793
+ className: `${TaskPanel_module_css_default.cardTitle}${isBound ? ` ${TaskPanel_module_css_default.cardTitleEditable}` : ""}`,
794
+ title: isBound ? t("task.title-edit") : void 0,
795
+ onDoubleClick: (e) => {
796
+ e.preventDefault();
797
+ e.stopPropagation();
798
+ if (isBound) setTitleEdit({ value: String(task.title ?? "") });
799
+ },
800
+ children: task.title
801
+ })]
802
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
803
+ className: TaskPanel_module_css_default.cardMeta,
804
+ children: [
805
+ steps.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
806
+ className: TaskPanel_module_css_default.progressText,
807
+ children: [
808
+ done,
809
+ "/",
810
+ steps.length
811
+ ]
812
+ }),
813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
814
+ className: TaskPanel_module_css_default.updated,
815
+ children: timeAgo(task.updatedAt || task.lastActiveAt)
816
+ }),
817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
818
+ className: TaskPanel_module_css_default.chevron,
819
+ children: expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
820
+ })
821
+ ]
822
+ })]
823
+ }),
824
+ steps.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
825
+ className: TaskPanel_module_css_default.track,
826
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
827
+ className: TaskPanel_module_css_default.trackFill,
828
+ style: { width: `${pct}%` }
829
+ })
830
+ }),
831
+ expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
832
+ className: TaskPanel_module_css_default.cardBody,
833
+ children: [
834
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
835
+ className: TaskPanel_module_css_default.section,
836
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
837
+ className: TaskPanel_module_css_default.sectionLabel,
838
+ children: [t("task.steps"), steps.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
839
+ className: TaskPanel_module_css_default.sectionCount,
840
+ children: steps.length
841
+ })]
842
+ }), steps.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
843
+ className: TaskPanel_module_css_default.stepsList,
844
+ children: steps.map((step, i) => {
845
+ const stepIsEditing = stepEdit !== null && stepEdit.index === i;
846
+ const stepIsDragging = dragState?.dragging && dragState.fromIndex === i;
847
+ const isDropTarget = dropTargetIndex === i;
848
+ const isDropTargetAfter = dropTargetIndex === i + 1;
849
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
850
+ "data-step-row": true,
851
+ className: `${TaskPanel_module_css_default.stepRow}${stepIsDragging ? ` ${TaskPanel_module_css_default.stepDragging}` : ""}${isDropTarget ? ` ${TaskPanel_module_css_default.dropTargetBefore}` : ""}${isDropTargetAfter ? ` ${TaskPanel_module_css_default.dropTargetAfter}` : ""}`,
852
+ onMouseDown: (e) => {
853
+ if (e.button !== 0) return;
854
+ if (!isBound || syncing) return;
855
+ if (e.target.closest("button, input, textarea, a")) return;
856
+ startDrag(i, step.content, e);
857
+ },
858
+ children: [isBound ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
859
+ type: "button",
860
+ className: TaskPanel_module_css_default.stepToggle,
861
+ disabled: syncing,
862
+ onClick: (e) => {
863
+ e.stopPropagation();
864
+ onCycleStatus(i);
865
+ },
866
+ title: `${t("step.completed")}/${t("step.in-progress")}/${t("step.pending")}`,
867
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepIcon, { status: step.status })
868
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepIcon, { status: step.status }), stepIsEditing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
869
+ className: TaskPanel_module_css_default.stepInput,
870
+ value: stepEdit.value,
871
+ autoFocus: true,
872
+ onChange: (e) => setStepEdit({
873
+ index: i,
874
+ value: e.target.value
875
+ }),
876
+ onBlur: () => commitStepEdit(i),
877
+ onKeyDown: (e) => {
878
+ if (e.key === "Enter") commitStepEdit(i);
879
+ else if (e.key === "Escape") setStepEdit(null);
880
+ }
881
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
882
+ className: `${TaskPanel_module_css_default.stepContent}${step.status === "completed" ? ` ${TaskPanel_module_css_default.stepContentDone}` : ""}${step.status === "in_progress" ? ` ${TaskPanel_module_css_default.stepContentRun}` : ""}${isBound ? ` ${TaskPanel_module_css_default.stepEditable}` : ""}`,
883
+ title: isBound && showHints ? t("step.edit-hint") : void 0,
884
+ onDoubleClick: (e) => {
885
+ e.stopPropagation();
886
+ if (isBound) setStepEdit({
887
+ index: i,
888
+ value: String(step.content ?? "")
889
+ });
890
+ },
891
+ children: step.content
892
+ })]
893
+ }, i);
894
+ })
895
+ }), dragState && dragState.dragging && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
896
+ className: TaskPanel_module_css_default.dragGhost,
897
+ style: {
898
+ left: dragState.clientX + 12,
899
+ top: dragState.clientY - 20
900
+ },
901
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
902
+ className: TaskPanel_module_css_default.dragGhostCard,
903
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
904
+ className: TaskPanel_module_css_default.dragGhostContent,
905
+ children: dragState.stepContent
906
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
907
+ type: "button",
908
+ className: TaskPanel_module_css_default.dragGhostClose,
909
+ style: { pointerEvents: "auto" },
910
+ onClick: (e) => {
911
+ e.stopPropagation();
912
+ cancelDrag();
913
+ },
914
+ title: showHints ? t("drag.cancel") : void 0,
915
+ "aria-label": t("drag.cancel"),
916
+ children: "×"
917
+ })]
918
+ })
919
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
920
+ className: TaskPanel_module_css_default.muted,
921
+ children: t("panel.empty-desc")
922
+ })]
923
+ }),
924
+ (task.files?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
925
+ className: TaskPanel_module_css_default.section,
926
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
927
+ className: TaskPanel_module_css_default.sectionLabel,
928
+ children: [t("task.files"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
929
+ className: TaskPanel_module_css_default.sectionCount,
930
+ children: task.files.length
931
+ })]
932
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
933
+ className: TaskPanel_module_css_default.filesList,
934
+ children: [task.files.slice(0, 12).map((file, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
935
+ className: TaskPanel_module_css_default.fileRow,
936
+ onClick: () => navigator.clipboard?.writeText(file.path),
937
+ title: `${t("file.copy")}: ${file.path}${file.line ? `:${file.line}` : ""}`,
938
+ children: [
939
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: TaskPanel_module_css_default.fileDot }),
940
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
941
+ className: TaskPanel_module_css_default.filePath,
942
+ children: file.path
943
+ }),
944
+ file.line && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
945
+ className: TaskPanel_module_css_default.fileLine,
946
+ children: [":", file.line]
947
+ })
948
+ ]
949
+ }) }, i)), (task.files?.length ?? 0) > 12 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
950
+ className: TaskPanel_module_css_default.muted,
951
+ children: [
952
+ "… 共 ",
953
+ task.files.length,
954
+ " 个"
955
+ ]
956
+ })]
957
+ })]
958
+ }),
959
+ onInsightAction && Array.isArray(task.insights) && task.insights.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
960
+ className: TaskPanel_module_css_default.section,
961
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
962
+ className: TaskPanel_module_css_default.sectionLabel,
963
+ children: [t("mem.section-label"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
964
+ className: TaskPanel_module_css_default.sectionCount,
965
+ children: task.insights.length
966
+ })]
967
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
968
+ className: TaskPanel_module_css_default.memList,
969
+ children: task.insights.map((ins) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
970
+ className: TaskPanel_module_css_default.memRow,
971
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
972
+ className: TaskPanel_module_css_default.memTitle,
973
+ children: [
974
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
975
+ className: TaskPanel_module_css_default.kindBadge,
976
+ children: insightKindLabel(ins.kind, t)
977
+ }),
978
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: ins.title || ins.id }),
979
+ ins.draft && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
980
+ className: `${TaskPanel_module_css_default.badge} ${TaskPanel_module_css_default.badgeDraft}`,
981
+ children: t("mem.draft")
982
+ })
983
+ ]
984
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
985
+ className: TaskPanel_module_css_default.memActs,
986
+ children: [
987
+ ins.draft && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
988
+ variant: "outline",
989
+ size: "sm",
990
+ disabled: syncing,
991
+ onClick: () => onInsightAction("confirm", ins.id),
992
+ children: t("mem.confirm")
993
+ }),
994
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
995
+ variant: "outline",
996
+ size: "sm",
997
+ disabled: syncing,
998
+ onClick: () => onInsightAction("promote", ins.id),
999
+ children: t("mem.promote")
1000
+ }),
1001
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1002
+ variant: "outline",
1003
+ size: "sm",
1004
+ disabled: syncing,
1005
+ onClick: () => onInsightAction("delete", ins.id),
1006
+ children: t("mem.delete")
1007
+ })
1008
+ ]
1009
+ })]
1010
+ }, ins.id))
1011
+ })]
1012
+ }),
1013
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1014
+ className: TaskPanel_module_css_default.cardFooter,
1015
+ children: [
1016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1017
+ variant: "outline",
1018
+ size: "sm",
1019
+ disabled: isBound || syncing,
1020
+ onClick: onSwitch,
1021
+ children: t("task.switch")
1022
+ }),
1023
+ isBound && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1024
+ variant: "outline",
1025
+ size: "sm",
1026
+ disabled: syncing,
1027
+ onClick: onUnbind,
1028
+ title: t("task.unbind"),
1029
+ children: t("task.unbind")
1030
+ }),
1031
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1032
+ variant: "outline",
1033
+ size: "sm",
1034
+ disabled: syncing,
1035
+ onClick: onArchive,
1036
+ children: t("task.archive")
1037
+ })
1038
+ ]
1039
+ })
1040
+ ]
1041
+ })
1042
+ ]
1043
+ });
1044
+ }
1045
+ //#endregion
1046
+ //#region src/client/MemoryView.tsx
487
1047
  /**
488
- * 错误边界:任务面板渲染一旦抛错,不再让 shell.overlay 把整条条目退休
489
- * (宿主对崩溃条目会永久移除直到重载页面)。捕获后显示一个纯文本兜底,
490
- * 点击重新渲染。
1048
+ * Memory View — 记忆三作用域列表(Task 草稿 / Project / Global)
1049
+ * 数据通道与任务面板一致:remote.commands.execute 执行 /insight 命令,取 JSON 载荷。
1050
+ * 动作:confirm(草稿审核) / promote(向上提升) / demote(向下降级,需绑定任务) / archive / restore / delete
491
1051
  */
1052
+ function parseInsightPayloadText(text) {
1053
+ if (!text) return null;
1054
+ let jsonBlock = null;
1055
+ const fenced = text.match(/```(?:json)?\s*\n([\s\S]*?)\n```/);
1056
+ if (fenced) jsonBlock = fenced[1];
1057
+ else {
1058
+ const lastBrace = text.lastIndexOf("{");
1059
+ if (lastBrace !== -1) jsonBlock = text.slice(lastBrace);
1060
+ }
1061
+ if (!jsonBlock) return null;
1062
+ try {
1063
+ const data = JSON.parse(jsonBlock);
1064
+ if (!Array.isArray(data.items)) return null;
1065
+ return {
1066
+ scope: data.scope ?? "project",
1067
+ count: data.count ?? data.items.length,
1068
+ items: data.items
1069
+ };
1070
+ } catch {
1071
+ return null;
1072
+ }
1073
+ }
1074
+ function kindText(kind, t) {
1075
+ return {
1076
+ lesson: t("mem.kind.lesson"),
1077
+ decision: t("mem.kind.decision"),
1078
+ procedure: t("mem.kind.procedure"),
1079
+ experience: t("mem.kind.experience")
1080
+ }[kind] ?? kind;
1081
+ }
1082
+ function MemoryView({ ctx, sessionId, scope, boundTaskId, t }) {
1083
+ const [payload, setPayload] = (0, react.useState)(null);
1084
+ const [error, setError] = (0, react.useState)(null);
1085
+ const [loading, setLoading] = (0, react.useState)(false);
1086
+ const [busy, setBusy] = (0, react.useState)(false);
1087
+ const [showNew, setShowNew] = (0, react.useState)(false);
1088
+ const [editingId, setEditingId] = (0, react.useState)(null);
1089
+ const [form, setForm] = (0, react.useState)({
1090
+ title: "",
1091
+ kind: "lesson",
1092
+ body: "",
1093
+ steps: "",
1094
+ trigger: ""
1095
+ });
1096
+ const [savedTip, setSavedTip] = (0, react.useState)(null);
1097
+ const runLine = (0, react.useCallback)(async (line) => {
1098
+ const commands = ctx?.remote?.commands;
1099
+ if (!sessionId || !commands || typeof commands.execute !== "function") return {
1100
+ ok: false,
1101
+ text: "no session / commands service"
1102
+ };
1103
+ try {
1104
+ const envelope = await commands.execute(sessionId, line, []);
1105
+ const execution = envelope && typeof envelope === "object" && "value" in envelope ? envelope.value : envelope;
1106
+ const result = execution?.result ?? execution;
1107
+ if (result?.kind === "error") return {
1108
+ ok: false,
1109
+ text: result.text ?? "command error"
1110
+ };
1111
+ return {
1112
+ ok: true,
1113
+ text: result?.text ?? ""
1114
+ };
1115
+ } catch (err) {
1116
+ return {
1117
+ ok: false,
1118
+ text: String(err?.message ?? err)
1119
+ };
1120
+ }
1121
+ }, [ctx, sessionId]);
1122
+ const inflightRef = (0, react.useRef)(false);
1123
+ const lastRunRef = (0, react.useRef)(0);
1124
+ const refresh = (0, react.useCallback)(async (force = false) => {
1125
+ if (inflightRef.current) return;
1126
+ const now = Date.now();
1127
+ if (!force && now - lastRunRef.current < 800) return;
1128
+ inflightRef.current = true;
1129
+ lastRunRef.current = now;
1130
+ setLoading(true);
1131
+ setError(null);
1132
+ try {
1133
+ const res = await runLine(`/insight list ${scope}`);
1134
+ if (!res.ok) {
1135
+ setError(res.text);
1136
+ setPayload(null);
1137
+ return;
1138
+ }
1139
+ const parsed = parseInsightPayloadText(res.text);
1140
+ setPayload(parsed);
1141
+ if (!parsed) setError("unparsable payload");
1142
+ } finally {
1143
+ inflightRef.current = false;
1144
+ setLoading(false);
1145
+ }
1146
+ }, [runLine, scope]);
1147
+ (0, react.useEffect)(() => {
1148
+ refresh();
1149
+ }, [refresh]);
1150
+ const act = async (action, id) => {
1151
+ if (busy) return;
1152
+ setBusy(true);
1153
+ setError(null);
1154
+ try {
1155
+ const line = `/insight ${action} ${scope} ${id}${action === "demote" && scope === "project" && boundTaskId ? ` ${boundTaskId}` : ""}`;
1156
+ const res = await runLine(line);
1157
+ if (!res.ok) setError(res.text);
1158
+ else refresh(true);
1159
+ } finally {
1160
+ setBusy(false);
1161
+ }
1162
+ };
1163
+ const rowToForm = (row) => {
1164
+ const main = row.kind === "lesson" ? row.fix : row.kind === "experience" ? row.solution : row.kind === "decision" ? row.reason : row.body;
1165
+ const body = main && main !== row.title ? main : "";
1166
+ return {
1167
+ title: row.title || "",
1168
+ kind: row.kind || "lesson",
1169
+ body,
1170
+ steps: Array.isArray(row.steps) ? row.steps.join("\n") : "",
1171
+ trigger: (row.trigger?.keywords || []).join(",")
1172
+ };
1173
+ };
1174
+ const startEdit = (row) => {
1175
+ setEditingId(row.id);
1176
+ setForm(rowToForm(row));
1177
+ setShowNew(true);
1178
+ setError(null);
1179
+ };
1180
+ const closeForm = () => {
1181
+ setShowNew(false);
1182
+ setEditingId(null);
1183
+ setForm({
1184
+ title: "",
1185
+ kind: "lesson",
1186
+ body: "",
1187
+ steps: "",
1188
+ trigger: ""
1189
+ });
1190
+ };
1191
+ const changeKind = (next) => {
1192
+ setForm((prev) => {
1193
+ if (prev.kind === next) return prev;
1194
+ const fromProc = prev.kind === "procedure";
1195
+ const toProc = next === "procedure";
1196
+ let body = prev.body;
1197
+ let steps = prev.steps;
1198
+ if (fromProc && !toProc && !body && steps.trim()) body = steps.trim();
1199
+ if (toProc && !fromProc && !steps.trim() && body.trim()) steps = body.trim();
1200
+ return {
1201
+ ...prev,
1202
+ kind: next,
1203
+ body,
1204
+ steps
1205
+ };
1206
+ });
1207
+ };
1208
+ const submitNew = async () => {
1209
+ if (scope === "task" && !editingId) return;
1210
+ if (busy) return;
1211
+ const title = form.title.trim();
1212
+ if (!title) return;
1213
+ setBusy(true);
1214
+ setError(null);
1215
+ try {
1216
+ const fields = {
1217
+ title,
1218
+ kind: form.kind
1219
+ };
1220
+ if (form.kind === "lesson") fields.pattern = title;
1221
+ if (form.kind === "decision") fields.choice = title;
1222
+ if (form.kind === "experience") fields.problem = title;
1223
+ if (form.body.trim()) {
1224
+ if (form.kind === "lesson") fields.fix = form.body.trim();
1225
+ else if (form.kind === "decision") fields.reason = form.body.trim();
1226
+ else if (form.kind === "experience") fields.solution = form.body.trim();
1227
+ else fields.body = form.body.trim();
1228
+ }
1229
+ if (form.kind === "procedure" && form.steps.trim()) fields.steps = form.steps.split("\n").map((s) => s.trim()).filter(Boolean);
1230
+ if (form.kind === "procedure" && form.trigger.trim()) fields.trigger = { keywords: form.trigger.split(/[,,]/).map((s) => s.trim()).filter(Boolean) };
1231
+ const line = editingId ? `/insight edit ${scope} ${editingId} ${JSON.stringify(fields)}` : `/insight save ${scope} ${JSON.stringify(fields)}`;
1232
+ const res = await runLine(line);
1233
+ if (!res.ok) setError(res.text);
1234
+ else {
1235
+ setSavedTip(t("mem.saved"));
1236
+ closeForm();
1237
+ window.setTimeout(() => setSavedTip(null), 2500);
1238
+ refresh(true);
1239
+ }
1240
+ } finally {
1241
+ setBusy(false);
1242
+ }
1243
+ };
1244
+ const items = payload?.items ?? [];
1245
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1246
+ className: TaskPanel_module_css_default.memoryPane,
1247
+ children: [
1248
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1249
+ className: TaskPanel_module_css_default.memToolbar,
1250
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1251
+ className: TaskPanel_module_css_default.memCount,
1252
+ children: [scope === "task" ? t("mem.scope.task") : scope === "global" ? t("mem.scope.global") : t("mem.scope.project"), payload ? ` · ${payload.count}` : ""]
1253
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1254
+ className: TaskPanel_module_css_default.memToolbarActs,
1255
+ children: [
1256
+ savedTip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1257
+ className: TaskPanel_module_css_default.badge,
1258
+ children: savedTip
1259
+ }),
1260
+ scope !== "task" && !showNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1261
+ variant: "outline",
1262
+ size: "sm",
1263
+ disabled: busy,
1264
+ onClick: () => {
1265
+ setShowNew(true);
1266
+ setEditingId(null);
1267
+ setError(null);
1268
+ },
1269
+ children: t("mem.new")
1270
+ }),
1271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1272
+ variant: "outline",
1273
+ size: "sm",
1274
+ onClick: () => void refresh(true),
1275
+ disabled: loading,
1276
+ title: t("panel.refresh"),
1277
+ "aria-label": t("panel.refresh"),
1278
+ children: "↻"
1279
+ })
1280
+ ]
1281
+ })]
1282
+ }),
1283
+ showNew && scope !== "task" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1284
+ className: TaskPanel_module_css_default.memNew,
1285
+ children: [
1286
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1287
+ className: TaskPanel_module_css_default.stepInput,
1288
+ placeholder: t("mem.f.title"),
1289
+ value: form.title,
1290
+ onChange: (e) => setForm({
1291
+ ...form,
1292
+ title: e.target.value
1293
+ }),
1294
+ autoFocus: true
1295
+ }),
1296
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1297
+ className: TaskPanel_module_css_default.memNewRow,
1298
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1299
+ className: TaskPanel_module_css_default.memLabel,
1300
+ children: [t("mem.f.kind"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1301
+ className: TaskPanel_module_css_default.stepInput,
1302
+ value: form.kind,
1303
+ onChange: (e) => changeKind(e.target.value),
1304
+ children: [
1305
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1306
+ value: "lesson",
1307
+ children: "lesson"
1308
+ }),
1309
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1310
+ value: "decision",
1311
+ children: "decision"
1312
+ }),
1313
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1314
+ value: "procedure",
1315
+ children: "procedure"
1316
+ }),
1317
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1318
+ value: "experience",
1319
+ children: "experience"
1320
+ })
1321
+ ]
1322
+ })]
1323
+ })
1324
+ }),
1325
+ form.kind !== "procedure" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1326
+ className: TaskPanel_module_css_default.stepInput,
1327
+ placeholder: t("mem.f.body"),
1328
+ rows: 2,
1329
+ value: form.body,
1330
+ onChange: (e) => setForm({
1331
+ ...form,
1332
+ body: e.target.value
1333
+ })
1334
+ }),
1335
+ form.kind === "procedure" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1336
+ className: TaskPanel_module_css_default.stepInput,
1337
+ placeholder: t("mem.f.steps"),
1338
+ rows: 3,
1339
+ value: form.steps,
1340
+ onChange: (e) => setForm({
1341
+ ...form,
1342
+ steps: e.target.value
1343
+ })
1344
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1345
+ className: TaskPanel_module_css_default.stepInput,
1346
+ placeholder: t("mem.f.trigger"),
1347
+ value: form.trigger,
1348
+ onChange: (e) => setForm({
1349
+ ...form,
1350
+ trigger: e.target.value
1351
+ })
1352
+ })] }),
1353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1354
+ className: TaskPanel_module_css_default.memActs,
1355
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1356
+ variant: "outline",
1357
+ size: "sm",
1358
+ disabled: busy || !form.title.trim(),
1359
+ onClick: () => void submitNew(),
1360
+ children: t("mem.save")
1361
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1362
+ variant: "outline",
1363
+ size: "sm",
1364
+ onClick: closeForm,
1365
+ children: t("mem.cancel")
1366
+ })]
1367
+ })
1368
+ ]
1369
+ }),
1370
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1371
+ className: TaskPanel_module_css_default.notice,
1372
+ children: [
1373
+ t("mem.error"),
1374
+ ": ",
1375
+ error
1376
+ ]
1377
+ }),
1378
+ loading && !items.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1379
+ className: TaskPanel_module_css_default.muted,
1380
+ children: t("mem.loading")
1381
+ }),
1382
+ !loading && !error && items.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1383
+ className: TaskPanel_module_css_default.muted,
1384
+ children: t("mem.empty")
1385
+ }),
1386
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1387
+ className: TaskPanel_module_css_default.memList,
1388
+ children: items.map((row) => {
1389
+ const canDemoteToTask = scope === "project" && !!boundTaskId;
1390
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1391
+ className: `${TaskPanel_module_css_default.memRow}${row.archived ? ` ${TaskPanel_module_css_default.memRowArchived}` : ""}`,
1392
+ children: [
1393
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1394
+ className: TaskPanel_module_css_default.memRowMain,
1395
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1396
+ className: TaskPanel_module_css_default.memTitle,
1397
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1398
+ className: TaskPanel_module_css_default.kindBadge,
1399
+ children: kindText(row.kind, t)
1400
+ }), row.title || row.body]
1401
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1402
+ className: TaskPanel_module_css_default.memMeta,
1403
+ children: [
1404
+ row.draft && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1405
+ className: `${TaskPanel_module_css_default.badge} ${TaskPanel_module_css_default.badgeDraft}`,
1406
+ children: t("mem.draft")
1407
+ }),
1408
+ row.archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1409
+ className: `${TaskPanel_module_css_default.badge} ${TaskPanel_module_css_default.badgeArchived}`,
1410
+ children: t("mem.archived")
1411
+ }),
1412
+ row.taskTitle && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1413
+ className: TaskPanel_module_css_default.badge,
1414
+ children: ["@", row.taskTitle]
1415
+ }),
1416
+ row.members !== void 0 && row.members > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1417
+ className: TaskPanel_module_css_default.badge,
1418
+ children: ["×", row.members]
1419
+ }),
1420
+ row.confidence != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1421
+ className: TaskPanel_module_css_default.badge,
1422
+ children: [Math.round(row.confidence * 100), "%"]
1423
+ }),
1424
+ row.trigger && row.trigger.keywords.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1425
+ className: TaskPanel_module_css_default.badge,
1426
+ children: ["⚡", row.trigger.keywords.slice(0, 2).join(",")]
1427
+ })
1428
+ ]
1429
+ })]
1430
+ }),
1431
+ row.body && row.body !== row.title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1432
+ className: TaskPanel_module_css_default.memBody,
1433
+ children: String(row.body).slice(0, 160)
1434
+ }),
1435
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1436
+ className: TaskPanel_module_css_default.memActs,
1437
+ children: [
1438
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1439
+ variant: "outline",
1440
+ size: "sm",
1441
+ disabled: busy,
1442
+ onClick: () => startEdit(row),
1443
+ children: t("mem.edit")
1444
+ }),
1445
+ scope === "task" && !row.archived && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [row.draft && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1446
+ variant: "outline",
1447
+ size: "sm",
1448
+ disabled: busy,
1449
+ onClick: () => void act("confirm", row.id),
1450
+ children: t("mem.confirm")
1451
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1452
+ variant: "outline",
1453
+ size: "sm",
1454
+ disabled: busy,
1455
+ onClick: () => void act("promote", row.id),
1456
+ children: t("mem.promote")
1457
+ })] }),
1458
+ scope === "project" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1459
+ variant: "outline",
1460
+ size: "sm",
1461
+ disabled: busy,
1462
+ onClick: () => void act("promote", row.id),
1463
+ children: t("mem.promote")
1464
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1465
+ variant: "outline",
1466
+ size: "sm",
1467
+ disabled: busy || !canDemoteToTask,
1468
+ title: !canDemoteToTask ? t("mem.needs-bound") : void 0,
1469
+ onClick: () => void act("demote", row.id),
1470
+ children: t("mem.demote")
1471
+ })] }),
1472
+ scope === "global" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1473
+ variant: "outline",
1474
+ size: "sm",
1475
+ disabled: busy,
1476
+ onClick: () => void act("demote", row.id),
1477
+ children: t("mem.demote")
1478
+ }),
1479
+ row.archived ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1480
+ variant: "outline",
1481
+ size: "sm",
1482
+ disabled: busy,
1483
+ onClick: () => void act("restore", row.id),
1484
+ children: t("mem.restore")
1485
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1486
+ variant: "outline",
1487
+ size: "sm",
1488
+ disabled: busy,
1489
+ onClick: () => void act("archive", row.id),
1490
+ children: t("mem.archive")
1491
+ }),
1492
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1493
+ variant: "outline",
1494
+ size: "sm",
1495
+ disabled: busy,
1496
+ onClick: () => void act("delete", row.id),
1497
+ children: t("mem.delete")
1498
+ })
1499
+ ]
1500
+ })
1501
+ ]
1502
+ }, row.id);
1503
+ })
1504
+ })
1505
+ ]
1506
+ });
1507
+ }
1508
+ //#endregion
1509
+ //#region src/client/TaskPanel.tsx
1510
+ /**
1511
+ * Task Panel — dsh web `shell.overlay` 浮动任务面板。
1512
+ * Container 组件:负责数据获取、命令桥接、状态协调
1513
+ * Presentational 组件在 TaskComponents.tsx
1514
+ */
1515
+ const STATUS_CYCLE = [
1516
+ "pending",
1517
+ "in_progress",
1518
+ "completed"
1519
+ ];
1520
+ const VIEW_CYCLE = [
1521
+ "task",
1522
+ "project",
1523
+ "global"
1524
+ ];
1525
+ function getT(ctx) {
1526
+ return createTranslate(ctx?.locale?.getSnapshot?.()?.active === "zh" ? zh : en);
1527
+ }
1528
+ function useSessionId(ctx) {
1529
+ const [, force] = (0, react.useState)(0);
1530
+ (0, react.useEffect)(() => {
1531
+ const list = ctx?.sessions?.list;
1532
+ if (!list || typeof list.subscribe !== "function") return;
1533
+ return list.subscribe(() => force((n) => n + 1));
1534
+ }, [ctx]);
1535
+ const snap = ctx?.sessions?.list?.getSnapshot?.();
1536
+ if (!snap) return null;
1537
+ if (snap.current) return snap.current;
1538
+ return (Array.isArray(snap.items) ? snap.items.find((s) => !s.blank) ?? snap.items[0] : void 0)?.sessionId ?? null;
1539
+ }
492
1540
  var PanelErrorBoundary = class extends react.Component {
493
1541
  state = { failed: false };
494
1542
  static getDerivedStateFromError() {
495
1543
  return { failed: true };
496
1544
  }
497
1545
  componentDidCatch(error) {
498
- console.warn("[dsh-project-memory] task panel render crashed (contained by boundary):", error);
1546
+ console.warn("[dsh-project-memory] task panel render crashed:", error);
499
1547
  }
500
1548
  render() {
501
1549
  if (!this.state.failed) return this.props.children;
@@ -511,23 +1559,29 @@ window.__ModuleLoader__.load({
511
1559
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelErrorBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskPanelView, { ctx }) });
512
1560
  }
513
1561
  function TaskPanelView({ ctx }) {
514
- const t = getT();
515
- const state = useTaskStore();
1562
+ const t = getT(ctx);
1563
+ const data = useTaskData();
1564
+ const ui = useTaskUI();
516
1565
  const sessionId = useSessionId(ctx);
517
1566
  const [syncing, setSyncing] = (0, react.useState)(false);
518
1567
  const [syncedAt, setSyncedAt] = (0, react.useState)(0);
519
- const [copiedPath, setCopiedPath] = (0, react.useState)(null);
520
1568
  const [syncError, setSyncError] = (0, react.useState)(null);
521
- const panelRef = (0, react.useRef)(null);
522
- const drag = (0, react.useRef)(null);
523
- const copyTimer = (0, react.useRef)(void 0);
524
- const actions = taskStore.actions;
525
- const activeTasks = state.tasks.filter((task) => !task.archived);
526
- const boundTask = state.boundTaskId ? state.tasks.find((task) => task.id === state.boundTaskId) ?? null : null;
1569
+ const [showHints, setShowHints] = (0, react.useState)(true);
1570
+ const [view, setView] = (0, react.useState)("task");
1571
+ const cycleView = () => {
1572
+ const i = VIEW_CYCLE.indexOf(view);
1573
+ setView(VIEW_CYCLE[(i + 1) % VIEW_CYCLE.length]);
1574
+ };
1575
+ const viewTitle = view === "task" ? t("panel.title") : view === "global" ? t("view.global") : t("view.project");
1576
+ const dataActions = useTaskDataActions();
1577
+ const uiActions = useTaskUIActions();
1578
+ const tasks = Array.isArray(data.tasks) ? data.tasks : [];
1579
+ const activeTasks = tasks.filter((task) => !task.archived);
1580
+ const boundTask = data.boundTaskId ? tasks.find((task) => task.id === data.boundTaskId) ?? null : null;
527
1581
  const applyPayload = (text) => {
528
1582
  const parsed = parseTaskPayloadText(text);
529
1583
  if (!parsed) return false;
530
- actions.setTasks({
1584
+ dataActions.setTasks({
531
1585
  tasks: parsed.tasks,
532
1586
  boundTaskId: parsed.boundId,
533
1587
  archivedCount: parsed.archived
@@ -562,7 +1616,10 @@ window.__ModuleLoader__.load({
562
1616
  if (syncing) return;
563
1617
  setSyncing(true);
564
1618
  try {
1619
+ const prevBoundId = data.boundTaskId;
565
1620
  await runLine("/tasks");
1621
+ const newBoundId = taskDataStore.getSnapshot().boundTaskId;
1622
+ if (newBoundId && newBoundId !== prevBoundId) await runLine(`/task switch ${newBoundId}`);
566
1623
  } finally {
567
1624
  setSyncing(false);
568
1625
  }
@@ -576,12 +1633,16 @@ window.__ModuleLoader__.load({
576
1633
  setSyncing(false);
577
1634
  }
578
1635
  };
579
- const [editing, setEditing] = (0, react.useState)(null);
580
- const STATUS_CYCLE = [
581
- "pending",
582
- "in_progress",
583
- "completed"
584
- ];
1636
+ const handleInsightTask = async (action, id) => {
1637
+ if (syncing) return;
1638
+ setSyncing(true);
1639
+ try {
1640
+ await runLine(`/insight ${action} task ${id}`);
1641
+ await refresh();
1642
+ } finally {
1643
+ setSyncing(false);
1644
+ }
1645
+ };
585
1646
  const pushSteps = (taskId, steps) => {
586
1647
  runLine(`/task todos ${JSON.stringify(steps.map((s) => ({
587
1648
  content: s.content,
@@ -589,7 +1650,7 @@ window.__ModuleLoader__.load({
589
1650
  })))}`);
590
1651
  };
591
1652
  const cycleStatus = (taskId, index) => {
592
- const task = state.tasks.find((tt) => tt.id === taskId);
1653
+ const task = data.tasks.find((tt) => tt.id === taskId);
593
1654
  if (!task) return;
594
1655
  const next = (task.steps || []).map((s, i) => {
595
1656
  if (i !== index) return s;
@@ -602,37 +1663,36 @@ window.__ModuleLoader__.load({
602
1663
  pushSteps(taskId, next);
603
1664
  };
604
1665
  const commitStepText = (taskId, index, value) => {
605
- const task = state.tasks.find((tt) => tt.id === taskId);
1666
+ const task = data.tasks.find((tt) => tt.id === taskId);
606
1667
  const trimmed = value.trim();
607
- if (!task) {
608
- setEditing(null);
609
- return;
610
- }
611
- const same = (task.steps || [])[index]?.content === trimmed;
612
- setEditing(null);
613
- if (same || !trimmed) return;
1668
+ if (!task) return;
1669
+ if ((task.steps || [])[index]?.content === trimmed || !trimmed) return;
614
1670
  const next = (task.steps || []).map((s, i) => i === index ? {
615
1671
  ...s,
616
1672
  content: trimmed
617
1673
  } : s);
618
1674
  pushSteps(taskId, next);
619
1675
  };
620
- const [editingTitle, setEditingTitle] = (0, react.useState)(null);
1676
+ const reorderSteps = (taskId, fromIndex, toIndex) => {
1677
+ if (fromIndex === toIndex) return;
1678
+ const task = data.tasks.find((tt) => tt.id === taskId);
1679
+ if (!task) return;
1680
+ const steps = [...task.steps || []];
1681
+ const [moved] = steps.splice(fromIndex, 1);
1682
+ const targetIndex = toIndex > fromIndex ? toIndex - 1 : toIndex;
1683
+ steps.splice(targetIndex, 0, moved);
1684
+ pushSteps(taskId, steps);
1685
+ };
621
1686
  const commitTitle = (taskId, value) => {
622
- const task = state.tasks.find((tt) => tt.id === taskId);
1687
+ const task = data.tasks.find((tt) => tt.id === taskId);
623
1688
  const trimmed = value.trim();
624
- if (!task) {
625
- setEditingTitle(null);
626
- return;
627
- }
628
- const same = task.title === trimmed;
629
- setEditingTitle(null);
630
- if (same || !trimmed) return;
1689
+ if (!task) return;
1690
+ if (task.title === trimmed || !trimmed) return;
631
1691
  runLine(`/task rename ${taskId} ${JSON.stringify(trimmed)}`);
632
1692
  };
633
1693
  const expand = () => {
634
- actions.open();
635
- if (Date.now() - Math.max(state.lastUpdate, syncedAt) > 3e4) refresh();
1694
+ uiActions.open();
1695
+ if (Date.now() - Math.max(data.lastUpdate, syncedAt) > 3e4) refresh();
636
1696
  };
637
1697
  const THEMES = [
638
1698
  "native",
@@ -640,12 +1700,14 @@ window.__ModuleLoader__.load({
640
1700
  "brutal",
641
1701
  "mono"
642
1702
  ];
643
- const style = THEMES.includes(state.theme) ? state.theme : "native";
1703
+ const style = THEMES.includes(ui.theme) ? ui.theme : "native";
644
1704
  const styleLabel = t(`style.${style}`);
645
1705
  const cycleTheme = () => {
646
1706
  const i = THEMES.indexOf(style);
647
- actions.setTheme(THEMES[(i + 1) % THEMES.length]);
1707
+ uiActions.setTheme(THEMES[(i + 1) % THEMES.length]);
648
1708
  };
1709
+ const panelRef = (0, react.useRef)(null);
1710
+ const drag = (0, react.useRef)(null);
649
1711
  const handleDragStart = (e) => {
650
1712
  if (e.target !== e.currentTarget) return;
651
1713
  const rect = panelRef.current?.getBoundingClientRect();
@@ -660,7 +1722,7 @@ window.__ModuleLoader__.load({
660
1722
  panelRef.current?.offsetHeight;
661
1723
  const x = Math.max(8, Math.min(window.innerWidth - width - 8, ev.clientX - drag.current.dx));
662
1724
  const y = Math.max(8, Math.min(window.innerHeight - 64, ev.clientY - drag.current.dy));
663
- actions.setPanelPosition({
1725
+ uiActions.setPanelPosition({
664
1726
  x,
665
1727
  y
666
1728
  });
@@ -673,31 +1735,36 @@ window.__ModuleLoader__.load({
673
1735
  window.addEventListener("mousemove", onMove);
674
1736
  window.addEventListener("mouseup", onUp);
675
1737
  };
676
- const copyPath = (path) => {
677
- if (copiedPath === path) return;
678
- navigator.clipboard?.writeText(path).catch(() => void 0);
679
- setCopiedPath(path);
680
- window.clearTimeout(copyTimer.current);
681
- copyTimer.current = window.setTimeout(() => setCopiedPath(null), 1200);
682
- };
683
- (0, react.useEffect)(() => () => window.clearTimeout(copyTimer.current), []);
684
1738
  (0, react.useEffect)(() => {
685
1739
  if (!sessionId) return;
686
- const snap = taskStore.getSnapshot();
687
- if (snap.lastUpdate === 0 && !snap.closed) {
688
- const timer = window.setTimeout(() => void refresh(), 300);
689
- return () => window.clearTimeout(timer);
690
- }
1740
+ if (!ui.closed) refresh();
691
1741
  }, [sessionId]);
692
- if (state.closed) return null;
693
- if (state.minimized) {
1742
+ (0, react.useEffect)(() => {
1743
+ const ctxWithEvents = ctx;
1744
+ const handler = () => {
1745
+ uiActions.open();
1746
+ if (Date.now() - Math.max(data.lastUpdate, syncedAt) > 3e4) refresh();
1747
+ };
1748
+ ctxWithEvents?.events?.on?.("dsh:task-panel:show", handler);
1749
+ return () => {
1750
+ ctxWithEvents?.events?.off?.("dsh:task-panel:show", handler);
1751
+ };
1752
+ }, [
1753
+ ctx,
1754
+ uiActions,
1755
+ data.lastUpdate,
1756
+ syncedAt,
1757
+ refresh
1758
+ ]);
1759
+ if (ui.closed) return null;
1760
+ if (ui.minimized) {
694
1761
  const label = boundTask ? `${t("minibar.current")}: ${boundTask.title}` : activeTasks.length > 0 ? `${t("minibar.current")}: ${activeTasks.length} ${t("minibar.tasks")}` : t("minibar.no-task");
695
1762
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniBar, {
696
1763
  label,
697
1764
  hint: t("minibar.click-expand"),
698
- position: state.panelPosition,
1765
+ position: ui.panelPosition,
699
1766
  theme: style,
700
- onMove: (pos) => actions.setPanelPosition(pos),
1767
+ onMove: uiActions.setPanelPosition,
701
1768
  open: expand
702
1769
  });
703
1770
  }
@@ -709,8 +1776,8 @@ window.__ModuleLoader__.load({
709
1776
  className: TaskPanel_module_css_default.panel,
710
1777
  ref: panelRef,
711
1778
  style: {
712
- left: state.panelPosition.x,
713
- top: state.panelPosition.y
1779
+ left: ui.panelPosition.x,
1780
+ top: ui.panelPosition.y
714
1781
  },
715
1782
  "data-theme": style === "native" ? void 0 : style,
716
1783
  children: [
@@ -724,7 +1791,7 @@ window.__ModuleLoader__.load({
724
1791
  className: TaskPanel_module_css_default.header,
725
1792
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
726
1793
  className: TaskPanel_module_css_default.headerLeft,
727
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1794
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
728
1795
  type: "button",
729
1796
  className: TaskPanel_module_css_default.headerIconBtn,
730
1797
  onClick: cycleTheme,
@@ -733,29 +1800,62 @@ window.__ModuleLoader__.load({
733
1800
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { className: TaskPanel_module_css_default.headerIcon })
734
1801
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
735
1802
  className: TaskPanel_module_css_default.headerTitle,
736
- children: t("panel.title")
1803
+ children: viewTitle
737
1804
  })]
738
1805
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
739
1806
  className: TaskPanel_module_css_default.headerRight,
740
1807
  children: [
741
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1808
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1809
+ variant: "ghost",
1810
+ size: "sm",
1811
+ onClick: cycleView,
1812
+ "aria-label": t("view.cycle"),
1813
+ title: `${t("view.cycle")}(${view} → ${VIEW_CYCLE[(VIEW_CYCLE.indexOf(view) + 1) % VIEW_CYCLE.length]})`,
1814
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1815
+ width: "16",
1816
+ height: "16",
1817
+ viewBox: "0 0 24 24",
1818
+ fill: "none",
1819
+ stroke: "currentColor",
1820
+ strokeWidth: "2",
1821
+ strokeLinecap: "round",
1822
+ strokeLinejoin: "round",
1823
+ "aria-hidden": "true",
1824
+ children: [
1825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 6h4" }),
1826
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 10h4" }),
1827
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 14h4" }),
1828
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 18h4" }),
1829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1830
+ width: "16",
1831
+ height: "20",
1832
+ x: "4",
1833
+ y: "2",
1834
+ rx: "2"
1835
+ }),
1836
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M15 2v20" }),
1837
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M15 7h5" }),
1838
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M15 12h5" }),
1839
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M15 17h5" })
1840
+ ]
1841
+ })
1842
+ }),
1843
+ view === "task" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
742
1844
  className: TaskPanel_module_css_default.counts,
743
1845
  children: activeTasks.length > 0 ? `${t("panel.active")} ${activeTasks.length} · ${doneCount}/${activeTasks.length}` : ""
744
1846
  }),
745
1847
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
746
1848
  variant: "ghost",
747
1849
  size: "sm",
748
- className: syncing ? TaskPanel_module_css_default.spinning : void 0,
749
- onClick: () => void refresh(),
750
- disabled: syncing,
751
- "aria-label": t("panel.refresh"),
752
- title: t("panel.refresh"),
753
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {})
1850
+ onClick: () => setShowHints(!showHints),
1851
+ "aria-label": showHints ? t("panel.hints-off") : t("panel.hints-on"),
1852
+ title: showHints ? t("panel.hints-off") : t("panel.hints-on"),
1853
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQuestionOutline14, { size: 16 })
754
1854
  }),
755
1855
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
756
1856
  variant: "ghost",
757
1857
  size: "sm",
758
- onClick: () => actions.minimize(),
1858
+ onClick: () => uiActions.minimize(),
759
1859
  "aria-label": t("panel.minimize"),
760
1860
  title: t("panel.minimize"),
761
1861
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
@@ -763,7 +1863,7 @@ window.__ModuleLoader__.load({
763
1863
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
764
1864
  variant: "ghost",
765
1865
  size: "sm",
766
- onClick: () => actions.close(),
1866
+ onClick: () => uiActions.close(),
767
1867
  "aria-label": t("panel.close"),
768
1868
  title: t("panel.close"),
769
1869
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
@@ -783,7 +1883,13 @@ window.__ModuleLoader__.load({
783
1883
  syncError
784
1884
  ]
785
1885
  }),
786
- activeTasks.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1886
+ view !== "task" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoryView, {
1887
+ ctx,
1888
+ sessionId,
1889
+ scope: view,
1890
+ boundTaskId: data.boundTaskId,
1891
+ t
1892
+ }) : activeTasks.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
787
1893
  className: TaskPanel_module_css_default.emptyState,
788
1894
  children: [
789
1895
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -802,185 +1908,25 @@ window.__ModuleLoader__.load({
802
1908
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
803
1909
  className: TaskPanel_module_css_default.taskList,
804
1910
  children: [activeTasks.map((task) => {
805
- const steps = task.steps || [];
806
- const done = steps.filter((s) => s.status === "completed").length;
807
- const expanded = state.expandedTaskIds.includes(task.id);
808
- const isBound = state.boundTaskId === task.id;
809
- const pct = steps.length > 0 ? Math.round(done / steps.length * 100) : 0;
810
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
811
- className: `${TaskPanel_module_css_default.card}${isBound ? ` ${TaskPanel_module_css_default.cardBound}` : ""}`,
812
- children: [
813
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
814
- className: TaskPanel_module_css_default.cardHead,
815
- onClick: () => {
816
- if (editingTitle?.taskId === task.id) return;
817
- actions.toggleTaskExpanded(task.id);
818
- },
819
- "aria-expanded": expanded,
820
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
821
- className: TaskPanel_module_css_default.cardTitleRow,
822
- children: [isBound && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
823
- className: TaskPanel_module_css_default.boundBadge,
824
- children: t("task.current")
825
- }), isBound && editingTitle?.taskId === task.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoEdit, {
826
- value: editingTitle.value,
827
- singleLine: true,
828
- onCommit: (v) => commitTitle(task.id, v),
829
- onCancel: () => setEditingTitle(null)
830
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
831
- className: `${TaskPanel_module_css_default.cardTitle}${isBound ? ` ${TaskPanel_module_css_default.cardTitleEditable}` : ""}`,
832
- title: isBound ? t("task.title-edit") : void 0,
833
- onDoubleClick: (e) => {
834
- e.preventDefault();
835
- e.stopPropagation();
836
- if (isBound) setEditingTitle({
837
- taskId: task.id,
838
- value: task.title
839
- });
840
- },
841
- children: task.title
842
- })]
843
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
844
- className: TaskPanel_module_css_default.cardMeta,
845
- children: [
846
- steps.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
847
- className: TaskPanel_module_css_default.progressText,
848
- children: [
849
- done,
850
- "/",
851
- steps.length
852
- ]
853
- }),
854
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
855
- className: TaskPanel_module_css_default.updated,
856
- children: timeAgo(task.updatedAt || task.lastActiveAt)
857
- }),
858
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
859
- className: TaskPanel_module_css_default.chevron,
860
- children: expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
861
- })
862
- ]
863
- })]
864
- }),
865
- steps.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
866
- className: TaskPanel_module_css_default.track,
867
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
868
- className: TaskPanel_module_css_default.trackFill,
869
- style: { width: `${pct}%` }
870
- })
871
- }),
872
- expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
873
- className: TaskPanel_module_css_default.cardBody,
874
- children: [
875
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
876
- className: TaskPanel_module_css_default.section,
877
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
878
- className: TaskPanel_module_css_default.sectionLabel,
879
- children: [t("task.steps"), steps.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
880
- className: TaskPanel_module_css_default.sectionCount,
881
- children: steps.length
882
- })]
883
- }), steps.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
884
- className: TaskPanel_module_css_default.stepsList,
885
- children: steps.map((step, i) => {
886
- const isEditing = isBound && editing?.taskId === task.id && editing.index === i;
887
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
888
- className: TaskPanel_module_css_default.stepRow,
889
- children: [isBound ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
890
- type: "button",
891
- className: TaskPanel_module_css_default.stepToggle,
892
- disabled: syncing,
893
- onClick: () => cycleStatus(task.id, i),
894
- title: `${t("step.completed")}/${t("step.in-progress")}/${t("step.pending")}(点击切换)`,
895
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepIcon, { status: step.status })
896
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepIcon, { status: step.status }), isEditing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoEdit, {
897
- value: editing.value,
898
- onCommit: (v) => commitStepText(task.id, i, v),
899
- onCancel: () => setEditing(null)
900
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
901
- className: `${TaskPanel_module_css_default.stepContent}${step.status === "completed" ? ` ${TaskPanel_module_css_default.stepContentDone}` : ""}${step.status === "in_progress" ? ` ${TaskPanel_module_css_default.stepContentRun}` : ""}${isBound ? ` ${TaskPanel_module_css_default.stepEditable}` : ""}`,
902
- title: isBound ? t("step.edit-hint") : void 0,
903
- onDoubleClick: () => {
904
- if (isBound) setEditing({
905
- taskId: task.id,
906
- index: i,
907
- value: String(step.content ?? "")
908
- });
909
- },
910
- children: step.content
911
- })]
912
- }, i);
913
- })
914
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
915
- className: TaskPanel_module_css_default.muted,
916
- children: t("panel.empty-desc")
917
- })]
918
- }),
919
- (task.files?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
920
- className: TaskPanel_module_css_default.section,
921
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
922
- className: TaskPanel_module_css_default.sectionLabel,
923
- children: [t("task.files"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
924
- className: TaskPanel_module_css_default.sectionCount,
925
- children: task.files.length
926
- })]
927
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
928
- className: TaskPanel_module_css_default.filesList,
929
- children: [task.files.slice(0, 12).map((file, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
930
- className: TaskPanel_module_css_default.fileRow,
931
- onClick: () => copyPath(file.path),
932
- title: `${t(copiedPath === file.path ? "file.copied" : "file.copy")}: ${file.path}${file.line ? `:${file.line}` : ""}`,
933
- children: [
934
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: TaskPanel_module_css_default.fileDot }),
935
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
936
- className: TaskPanel_module_css_default.filePath,
937
- children: file.path
938
- }),
939
- file.line && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
940
- className: TaskPanel_module_css_default.fileLine,
941
- children: [":", file.line]
942
- })
943
- ]
944
- }) }, i)), (task.files?.length ?? 0) > 12 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
945
- className: TaskPanel_module_css_default.muted,
946
- children: [
947
- "… 共 ",
948
- task.files.length,
949
- " 个"
950
- ]
951
- })]
952
- })]
953
- }),
954
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
955
- className: TaskPanel_module_css_default.cardFooter,
956
- children: [
957
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
958
- variant: "outline",
959
- size: "sm",
960
- disabled: isBound || syncing,
961
- onClick: () => void handleAction("switch", task.id),
962
- children: t("task.switch")
963
- }),
964
- isBound && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
965
- variant: "outline",
966
- size: "sm",
967
- disabled: syncing,
968
- onClick: () => void runLine("/task unbind"),
969
- title: t("task.unbind"),
970
- children: t("task.unbind")
971
- }),
972
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
973
- variant: "outline",
974
- size: "sm",
975
- disabled: syncing,
976
- onClick: () => void handleAction("archive", task.id),
977
- children: t("task.archive")
978
- })
979
- ]
980
- })
981
- ]
982
- })
983
- ]
1911
+ task.steps;
1912
+ const expanded = ui.expandedTaskIds.includes(task.id);
1913
+ const isBound = data.boundTaskId === task.id;
1914
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskCard, {
1915
+ task,
1916
+ isBound,
1917
+ expanded,
1918
+ onToggleExpand: () => uiActions.toggleTaskExpanded(task.id),
1919
+ onSwitch: () => handleAction("switch", task.id),
1920
+ onUnbind: () => runLine("/task unbind"),
1921
+ onArchive: () => handleAction("archive", task.id),
1922
+ onRename: (value) => commitTitle(task.id, value),
1923
+ onEditStep: (index, value) => commitStepText(task.id, index, value),
1924
+ onCycleStatus: (index) => cycleStatus(task.id, index),
1925
+ onReorderSteps: (from, to) => reorderSteps(task.id, from, to),
1926
+ onInsightAction: (action, id) => void handleInsightTask(action, id),
1927
+ showHints,
1928
+ syncing,
1929
+ t
984
1930
  }, task.id);
985
1931
  }), syncing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
986
1932
  className: TaskPanel_module_css_default.syncHint,
@@ -1008,31 +1954,27 @@ window.__ModuleLoader__.load({
1008
1954
  //#endregion
1009
1955
  //#region src/client/TaskCommandNode.tsx
1010
1956
  /**
1011
- * /tasks、/task 命令在会话中的节点渲染器(conversation.chat.commandview,按命令名 key)。
1012
- *
1013
- * 目标:不再让大段文本+JSON 出现在会话里——
1014
- * - 列表型(/tasks、无动词的 /task):只渲染一行极简“已同步 N 套”状态;
1015
- * 同时把节点文本里的 JSON 载荷解析进浮动面板(侧边卡片由它驱动),
1016
- * 面板若被收起则自动展开(等价“输入 /task 就看到卡片”)。
1017
- * - 动作型(/task switch|archive):渲染一行操作结果(切换/归档反馈)。
1018
- * - 错误/执行中:一行极简状态,不倾倒原文。
1957
+ * /tasks、/task、/insight 命令在会话中的节点渲染器(conversation.chat.commandview,按命令名 key)。
1958
+ * 列表型命令(/tasks、无动词 /task、/insight list)只显示一行摘要;/insight 的动作类只显示一句结果——
1959
+ * 目的:大 JSON 载荷绝不整段渲染进对话(否则切视图会刷屏)。
1019
1960
  */
1020
- /** node = CommandRowOwnerProps.node(CommandNode: name / args / outcome{kind,text}) */
1021
1961
  function TaskCommandNode({ node }) {
1022
1962
  const name = node?.name ?? "task";
1023
1963
  const outcome = node?.outcome ?? null;
1024
1964
  const verb = (typeof node?.args === "string" ? node.args : "").trim().split(/\s+/)[0] ?? "";
1025
1965
  const isList = name === "tasks" || !verb;
1026
1966
  const text = outcome?.text ?? "";
1027
- const parsed = parseTaskPayloadText(text);
1967
+ const parsedTasks = name !== "insight" ? parseTaskPayloadText(text) : null;
1968
+ const parsedInsights = name === "insight" ? parseInsightPayloadText(text) : null;
1969
+ const live = (0, react.useRef)(outcome === null);
1028
1970
  (0, react.useEffect)(() => {
1029
- if (!parsed) return;
1030
- taskStore.actions.setTasks({
1031
- tasks: parsed.tasks,
1032
- boundTaskId: parsed.boundId,
1033
- archivedCount: parsed.archived
1971
+ if (!parsedTasks || !live.current) return;
1972
+ taskDataStore.actions.setTasks({
1973
+ tasks: parsedTasks.tasks,
1974
+ boundTaskId: parsedTasks.boundId,
1975
+ archivedCount: parsedTasks.archived
1034
1976
  });
1035
- if (isList) taskStore.actions.open();
1977
+ if (isList) taskUIStore.actions.open();
1036
1978
  }, [text]);
1037
1979
  if (outcome === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1038
1980
  className: TaskCommandNode_module_css_default.row,
@@ -1056,6 +1998,34 @@ window.__ModuleLoader__.load({
1056
1998
  ]
1057
1999
  });
1058
2000
  }
2001
+ if (name === "insight") {
2002
+ if (verb === "list" && parsedInsights) {
2003
+ const scope = parsedInsights.scope;
2004
+ const count = parsedInsights.count ?? parsedInsights.items.length;
2005
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2006
+ className: TaskCommandNode_module_css_default.row,
2007
+ "data-variant": "ok",
2008
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { className: TaskCommandNode_module_css_default.icon }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2009
+ "记忆 ",
2010
+ scope,
2011
+ " · ",
2012
+ count,
2013
+ " 条 · 已同步到记忆视图"
2014
+ ] })]
2015
+ });
2016
+ }
2017
+ const note = String(text ?? "").split("\n")[0].slice(0, 120);
2018
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2019
+ className: TaskCommandNode_module_css_default.row,
2020
+ "data-variant": "ok",
2021
+ children: [
2022
+ "/insight ",
2023
+ verb,
2024
+ " ",
2025
+ note || "已完成"
2026
+ ]
2027
+ });
2028
+ }
1059
2029
  if (!isList) {
1060
2030
  const note = String(text ?? "").split(/\n{2,}/)[0].replace(/```.*$/s, "").trim().slice(0, 200);
1061
2031
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1069,9 +2039,9 @@ window.__ModuleLoader__.load({
1069
2039
  ]
1070
2040
  });
1071
2041
  }
1072
- const count = parsed ? parsed.tasks.length : 0;
1073
- const archived = parsed ? parsed.archived : 0;
1074
- const label = parsed ? `任务 ${count} 套${archived ? `(归档 ${archived})` : ""} · 已同步到任务面板` : `/${name} 已执行`;
2042
+ const count = parsedTasks ? parsedTasks.tasks.length : 0;
2043
+ const archived = parsedTasks ? parsedTasks.archived : 0;
2044
+ const label = parsedTasks ? `任务 ${count} 套${archived ? `(归档 ${archived})` : ""} · 已同步到任务面板` : `/${name} 已执行`;
1075
2045
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1076
2046
  className: TaskCommandNode_module_css_default.row,
1077
2047
  "data-variant": "ok",
@@ -1100,7 +2070,8 @@ window.__ModuleLoader__.load({
1100
2070
  "slots",
1101
2071
  "sessions",
1102
2072
  "remote",
1103
- "remote.commands"
2073
+ "remote.commands",
2074
+ "locale"
1104
2075
  ];
1105
2076
  function apply(ctx) {
1106
2077
  const slots = ctx?.slots;
@@ -1114,7 +2085,11 @@ window.__ModuleLoader__.load({
1114
2085
  id: "dsh-project-memory-task-panel",
1115
2086
  order: 100
1116
2087
  }, () => (0, react.createElement)(TaskPanelEntry, { ctx })));
1117
- for (const key of ["tasks", "task"]) slots.inject("conversation.chat.commandview", () => slots.register({
2088
+ for (const key of [
2089
+ "tasks",
2090
+ "task",
2091
+ "insight"
2092
+ ]) slots.inject("conversation.chat.commandview", () => slots.register({
1118
2093
  name: "conversation.chat.commandview",
1119
2094
  key
1120
2095
  }, TaskCommandNode));