@yolk_vat-y/dsh-project-memory 0.4.0 → 0.4.2

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.
@@ -0,0 +1,1133 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@yolk_vat-y/dsh-project-memory",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region src/client/locales.ts
11
+ /**
12
+ * Locale dictionaries for Task Panel
13
+ */
14
+ const zh = {
15
+ "panel.title": "工作流任务",
16
+ "panel.active": "进行中",
17
+ "panel.total": "总计",
18
+ "panel.minimize": "收起为顶部迷你条",
19
+ "panel.close": "关闭面板(隐藏,/task 可唤起)",
20
+ "panel.refresh": "同步最新任务",
21
+ "panel.style": "切换面板风格",
22
+ "style.native": "原生",
23
+ "style.glass": "玻璃",
24
+ "style.brutal": "粗野",
25
+ "style.mono": "终端",
26
+ "panel.syncing": "同步中…",
27
+ "panel.sync-failed": "同步失败",
28
+ "panel.empty": "暂无任务",
29
+ "panel.empty-desc": "让模型开始工作并维护 todo 清单后会自动建档",
30
+ "panel.no-session": "还没有会话",
31
+ "task.progress": "进度",
32
+ "task.steps": "步骤",
33
+ "task.files": "文件",
34
+ "task.current": "当前",
35
+ "task.title-edit": "双击修改标题",
36
+ "task.switch": "切换到此任务",
37
+ "task.unbind": "取消当前任务",
38
+ "task.archive": "归档",
39
+ "task.drag": "拖拽移动",
40
+ "step.completed": "已完成",
41
+ "step.in-progress": "进行中",
42
+ "step.pending": "待办",
43
+ "step.edit-hint": "双击编辑步骤文案;点击图标循环 待办/进行中/已完成",
44
+ "file.copy": "复制路径",
45
+ "file.copied": "已复制路径",
46
+ "minibar.current": "当前任务",
47
+ "minibar.click-expand": "点击展开并同步",
48
+ "minibar.tasks": "个任务",
49
+ "minibar.no-task": "暂无任务 · 点击查看"
50
+ };
51
+ const en = {
52
+ "panel.title": "Task Flow",
53
+ "panel.active": "Active",
54
+ "panel.total": "Total",
55
+ "panel.minimize": "Collapse to mini bar",
56
+ "panel.close": "Close panel (hidden; /task reopens)",
57
+ "panel.refresh": "Sync latest tasks",
58
+ "panel.style": "Panel style",
59
+ "style.native": "Native",
60
+ "style.glass": "Glass",
61
+ "style.brutal": "Brutal",
62
+ "style.mono": "Mono",
63
+ "panel.syncing": "Syncing…",
64
+ "panel.sync-failed": "Sync failed",
65
+ "panel.empty": "No Tasks",
66
+ "panel.empty-desc": "Tasks are created automatically when the model maintains a todo list",
67
+ "panel.no-session": "No session yet",
68
+ "task.progress": "Progress",
69
+ "task.steps": "Steps",
70
+ "task.files": "Files",
71
+ "task.current": "Current",
72
+ "task.title-edit": "Double-click to rename",
73
+ "task.switch": "Switch to this task",
74
+ "task.unbind": "Unbind current task",
75
+ "task.archive": "Archive",
76
+ "task.drag": "Drag to move",
77
+ "step.completed": "Completed",
78
+ "step.in-progress": "In Progress",
79
+ "step.pending": "Pending",
80
+ "step.edit-hint": "Double-click to edit; click the icon to cycle status",
81
+ "file.copy": "Copy path",
82
+ "file.copied": "Path copied",
83
+ "minibar.current": "Current task",
84
+ "minibar.click-expand": "Click to expand & sync",
85
+ "minibar.tasks": "tasks",
86
+ "minibar.no-task": "No tasks · click to view"
87
+ };
88
+ function createTranslate(dict) {
89
+ return (key, params) => {
90
+ let text = dict[key] ?? key;
91
+ if (params) for (const [k, v] of Object.entries(params)) text = text.replace(new RegExp(`\\{${k}\\}`, "g"), String(v));
92
+ return text;
93
+ };
94
+ }
95
+ //#endregion
96
+ //#region src/client/task-store.ts
97
+ /**
98
+ * Task Panel Client Store(自包含:不依赖宿主 store 包)
99
+ * Reactive store for task list, bound task, UI state.
100
+ * 数据由 /tasks、/task 命令执行结果(JSON 载荷)写入,localStorage 持久化 UI 状态。
101
+ */
102
+ /** 从命令输出文本中解析任务快照 JSON(支持 fenced ```json 块或尾部对象)。 */
103
+ function parseTaskPayloadText(text) {
104
+ if (!text) return null;
105
+ let jsonBlock = null;
106
+ const fenced = text.match(/```(?:json)?\s*\n([\s\S]*?)\n```/);
107
+ if (fenced) jsonBlock = fenced[1];
108
+ else {
109
+ const lastBrace = text.lastIndexOf("{");
110
+ if (lastBrace !== -1) jsonBlock = text.slice(lastBrace);
111
+ }
112
+ if (!jsonBlock) return null;
113
+ try {
114
+ const data = JSON.parse(jsonBlock);
115
+ if (!Array.isArray(data.tasks)) return null;
116
+ return {
117
+ tasks: data.tasks,
118
+ boundId: data.boundId ?? null,
119
+ archived: data.archived ?? 0
120
+ };
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+ const STORAGE_KEY = "dsh-pm-task-panel-state";
126
+ function defaultPosition() {
127
+ if (typeof window !== "undefined") return {
128
+ x: Math.max(16, window.innerWidth - 408),
129
+ y: 72
130
+ };
131
+ return {
132
+ x: 0,
133
+ y: 0
134
+ };
135
+ }
136
+ function getDefaultState() {
137
+ if (typeof window !== "undefined") try {
138
+ const saved = localStorage.getItem(STORAGE_KEY);
139
+ if (saved) {
140
+ const parsed = JSON.parse(saved);
141
+ return {
142
+ tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
143
+ boundTaskId: parsed.boundTaskId ?? null,
144
+ archivedCount: parsed.archivedCount ?? 0,
145
+ expandedTaskIds: Array.isArray(parsed.expandedTaskIds) ? parsed.expandedTaskIds : [],
146
+ panelPosition: parsed.panelPosition ?? defaultPosition(),
147
+ minimized: parsed.minimized !== false,
148
+ theme: typeof parsed.theme === "string" ? parsed.theme : "native",
149
+ closed: !!parsed.closed,
150
+ lastUpdate: parsed.lastUpdate ?? 0
151
+ };
152
+ }
153
+ } catch {}
154
+ return {
155
+ tasks: [],
156
+ boundTaskId: null,
157
+ archivedCount: 0,
158
+ expandedTaskIds: [],
159
+ panelPosition: defaultPosition(),
160
+ minimized: true,
161
+ closed: false,
162
+ theme: "native",
163
+ lastUpdate: 0
164
+ };
165
+ }
166
+ function persist(state) {
167
+ if (typeof window === "undefined") return;
168
+ try {
169
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
170
+ } catch {}
171
+ }
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();
178
+ }
179
+ const taskStore = {
180
+ subscribe(listener) {
181
+ listeners.add(listener);
182
+ return () => {
183
+ listeners.delete(listener);
184
+ };
185
+ },
186
+ getSnapshot() {
187
+ return state;
188
+ },
189
+ 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
+ toggleTaskExpanded(taskId) {
200
+ const prev = state;
201
+ const ids = prev.expandedTaskIds.includes(taskId) ? prev.expandedTaskIds.filter((id) => id !== taskId) : [...prev.expandedTaskIds, taskId];
202
+ setState({
203
+ ...prev,
204
+ expandedTaskIds: ids
205
+ });
206
+ },
207
+ expandAll() {
208
+ const prev = state;
209
+ setState({
210
+ ...prev,
211
+ expandedTaskIds: prev.tasks.map((t) => t.id)
212
+ });
213
+ },
214
+ setPanelPosition(pos) {
215
+ setState({
216
+ ...state,
217
+ panelPosition: pos
218
+ });
219
+ },
220
+ /** 展开面板(同时解除隐藏/折叠)。 */
221
+ open() {
222
+ setState({
223
+ ...state,
224
+ minimized: false,
225
+ closed: false
226
+ });
227
+ },
228
+ /** 折叠成顶部迷你条(仍可见)。 */
229
+ minimize() {
230
+ setState({
231
+ ...state,
232
+ minimized: true,
233
+ closed: false
234
+ });
235
+ },
236
+ /** 彻底隐藏(无迷你条);仅能通过 /task、/tasks 或重新加载唤起。 */
237
+ close() {
238
+ setState({
239
+ ...state,
240
+ minimized: true,
241
+ closed: true
242
+ });
243
+ },
244
+ setTheme(theme) {
245
+ setState({
246
+ ...state,
247
+ theme
248
+ });
249
+ },
250
+ setMinimized(minimized) {
251
+ setState({
252
+ ...state,
253
+ minimized,
254
+ closed: minimized ? state.closed : false
255
+ });
256
+ },
257
+ reset() {
258
+ setState(getDefaultState());
259
+ }
260
+ }
261
+ };
262
+ function useTaskStore() {
263
+ return (0, react.useSyncExternalStore)(taskStore.subscribe, taskStore.getSnapshot, taskStore.getSnapshot);
264
+ }
265
+ //#endregion
266
+ //#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}";
268
+ const tagId$1 = "@yolk_vat-y/dsh-project-memory/TaskPanel.module.css";
269
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
270
+ const tag = document.createElement("style");
271
+ tag.dataset.plugin = "@yolk_vat-y/dsh-project-memory";
272
+ tag.dataset.pluginCss = tagId$1;
273
+ tag.textContent = css$1;
274
+ document.head.appendChild(tag);
275
+ }
276
+ 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",
290
+ "syncHint": "vZSZnG_syncHint",
291
+ "emptyDesc": "vZSZnG_emptyDesc",
292
+ "stepsList": "vZSZnG_stepsList",
293
+ "stepRow": "vZSZnG_stepRow",
294
+ "trackFill": "vZSZnG_trackFill",
295
+ "stepContentDone": "vZSZnG_stepContentDone",
296
+ "filesList": "vZSZnG_filesList",
297
+ "miniBar": "vZSZnG_miniBar",
298
+ "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",
308
+ "cardHead": "vZSZnG_cardHead",
309
+ "pill": "vZSZnG_pill",
310
+ "chevron": "vZSZnG_chevron",
311
+ "header": "vZSZnG_header",
312
+ "fileRow": "vZSZnG_fileRow",
313
+ "emptyState": "vZSZnG_emptyState",
314
+ "headerIconBtn": "vZSZnG_headerIconBtn",
315
+ "headerIcon": "vZSZnG_headerIcon",
316
+ "stepToggle": "vZSZnG_stepToggle",
317
+ "taskList": "vZSZnG_taskList",
318
+ "cardTitleEditable": "vZSZnG_cardTitleEditable",
319
+ "notice": "vZSZnG_notice",
320
+ "softBadge": "vZSZnG_softBadge",
321
+ "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
+ "stepContentRun": "vZSZnG_stepContentRun",
329
+ "emptyTitle": "vZSZnG_emptyTitle",
330
+ "progressText": "vZSZnG_progressText",
331
+ "cardBody": "vZSZnG_cardBody",
332
+ "stepRun": "vZSZnG_stepRun",
333
+ "cardFooter": "vZSZnG_cardFooter",
334
+ "sectionLabel": "vZSZnG_sectionLabel",
335
+ "miniChevron": "vZSZnG_miniChevron",
336
+ "headerLeft": "vZSZnG_headerLeft",
337
+ "section": "vZSZnG_section"
338
+ };
339
+ //#endregion
340
+ //#region src/client/TaskPanel.tsx
341
+ /**
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 模块)。
353
+ */
354
+ function getT() {
355
+ return createTranslate(typeof navigator !== "undefined" && navigator.language.startsWith("zh") ? zh : en);
356
+ }
357
+ function timeAgo(iso) {
358
+ if (!iso) return "";
359
+ const diff = Date.now() - new Date(iso).getTime();
360
+ const m = Math.floor(diff / 6e4);
361
+ if (m < 1) return "刚刚";
362
+ if (m < 60) return `${m}分钟前`;
363
+ const h = Math.floor(m / 60);
364
+ if (h < 24) return `${h}小时前`;
365
+ return `${Math.floor(h / 24)}天前`;
366
+ }
367
+ function StepIcon({ status }) {
368
+ if (status === "completed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { className: TaskPanel_module_css_default.stepDone });
369
+ 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
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: TaskPanel_module_css_default.stepPending });
371
+ }
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
+ function MiniBar({ label, hint, position, theme, onMove, open }) {
429
+ const barRef = (0, react.useRef)(null);
430
+ const down = (0, react.useRef)(null);
431
+ const onMouseDown = (e) => {
432
+ if (e.button !== 0) return;
433
+ const rect = barRef.current?.getBoundingClientRect();
434
+ if (!rect) return;
435
+ down.current = {
436
+ dx: e.clientX - rect.left,
437
+ dy: e.clientY - rect.top,
438
+ sx: e.clientX,
439
+ sy: e.clientY,
440
+ moved: false
441
+ };
442
+ const onMoveEv = (ev) => {
443
+ const d = down.current;
444
+ if (!d) return;
445
+ if (!d.moved && Math.abs(ev.clientX - d.sx) + Math.abs(ev.clientY - d.sy) > 4) d.moved = true;
446
+ if (d.moved) {
447
+ const w = barRef.current?.offsetWidth ?? 260;
448
+ const h = barRef.current?.offsetHeight ?? 34;
449
+ onMove({
450
+ x: Math.max(8, Math.min(window.innerWidth - w - 8, ev.clientX - d.dx)),
451
+ y: Math.max(8, Math.min(window.innerHeight - h - 8, ev.clientY - d.dy))
452
+ });
453
+ }
454
+ };
455
+ const onUpEv = () => {
456
+ const d = down.current;
457
+ down.current = null;
458
+ window.removeEventListener("mousemove", onMoveEv);
459
+ window.removeEventListener("mouseup", onUpEv);
460
+ if (d && !d.moved) open();
461
+ };
462
+ window.addEventListener("mousemove", onMoveEv);
463
+ window.addEventListener("mouseup", onUpEv);
464
+ };
465
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
466
+ ref: barRef,
467
+ className: TaskPanel_module_css_default.miniBar,
468
+ style: {
469
+ left: position.x,
470
+ top: position.y
471
+ },
472
+ "data-theme": theme === "native" ? void 0 : theme,
473
+ onMouseDown,
474
+ role: "button",
475
+ title: hint,
476
+ "aria-label": hint,
477
+ children: [
478
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { className: TaskPanel_module_css_default.miniIcon }),
479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
480
+ className: TaskPanel_module_css_default.miniText,
481
+ children: label
482
+ }),
483
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { className: TaskPanel_module_css_default.miniChevron })
484
+ ]
485
+ });
486
+ }
487
+ /**
488
+ * 错误边界:任务面板渲染一旦抛错,不再让 shell.overlay 把整条条目退休
489
+ * (宿主对崩溃条目会永久移除直到重载页面)。捕获后显示一个纯文本兜底,
490
+ * 点击重新渲染。
491
+ */
492
+ var PanelErrorBoundary = class extends react.Component {
493
+ state = { failed: false };
494
+ static getDerivedStateFromError() {
495
+ return { failed: true };
496
+ }
497
+ componentDidCatch(error) {
498
+ console.warn("[dsh-project-memory] task panel render crashed (contained by boundary):", error);
499
+ }
500
+ render() {
501
+ if (!this.state.failed) return this.props.children;
502
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
503
+ className: TaskPanel_module_css_default.boundaryFallback,
504
+ onClick: () => this.setState({ failed: false }),
505
+ title: "重新渲染任务面板",
506
+ children: "任务面板(点击重试)"
507
+ });
508
+ }
509
+ };
510
+ function TaskPanelEntry({ ctx }) {
511
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelErrorBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskPanelView, { ctx }) });
512
+ }
513
+ function TaskPanelView({ ctx }) {
514
+ const t = getT();
515
+ const state = useTaskStore();
516
+ const sessionId = useSessionId(ctx);
517
+ const [syncing, setSyncing] = (0, react.useState)(false);
518
+ const [syncedAt, setSyncedAt] = (0, react.useState)(0);
519
+ const [copiedPath, setCopiedPath] = (0, react.useState)(null);
520
+ 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;
527
+ const applyPayload = (text) => {
528
+ const parsed = parseTaskPayloadText(text);
529
+ if (!parsed) return false;
530
+ actions.setTasks({
531
+ tasks: parsed.tasks,
532
+ boundTaskId: parsed.boundId,
533
+ archivedCount: parsed.archived
534
+ });
535
+ setSyncedAt(Date.now());
536
+ setSyncError(null);
537
+ return true;
538
+ };
539
+ const runLine = async (line) => {
540
+ const commands = ctx?.remote?.commands;
541
+ if (!sessionId || !commands || typeof commands.execute !== "function") {
542
+ setSyncError("no session / commands service");
543
+ return false;
544
+ }
545
+ let response;
546
+ try {
547
+ response = await commands.execute(sessionId, line, []);
548
+ } catch (err) {
549
+ setSyncError(String(err?.message ?? err));
550
+ return false;
551
+ }
552
+ const envelope = response;
553
+ const execution = envelope && typeof envelope === "object" && "value" in envelope ? envelope.value : envelope;
554
+ const result = execution?.result ?? execution;
555
+ if (result?.kind === "error") {
556
+ setSyncError(result.text ?? "command error");
557
+ return false;
558
+ }
559
+ return applyPayload(result?.text);
560
+ };
561
+ const refresh = async () => {
562
+ if (syncing) return;
563
+ setSyncing(true);
564
+ try {
565
+ await runLine("/tasks");
566
+ } finally {
567
+ setSyncing(false);
568
+ }
569
+ };
570
+ const handleAction = async (verb, taskId) => {
571
+ if (syncing) return;
572
+ setSyncing(true);
573
+ try {
574
+ await runLine(`/task ${verb} ${taskId}`);
575
+ } finally {
576
+ setSyncing(false);
577
+ }
578
+ };
579
+ const [editing, setEditing] = (0, react.useState)(null);
580
+ const STATUS_CYCLE = [
581
+ "pending",
582
+ "in_progress",
583
+ "completed"
584
+ ];
585
+ const pushSteps = (taskId, steps) => {
586
+ runLine(`/task todos ${JSON.stringify(steps.map((s) => ({
587
+ content: s.content,
588
+ status: s.status
589
+ })))}`);
590
+ };
591
+ const cycleStatus = (taskId, index) => {
592
+ const task = state.tasks.find((tt) => tt.id === taskId);
593
+ if (!task) return;
594
+ const next = (task.steps || []).map((s, i) => {
595
+ if (i !== index) return s;
596
+ const cur = STATUS_CYCLE.indexOf(s.status);
597
+ return {
598
+ ...s,
599
+ status: STATUS_CYCLE[(cur + 1) % STATUS_CYCLE.length]
600
+ };
601
+ });
602
+ pushSteps(taskId, next);
603
+ };
604
+ const commitStepText = (taskId, index, value) => {
605
+ const task = state.tasks.find((tt) => tt.id === taskId);
606
+ 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;
614
+ const next = (task.steps || []).map((s, i) => i === index ? {
615
+ ...s,
616
+ content: trimmed
617
+ } : s);
618
+ pushSteps(taskId, next);
619
+ };
620
+ const [editingTitle, setEditingTitle] = (0, react.useState)(null);
621
+ const commitTitle = (taskId, value) => {
622
+ const task = state.tasks.find((tt) => tt.id === taskId);
623
+ 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;
631
+ runLine(`/task rename ${taskId} ${JSON.stringify(trimmed)}`);
632
+ };
633
+ const expand = () => {
634
+ actions.open();
635
+ if (Date.now() - Math.max(state.lastUpdate, syncedAt) > 3e4) refresh();
636
+ };
637
+ const THEMES = [
638
+ "native",
639
+ "glass",
640
+ "brutal",
641
+ "mono"
642
+ ];
643
+ const style = THEMES.includes(state.theme) ? state.theme : "native";
644
+ const styleLabel = t(`style.${style}`);
645
+ const cycleTheme = () => {
646
+ const i = THEMES.indexOf(style);
647
+ actions.setTheme(THEMES[(i + 1) % THEMES.length]);
648
+ };
649
+ const handleDragStart = (e) => {
650
+ if (e.target !== e.currentTarget) return;
651
+ const rect = panelRef.current?.getBoundingClientRect();
652
+ if (!rect) return;
653
+ drag.current = {
654
+ dx: e.clientX - rect.left,
655
+ dy: e.clientY - rect.top
656
+ };
657
+ const onMove = (ev) => {
658
+ if (!drag.current) return;
659
+ const width = panelRef.current?.offsetWidth ?? 380;
660
+ panelRef.current?.offsetHeight;
661
+ const x = Math.max(8, Math.min(window.innerWidth - width - 8, ev.clientX - drag.current.dx));
662
+ const y = Math.max(8, Math.min(window.innerHeight - 64, ev.clientY - drag.current.dy));
663
+ actions.setPanelPosition({
664
+ x,
665
+ y
666
+ });
667
+ };
668
+ const onUp = () => {
669
+ drag.current = null;
670
+ window.removeEventListener("mousemove", onMove);
671
+ window.removeEventListener("mouseup", onUp);
672
+ };
673
+ window.addEventListener("mousemove", onMove);
674
+ window.addEventListener("mouseup", onUp);
675
+ };
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
+ (0, react.useEffect)(() => {
685
+ 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
+ }
691
+ }, [sessionId]);
692
+ if (state.closed) return null;
693
+ if (state.minimized) {
694
+ const label = boundTask ? `${t("minibar.current")}: ${boundTask.title}` : activeTasks.length > 0 ? `${t("minibar.current")}: ${activeTasks.length} ${t("minibar.tasks")}` : t("minibar.no-task");
695
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniBar, {
696
+ label,
697
+ hint: t("minibar.click-expand"),
698
+ position: state.panelPosition,
699
+ theme: style,
700
+ onMove: (pos) => actions.setPanelPosition(pos),
701
+ open: expand
702
+ });
703
+ }
704
+ const doneCount = activeTasks.filter((task) => {
705
+ const steps = task.steps || [];
706
+ return steps.length > 0 && steps.every((s) => s.status === "completed");
707
+ }).length;
708
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
709
+ className: TaskPanel_module_css_default.panel,
710
+ ref: panelRef,
711
+ style: {
712
+ left: state.panelPosition.x,
713
+ top: state.panelPosition.y
714
+ },
715
+ "data-theme": style === "native" ? void 0 : style,
716
+ children: [
717
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
718
+ className: TaskPanel_module_css_default.dragHandle,
719
+ onMouseDown: handleDragStart,
720
+ title: t("task.drag"),
721
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: TaskPanel_module_css_default.handleGrip })
722
+ }),
723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
724
+ className: TaskPanel_module_css_default.header,
725
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
726
+ className: TaskPanel_module_css_default.headerLeft,
727
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
728
+ type: "button",
729
+ className: TaskPanel_module_css_default.headerIconBtn,
730
+ onClick: cycleTheme,
731
+ title: styleLabel,
732
+ "aria-label": styleLabel,
733
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { className: TaskPanel_module_css_default.headerIcon })
734
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
735
+ className: TaskPanel_module_css_default.headerTitle,
736
+ children: t("panel.title")
737
+ })]
738
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
739
+ className: TaskPanel_module_css_default.headerRight,
740
+ children: [
741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
742
+ className: TaskPanel_module_css_default.counts,
743
+ children: activeTasks.length > 0 ? `${t("panel.active")} ${activeTasks.length} · ${doneCount}/${activeTasks.length}` : ""
744
+ }),
745
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
746
+ variant: "ghost",
747
+ 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, {})
754
+ }),
755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
756
+ variant: "ghost",
757
+ size: "sm",
758
+ onClick: () => actions.minimize(),
759
+ "aria-label": t("panel.minimize"),
760
+ title: t("panel.minimize"),
761
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
762
+ }),
763
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
764
+ variant: "ghost",
765
+ size: "sm",
766
+ onClick: () => actions.close(),
767
+ "aria-label": t("panel.close"),
768
+ title: t("panel.close"),
769
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
770
+ })
771
+ ]
772
+ })]
773
+ }),
774
+ !sessionId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
775
+ className: TaskPanel_module_css_default.notice,
776
+ children: t("panel.no-session")
777
+ }),
778
+ syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
779
+ className: TaskPanel_module_css_default.notice,
780
+ children: [
781
+ t("panel.sync-failed"),
782
+ ": ",
783
+ syncError
784
+ ]
785
+ }),
786
+ activeTasks.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
787
+ className: TaskPanel_module_css_default.emptyState,
788
+ children: [
789
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
790
+ className: TaskPanel_module_css_default.emptyTitle,
791
+ children: t("panel.empty")
792
+ }),
793
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
794
+ className: TaskPanel_module_css_default.emptyDesc,
795
+ children: t("panel.empty-desc")
796
+ }),
797
+ syncing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
798
+ className: TaskPanel_module_css_default.syncHint,
799
+ children: t("panel.syncing")
800
+ })
801
+ ]
802
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
803
+ className: TaskPanel_module_css_default.taskList,
804
+ 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
+ ]
984
+ }, task.id);
985
+ }), syncing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
986
+ className: TaskPanel_module_css_default.syncHint,
987
+ children: t("panel.syncing")
988
+ })]
989
+ })
990
+ ]
991
+ });
992
+ }
993
+ //#endregion
994
+ //#region \0dsh-css:/home/sxt/project/dsh-project-memory/src/client/TaskCommandNode.module.css.mjs
995
+ const css = ".TPe-xa_row{max-width:100%;color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;align-items:center;gap:6px;padding:2px 0;font-size:12px;line-height:20px;display:inline-flex;overflow:hidden}.TPe-xa_row[data-variant=ok]{color:var(--dsw-alias-label-secondary)}.TPe-xa_row[data-variant=error]{color:var(--dsw-alias-state-warn-label,#b7791f)}.TPe-xa_row[data-variant=running]{color:var(--dsw-alias-label-dimmed)}.TPe-xa_icon{color:var(--dsw-alias-label-tertiary);flex:none}";
996
+ const tagId = "@yolk_vat-y/dsh-project-memory/TaskCommandNode.module.css";
997
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
998
+ const tag = document.createElement("style");
999
+ tag.dataset.plugin = "@yolk_vat-y/dsh-project-memory";
1000
+ tag.dataset.pluginCss = tagId;
1001
+ tag.textContent = css;
1002
+ document.head.appendChild(tag);
1003
+ }
1004
+ var TaskCommandNode_module_css_default = {
1005
+ "row": "TPe-xa_row",
1006
+ "icon": "TPe-xa_icon"
1007
+ };
1008
+ //#endregion
1009
+ //#region src/client/TaskCommandNode.tsx
1010
+ /**
1011
+ * /tasks、/task 命令在会话中的节点渲染器(conversation.chat.commandview,按命令名 key)。
1012
+ *
1013
+ * 目标:不再让大段文本+JSON 出现在会话里——
1014
+ * - 列表型(/tasks、无动词的 /task):只渲染一行极简“已同步 N 套”状态;
1015
+ * 同时把节点文本里的 JSON 载荷解析进浮动面板(侧边卡片由它驱动),
1016
+ * 面板若被收起则自动展开(等价“输入 /task 就看到卡片”)。
1017
+ * - 动作型(/task switch|archive):渲染一行操作结果(切换/归档反馈)。
1018
+ * - 错误/执行中:一行极简状态,不倾倒原文。
1019
+ */
1020
+ /** node = CommandRowOwnerProps.node(CommandNode: name / args / outcome{kind,text}) */
1021
+ function TaskCommandNode({ node }) {
1022
+ const name = node?.name ?? "task";
1023
+ const outcome = node?.outcome ?? null;
1024
+ const verb = (typeof node?.args === "string" ? node.args : "").trim().split(/\s+/)[0] ?? "";
1025
+ const isList = name === "tasks" || !verb;
1026
+ const text = outcome?.text ?? "";
1027
+ const parsed = parseTaskPayloadText(text);
1028
+ (0, react.useEffect)(() => {
1029
+ if (!parsed) return;
1030
+ taskStore.actions.setTasks({
1031
+ tasks: parsed.tasks,
1032
+ boundTaskId: parsed.boundId,
1033
+ archivedCount: parsed.archived
1034
+ });
1035
+ if (isList) taskStore.actions.open();
1036
+ }, [text]);
1037
+ if (outcome === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1038
+ className: TaskCommandNode_module_css_default.row,
1039
+ "data-variant": "running",
1040
+ children: [
1041
+ "/",
1042
+ name,
1043
+ " 执行中…"
1044
+ ]
1045
+ });
1046
+ if (outcome.kind === "error") {
1047
+ const brief = String(text ?? "").split("\n")[0].slice(0, 120);
1048
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1049
+ className: TaskCommandNode_module_css_default.row,
1050
+ "data-variant": "error",
1051
+ children: [
1052
+ "/",
1053
+ name,
1054
+ " 失败",
1055
+ brief ? `:${brief}` : ""
1056
+ ]
1057
+ });
1058
+ }
1059
+ if (!isList) {
1060
+ const note = String(text ?? "").split(/\n{2,}/)[0].replace(/```.*$/s, "").trim().slice(0, 200);
1061
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1062
+ className: TaskCommandNode_module_css_default.row,
1063
+ "data-variant": "ok",
1064
+ children: [
1065
+ "/",
1066
+ name,
1067
+ " ",
1068
+ note || "已完成"
1069
+ ]
1070
+ });
1071
+ }
1072
+ const count = parsed ? parsed.tasks.length : 0;
1073
+ const archived = parsed ? parsed.archived : 0;
1074
+ const label = parsed ? `任务 ${count} 套${archived ? `(归档 ${archived})` : ""} · 已同步到任务面板` : `/${name} 已执行`;
1075
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1076
+ className: TaskCommandNode_module_css_default.row,
1077
+ "data-variant": "ok",
1078
+ 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.jsx)("span", { children: label })]
1079
+ });
1080
+ }
1081
+ //#endregion
1082
+ //#region src/client/client.ts
1083
+ /**
1084
+ * dsh-project-memory Client Entry
1085
+ *
1086
+ * dsh web (rc.1) 的 client 插件契约为 cordis client plugin:
1087
+ * export const inject = [<client 服务名>...]
1088
+ * export function apply(ctx) { ... }
1089
+ * 面板注册进 `shell.overlay`(Frame 级浮动层,additive 列表槽):
1090
+ * 该槽由 ui-layout 的 AppFrame 声明渲染(scope root,独立于滚动容器),
1091
+ * 默认 click-through,条目自身需开启 pointer-events。
1092
+ *
1093
+ * 只依赖宿主 seed 提供的模块(react / dsh-client-ui-primitives),
1094
+ * 数据经 remote.commands.execute 执行 /tasks 命令获取 JSON 快照。
1095
+ */
1096
+ const NS = "dsh-project-memory";
1097
+ const name = NS;
1098
+ /** Required client services: slots registry, session scopes, commands remote (data 通道). */
1099
+ const inject = [
1100
+ "slots",
1101
+ "sessions",
1102
+ "remote",
1103
+ "remote.commands"
1104
+ ];
1105
+ function apply(ctx) {
1106
+ const slots = ctx?.slots;
1107
+ if (!slots || typeof slots.inject !== "function") {
1108
+ console.warn(`[${NS}] host has no slots service — task panel disabled`);
1109
+ return;
1110
+ }
1111
+ try {
1112
+ slots.inject("shell.overlay", () => slots.register({
1113
+ name: "shell.overlay",
1114
+ id: "dsh-project-memory-task-panel",
1115
+ order: 100
1116
+ }, () => (0, react.createElement)(TaskPanelEntry, { ctx })));
1117
+ for (const key of ["tasks", "task"]) slots.inject("conversation.chat.commandview", () => slots.register({
1118
+ name: "conversation.chat.commandview",
1119
+ key
1120
+ }, TaskCommandNode));
1121
+ } catch (err) {
1122
+ console.warn(`[${NS}] task panel registration failed:`, err);
1123
+ }
1124
+ }
1125
+ //#endregion
1126
+ exports.apply = apply;
1127
+ exports.inject = inject;
1128
+ exports.name = name;
1129
+ return module.exports;
1130
+ }
1131
+ });
1132
+
1133
+ //# sourceMappingURL=client.js.map