@joekytc/dsh-swarm 0.1.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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +504 -0
  3. package/README.zh-CN.md +452 -0
  4. package/client/BoardCard.tsx +41 -0
  5. package/client/ConnectionBanner.tsx +16 -0
  6. package/client/KanbanBoard.tsx +168 -0
  7. package/client/KanbanTab.tsx +26 -0
  8. package/client/RenameModal.tsx +38 -0
  9. package/client/TaskDrawer.tsx +212 -0
  10. package/client/WorkflowRail.tsx +175 -0
  11. package/client/board-store.ts +196 -0
  12. package/client/css.d.ts +4 -0
  13. package/client/index.ts +31 -0
  14. package/client/kanban.css +653 -0
  15. package/client/useKanbanBoard.ts +7 -0
  16. package/client/workflow-model.ts +229 -0
  17. package/cordis.patch.yml +88 -0
  18. package/lib/client.js +1205 -0
  19. package/lib/config.d.ts +46 -0
  20. package/lib/config.js +43 -0
  21. package/lib/dispatcher/agent-runner.d.ts +23 -0
  22. package/lib/dispatcher/agent-runner.js +429 -0
  23. package/lib/dispatcher/chain-auditor.d.ts +47 -0
  24. package/lib/dispatcher/chain-auditor.js +194 -0
  25. package/lib/dispatcher/dispatcher.d.ts +50 -0
  26. package/lib/dispatcher/dispatcher.js +280 -0
  27. package/lib/dispatcher/event-waker.d.ts +13 -0
  28. package/lib/dispatcher/event-waker.js +24 -0
  29. package/lib/dispatcher/git-credentials.d.ts +33 -0
  30. package/lib/dispatcher/git-credentials.js +78 -0
  31. package/lib/dispatcher/merge-gate.d.ts +28 -0
  32. package/lib/dispatcher/merge-gate.js +74 -0
  33. package/lib/dispatcher/model-candidates.d.ts +13 -0
  34. package/lib/dispatcher/model-candidates.js +31 -0
  35. package/lib/dispatcher/session-events.d.ts +28 -0
  36. package/lib/dispatcher/session-events.js +33 -0
  37. package/lib/dispatcher/target-repo.d.ts +15 -0
  38. package/lib/dispatcher/target-repo.js +42 -0
  39. package/lib/dispatcher/v-orchestrator.d.ts +74 -0
  40. package/lib/dispatcher/v-orchestrator.js +452 -0
  41. package/lib/dispatcher/watchdog.d.ts +15 -0
  42. package/lib/dispatcher/watchdog.js +30 -0
  43. package/lib/dispatcher/workspace-attach.d.ts +40 -0
  44. package/lib/dispatcher/workspace-attach.js +112 -0
  45. package/lib/domain/delivery-contract.d.ts +18 -0
  46. package/lib/domain/delivery-contract.js +80 -0
  47. package/lib/domain/delivery-evidence.d.ts +12 -0
  48. package/lib/domain/delivery-evidence.js +41 -0
  49. package/lib/domain/event-store.d.ts +20 -0
  50. package/lib/domain/event-store.js +53 -0
  51. package/lib/domain/kanban-service.d.ts +94 -0
  52. package/lib/domain/kanban-service.js +430 -0
  53. package/lib/domain/permissions.d.ts +7 -0
  54. package/lib/domain/permissions.js +54 -0
  55. package/lib/domain/planning-checklist.d.ts +22 -0
  56. package/lib/domain/planning-checklist.js +81 -0
  57. package/lib/domain/prefetch-manifest.d.ts +21 -0
  58. package/lib/domain/prefetch-manifest.js +73 -0
  59. package/lib/domain/projection.d.ts +3 -0
  60. package/lib/domain/projection.js +165 -0
  61. package/lib/domain/review-evidence.d.ts +13 -0
  62. package/lib/domain/review-evidence.js +76 -0
  63. package/lib/domain/state-machine.d.ts +4 -0
  64. package/lib/domain/state-machine.js +32 -0
  65. package/lib/domain/task-parents.d.ts +17 -0
  66. package/lib/domain/task-parents.js +39 -0
  67. package/lib/domain/tdd-classify.d.ts +5 -0
  68. package/lib/domain/tdd-classify.js +20 -0
  69. package/lib/domain/types.d.ts +142 -0
  70. package/lib/domain/types.js +2 -0
  71. package/lib/index.d.ts +5 -0
  72. package/lib/index.js +56 -0
  73. package/lib/roles/preset-installer.d.ts +8 -0
  74. package/lib/roles/preset-installer.js +53 -0
  75. package/lib/roles/toolsets.d.ts +62 -0
  76. package/lib/roles/toolsets.js +332 -0
  77. package/lib/roles/wiki-worker.d.ts +20 -0
  78. package/lib/roles/wiki-worker.js +44 -0
  79. package/lib/routes/kanban-http.d.ts +6 -0
  80. package/lib/routes/kanban-http.js +159 -0
  81. package/lib/routes/kanban-sse.d.ts +8 -0
  82. package/lib/routes/kanban-sse.js +58 -0
  83. package/lib/routes/planning-driver.d.ts +16 -0
  84. package/lib/routes/planning-driver.js +52 -0
  85. package/lib/routes/prefix-router.d.ts +29 -0
  86. package/lib/routes/prefix-router.js +28 -0
  87. package/lib/services/kanban-provider.d.ts +16 -0
  88. package/lib/services/kanban-provider.js +14 -0
  89. package/lib/tools/kanban-tools.d.ts +11 -0
  90. package/lib/tools/kanban-tools.js +169 -0
  91. package/lib/tools/main-session-tools.d.ts +20 -0
  92. package/lib/tools/main-session-tools.js +170 -0
  93. package/lib/tools/planning-tools.d.ts +32 -0
  94. package/lib/tools/planning-tools.js +102 -0
  95. package/lib/tools/prefetch-tools.d.ts +5 -0
  96. package/lib/tools/prefetch-tools.js +59 -0
  97. package/lib/tools/spec-card-tools.d.ts +4 -0
  98. package/lib/tools/spec-card-tools.js +75 -0
  99. package/lib/tools/wiki-tools.d.ts +4 -0
  100. package/lib/tools/wiki-tools.js +57 -0
  101. package/lib/wiki/kb-linkage.d.ts +9 -0
  102. package/lib/wiki/kb-linkage.js +87 -0
  103. package/lib/wiki/page-path.d.ts +6 -0
  104. package/lib/wiki/page-path.js +28 -0
  105. package/lib/wiki/wiki-vault-client.d.ts +28 -0
  106. package/lib/wiki/wiki-vault-client.js +48 -0
  107. package/package.json +83 -0
  108. package/personas/kanban-d/agent.cordis.yml +157 -0
  109. package/personas/kanban-d/preset.yml +2 -0
  110. package/personas/kanban-dt/agent.cordis.yml +71 -0
  111. package/personas/kanban-dt/preset.yml +2 -0
  112. package/personas/kanban-p/agent.cordis.yml +66 -0
  113. package/personas/kanban-p/preset.yml +2 -0
  114. package/personas/kanban-pt/agent.cordis.yml +47 -0
  115. package/personas/kanban-pt/preset.yml +2 -0
  116. package/personas/kanban-v/agent.cordis.yml +47 -0
  117. package/personas/kanban-v/preset.yml +2 -0
  118. package/personas/kanban-w/agent.cordis.yml +47 -0
  119. package/personas/kanban-w/preset.yml +2 -0
  120. package/personas/persona-d.md +26 -0
  121. package/personas/persona-dt.md +18 -0
  122. package/personas/persona-p.md +13 -0
  123. package/personas/persona-pt.md +13 -0
  124. package/personas/persona-v.md +18 -0
  125. package/personas/persona-w.md +12 -0
package/lib/client.js ADDED
@@ -0,0 +1,1205 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@joekytc/dsh-swarm",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ "use strict";
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __export = (target, all) => {
13
+ for (var name2 in all)
14
+ __defProp(target, name2, { get: all[name2], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
+
26
+ // client/index.ts
27
+ var index_exports = {};
28
+ __export(index_exports, {
29
+ apply: () => apply,
30
+ inject: () => inject,
31
+ name: () => name
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // client/KanbanTab.tsx
36
+ var import_react6 = require("react");
37
+
38
+ // src/domain/state-machine.ts
39
+ var TASK_TRANSITIONS = {
40
+ triage: { "task/claimed": "running" },
41
+ todo: { "task/claimed": "running", "task/archived": "archived" },
42
+ ready: { "task/claimed": "running", "task/archived": "archived" },
43
+ running: { "task/completed": "done", "task/blocked": "blocked", "task/failed": "failed", "task/heartbeat": "running" },
44
+ blocked: { "task/unblocked": "ready", "task/archived": "archived" },
45
+ done: { "task/archived": "archived" },
46
+ failed: { "task/claimed": "running", "task/blocked": "blocked", "task/archived": "archived" },
47
+ archived: {}
48
+ };
49
+ var CHAIN_TRANSITIONS = {
50
+ planning: { "chain/executing": "executing" },
51
+ executing: { "chain/completed": "completed", "chain/aborted": "aborted" },
52
+ completed: {},
53
+ aborted: {}
54
+ };
55
+ var SPEC_TRANSITIONS = {
56
+ draft: { "spec-card/approved": "approved" },
57
+ approved: {}
58
+ };
59
+ function step(name2, table, current, kind) {
60
+ const next = table[current]?.[kind];
61
+ if (next === void 0) throw new Error(`illegal transition: ${current} --${kind}--> (none)`);
62
+ return next;
63
+ }
64
+ var transitionTask = (c, k) => step("task", TASK_TRANSITIONS, c, k);
65
+ var transitionChain = (c, k) => step("chain", CHAIN_TRANSITIONS, c, k);
66
+ var transitionSpecCard = (c, k) => step("spec", SPEC_TRANSITIONS, c, k);
67
+
68
+ // src/domain/projection.ts
69
+ function applyTo(state, ev) {
70
+ const next = { ...state, events: [...state.events, ev] };
71
+ switch (ev.kind) {
72
+ case "chain/created": {
73
+ const p = ev.payload;
74
+ next.chains = new Map(state.chains).set(p.id, { ...p, status: "planning", workspaceDir: p.workspaceDir ?? null });
75
+ break;
76
+ }
77
+ case "chain/executing":
78
+ case "chain/completed":
79
+ case "chain/aborted": {
80
+ const c = state.chains.get(ev.chainId);
81
+ if (!c) throw new Error("projection: unknown chain " + ev.chainId);
82
+ next.chains = new Map(state.chains).set(ev.chainId, { ...c, status: transitionChain(c.status, ev.kind) });
83
+ break;
84
+ }
85
+ case "chain/root-task-set": {
86
+ const c = state.chains.get(ev.chainId);
87
+ if (!c) throw new Error("projection: unknown chain " + ev.chainId);
88
+ next.chains = new Map(state.chains).set(ev.chainId, { ...c, rootTaskId: String(ev.payload["rootTaskId"] ?? "") });
89
+ break;
90
+ }
91
+ // D23:audit 事件不改 Chain 状态,只写验收核对视图(auditWarnings)
92
+ case "chain/audit-warning": {
93
+ const c = state.chains.get(ev.chainId);
94
+ if (!c) throw new Error("projection: unknown chain " + ev.chainId);
95
+ const evidence = ev.payload["evidence"] ?? [];
96
+ const audit = {
97
+ evidence,
98
+ warnedAt: ev.at,
99
+ warnedSeq: ev.seq,
100
+ confirmedAt: null,
101
+ confirmedBy: null,
102
+ confirmedSeq: null
103
+ };
104
+ next.auditWarnings = new Map(state.auditWarnings).set(ev.chainId, audit);
105
+ break;
106
+ }
107
+ case "chain/audit-confirmed": {
108
+ const c = state.chains.get(ev.chainId);
109
+ if (!c) throw new Error("projection: unknown chain " + ev.chainId);
110
+ const existing = state.auditWarnings.get(ev.chainId);
111
+ if (!existing) throw new Error("projection: audit-confirmed without audit-warning: " + ev.chainId);
112
+ next.auditWarnings = new Map(state.auditWarnings).set(ev.chainId, {
113
+ ...existing,
114
+ confirmedAt: ev.at,
115
+ confirmedBy: ev.author,
116
+ confirmedSeq: ev.seq
117
+ });
118
+ break;
119
+ }
120
+ // T7:链标题改名(非状态转换,只更新 title;快照重放可见)
121
+ case "chain/title-updated": {
122
+ const c = state.chains.get(ev.chainId);
123
+ if (!c) throw new Error("projection: unknown chain " + ev.chainId);
124
+ next.chains = new Map(state.chains).set(ev.chainId, { ...c, title: String(ev.payload["to"] ?? "") });
125
+ break;
126
+ }
127
+ // T7:任务标题改名(非状态转换,只更新 title)
128
+ case "task/renamed": {
129
+ if (!ev.taskId) throw new Error("projection: event without taskId");
130
+ const t = state.tasks.get(ev.taskId);
131
+ if (!t) throw new Error("projection: unknown task " + ev.taskId);
132
+ next.tasks = new Map(state.tasks).set(ev.taskId, { ...t, title: String(ev.payload["to"] ?? "") });
133
+ break;
134
+ }
135
+ case "task/created": {
136
+ const p = ev.payload;
137
+ if (!p.id || !p.assignee) throw new Error("projection: malformed task/created");
138
+ const normalized = {
139
+ ...p,
140
+ status: p.status ?? "todo",
141
+ parents: p.parents ?? [],
142
+ children: p.children ?? [],
143
+ attempts: p.attempts ?? 0,
144
+ heartbeats: p.heartbeats ?? [],
145
+ sessionId: p.sessionId ?? "kbn-" + p.id,
146
+ reworkOfTaskId: p.reworkOfTaskId ?? null,
147
+ resumeSessionId: p.resumeSessionId ?? null,
148
+ reviewAttempt: p.reviewAttempt ?? 0,
149
+ reviewStatus: p.reviewStatus ?? "not-required"
150
+ };
151
+ next.tasks = new Map(state.tasks).set(p.id, normalized);
152
+ break;
153
+ }
154
+ case "task/claimed":
155
+ case "task/heartbeat":
156
+ case "task/completed":
157
+ case "task/blocked":
158
+ case "task/unblocked":
159
+ case "task/failed":
160
+ case "task/archived": {
161
+ if (!ev.taskId) throw new Error("projection: event without taskId");
162
+ const t = state.tasks.get(ev.taskId);
163
+ if (!t) throw new Error("projection: unknown task " + ev.taskId);
164
+ const updated = { ...t, status: transitionTask(t.status, ev.kind) };
165
+ if (ev.kind === "task/heartbeat") updated.heartbeats = [...t.heartbeats, ev.at];
166
+ if (ev.kind === "task/failed" && !ev.payload["infra"]) updated.attempts = t.attempts + 1;
167
+ if (ev.kind === "task/unblocked") updated.attempts = 0;
168
+ if (ev.kind === "task/completed") {
169
+ const p = ev.payload;
170
+ next.handoffs = new Map(state.handoffs).set(ev.taskId, p);
171
+ }
172
+ next.tasks = new Map(state.tasks).set(ev.taskId, updated);
173
+ break;
174
+ }
175
+ // 评审事件(交付质量链):非状态转换——按 payload.targetTaskId 更新被评审任务 reviewStatus
176
+ case "review/passed":
177
+ case "review/failed":
178
+ case "review/gave-up": {
179
+ const targetId = String(ev.payload["targetTaskId"] ?? "");
180
+ const t = state.tasks.get(targetId);
181
+ if (!t) throw new Error("projection: unknown review target " + targetId);
182
+ const status = ev.kind === "review/passed" ? "passed" : ev.kind === "review/failed" ? "failed" : "gave-up";
183
+ next.tasks = new Map(state.tasks).set(targetId, { ...t, reviewStatus: status });
184
+ break;
185
+ }
186
+ case "spec-card/created":
187
+ case "spec-card/edited": {
188
+ const p = ev.payload;
189
+ next.specCards = new Map(state.specCards).set(p.id, p);
190
+ if (ev.kind === "spec-card/created") {
191
+ const c = state.chains.get(p.chainId);
192
+ if (c && !c.specCardId) {
193
+ next.chains = new Map(state.chains).set(p.chainId, { ...c, specCardId: p.id });
194
+ }
195
+ }
196
+ break;
197
+ }
198
+ case "spec-card/approved": {
199
+ const id = String(ev.payload["id"] ?? "");
200
+ const s = state.specCards.get(id);
201
+ if (!s) throw new Error("projection: unknown spec card " + id);
202
+ next.specCards = new Map(state.specCards).set(id, { ...s, status: transitionSpecCard(s.status, ev.kind), approvedAt: ev.at, approvedBy: ev.author });
203
+ break;
204
+ }
205
+ }
206
+ return next;
207
+ }
208
+
209
+ // client/board-store.ts
210
+ function hydrate(wire) {
211
+ return {
212
+ chains: new Map(wire.chains.map((value) => [value.id, value])),
213
+ tasks: new Map(wire.tasks.map((value) => [value.id, value])),
214
+ specCards: new Map(wire.specCards.map((value) => [value.id, value])),
215
+ handoffs: new Map(wire.handoffs.map(({ id, ...value }) => [id, value])),
216
+ auditWarnings: new Map(wire.auditWarnings.map(({ chainId, ...value }) => [chainId, value])),
217
+ events: wire.events
218
+ };
219
+ }
220
+ var OPTIMISTIC_STATUS = {
221
+ block: "blocked",
222
+ unblock: "ready",
223
+ complete: "done",
224
+ archive: "archived",
225
+ retry: "running"
226
+ };
227
+ function applyOptimistic(board, taskId, status) {
228
+ const task = board.tasks.get(taskId);
229
+ if (!task || task.status === status) return board;
230
+ return { ...board, tasks: new Map(board.tasks).set(taskId, { ...task, status }) };
231
+ }
232
+ function createBoardStore(deps = {}) {
233
+ const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
234
+ const eventSourceFactory = deps.eventSourceFactory ?? ((url) => new EventSource(url));
235
+ let snapshot = { board: null, lastSeq: -1, connection: "loading", lastSuccessAt: null, error: null, actionError: null };
236
+ const listeners = /* @__PURE__ */ new Set();
237
+ let generation = 0;
238
+ let source = null;
239
+ let started = false;
240
+ const commit = (patch) => {
241
+ snapshot = { ...snapshot, ...patch };
242
+ for (const listener of [...listeners]) listener();
243
+ };
244
+ const resync = async () => {
245
+ const gen = ++generation;
246
+ source?.close();
247
+ source = null;
248
+ commit({ connection: "loading" });
249
+ try {
250
+ const res = await fetchImpl("/kanban/board");
251
+ if (!res.ok) throw new Error(`board snapshot failed: ${res.status}`);
252
+ const wire = await res.json();
253
+ if (gen !== generation) return;
254
+ commit({
255
+ board: hydrate(wire),
256
+ lastSeq: wire.lastSeq,
257
+ connection: "ready",
258
+ lastSuccessAt: Date.now(),
259
+ error: null
260
+ });
261
+ installSource(gen, wire.lastSeq);
262
+ } catch (err) {
263
+ if (gen !== generation) return;
264
+ commit({ connection: "error", error: String(err) });
265
+ }
266
+ };
267
+ const installSource = (gen, lastSeq) => {
268
+ const next = eventSourceFactory(`/kanban/events?after=${lastSeq}`);
269
+ if (gen !== generation) {
270
+ next.close();
271
+ return;
272
+ }
273
+ source = next;
274
+ next.onopen = () => {
275
+ if (gen !== generation) return;
276
+ commit({ connection: "ready" });
277
+ };
278
+ next.onerror = () => {
279
+ if (gen !== generation) return;
280
+ commit({ connection: "reconnecting" });
281
+ };
282
+ next.onmessage = (message) => {
283
+ if (gen !== generation) return;
284
+ const event = JSON.parse(message.data);
285
+ if (event.seq <= snapshot.lastSeq) return;
286
+ if (event.seq === snapshot.lastSeq + 1) {
287
+ const board = snapshot.board ? applyTo(snapshot.board, event) : snapshot.board;
288
+ if (board) commit({ board, lastSeq: event.seq });
289
+ return;
290
+ }
291
+ void resync();
292
+ };
293
+ };
294
+ return {
295
+ async start() {
296
+ if (started) return;
297
+ started = true;
298
+ await resync();
299
+ },
300
+ stop() {
301
+ generation += 1;
302
+ source?.close();
303
+ source = null;
304
+ started = false;
305
+ },
306
+ async retry() {
307
+ if (!started) return;
308
+ await resync();
309
+ },
310
+ subscribe(listener) {
311
+ listeners.add(listener);
312
+ return () => {
313
+ listeners.delete(listener);
314
+ };
315
+ },
316
+ getSnapshot() {
317
+ return snapshot;
318
+ },
319
+ async postAction(action) {
320
+ const a = action;
321
+ const type = a.type ?? "";
322
+ const taskId = a.taskId ?? "";
323
+ const optimistic = taskId ? OPTIMISTIC_STATUS[type] : void 0;
324
+ const before = optimistic ? snapshot.board : void 0;
325
+ const beforeSeq = snapshot.lastSeq;
326
+ if (before && optimistic) commit({ actionError: null, board: applyOptimistic(before, taskId, optimistic) });
327
+ try {
328
+ const res = await fetchImpl("/kanban/action", {
329
+ method: "POST",
330
+ headers: { "Content-Type": "application/json" },
331
+ body: JSON.stringify(action)
332
+ });
333
+ const data = await res.json();
334
+ if (!res.ok || data.ok === false) throw new Error(data.error ?? "action failed");
335
+ commit({ actionError: null });
336
+ return data;
337
+ } catch (err) {
338
+ const patch = { actionError: { taskId, message: String(err) } };
339
+ if (before) patch.board = before;
340
+ commit(patch);
341
+ if (snapshot.lastSeq !== beforeSeq) void resync();
342
+ throw err;
343
+ }
344
+ }
345
+ };
346
+ }
347
+
348
+ // client/useKanbanBoard.ts
349
+ var import_react = require("react");
350
+ function useKanbanBoard(store) {
351
+ return (0, import_react.useSyncExternalStore)(store.subscribe, store.getSnapshot, store.getSnapshot);
352
+ }
353
+
354
+ // client/KanbanBoard.tsx
355
+ var import_react5 = require("react");
356
+
357
+ // client/workflow-model.ts
358
+ var CHAIN_FILTERS = ["executing", "blocked", "failed", "completed"];
359
+ var CHAIN_FILTER_LABEL = {
360
+ executing: "\u6267\u884C\u4E2D",
361
+ blocked: "\u963B\u585E",
362
+ failed: "\u5931\u8D25",
363
+ completed: "\u5DF2\u5B8C\u6210"
364
+ };
365
+ function chainFilterStateOf(chain, chainTasks) {
366
+ const archived = chain.status === "aborted" || chainTasks.length > 0 && chainTasks.every((t) => t.status === "archived");
367
+ const blocked = chainTasks.some((t) => t.status === "blocked");
368
+ const failed = chainTasks.some((t) => t.status === "failed");
369
+ return {
370
+ executing: chain.status === "executing" && !blocked && !failed,
371
+ blocked,
372
+ failed,
373
+ completed: chain.status === "completed" || chain.status === "aborted" || archived,
374
+ archived
375
+ };
376
+ }
377
+ var STATUS_LABEL = {
378
+ triage: "\u5206\u8BCA",
379
+ todo: "\u5F85\u529E",
380
+ ready: "\u5C31\u7EEA",
381
+ running: "\u6267\u884C\u4E2D",
382
+ blocked: "\u53D7\u963B",
383
+ done: "\u5B8C\u6210",
384
+ failed: "\u5931\u8D25",
385
+ archived: "\u5DF2\u5F52\u6863"
386
+ };
387
+ function statusLabelOf(status) {
388
+ return STATUS_LABEL[status];
389
+ }
390
+ function phaseOf(task, ordered) {
391
+ if (task.assignee === "p") return "P";
392
+ if (task.assignee === "d") return "D";
393
+ if (task.assignee === "w" && task.mode === "kb") {
394
+ const dIndex = ordered.findIndex((value) => value.assignee === "d");
395
+ return dIndex >= 0 && ordered.indexOf(task) > dIndex ? "W3" : "W2";
396
+ }
397
+ return task.assignee.toUpperCase();
398
+ }
399
+ function lineStateOf(task, selectedTaskId) {
400
+ if (task.status === "blocked" || task.status === "failed") return "blocked";
401
+ if (task.id === selectedTaskId || task.status === "running") return "active";
402
+ if (task.status === "done" || task.status === "archived") return "complete";
403
+ return "pending";
404
+ }
405
+ function taskOrder(tasks, state) {
406
+ const seq = /* @__PURE__ */ new Map();
407
+ for (const ev of state.events) {
408
+ if (ev.kind === "task/created" && ev.taskId) seq.set(ev.taskId, ev.seq);
409
+ }
410
+ return [...tasks].sort((a, b) => {
411
+ const aSeq = seq.get(a.id) ?? Number.MAX_SAFE_INTEGER;
412
+ const bSeq = seq.get(b.id) ?? Number.MAX_SAFE_INTEGER;
413
+ return aSeq - bSeq || a.id.localeCompare(b.id);
414
+ });
415
+ }
416
+ function activityLabel(task, state, now) {
417
+ let lastAt = 0;
418
+ for (const h of task.heartbeats) lastAt = Math.max(lastAt, h);
419
+ for (const ev of state.events) {
420
+ if (ev.taskId === task.id) lastAt = Math.max(lastAt, ev.at);
421
+ }
422
+ if (!lastAt) return "\u5F85\u542F\u52A8";
423
+ const diff = Math.max(0, now - lastAt);
424
+ if (diff < 6e4) return "\u521A\u521A";
425
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m`;
426
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h`;
427
+ return `${Math.floor(diff / 864e5)}d`;
428
+ }
429
+ function blockedSummary(chainId, state) {
430
+ let latest = null;
431
+ let latestSeq = -1;
432
+ for (const ev of state.events) {
433
+ if (ev.chainId !== chainId) continue;
434
+ if (ev.kind === "task/blocked" || ev.kind === "task/failed") {
435
+ if (ev.seq > latestSeq) {
436
+ latestSeq = ev.seq;
437
+ latest = String(ev.payload["reason"] ?? "");
438
+ }
439
+ }
440
+ }
441
+ return latest;
442
+ }
443
+ function relatedIds(state, chainId, selectedTaskId) {
444
+ const set = /* @__PURE__ */ new Set();
445
+ const chainTasks = [...state.tasks.values()].filter((t) => t.chainId === chainId);
446
+ const byId = new Map(chainTasks.map((t) => [t.id, t]));
447
+ const childrenBy = /* @__PURE__ */ new Map();
448
+ for (const t of chainTasks) {
449
+ for (const p of t.parents) {
450
+ const list = childrenBy.get(p) ?? [];
451
+ list.push(t.id);
452
+ childrenBy.set(p, list);
453
+ }
454
+ }
455
+ if (!byId.has(selectedTaskId)) return set;
456
+ set.add(selectedTaskId);
457
+ let frontier = [selectedTaskId];
458
+ while (frontier.length > 0) {
459
+ const next = [];
460
+ for (const id of frontier) {
461
+ const t = byId.get(id);
462
+ if (!t) continue;
463
+ for (const p of t.parents) if (byId.has(p) && !set.has(p)) {
464
+ set.add(p);
465
+ next.push(p);
466
+ }
467
+ for (const c of childrenBy.get(id) ?? []) if (byId.has(c) && !set.has(c)) {
468
+ set.add(c);
469
+ next.push(c);
470
+ }
471
+ }
472
+ frontier = next;
473
+ }
474
+ return set;
475
+ }
476
+ function sortRankOf(chain, tasks) {
477
+ if (tasks.some((t) => t.status === "blocked" || t.status === "failed")) return 0;
478
+ if (chain.status === "executing") return 1;
479
+ if (chain.status === "planning") return 2;
480
+ return 3;
481
+ }
482
+ function deriveWorkflowBoard(state, opts) {
483
+ const filter = opts.statusFilter ?? /* @__PURE__ */ new Set();
484
+ const views = [];
485
+ for (const chain of state.chains.values()) {
486
+ const chainTasks = [...state.tasks.values()].filter((t) => t.chainId === chain.id);
487
+ const st = chainFilterStateOf(chain, chainTasks);
488
+ const archived = st.archived;
489
+ const matchesFilter = filter.has("executing") && st.executing || filter.has("blocked") && st.blocked || filter.has("failed") && st.failed || filter.has("completed") && st.completed;
490
+ const selectedInChain = opts.selectedTaskId != null && chainTasks.some((t) => t.id === opts.selectedTaskId);
491
+ if (selectedInChain) {
492
+ } else if (filter.size > 0) {
493
+ if (!matchesFilter) continue;
494
+ } else if (archived) continue;
495
+ const ordered = taskOrder(chainTasks, state);
496
+ const related = opts.selectedTaskId ? relatedIds(state, chain.id, opts.selectedTaskId) : /* @__PURE__ */ new Set();
497
+ let lastActivityAt = chain.createdAt;
498
+ for (const ev of state.events) {
499
+ if (ev.chainId === chain.id) lastActivityAt = Math.max(lastActivityAt, ev.at);
500
+ }
501
+ const auditRec = state.auditWarnings.get(chain.id);
502
+ views.push({
503
+ chain,
504
+ sortRank: sortRankOf(chain, chainTasks),
505
+ lastActivityAt,
506
+ blockedSummary: blockedSummary(chain.id, state),
507
+ audit: auditRec ? {
508
+ warned: true,
509
+ confirmed: auditRec.confirmedAt !== null,
510
+ evidenceCount: auditRec.evidence.length,
511
+ evidence: auditRec.evidence
512
+ } : null,
513
+ tasks: ordered.map((task) => ({
514
+ task,
515
+ phase: phaseOf(task, ordered),
516
+ statusLabel: statusLabelOf(task.status),
517
+ activityLabel: activityLabel(task, state, opts.now),
518
+ // D15/D17:阻塞/失败任务优先展示阻塞原因,其余展示父依赖
519
+ dependencyLabel: task.status === "blocked" || task.status === "failed" ? blockedSummary(chain.id, state) ?? "" : task.parents.map((id) => state.tasks.get(id)?.title ?? id).join(", "),
520
+ lineState: lineStateOf(task, opts.selectedTaskId),
521
+ selected: task.id === opts.selectedTaskId,
522
+ related: opts.selectedTaskId === task.id || related.has(task.id)
523
+ }))
524
+ });
525
+ }
526
+ views.sort((a, b) => a.sortRank - b.sortRank || b.lastActivityAt - a.lastActivityAt);
527
+ return views;
528
+ }
529
+
530
+ // client/WorkflowRail.tsx
531
+ var import_react3 = require("react");
532
+
533
+ // client/BoardCard.tsx
534
+ var import_jsx_runtime = require("react/jsx-runtime");
535
+ function BoardCard(props) {
536
+ const { view } = props;
537
+ const { task } = view;
538
+ const blocked = view.lineState === "blocked" && view.dependencyLabel.length > 0;
539
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
540
+ "div",
541
+ {
542
+ role: "button",
543
+ tabIndex: 0,
544
+ className: `dsh-kb-task dsh-kb-task--${view.lineState}${view.related ? " dsh-kb-task--related" : ""}`,
545
+ "data-selected": view.selected || void 0,
546
+ onClick: () => props.onOpen(task.id),
547
+ onKeyDown: (e) => {
548
+ if (e.key === "Enter" || e.key === " ") {
549
+ e.preventDefault();
550
+ props.onOpen(task.id);
551
+ }
552
+ },
553
+ "aria-label": `${view.phase} ${task.title} ${view.statusLabel}`,
554
+ children: [
555
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `dsh-kb-profile dsh-kb-profile--${task.assignee}`, children: task.assignee.toUpperCase() }),
556
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-kb-task__title", children: task.title }),
557
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-kb-task__status-row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-kb-task__status", children: view.statusLabel }) }),
558
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-kb-task__meta", children: [
559
+ view.phase,
560
+ " \xB7 ",
561
+ view.activityLabel,
562
+ !blocked && view.dependencyLabel ? ` \xB7 ${view.dependencyLabel}` : ""
563
+ ] }),
564
+ blocked && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-kb-task__warn", children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: [
566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 1.5 14.5 13.5h-13L8 1.5Z", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round" }),
567
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 6.2v3.6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
568
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "11.6", r: "0.9", fill: "currentColor" })
569
+ ] }),
570
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: view.dependencyLabel })
571
+ ] })
572
+ ]
573
+ }
574
+ );
575
+ }
576
+
577
+ // client/RenameModal.tsx
578
+ var import_react2 = require("react");
579
+ var import_jsx_runtime2 = require("react/jsx-runtime");
580
+ function RenameModal(props) {
581
+ const [value, setValue] = (0, import_react2.useState)(props.initialValue);
582
+ const save = () => {
583
+ const next = value.trim();
584
+ if (next) props.onSave(next);
585
+ };
586
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-kb-rename-overlay", onClick: (e) => {
587
+ e.stopPropagation();
588
+ props.onCancel();
589
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
590
+ "div",
591
+ {
592
+ className: "dsh-kb-rename-modal",
593
+ role: "dialog",
594
+ "aria-modal": "true",
595
+ "aria-label": props.title,
596
+ onClick: (e) => e.stopPropagation(),
597
+ children: [
598
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-kb-rename-modal__label", children: props.title }),
599
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
600
+ "input",
601
+ {
602
+ className: "dsh-kb-rename-modal__input",
603
+ "aria-label": props.title,
604
+ value,
605
+ autoFocus: true,
606
+ onChange: (e) => setValue(e.target.value),
607
+ onKeyDown: (e) => {
608
+ if (e.key === "Enter") save();
609
+ if (e.key === "Escape") props.onCancel();
610
+ }
611
+ }
612
+ ),
613
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-kb-rename-modal__actions", children: [
614
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-kb-rename-cancel", onClick: props.onCancel, children: "\u53D6\u6D88" }),
615
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-kb-rename-save", onClick: save, children: "\u4FDD\u5B58" })
616
+ ] })
617
+ ]
618
+ }
619
+ ) });
620
+ }
621
+
622
+ // client/WorkflowRail.tsx
623
+ var import_jsx_runtime3 = require("react/jsx-runtime");
624
+ function matches(view, query) {
625
+ const q = query.trim().toLowerCase();
626
+ if (!q) return true;
627
+ if (view.chain.title.toLowerCase().includes(q)) return true;
628
+ return view.tasks.some((item) => item.task.title.toLowerCase().includes(q));
629
+ }
630
+ function WorkflowRail(props) {
631
+ const searching = props.query.trim().length > 0;
632
+ const visible = props.chains.filter((view) => matches(view, props.query));
633
+ const [renamingChainId, setRenamingChainId] = (0, import_react3.useState)(null);
634
+ const renamingChain = renamingChainId ? props.chains.find((v) => v.chain.id === renamingChainId)?.chain : void 0;
635
+ const [deletingChainId, setDeletingChainId] = (0, import_react3.useState)(null);
636
+ const [deleteError, setDeleteError] = (0, import_react3.useState)(null);
637
+ const deletingView = deletingChainId ? props.chains.find((v) => v.chain.id === deletingChainId) : void 0;
638
+ const confirmDelete = async () => {
639
+ if (!deletingChainId) return;
640
+ setDeleteError(null);
641
+ try {
642
+ await props.onDeleteChain?.(deletingChainId);
643
+ setDeletingChainId(null);
644
+ } catch (err) {
645
+ setDeleteError(String(err));
646
+ }
647
+ };
648
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-kb-rail", children: [
649
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-kb-filters", role: "group", "aria-label": "\u6309\u94FE\u8DEF\u72B6\u6001\u7B5B\u9009", children: CHAIN_FILTERS.map((f) => {
650
+ const active = props.statusFilter.has(f);
651
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
652
+ "button",
653
+ {
654
+ type: "button",
655
+ className: `dsh-kb-filter${active ? " dsh-kb-filter--active" : ""}`,
656
+ "aria-pressed": active,
657
+ onClick: () => props.onToggleFilter(f),
658
+ children: CHAIN_FILTER_LABEL[f]
659
+ },
660
+ f
661
+ );
662
+ }) }),
663
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-kb-rail__list", role: "list", "aria-label": "\u4EFB\u52A1\u94FE\u8DEF", children: visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-kb-empty", role: "status", children: searching ? "\u65E0\u5339\u914D\u94FE\u8DEF" : "\u6682\u65E0\u770B\u677F\u4EFB\u52A1\uFF0C\u8F93\u5165 /plan: \u5F00\u542F\u65B0\u94FE\u8DEF" }) : visible.map((view) => {
664
+ const matched = searching ? view.tasks.filter((item) => item.task.title.toLowerCase().includes(props.query.trim().toLowerCase())) : view.tasks;
665
+ const expanded = searching || !props.collapsedChainIds.has(view.chain.id);
666
+ const done = view.tasks.filter((item) => item.task.status === "done" || item.task.status === "archived").length;
667
+ const blocked = view.tasks.some((item) => item.task.status === "blocked" || item.task.status === "failed");
668
+ const summary = view.blockedSummary ?? (blocked ? "\u94FE\u8DEF\u53D7\u963B" : `${done}/${view.tasks.length}`);
669
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: `dsh-kb-chain dsh-kb-chain--${view.chain.status}`, children: [
670
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
671
+ "div",
672
+ {
673
+ role: "button",
674
+ tabIndex: 0,
675
+ className: "dsh-kb-chain__title",
676
+ "aria-expanded": expanded,
677
+ onClick: () => props.onToggleChain(view.chain.id),
678
+ onKeyDown: (e) => {
679
+ if (e.key === "Enter" || e.key === " ") {
680
+ e.preventDefault();
681
+ props.onToggleChain(view.chain.id);
682
+ }
683
+ },
684
+ children: [
685
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-kb-chain__chevron", "aria-hidden": "true" }),
686
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dsh-kb-chain__name", children: view.chain.title }),
687
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "dsh-kb-chain__meta", children: [
688
+ done,
689
+ "/",
690
+ view.tasks.length
691
+ ] }),
692
+ props.onDeleteChain && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
693
+ "button",
694
+ {
695
+ type: "button",
696
+ className: "dsh-kb-chain__delete",
697
+ "aria-label": "\u5220\u9664\u9700\u6C42",
698
+ onClick: (e) => {
699
+ e.stopPropagation();
700
+ setDeleteError(null);
701
+ setDeletingChainId(view.chain.id);
702
+ },
703
+ onKeyDown: (e) => {
704
+ e.stopPropagation();
705
+ },
706
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: [
707
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M2.5 4h11M6.5 4V2.5h3V4M4 4l.7 9.5h6.6L12 4", fill: "none", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round" }),
708
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M6.6 6.5v4.5M9.4 6.5v4.5", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })
709
+ ] })
710
+ }
711
+ ),
712
+ props.onRenameChain && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
713
+ "button",
714
+ {
715
+ type: "button",
716
+ className: "dsh-kb-chain__rename",
717
+ "aria-label": "\u6539\u94FE\u6807\u9898",
718
+ onClick: (e) => {
719
+ e.stopPropagation();
720
+ setRenamingChainId(view.chain.id);
721
+ },
722
+ onKeyDown: (e) => {
723
+ e.stopPropagation();
724
+ },
725
+ children: "\u270E"
726
+ }
727
+ )
728
+ ]
729
+ }
730
+ ),
731
+ (blocked || view.blockedSummary) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-kb-chain__warning", children: summary }),
732
+ view.audit && !view.audit.confirmed && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-kb-chain__warning dsh-kb-chain__audit", children: [
733
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
734
+ "\u26A0 \u4E3B agent \u7591\u4F3C\u8D8A\u6743\u5199\u5DE5\u4F5C\u533A\u4EA7\u7269\uFF08",
735
+ view.audit.evidenceCount,
736
+ " \u6761\u7EBF\u7D22\uFF09\uFF0C\u6700\u7EC8\u6C47\u62A5\u5DF2\u963B\u585E\uFF0C\u8BF7\u6838\u5BF9\u4EA7\u7269\u5F52\u5C5E"
737
+ ] }),
738
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "dsh-kb-audit-confirm", onClick: () => props.onConfirmAudit?.(view.chain.id), children: "\u786E\u8BA4\u4EA7\u7269\u5F52\u5C5E" })
739
+ ] }),
740
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { className: "dsh-kb-nodes", children: (matched.length > 0 ? matched : view.tasks).map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { className: `dsh-kb-node dsh-kb-node--${item.lineState}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BoardCard, { view: item, onOpen: props.onOpenTask }) }, item.task.id)) })
741
+ ] }, view.chain.id);
742
+ }) }),
743
+ renamingChain && props.onRenameChain && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
744
+ RenameModal,
745
+ {
746
+ title: "\u6539\u94FE\u6807\u9898",
747
+ initialValue: renamingChain.title,
748
+ onSave: (title) => {
749
+ props.onRenameChain?.(renamingChain.id, title);
750
+ setRenamingChainId(null);
751
+ },
752
+ onCancel: () => setRenamingChainId(null)
753
+ }
754
+ ),
755
+ deletingView && props.onDeleteChain && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-kb-rename-overlay", onClick: (e) => {
756
+ e.stopPropagation();
757
+ setDeletingChainId(null);
758
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
759
+ "div",
760
+ {
761
+ className: "dsh-kb-rename-modal",
762
+ role: "dialog",
763
+ "aria-modal": "true",
764
+ "aria-label": "\u5220\u9664\u9700\u6C42",
765
+ onClick: (e) => e.stopPropagation(),
766
+ children: [
767
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dsh-kb-rename-modal__label", children: "\u5220\u9664\u9700\u6C42" }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-kb-delete-modal__text", children: [
769
+ "\u5C06\u6C38\u4E45\u5220\u9664\u300C",
770
+ deletingView.chain.title || "\u672A\u547D\u540D\u9700\u6C42",
771
+ "\u300D\u53CA\u5176\u4E0B ",
772
+ deletingView.tasks.length,
773
+ " \u5F20\u89D2\u8272\u5361\uFF0C\u4E0D\u53EF\u6062\u590D\u3002\u786E\u8BA4\u5220\u9664\uFF1F"
774
+ ] }),
775
+ deleteError && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-kb-delete-modal__error", role: "alert", children: [
776
+ "\u5220\u9664\u5931\u8D25\uFF1A",
777
+ deleteError
778
+ ] }),
779
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dsh-kb-rename-modal__actions", children: [
780
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "dsh-kb-rename-cancel", onClick: () => setDeletingChainId(null), children: "\u53D6\u6D88" }),
781
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "dsh-kb-delete-confirm", onClick: () => void confirmDelete(), children: "\u5220\u9664" })
782
+ ] })
783
+ ]
784
+ }
785
+ ) })
786
+ ] });
787
+ }
788
+
789
+ // client/TaskDrawer.tsx
790
+ var import_react4 = require("react");
791
+ var import_jsx_runtime4 = require("react/jsx-runtime");
792
+ var ROLE_NAME = {
793
+ v: "orchestrator",
794
+ p: "planner",
795
+ w: "wiki-bridge",
796
+ d: "fullstack-dev",
797
+ pt: "plan-review",
798
+ dt: "impl-review"
799
+ };
800
+ var TABS = [
801
+ ["overview", "\u6982\u89C8"],
802
+ ["timeline", "\u8F68\u8FF9"],
803
+ ["handoff", "\u4EA4\u63A5"],
804
+ ["spec", "\u89C4\u683C"],
805
+ ["comments", "\u8BC4\u8BBA"]
806
+ ];
807
+ function formatValue(value) {
808
+ if (Array.isArray(value)) return value.map(String).join(", ");
809
+ if (typeof value === "object" && value !== null) return JSON.stringify(value);
810
+ return String(value ?? "");
811
+ }
812
+ function formatTime(at) {
813
+ return new Date(at).toLocaleString();
814
+ }
815
+ function TaskDrawer(props) {
816
+ const { task, events, handoff, specCard, chain } = props;
817
+ const [tab, setTab] = (0, import_react4.useState)("overview");
818
+ const [pending, setPending] = (0, import_react4.useState)(null);
819
+ const timeline = events.filter((e) => e.taskId === task.id).toSorted((a, b) => a.seq - b.seq);
820
+ const comments = timeline.filter((e) => e.kind === "task/commented");
821
+ const submitComment = (el) => {
822
+ const value = el.value.trim();
823
+ if (!value) return;
824
+ props.onComment(value);
825
+ el.value = "";
826
+ };
827
+ const arm = (kind) => {
828
+ setPending({ kind, value: "" });
829
+ if (kind === "archive") {
830
+ window.setTimeout(() => setPending((current) => current?.kind === "archive" ? null : current), 3e3);
831
+ }
832
+ };
833
+ const submitPayload = (kind) => {
834
+ if (pending?.kind !== kind || !pending.value.trim()) return;
835
+ const value = pending.value;
836
+ setPending(null);
837
+ if (kind === "block") props.onAction({ type: "block", taskId: task.id, reason: value });
838
+ else props.onAction({ type: "complete", taskId: task.id, summary: value });
839
+ };
840
+ const submitArchive = () => {
841
+ if (pending?.kind !== "archive") return;
842
+ setPending(null);
843
+ props.onAction({ type: "archive", taskId: task.id });
844
+ };
845
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-kb-detail", children: [
846
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "dsh-kb-detail__header", children: [
847
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": "\u8FD4\u56DE\u4EFB\u52A1\u5217\u8868", onClick: props.onClose, children: "\u2190" }),
848
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dsh-kb-profile dsh-kb-profile--${task.assignee}`, children: task.assignee.toUpperCase() }),
849
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-kb-detail__identity", children: [
850
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: task.title }),
851
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
852
+ task.id,
853
+ " \xB7 ",
854
+ task.mode,
855
+ " \xB7 attempt ",
856
+ task.attempts + 1
857
+ ] })
858
+ ] }),
859
+ props.unreadCount ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { type: "button", className: "dsh-kb-unread", onClick: () => setTab("timeline"), children: [
860
+ props.unreadCount,
861
+ " \u6761\u65B0\u66F4\u65B0"
862
+ ] }) : null,
863
+ !props.readOnly && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-kb-detail__actions", children: [
864
+ task.status === "running" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => arm("complete"), children: "\u5B8C\u6210" }),
865
+ task.status === "running" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => arm("block"), children: "\u963B\u585E" }),
866
+ task.status === "blocked" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => props.onAction({ type: "unblock", taskId: task.id }), children: "\u89E3\u9664\u963B\u585E" }),
867
+ task.status === "failed" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => props.onAction({ type: "retry", taskId: task.id }), children: "\u91CD\u8BD5" }),
868
+ ["done", "failed", "blocked"].includes(task.status) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "data-confirming": pending?.kind === "archive" || void 0, onClick: pending?.kind === "archive" ? submitArchive : () => arm("archive"), children: pending?.kind === "archive" ? "\u786E\u8BA4\u5F52\u6863" : "\u5F52\u6863" }),
869
+ pending?.kind === "complete" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dsh-kb-action-form", children: [
870
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "\u4EA4\u63A5\u6458\u8981", value: pending.value, onChange: (e) => setPending({ kind: "complete", value: e.target.value }), placeholder: "\u4EA4\u63A5\u6458\u8981" }),
871
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", disabled: !pending.value.trim(), onClick: () => submitPayload("complete"), children: "\u786E\u8BA4\u5B8C\u6210" }),
872
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => setPending(null), children: "\u53D6\u6D88" })
873
+ ] }),
874
+ pending?.kind === "block" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dsh-kb-action-form", children: [
875
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { "aria-label": "\u963B\u585E\u539F\u56E0", value: pending.value, onChange: (e) => setPending({ kind: "block", value: e.target.value }), placeholder: "\u963B\u585E\u539F\u56E0" }),
876
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", disabled: !pending.value.trim(), onClick: () => submitPayload("block"), children: "\u786E\u8BA4\u963B\u585E" }),
877
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => setPending(null), children: "\u53D6\u6D88" })
878
+ ] })
879
+ ] })
880
+ ] }),
881
+ props.actionError?.taskId === task.id && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-kb-action-error", role: "alert", children: [
882
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
883
+ "\u64CD\u4F5C\u5931\u8D25\uFF1A",
884
+ props.actionError.message
885
+ ] }),
886
+ props.onRetry && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: props.onRetry, children: "\u91CD\u8BD5\u64CD\u4F5C" })
887
+ ] }),
888
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { role: "tablist", "aria-label": "\u4EFB\u52A1\u8BE6\u60C5", children: TABS.map(([id, label]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", role: "tab", "aria-selected": tab === id, onClick: () => setTab(id), children: label }, id)) }),
889
+ tab === "overview" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { role: "tabpanel", children: [
890
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Workflow \u4E0A\u4E0B\u6587" }),
891
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { children: [
892
+ props.upstream.at(-1)?.title ?? "\u65E0\u4E0A\u6E38",
893
+ " \u2192 ",
894
+ task.title,
895
+ " \u2192 ",
896
+ props.downstream[0]?.title ?? "\u65E0\u4E0B\u6E38"
897
+ ] }),
898
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("dl", { children: [
899
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: "Profile" }),
900
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("dd", { children: [
901
+ task.assignee.toUpperCase(),
902
+ " / ",
903
+ ROLE_NAME[task.assignee]
904
+ ] }),
905
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: "\u72B6\u6001" }),
906
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: statusLabelOf(task.status) }),
907
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: "Chain" }),
908
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: chain.title }),
909
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: "\u4F18\u5148\u7EA7" }),
910
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: task.priority }),
911
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: "\u5FC3\u8DF3" }),
912
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: task.heartbeats.length }),
913
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: "\u4F9D\u8D56" }),
914
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { children: props.parentTasks && props.parentTasks.length > 0 ? props.parentTasks.map((p) => p.title).join(" \u2192 ") : "\u65E0" }),
915
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { children: "\u91CD\u8BD5" }),
916
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("dd", { children: [
917
+ task.attempts,
918
+ " \u6B21",
919
+ task.status === "failed" ? " \xB7 \u53EF\u7ACB\u5373\u91CD\u8BD5" : task.status === "blocked" ? " \xB7 \u89E3\u9664\u963B\u585E\u540E\u53EF\u91CD\u8BD5" : ""
920
+ ] })
921
+ ] }),
922
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: task.body || "\u65E0\u9644\u52A0\u4EFB\u52A1\u63CF\u8FF0" })
923
+ ] }),
924
+ tab === "timeline" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { role: "tabpanel", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ol", { children: timeline.map((event) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { children: [
925
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: event.kind }),
926
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
927
+ "seq ",
928
+ event.seq,
929
+ " \xB7 ",
930
+ event.author,
931
+ " \xB7 ",
932
+ event.at
933
+ ] })
934
+ ] }, event.seq)) }) }),
935
+ tab === "handoff" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { role: "tabpanel", children: [
936
+ props.parentTasks && props.parentTasks.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
937
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "\u7236\u4EFB\u52A1\u539F\u6587" }),
938
+ props.parentTasks.map((p) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { children: [
939
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: p.title }),
940
+ p.body ? `\uFF1A${p.body}` : "\uFF08\u65E0\u6B63\u6587\uFF09"
941
+ ] }, p.id))
942
+ ] }),
943
+ props.parentHandoffs.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
944
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "\u7236\u4EFB\u52A1\u4EA4\u63A5" }),
945
+ props.parentHandoffs.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { children: [
946
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: h.summary }),
947
+ " ",
948
+ formatValue(h.metadata)
949
+ ] }, i))
950
+ ] }),
951
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "\u5F53\u524D\u4EFB\u52A1\u4EA4\u63A5" }),
952
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: handoff?.summary ?? "\u5F53\u524D\u4EFB\u52A1\u5C1A\u65E0\u4EA4\u63A5" }),
953
+ handoff && Object.entries(handoff.metadata).map(([key, value]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { children: [
954
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: key }),
955
+ ": ",
956
+ formatValue(value)
957
+ ] }, key))
958
+ ] }),
959
+ tab === "spec" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { role: "tabpanel", children: [
960
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Problem" }),
961
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: specCard?.sections.problem ?? "\u65E0\u89C4\u683C\u5361" }),
962
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Solution" }),
963
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: specCard?.sections.solution ?? "\u65E0" }),
964
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "User stories" }),
965
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: specCard?.sections.user_stories.join("; ") || "\u65E0" }),
966
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Implementation decisions" }),
967
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: specCard?.sections.impl_decisions.join("; ") || "\u65E0" }),
968
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Testing" }),
969
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: specCard?.sections.testing ?? "\u65E0" }),
970
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Out of scope" }),
971
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: specCard?.sections.out_of_scope ?? "\u65E0" }),
972
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "\u9644\u4EF6" }),
973
+ specCard && specCard.attachments.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "dsh-kb-spec-attachments", children: specCard.attachments.map((a) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { children: [
974
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dsh-kb-spec-attachment__kind", children: a.kind }),
975
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: a.name }),
976
+ " ",
977
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: a.ref })
978
+ ] }, `${a.name}-${a.ref}`)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "\u65E0\u9644\u4EF6" })
979
+ ] }),
980
+ tab === "comments" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { role: "tabpanel", children: [
981
+ comments.map((event) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { children: [
982
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: event.author }),
983
+ " ",
984
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("time", { dateTime: new Date(event.at).toISOString(), children: formatTime(event.at) }),
985
+ ": ",
986
+ String(event.payload["body"])
987
+ ] }, event.seq)),
988
+ !props.readOnly && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
989
+ "input",
990
+ {
991
+ "aria-label": "\u6DFB\u52A0\u8BC4\u8BBA",
992
+ onKeyDown: (e) => {
993
+ if (e.key === "Enter") submitComment(e.target);
994
+ }
995
+ }
996
+ )
997
+ ] })
998
+ ] });
999
+ }
1000
+
1001
+ // client/KanbanBoard.tsx
1002
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1003
+ function defaultCollapsed() {
1004
+ return /* @__PURE__ */ new Set();
1005
+ }
1006
+ function KanbanBoard(props) {
1007
+ const { board } = props.snapshot;
1008
+ const [collapsed, setCollapsed] = (0, import_react5.useState)(() => defaultCollapsed());
1009
+ const [query, setQuery] = (0, import_react5.useState)("");
1010
+ const [statusFilter, setStatusFilter] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
1011
+ const [selectedId, setSelectedId] = (0, import_react5.useState)(() => history.state?.kanbanTaskId ?? null);
1012
+ const [failedAction, setFailedAction] = (0, import_react5.useState)(null);
1013
+ const listRef = (0, import_react5.useRef)(null);
1014
+ const saved = (0, import_react5.useRef)({ collapsed: [], scrollTop: 0 });
1015
+ const snapshotRef = (0, import_react5.useRef)(props.snapshot);
1016
+ snapshotRef.current = props.snapshot;
1017
+ const detailOpenedSeq = (0, import_react5.useRef)(null);
1018
+ (0, import_react5.useEffect)(() => {
1019
+ const onPop = () => {
1020
+ const id = history.state?.kanbanTaskId ?? null;
1021
+ detailOpenedSeq.current = id ? snapshotRef.current.lastSeq : null;
1022
+ setSelectedId(id);
1023
+ if (!id) {
1024
+ setCollapsed(new Set(saved.current.collapsed));
1025
+ requestAnimationFrame(() => {
1026
+ if (listRef.current) listRef.current.scrollTop = saved.current.scrollTop;
1027
+ });
1028
+ }
1029
+ };
1030
+ window.addEventListener("popstate", onPop);
1031
+ return () => window.removeEventListener("popstate", onPop);
1032
+ }, []);
1033
+ (0, import_react5.useEffect)(() => {
1034
+ const onKey = (e) => {
1035
+ if (e.key === "Escape" && selectedId) history.back();
1036
+ };
1037
+ window.addEventListener("keydown", onKey);
1038
+ return () => window.removeEventListener("keydown", onKey);
1039
+ }, [selectedId]);
1040
+ const views = (0, import_react5.useMemo)(
1041
+ () => board ? deriveWorkflowBoard(board, { selectedTaskId: selectedId, now: Date.now(), statusFilter }) : [],
1042
+ [board, selectedId, statusFilter]
1043
+ );
1044
+ const runAction = async (action) => {
1045
+ try {
1046
+ await props.postAction(action);
1047
+ } catch {
1048
+ const a = action;
1049
+ setFailedAction({ taskId: a.taskId ?? "", action });
1050
+ }
1051
+ };
1052
+ const toggleFilter = (f) => {
1053
+ setStatusFilter((current) => {
1054
+ const next = new Set(current);
1055
+ if (next.has(f)) next.delete(f);
1056
+ else next.add(f);
1057
+ return next;
1058
+ });
1059
+ };
1060
+ const toggleChain = (chainId) => {
1061
+ setCollapsed((current) => {
1062
+ const next = new Set(current);
1063
+ if (next.has(chainId)) next.delete(chainId);
1064
+ else next.add(chainId);
1065
+ return next;
1066
+ });
1067
+ };
1068
+ if (!board) {
1069
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-kb-loading", children: "\u52A0\u8F7D\u770B\u677F\u2026" });
1070
+ }
1071
+ const selectedView = selectedId ? views.flatMap((v) => v.tasks).find((v) => v.task.id === selectedId) ?? null : null;
1072
+ if (selectedView) {
1073
+ const task = selectedView.task;
1074
+ const chain = board.chains.get(task.chainId);
1075
+ const specCard = chain.specCardId ? board.specCards.get(chain.specCardId) ?? null : null;
1076
+ const handoff = board.handoffs.get(task.id) ?? null;
1077
+ const parentHandoffs = task.parents.map((id) => board.handoffs.get(id)).filter((h) => h !== void 0);
1078
+ const parentTasks = task.parents.map((id) => board.tasks.get(id)).filter((t) => t !== void 0);
1079
+ const chainTasks = views.find((v) => v.chain.id === task.chainId)?.tasks ?? [];
1080
+ const selectedIndex = chainTasks.findIndex((v) => v.task.id === task.id);
1081
+ const related = chainTasks.filter((v) => v.related).map((v) => v.task);
1082
+ const upstream = related.filter((t) => chainTasks.findIndex((v) => v.task.id === t.id) < selectedIndex);
1083
+ const downstream = related.filter((t) => chainTasks.findIndex((v) => v.task.id === t.id) > selectedIndex);
1084
+ const unreadCount = board.events.filter((e) => e.taskId === task.id && e.seq > (detailOpenedSeq.current ?? props.snapshot.lastSeq)).length;
1085
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1086
+ TaskDrawer,
1087
+ {
1088
+ task,
1089
+ chain,
1090
+ events: board.events,
1091
+ handoff,
1092
+ parentHandoffs,
1093
+ parentTasks,
1094
+ specCard,
1095
+ upstream,
1096
+ downstream,
1097
+ unreadCount,
1098
+ actionError: props.snapshot.actionError,
1099
+ readOnly: task.status === "archived",
1100
+ onRetry: failedAction && failedAction.taskId === task.id ? () => void runAction(failedAction.action) : void 0,
1101
+ onComment: (body) => void runAction({ type: "comment", taskId: task.id, body }),
1102
+ onAction: (action) => void runAction(action),
1103
+ onClose: () => history.back()
1104
+ }
1105
+ );
1106
+ }
1107
+ const openTask = (taskId) => {
1108
+ detailOpenedSeq.current = props.snapshot.lastSeq;
1109
+ saved.current = { collapsed: [...collapsed], scrollTop: listRef.current?.scrollTop ?? 0 };
1110
+ history.pushState({ ...history.state, kanbanTaskId: taskId }, "");
1111
+ setSelectedId(taskId);
1112
+ };
1113
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-kb-tab-body", ref: listRef, children: [
1114
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-kb-toolbar", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1115
+ "input",
1116
+ {
1117
+ "aria-label": "\u641C\u7D22\u94FE\u8DEF",
1118
+ value: query,
1119
+ onChange: (e) => setQuery(e.target.value),
1120
+ placeholder: "\u641C\u7D22\u94FE\u8DEF/\u4EFB\u52A1"
1121
+ }
1122
+ ) }),
1123
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1124
+ WorkflowRail,
1125
+ {
1126
+ chains: views,
1127
+ collapsedChainIds: collapsed,
1128
+ query,
1129
+ statusFilter,
1130
+ onToggleFilter: toggleFilter,
1131
+ onToggleChain: toggleChain,
1132
+ onOpenTask: openTask,
1133
+ onConfirmAudit: (chainId) => void runAction({ type: "confirm-audit", chainId }),
1134
+ onRenameChain: (chainId, title) => void runAction({ type: "rename", chainId, title }),
1135
+ onDeleteChain: async (chainId) => {
1136
+ await props.postAction({ type: "delete", chainId });
1137
+ await props.onResync?.();
1138
+ }
1139
+ }
1140
+ )
1141
+ ] });
1142
+ }
1143
+
1144
+ // client/ConnectionBanner.tsx
1145
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1146
+ function ConnectionBanner(props) {
1147
+ if (props.connection === "ready") return null;
1148
+ const label = props.connection === "loading" ? "\u6B63\u5728\u52A0\u8F7D" : props.connection === "reconnecting" ? "\u6B63\u5728\u91CD\u8FDE" : "\u8FDE\u63A5\u9519\u8BEF";
1149
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `dsh-kb-banner dsh-kb-banner--${props.connection}`, role: "status", children: [
1150
+ label,
1151
+ props.connection === "error" && props.lastSuccessAt ? ` \xB7 \u6700\u540E\u6210\u529F ${new Date(props.lastSuccessAt).toLocaleTimeString()}` : "",
1152
+ props.connection === "error" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "dsh-kb-banner__retry", onClick: props.onRetry, children: "\u91CD\u8BD5" })
1153
+ ] });
1154
+ }
1155
+
1156
+ // client/KanbanTab.tsx
1157
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1158
+ function KanbanTab(props = {}) {
1159
+ const own = (0, import_react6.useMemo)(() => props.store ?? createBoardStore(), [props.store]);
1160
+ const snapshot = useKanbanBoard(own);
1161
+ (0, import_react6.useEffect)(() => {
1162
+ if (props.store) return;
1163
+ void own.start();
1164
+ return () => {
1165
+ own.stop();
1166
+ };
1167
+ }, [own, props.store]);
1168
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-kb-tab", role: "region", "aria-label": "\u770B\u677F", children: [
1169
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ConnectionBanner, { connection: snapshot.connection, lastSuccessAt: snapshot.lastSuccessAt, onRetry: () => void own.retry() }),
1170
+ snapshot.board ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(KanbanBoard, { snapshot, postAction: (action) => own.postAction(action), onResync: () => own.retry() }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-kb-loading", children: "\u52A0\u8F7D\u770B\u677F\u2026" })
1171
+ ] });
1172
+ }
1173
+
1174
+ // client/kanban.css
1175
+ var kanban_default = "/* T32\uFF1A\u5361\u7247/\u8FDE\u63A5\u7EBF\u7B49\u5C40\u90E8\u8FC7\u6E21\u4FDD\u6301 120-180ms\uFF1Bprefers-reduced-motion \u5168\u5C40\u5173\u95ED */\n.dsh-kb-tab {\n /* DSH \u8BBE\u8BA1\u4EE4\u724C\uFF08\u6D45/\u6DF1\u8272\u968F body[data-ds-dark-theme] \u81EA\u52A8\u5207\u6362\uFF09\u2014\u2014\u7981\u6B62\u786C\u7F16\u7801\u989C\u8272 */\n --dsh-kb-surface: var(--dsw-alias-bg-layer-1);\n --dsh-kb-surface-elev: var(--dsw-alias-bg-layer-3);\n --dsh-kb-text-primary: var(--dsw-alias-label-primary);\n --dsh-kb-text-secondary: var(--dsw-alias-label-secondary);\n --dsh-kb-text-tertiary: var(--dsw-alias-label-tertiary);\n --dsh-kb-border: var(--dsw-alias-border-l2);\n --dsh-kb-hover: var(--dsw-alias-interactive-bg-hover);\n --dsh-kb-mask: var(--dsw-alias-bg-mask-3);\n --dsh-kb-input: var(--dsw-specific-input-major);\n --dsh-kb-inline-code: var(--dsw-alias-markdown-inline-code);\n /* \u5927\u5706\u89D2\u5951\u7EA6\uFF08\u5BF9\u9F50 DSH \u5361\u7247 16px\uFF09\uFF0C\u540E\u7EED UI \u4E00\u5F8B\u590D\u7528 */\n --dsh-kb-radius-lg: 16px;\n --dsh-kb-radius-md: 10px;\n --dsh-kb-radius-sm: 8px;\n /* \u72B6\u6001\u8272 \u2192 DSH \u8BED\u4E49\u4EE4\u724C */\n --dsh-kb-complete: var(--dsw-alias-state-success-primary);\n --dsh-kb-active: var(--dsw-alias-state-business-primary);\n --dsh-kb-blocked: var(--dsw-alias-state-error-primary);\n --dsh-kb-pending: currentColor;\n /* \u89D2\u8272\u8272\uFF1ADSH \u6709\u7B49\u4EF7\u4EE4\u724C\u5219\u590D\u7528\uFF1Bp/pt/dt \u65E0 DSH \u7B49\u4EF7\u8272\uFF0C\u4FDD\u7559\u539F\u503C */\n --dsh-kb-v: var(--dsw-alias-state-business-primary);\n --dsh-kb-p: #a855f7;\n --dsh-kb-w: var(--dsw-alias-state-success-primary);\n --dsh-kb-d: var(--dsw-alias-state-warn-primary);\n --dsh-kb-pt: #6366f1; /* \u8BA1\u5212\u8BC4\u5BA1\uFF1A\u975B/\u6D45\u84DD */\n --dsh-kb-dt: #ec4899; /* \u5B9E\u73B0\u8BC4\u5BA1\uFF1A\u73AB\u7EA2 */\n height: 100%;\n width: 100%;\n min-width: 715px; /* \u770B\u677F\u6700\u5C0F\u5BBD\u5EA6 */\n max-width: 780px; /* \u770B\u677F\u6700\u5927\u5BBD\u5EA6 */\n margin-inline: auto;\n display: flex;\n flex-direction: column;\n color: inherit;\n background: var(--dsw-alias-bg-base);\n pointer-events: auto;\n font-size: 13px;\n line-height: 1.45;\n overflow: hidden;\n}\n\n.dsh-kb-banner {\n flex: 0 0 auto;\n padding: 6px 12px;\n font-size: 12px;\n border-bottom: 1px solid var(--dsh-kb-border);\n}\n.dsh-kb-banner--reconnecting,\n.dsh-kb-banner--loading {\n color: var(--dsh-kb-active);\n}\n.dsh-kb-banner--error {\n color: var(--dsh-kb-blocked);\n}\n\n.dsh-kb-tab-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: 8px;\n}\n\n.dsh-kb-loading {\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n color: currentColor;\n opacity: 0.7;\n}\n\n.dsh-kb-toolbar {\n position: sticky;\n top: 0;\n z-index: 1;\n padding-bottom: 8px;\n}\n.dsh-kb-toolbar input {\n width: 100%;\n box-sizing: border-box;\n padding: 6px 10px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: var(--dsh-kb-input);\n color: var(--dsh-kb-text-primary);\n}\n\n.dsh-kb-rail {\n display: flex;\n flex-direction: column;\n}\n/* \u72B6\u6001\u7B5B\u9009 chip \u7EC4\uFF1A\u6267\u884C\u4E2D/\u963B\u585E/\u5931\u8D25/\u5DF2\u5B8C\u6210\uFF08\u591A\u9009\u5E76\u96C6\uFF1B\u7A7A=\u9ED8\u8BA4\u89C6\u56FE\uFF09 */\n.dsh-kb-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 0 0 8px;\n}\n.dsh-kb-filter {\n display: inline-flex;\n align-items: center;\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: transparent;\n color: currentColor;\n font: inherit;\n cursor: pointer;\n user-select: none;\n transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;\n}\n.dsh-kb-filter:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-filter--active {\n border-color: var(--dsh-kb-active);\n color: var(--dsh-kb-active);\n background: color-mix(in srgb, var(--dsh-kb-active) 10%, transparent);\n}\n\n.dsh-kb-chain {\n margin-bottom: 10px;\n}\n.dsh-kb-chain__title {\n display: flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n width: 100%;\n padding: 6px 8px;\n border: 0;\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-chain__title:hover {\n background: var(--dsh-kb-hover);\n}\n/* \u6BCF\u6761\u94FE\u8DEF\u524D\u7684\u6298\u53E0\u7BAD\u5934\uFF08\u4E0E DSH \u6298\u53E0\u4EA4\u4E92\u4E00\u81F4\uFF1A\u7EC6 chevron\uFF0C\u5C55\u5F00\u65F6\u65CB\u8F6C\uFF0C150ms \u8FC7\u6E21\uFF09 */\n.dsh-kb-chain__chevron {\n flex: 0 0 auto;\n width: 7px;\n height: 7px;\n border-top: 1.5px solid currentColor;\n border-right: 1.5px solid currentColor;\n transform: rotate(45deg);\n opacity: 0.7;\n transition: transform 150ms ease;\n}\n.dsh-kb-chain__title[aria-expanded='true'] .dsh-kb-chain__chevron {\n transform: rotate(135deg);\n}\n.dsh-kb-chain__name {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-chain__meta {\n flex: 0 0 auto;\n white-space: nowrap;\n opacity: 0.7;\n}\n.dsh-kb-chain__warning {\n padding: 4px 8px;\n margin: 2px 0 6px;\n border-left: 3px solid var(--dsh-kb-blocked);\n color: var(--dsh-kb-blocked);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n/* D23\uFF1A\u94FE\u5B8C\u6210\u9A8C\u6536\u6838\u5BF9\u8B66\u544A\u884C\uFF08completed \u94FE\u672A\u786E\u8BA4\u524D\u963B\u585E\u6700\u7EC8\u6C47\u62A5\uFF09 */\n.dsh-kb-chain__audit {\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: normal;\n}\n.dsh-kb-chain__audit span {\n flex: 1 1 auto;\n overflow: hidden;\n}\n.dsh-kb-audit-confirm {\n flex: 0 0 auto;\n border: 1px solid var(--dsh-kb-blocked);\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: var(--dsh-kb-blocked);\n padding: 2px 10px;\n cursor: pointer;\n font: inherit;\n transition: background-color 150ms ease, color 150ms ease;\n}\n.dsh-kb-audit-confirm:hover {\n background: var(--dsh-kb-blocked);\n color: #fff;\n}\n\n.dsh-kb-nodes {\n list-style: none;\n margin: 0;\n padding: 0 0 0 12px;\n display: flex;\n flex-direction: column;\n gap: 0;\n}\n.dsh-kb-node {\n position: relative;\n padding-left: 12px;\n}\n.dsh-kb-node::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--dsh-kb-pending);\n transition: background-color 150ms ease;\n}\n.dsh-kb-node--complete::before { background: var(--dsh-kb-complete); }\n.dsh-kb-node--active::before { background: var(--dsh-kb-active); }\n.dsh-kb-node--pending::before {\n background: repeating-linear-gradient(to bottom, currentColor 0 3px, transparent 3px 6px);\n}\n.dsh-kb-node--blocked::before {\n background: repeating-linear-gradient(to bottom, var(--dsh-kb-blocked) 0 4px, transparent 4px 8px);\n}\n\n.dsh-kb-task {\n display: grid;\n grid-template-columns: 22px 1fr auto;\n grid-template-rows: auto auto;\n gap: 2px 8px;\n width: 100%;\n box-sizing: border-box;\n padding: 6px 8px;\n margin: 4px 0;\n border: 1px solid transparent;\n border-radius: var(--dsh-kb-radius-lg);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: border-color 150ms ease, background-color 150ms ease, opacity 150ms ease;\n}\n.dsh-kb-task:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-task[data-selected],\n.dsh-kb-task--active {\n border-color: var(--dsh-kb-active);\n}\n/* T32\uFF1A\u5B8C\u6210\u4EFB\u52A1\u4FDD\u7559\u5728\u8F68\u9053\u4F46\u89C6\u89C9\u964D\u6743\uFF1B\u72B6\u6001\u8272\u4E0D\u53D8\u3001\u4E0D\u6574\u5361\u67D3\u8272 */\n.dsh-kb-task--complete {\n opacity: 0.72;\n}\n.dsh-kb-task__title {\n grid-column: 2;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task__status {\n grid-column: 3;\n color: currentColor;\n opacity: 0.75;\n}\n.dsh-kb-task__meta {\n grid-column: 2 / 4;\n opacity: 0.6;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task__warn {\n grid-column: 2 / 4;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--dsh-kb-blocked);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task__warn svg {\n flex: 0 0 auto;\n}\n.dsh-kb-task__warn span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task--complete .dsh-kb-task__status { color: var(--dsh-kb-complete); }\n.dsh-kb-task--blocked .dsh-kb-task__status { color: var(--dsh-kb-blocked); }\n/* D17\uFF1A\u9009\u4E2D\u4EFB\u52A1\u6240\u5728\u94FE\u8DEF\u7684\u4E0A\u6E38/\u5F53\u524D/\u4E0B\u6E38\u8DEF\u5F84\u5361\u9AD8\u4EAE\uFF1B\u4E0D\u6539\u53D8\u8FDE\u63A5\u7EBF\u8BED\u4E49\uFF0C\u9009\u4E2D\u5361\u4FDD\u6301\u5F3A\u8FB9\u6846 */\n.dsh-kb-task--related:not(.dsh-kb-task--active) {\n border-color: color-mix(in srgb, var(--dsh-kb-active) 35%, transparent);\n background: color-mix(in srgb, var(--dsh-kb-active) 6%, transparent);\n}\n\n.dsh-kb-empty {\n padding: 24px 12px;\n text-align: center;\n color: var(--dsh-kb-text-secondary);\n border: 1px dashed var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-lg);\n}\n\n.dsh-kb-profile {\n grid-row: 1 / 3;\n align-self: start;\n width: 20px;\n height: 20px;\n border-radius: var(--dsh-kb-radius-sm);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 700;\n color: #fff;\n}\n.dsh-kb-profile--v { background: var(--dsh-kb-v); }\n.dsh-kb-profile--p { background: var(--dsh-kb-p); }\n.dsh-kb-profile--w { background: var(--dsh-kb-w); }\n.dsh-kb-profile--d { background: var(--dsh-kb-d); }\n.dsh-kb-profile--pt { background: var(--dsh-kb-pt); }\n.dsh-kb-profile--dt { background: var(--dsh-kb-dt); }\n\n/* T27\uFF1A\u4EFB\u52A1\u5361\u5F39\u7A97\uFF08\u539F\u4F4D\u8BE6\u60C5\uFF09\u2014\u2014DSH \u9762\u677F\u98CE\u683C\uFF1A\u5927\u5706\u89D2 + \u62AC\u5347\u8868\u9762 + \u6D45/\u6DF1\u8272\u4EE4\u724C */\n.dsh-kb-detail {\n display: flex;\n flex-direction: column;\n min-height: calc(100% - 16px);\n margin: 8px;\n padding: 12px 16px;\n box-sizing: border-box;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-lg);\n background: var(--dsh-kb-surface);\n color: var(--dsh-kb-text-primary);\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n}\n.dsh-kb-detail__header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--dsh-kb-border);\n}\n.dsh-kb-detail__header > button:not(.dsh-kb-unread) {\n flex: 0 0 auto;\n width: 28px;\n height: 28px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n cursor: pointer;\n font: inherit;\n line-height: 1;\n transition: background-color 150ms ease;\n}\n.dsh-kb-detail__header > button:not(.dsh-kb-unread):hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-detail__identity {\n flex: 1 1 auto;\n min-width: 0;\n}\n.dsh-kb-detail__identity strong,\n.dsh-kb-detail__identity span {\n display: block;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-detail__actions {\n display: flex;\n gap: 4px;\n flex: 0 0 auto;\n}\n.dsh-kb-detail__actions button {\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n padding: 4px 10px;\n cursor: pointer;\n font: inherit;\n transition: border-color 150ms ease, color 150ms ease, background-color 150ms ease;\n}\n.dsh-kb-detail__actions button:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-detail__actions button[data-confirming='true'] {\n border-color: var(--dsh-kb-blocked);\n color: var(--dsh-kb-blocked);\n}\n.dsh-kb-detail__actions button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-kb-action-form {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n.dsh-kb-action-form input {\n width: 140px;\n padding: 3px 8px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-sm);\n background: var(--dsh-kb-input);\n color: var(--dsh-kb-text-primary);\n font: inherit;\n font-size: 12px;\n}\n.dsh-kb-unread {\n flex: 0 0 auto;\n border: 1px solid var(--dsh-kb-active);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsh-kb-active) 12%, transparent);\n color: var(--dsh-kb-active);\n padding: 2px 8px;\n font: inherit;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n.dsh-kb-action-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n margin: 4px 0;\n border-left: 3px solid var(--dsh-kb-blocked);\n color: var(--dsh-kb-blocked);\n font-size: 12px;\n}\n.dsh-kb-action-error span {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-action-error button {\n flex: 0 0 auto;\n border: 1px solid currentColor;\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: inherit;\n padding: 1px 6px;\n cursor: pointer;\n font: inherit;\n}\n.dsh-kb-detail [role='tablist'] {\n display: flex;\n gap: 4px;\n padding: 8px 0;\n border-bottom: 1px solid var(--dsh-kb-border);\n}\n.dsh-kb-detail [role='tab'] {\n border: 0;\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: var(--dsh-kb-text-secondary);\n padding: 4px 10px;\n cursor: pointer;\n transition: background-color 150ms ease, color 150ms ease;\n}\n.dsh-kb-detail [role='tab']:hover {\n color: var(--dsh-kb-text-primary);\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-detail [role='tab'][aria-selected='true'] {\n background: var(--dsh-kb-hover);\n color: var(--dsh-kb-text-primary);\n}\n.dsh-kb-detail [role='tabpanel'] {\n padding: 8px 0;\n overflow: auto;\n}\n.dsh-kb-detail h4 {\n margin: 8px 0 4px;\n font-size: 12px;\n text-transform: none;\n color: var(--dsh-kb-text-secondary);\n}\n.dsh-kb-detail code {\n background: var(--dsh-kb-inline-code);\n border-radius: var(--dsh-kb-radius-sm);\n padding: 1px 4px;\n}\n.dsh-kb-detail dl {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 4px 8px;\n margin: 8px 0;\n}\n.dsh-kb-detail dt { opacity: 0.7; color: var(--dsh-kb-text-tertiary); }\n.dsh-kb-detail dd { margin: 0; }\n.dsh-kb-detail time { opacity: 0.6; font-size: 12px; margin-inline-end: 4px; }\n\n.dsh-kb-spec-attachments {\n list-style: none;\n margin: 4px 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-kb-spec-attachments li {\n display: flex;\n align-items: baseline;\n gap: 6px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-spec-attachment__kind {\n font-size: 11px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-sm);\n padding: 0 4px;\n opacity: 0.8;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dsh-kb-tab * {\n transition-duration: 0ms !important;\n }\n}\n\n/* \u6574\u94FE\u5220\u9664\u6309\u94AE\uFF1A\u9ED8\u8BA4\u9690\u85CF\uFF0Chover \u9700\u6C42\u6807\u9898\u884C\u65F6\u51FA\u73B0\uFF08GUI \u4EC5 human\uFF0C\u4E8C\u6B21\u786E\u8BA4\uFF09 */\n.dsh-kb-chain__delete {\n flex: 0 0 auto;\n border: 0;\n background: transparent;\n color: currentColor;\n opacity: 0;\n font: inherit;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n transition: opacity 150ms ease, color 150ms ease;\n}\n.dsh-kb-chain__title:hover .dsh-kb-chain__delete,\n.dsh-kb-chain__delete:focus-visible { opacity: 0.7; }\n.dsh-kb-chain__delete:hover { opacity: 1; color: var(--dsh-kb-blocked); }\n/* \u5220\u9664\u4E8C\u6B21\u786E\u8BA4\u5F39\u7A97\uFF08\u590D\u7528 rename \u5F39\u7A97\u5916\u58F3/\u5706\u89D2\u5951\u7EA6\uFF09 */\n.dsh-kb-delete-modal__text {\n color: var(--dsh-kb-text-secondary);\n font-size: 13px;\n line-height: 1.5;\n}\n.dsh-kb-delete-modal__error {\n color: var(--dsh-kb-blocked);\n font-size: 12px;\n}\n.dsh-kb-rename-modal__actions .dsh-kb-delete-confirm {\n border-color: transparent;\n background: var(--dsw-alias-state-error-primary);\n color: #fff;\n}\n/* T7\uFF1A\u94FE\u6807\u9898\u6539\u540D\u94C5\u7B14\u6309\u94AE + \u8F7B\u91CF\u5F39\u7A97\uFF08GUI \u4EC5 human\uFF1B\u89D2\u8272\u5361\u4E0D\u53EF\u6539\u540D\uFF09 */\n.dsh-kb-chain__rename {\n flex: 0 0 auto;\n border: 0;\n background: transparent;\n color: currentColor;\n opacity: 0.55;\n font: inherit;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n}\n.dsh-kb-chain__rename:hover { opacity: 1; }\n.dsh-kb-task__status-row {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.dsh-kb-rename-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n background: var(--dsh-kb-mask);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.dsh-kb-rename-modal {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 280px;\n padding: 16px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-lg);\n background: var(--dsh-kb-surface-elev);\n color: var(--dsh-kb-text-primary);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n}\n.dsh-kb-rename-modal__label {\n color: var(--dsh-kb-text-primary);\n font-size: 13px;\n font-weight: 600;\n}\n.dsh-kb-rename-modal__input {\n padding: 6px 10px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: var(--dsh-kb-input);\n color: var(--dsh-kb-text-primary);\n font: inherit;\n}\n.dsh-kb-rename-modal__input:focus {\n outline: none;\n border-color: var(--dsh-kb-active);\n}\n.dsh-kb-rename-modal__actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-kb-rename-modal__actions button {\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n font: inherit;\n padding: 4px 14px;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-rename-modal__actions button:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-rename-modal__actions .dsh-kb-rename-save {\n border-color: transparent;\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n";
1176
+
1177
+ // client/index.ts
1178
+ var name = "kanban-board";
1179
+ var inject = ["slots"];
1180
+ function apply(ctx) {
1181
+ let style = null;
1182
+ if (typeof document !== "undefined") {
1183
+ style = document.head.querySelector("style[data-dsh-swarm]");
1184
+ if (!style) {
1185
+ style = document.createElement("style");
1186
+ style.setAttribute("data-dsh-swarm", "");
1187
+ style.textContent = kanban_default;
1188
+ document.head.appendChild(style);
1189
+ }
1190
+ }
1191
+ ctx.slots.inject(
1192
+ "conversation.view",
1193
+ () => ctx.slots.register(
1194
+ { name: "conversation.view", id: "kanban", order: 20, label: "\u770B\u677F" },
1195
+ KanbanTab
1196
+ )
1197
+ );
1198
+ return () => {
1199
+ if (style) style.remove();
1200
+ };
1201
+ }
1202
+
1203
+ return module.exports;
1204
+ }
1205
+ });