dsh-punky-swarm 0.3.6

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 (101) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/LICENSE +661 -0
  3. package/README.en.md +281 -0
  4. package/README.md +281 -0
  5. package/cordis.patch.yml +59 -0
  6. package/lib/acps/certs.js +310 -0
  7. package/lib/acps/discovery-client.js +283 -0
  8. package/lib/acps/registry-client.js +527 -0
  9. package/lib/acps/server.js +302 -0
  10. package/lib/aip/agent-descriptor.js +170 -0
  11. package/lib/aip/identity.js +469 -0
  12. package/lib/aip/tool-descriptor.js +93 -0
  13. package/lib/api.js +266 -0
  14. package/lib/artifact-types.js +48 -0
  15. package/lib/assembly/audit-blind-review.js +170 -0
  16. package/lib/assembly/schema.js +253 -0
  17. package/lib/assembly.js +41 -0
  18. package/lib/assets.js +103 -0
  19. package/lib/bridge/trajectory.js +202 -0
  20. package/lib/client.js +704 -0
  21. package/lib/comms/acps-bridge.js +219 -0
  22. package/lib/comms/aip-format.js +141 -0
  23. package/lib/comms/budget.js +124 -0
  24. package/lib/comms/mailbox.js +221 -0
  25. package/lib/comms/topic-runtime.js +76 -0
  26. package/lib/comms/topic.js +115 -0
  27. package/lib/discovery/filter.js +199 -0
  28. package/lib/discovery/schema.js +157 -0
  29. package/lib/discovery/service.js +251 -0
  30. package/lib/hot/config-watch.js +252 -0
  31. package/lib/index.js +433 -0
  32. package/lib/lock.js +69 -0
  33. package/lib/panel/batch-detail.js +163 -0
  34. package/lib/panel/batch-list.js +84 -0
  35. package/lib/panel/locales.js +93 -0
  36. package/lib/panel/main.js +270 -0
  37. package/lib/panel/stream.js +348 -0
  38. package/lib/panel/theme.js +103 -0
  39. package/lib/panel/widgets.js +63 -0
  40. package/lib/schema.d.ts +192 -0
  41. package/lib/schema.js +259 -0
  42. package/lib/schema.ts +398 -0
  43. package/lib/state/archive.js +174 -0
  44. package/lib/state/command-exec.js +148 -0
  45. package/lib/state/constants.js +37 -0
  46. package/lib/state/corrupt-registry.js +103 -0
  47. package/lib/state/event-types.js +65 -0
  48. package/lib/state/gates.d.ts +226 -0
  49. package/lib/state/gates.js +428 -0
  50. package/lib/state/gates.ts +348 -0
  51. package/lib/state/machine-rules.d.ts +14 -0
  52. package/lib/state/machine-rules.js +76 -0
  53. package/lib/state/machine-rules.ts +88 -0
  54. package/lib/state/machine.js +68 -0
  55. package/lib/state/resume.js +201 -0
  56. package/lib/state/schema-v3.d.ts +10 -0
  57. package/lib/state/schema-v3.js +57 -0
  58. package/lib/state/schema-v3.ts +80 -0
  59. package/lib/state/store.js +607 -0
  60. package/lib/state/task-utils.js +40 -0
  61. package/lib/tools/core.js +323 -0
  62. package/lib/tools/git-utils.js +44 -0
  63. package/lib/tools/lane-tools.js +455 -0
  64. package/lib/tools/log-tools.js +173 -0
  65. package/lib/tools/mailbox-tools.js +121 -0
  66. package/lib/tools/merge-agent.js +127 -0
  67. package/lib/tools/register.js +85 -0
  68. package/lib/tools/shared.js +32 -0
  69. package/lib/types/contracts.d.ts +277 -0
  70. package/lib/types/contracts.js +17 -0
  71. package/lib/types/contracts.ts +279 -0
  72. package/lib/verify/evidence.js +249 -0
  73. package/lib/verify/gate.js +161 -0
  74. package/lib/verify/mount.js +48 -0
  75. package/lib/verify/selector.js +101 -0
  76. package/lib/watch/lane-heartbeat.js +333 -0
  77. package/lib/wave-plan.d.ts +48 -0
  78. package/lib/wave-plan.js +473 -0
  79. package/lib/wave-plan.ts +465 -0
  80. package/package.json +71 -0
  81. package/presets/jiufeng/NOTICE +5 -0
  82. package/presets/jiufeng/agent.cordis.yml +275 -0
  83. package/presets/jiufeng/preset.yml +3 -0
  84. package/skills/jiufeng-team/SKILL.md +214 -0
  85. package/skills/jiufeng-team/references/constitution.md +62 -0
  86. package/skills/jiufeng-team/references/roles/coder.md +17 -0
  87. package/skills/jiufeng-team/references/roles/coordinator.md +13 -0
  88. package/skills/jiufeng-team/references/roles/designer.md +14 -0
  89. package/skills/jiufeng-team/references/roles/doc-manager.md +13 -0
  90. package/skills/jiufeng-team/references/roles/manager.md +22 -0
  91. package/skills/jiufeng-team/references/roles/reviewer.md +19 -0
  92. package/skills/jiufeng-team/references/roles/supervisor.md +13 -0
  93. package/skills/jiufeng-team/references/roles/tester.md +13 -0
  94. package/skills/jiufeng-team/references/templates/call-chain-matrix-template.md +121 -0
  95. package/skills/jiufeng-team/references/templates/endpoint-behavior-template.md +60 -0
  96. package/skills/jiufeng-team/references/templates/gap-list.json +33 -0
  97. package/skills/jiufeng-team/references/templates/leader-decision-pack.md +92 -0
  98. package/skills/jiufeng-team/references/templates/plan-template.md +61 -0
  99. package/skills/jiufeng-team/references/templates/success-pattern-seeds.md +126 -0
  100. package/skills/jiufeng-team/references/workflow.md +98 -0
  101. package/skills/jiufeng-team/scripts/check-v3-density.mjs +98 -0
package/lib/client.js ADDED
@@ -0,0 +1,704 @@
1
+ /*
2
+ Copyright (C) 2025-2026 Punky
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ window.__ModuleLoader__.load({
19
+ id: "dsh-punky-swarm",
20
+ factory: (require) => {
21
+ var module = { exports: {} };
22
+ var exports = module.exports;
23
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
24
+ "use strict";
25
+ // dsh-punky-swarm 蟛蜞集群监控面板客户端(只读):注册 conversation.view 第三分页(对话/轨迹/蟛蜞集群)
26
+ const React = require('react');
27
+ const { useState, useEffect } = React;
28
+
29
+ // ===== [panel-segment] locales.js =====
30
+ const zh = {
31
+ "view.cluster": "蟛蜞集群",
32
+ "live": "实时",
33
+ "refresh.auto": "3s 自动刷新",
34
+ "stream.live": "实时推送",
35
+ "stream.fallback": "已降级 3s 轮询",
36
+ "updated": "更新于",
37
+ "stat.total": "总批次",
38
+ "stat.running": "运行中",
39
+ "stat.done": "已完结",
40
+ "stat.issues": "异常",
41
+ "batch.title": "批次列表",
42
+ "batch.progress": "进度",
43
+ "batch.release": "可自动放行",
44
+ "batch.done": "已完结",
45
+ "lanes": "子任务",
46
+ "events": "事件",
47
+ "concurrency": "并发",
48
+ "event.timeline": "事件时间线",
49
+ "mailbox.title": "收件箱(只读)",
50
+ "mailbox.inbox": "派发",
51
+ "mailbox.broadcast": "广播",
52
+ "mailbox.hint": "暂无未读",
53
+ "empty": "暂无批次",
54
+ "empty.hint": "创建 wave_plan 批次后在此查看",
55
+ "load.error": "加载失败",
56
+ "gate.missing": "缺",
57
+ "attempt": "返工",
58
+ "upgrade": "升级人工",
59
+ "task.deps": "依赖",
60
+ "task.layer": "层",
61
+ "gate.consume": "消费缺失",
62
+ "gate.outputs": "产物缺失",
63
+ "gate.produce": "产出缺失",
64
+ "gate.contract": "契约问题"
65
+ };
66
+ const en = {
67
+ "view.cluster": "Punky swarm",
68
+ "live": "Live",
69
+ "refresh.auto": "3s auto refresh",
70
+ "stream.live": "Live push",
71
+ "stream.fallback": "3s polling fallback",
72
+ "updated": "updated",
73
+ "stat.total": "Batches",
74
+ "stat.running": "Running",
75
+ "stat.done": "Done",
76
+ "stat.issues": "Issues",
77
+ "batch.title": "Batches",
78
+ "batch.progress": "progress",
79
+ "batch.release": "auto-release",
80
+ "batch.done": "done",
81
+ "lanes": "lanes",
82
+ "events": "events",
83
+ "concurrency": "concurrency",
84
+ "event.timeline": "Event timeline",
85
+ "mailbox.title": "Inbox (read-only)",
86
+ "mailbox.inbox": "dispatch",
87
+ "mailbox.broadcast": "broadcast",
88
+ "mailbox.hint": "nothing unread",
89
+ "empty": "No batches",
90
+ "empty.hint": "Create a wave_plan batch to see it here",
91
+ "load.error": "Load failed",
92
+ "gate.missing": "missing",
93
+ "attempt": "rework",
94
+ "upgrade": "escalate",
95
+ "task.deps": "deps",
96
+ "task.layer": "layer",
97
+ "gate.consume": "consume missing",
98
+ "gate.outputs": "outputs missing",
99
+ "gate.produce": "produce missing",
100
+ "gate.contract": "contract problem"
101
+ };
102
+
103
+ // module-level translator: zh-first, en fallback (matches the original panel behavior)
104
+ function tt(k) { return zh[k] || en[k] || k; }
105
+ // ===== [panel-segment] theme.js =====
106
+ // ================= theme-aware palette =================
107
+ // DSH 主题开关:body[data-ds-dark-theme](深色)vs 默认浅色。令牌 var(--dsw-alias-*)
108
+ // 会随主题自动切换;这里只负责「令牌缺失时」的兜底色板,并按主题切换重渲染。
109
+ let CURRENT_THEME = 'dark';
110
+ function detectTheme() {
111
+ try {
112
+ if (typeof document !== 'undefined' && document.body) {
113
+ return document.body.hasAttribute('data-ds-dark-theme') ? 'dark' : 'light';
114
+ }
115
+ } catch {}
116
+ return 'dark';
117
+ }
118
+ try { CURRENT_THEME = detectTheme(); } catch {}
119
+
120
+ const P = {
121
+ light: {
122
+ card: '#ffffff', border: '#dfe5ee',
123
+ text: '#1f2937', text2: '#475569', text3: '#64748b', dim: '#64748b',
124
+ accent: '#3b82f6', success: '#15803d', warn: '#b45309', error: '#dc2626', info: '#2563eb',
125
+ skeleton: '#eef2f7', selBg: 'rgba(59,130,246,0.10)',
126
+ chipPending: 'rgba(100,116,139,0.20)', chipRunning: 'rgba(180,83,9,0.12)',
127
+ chipReview: 'rgba(37,99,235,0.10)', chipMerged: 'rgba(21,128,61,0.12)',
128
+ chipFailed: 'rgba(220,38,38,0.10)', chipSkipped: 'rgba(100,116,139,0.16)',
129
+ chipConflict: 'rgba(234,88,12,0.12)', chipIdle: 'rgba(100,116,139,0.14)',
130
+ haloSuccess: 'rgba(21,128,61,0.16)', haloWarn: 'rgba(180,83,9,0.16)',
131
+ gateBg: 'rgba(180,83,9,0.12)', escBg: 'rgba(234,88,12,0.12)', escFg: '#c2410c'
132
+ },
133
+ dark: {
134
+ card: '#141d31', border: '#26304a',
135
+ text: '#e6ebf4', text2: '#a8b3c7', text3: '#7f8ca3', dim: '#8b96ab',
136
+ accent: '#4f8cff', success: '#3fb950', warn: '#d29922', error: '#f85149', info: '#58a6ff',
137
+ skeleton: '#1d2740', selBg: 'rgba(79,140,255,0.12)',
138
+ chipPending: 'rgba(127,140,163,0.22)', chipRunning: 'rgba(210,153,34,0.16)',
139
+ chipReview: 'rgba(88,166,255,0.16)', chipMerged: 'rgba(63,185,80,0.16)',
140
+ chipFailed: 'rgba(248,81,73,0.16)', chipSkipped: 'rgba(127,140,163,0.18)',
141
+ chipConflict: 'rgba(224,104,46,0.18)', chipIdle: 'rgba(127,140,163,0.16)',
142
+ haloSuccess: 'rgba(63,185,80,0.18)', haloWarn: 'rgba(210,153,34,0.18)',
143
+ gateBg: 'rgba(210,153,34,0.16)', escBg: 'rgba(224,104,46,0.16)', escFg: '#e0682e'
144
+ }
145
+ };
146
+ const pal = () => P[CURRENT_THEME] || P.dark;
147
+
148
+ // 令牌优先、兜底随主题:getter 在每次渲染时求值
149
+ const T = {
150
+ get card() { return 'var(--dsw-alias-bg-layer-3, ' + pal().card + ')'; },
151
+ get border() { return 'var(--dsw-alias-border-l1, ' + pal().border + ')'; },
152
+ get text() { return 'var(--dsw-alias-label-primary, ' + pal().text + ')'; },
153
+ get text2() { return 'var(--dsw-alias-label-secondary, ' + pal().text2 + ')'; },
154
+ get text3() { return 'var(--dsw-alias-label-tertiary, ' + pal().text3 + ')'; },
155
+ get dim() { return 'var(--dsw-alias-label-dimmed, ' + pal().dim + ')'; },
156
+ get accent() { return 'var(--dsw-alias-brand-primary, ' + pal().accent + ')'; },
157
+ get success() { return 'var(--dsw-alias-state-success-primary, ' + pal().success + ')'; },
158
+ get warn() { return 'var(--dsw-alias-state-warn-primary, ' + pal().warn + ')'; },
159
+ get error() { return 'var(--dsw-alias-state-error-primary, ' + pal().error + ')'; },
160
+ get info() { return 'var(--dsw-alias-state-business-primary, ' + pal().info + ')'; },
161
+ get skeleton() { return 'var(--dsw-alias-bg-skeleton, ' + pal().skeleton + ')'; },
162
+ get selBg() { return 'var(--dsw-alias-interactive-bg-active, ' + pal().selBg + ')'; },
163
+ get font() { return 'var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif)'; },
164
+ get mono() { return 'var(--dsw-font-mono, "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace)'; }
165
+ };
166
+
167
+ function chip(fgKey, bgKey, fgVar) {
168
+ return {
169
+ get fg() { return fgVar ? 'var(' + fgVar + ', ' + pal()[fgKey] + ')' : pal()[fgKey]; },
170
+ get bg() { return pal()[bgKey]; }
171
+ };
172
+ }
173
+ const STATE = {
174
+ pending: chip('text2', 'chipPending', '--dsw-alias-label-secondary'),
175
+ running: chip('warn', 'chipRunning', '--dsw-alias-state-warn-primary'),
176
+ review: chip('info', 'chipReview', '--dsw-alias-state-business-primary'),
177
+ merged: chip('success', 'chipMerged', '--dsw-alias-state-success-primary'),
178
+ failed: chip('error', 'chipFailed', '--dsw-alias-state-error-primary'),
179
+ skipped: chip('text2', 'chipSkipped', '--dsw-alias-label-secondary'),
180
+ conflict: chip('escFg', 'chipConflict', '--dsw-alias-state-warn-primary'),
181
+ idle: chip('text2', 'chipIdle', '--dsw-alias-label-secondary')
182
+ };
183
+ const PHASE = {
184
+ planning: chip('text2', 'chipSkipped', '--dsw-alias-label-secondary'),
185
+ running: chip('warn', 'chipRunning', '--dsw-alias-state-warn-primary'),
186
+ paused: chip('info', 'chipReview', '--dsw-alias-state-business-primary'),
187
+ aborted: chip('error', 'chipFailed', '--dsw-alias-state-error-primary'),
188
+ complete: chip('success', 'chipMerged', '--dsw-alias-state-success-primary')
189
+ };
190
+ // =======================================================
191
+ // ===== [panel-segment] widgets.js =====
192
+ function Chip({ st, children, style }) {
193
+ const c = st || chip('text2', 'chipPending', '--dsw-alias-label-secondary');
194
+ return React.createElement('span', {
195
+ style: Object.assign({
196
+ display: 'inline-flex', alignItems: 'center', gap: 4,
197
+ color: c.fg, background: c.bg,
198
+ borderRadius: 999, padding: '1px 8px', fontSize: 10.5,
199
+ fontWeight: 600, letterSpacing: 0.2, lineHeight: '16px',
200
+ fontFamily: T.mono, whiteSpace: 'nowrap'
201
+ }, style || null)
202
+ }, children);
203
+ }
204
+
205
+ function Dot({ color }) {
206
+ return React.createElement('span', { style: { width: 6, height: 6, borderRadius: 999, background: color, display: 'inline-block' } });
207
+ }
208
+
209
+ function Progress({ value, color, height }) {
210
+ const h = height || 4;
211
+ return React.createElement('div', {
212
+ style: { height: h, borderRadius: 999, background: T.skeleton, overflow: 'hidden', flex: 1 }
213
+ }, React.createElement('div', {
214
+ style: { width: Math.max(0, Math.min(100, value)) + '%', height: '100%', borderRadius: 999, background: color || T.accent, transition: 'width .3s ease' }
215
+ }));
216
+ }
217
+
218
+ function Stat({ label, value, color }) {
219
+ return React.createElement('div', {
220
+ style: Object.assign({}, cardBase, { padding: '6px 10px', minWidth: 0, flex: 1, display: 'flex', flexDirection: 'column', gap: 2 })
221
+ },
222
+ React.createElement('span', { style: { fontSize: 10.5, color: T.text3, lineHeight: 1.2 } }, label),
223
+ React.createElement('span', { style: { fontSize: 17, fontWeight: 700, fontFamily: T.mono, color: color || T.text, lineHeight: 1.15, fontVariantNumeric: 'tabular-nums' } }, value)
224
+ );
225
+ }
226
+
227
+ function SectionTitle({ children }) {
228
+ return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 } },
229
+ React.createElement('span', { style: { width: 3, height: 12, borderRadius: 999, background: T.accent } }),
230
+ React.createElement('span', { style: { fontSize: 11.5, fontWeight: 600, color: T.text, letterSpacing: 0.3 } }, children)
231
+ );
232
+ }
233
+
234
+ function Skeleton({ h, w, style }) {
235
+ return React.createElement('div', { className: 'psw-shimmer', style: Object.assign({ height: h || 12, width: w || '100%', borderRadius: 6 }, style || null) });
236
+ }
237
+ // ===== [panel-segment] batch-list.js =====
238
+ function BatchList({ batches, selected, onSelect, loading }) {
239
+ return React.createElement('div', {
240
+ className: 'psw-list psw-scroll',
241
+ style: { width: 276, flex: 'none', display: 'flex', flexDirection: 'column', gap: 8, overflowY: 'auto', paddingRight: 4 }
242
+ },
243
+ React.createElement(SectionTitle, null, tt('batch.title') + ' · ' + (batches ? batches.length : 0)),
244
+ loading && !batches
245
+ ? React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 8 } },
246
+ React.createElement(Skeleton, { h: 52 }),
247
+ React.createElement(Skeleton, { h: 52 }),
248
+ React.createElement(Skeleton, { h: 52 })
249
+ )
250
+ : batches && batches.length
251
+ ? batches.map((b) => {
252
+ const st = PHASE[b.phase] || PHASE.planning;
253
+ const vals = Object.values(b.lanes || {});
254
+ const done = vals.filter((s) => TERMINAL.indexOf(s) >= 0).length;
255
+ const total = vals.length;
256
+ const sel = selected && (typeof selected === 'object'
257
+ ? selected.session === b.session && selected.batchId === b.batchId
258
+ : selected === b.batchId);
259
+ return React.createElement('button', {
260
+ key: b.session ? b.session + ':' + b.batchId : b.batchId,
261
+ type: 'button',
262
+ className: 'psw-btn',
263
+ onClick: () => onSelect(b),
264
+ 'aria-pressed': sel,
265
+ style: Object.assign({}, cardBase, {
266
+ textAlign: 'left', cursor: 'pointer', padding: '8px 10px',
267
+ display: 'flex', flexDirection: 'column', gap: 6, width: '100%',
268
+ ...(sel ? { borderColor: T.accent, boxShadow: '0 0 0 1px ' + T.accent } : {}),
269
+ background: sel ? T.selBg : T.card
270
+ })
271
+ },
272
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 } },
273
+ React.createElement(Chip, { st: st }, b.phase),
274
+ React.createElement('span', { style: { flex: 1, fontWeight: 700, fontSize: 12.5, fontFamily: T.mono, color: T.text, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, b.batchId)
275
+ ),
276
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
277
+ React.createElement(Progress, { value: total ? (done / total) * 100 : 0, color: st.fg, height: 3 }),
278
+ React.createElement('span', { style: { fontSize: 10.5, color: T.text3, fontFamily: T.mono, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' } }, done + '/' + total)
279
+ ),
280
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 10.5, color: T.text3 } },
281
+ b.sessionShort && !b.isOwnSession
282
+ ? React.createElement('span', { title: b.session, style: { fontFamily: T.mono, opacity: 0.9, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 110 } }, b.sessionShort + ' ·')
283
+ : null,
284
+ b.autoReleaseable
285
+ ? React.createElement(React.Fragment, null, React.createElement(Dot, { color: T.success }), React.createElement('span', null, tt('batch.release')))
286
+ : b.phase === 'complete' || b.phase === 'aborted'
287
+ ? React.createElement('span', null, tt('batch.done'))
288
+ : React.createElement('span', null, tt('batch.progress') + ' · ' + (b.concurrency != null ? tt('concurrency') + '=' + b.concurrency : ''))
289
+ )
290
+ );
291
+ })
292
+ : React.createElement('div', { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, padding: '28px 12px', color: T.text3, textAlign: 'center' } },
293
+ React.createElement('svg', { width: 36, height: 36, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, 'aria-hidden': 'true' },
294
+ React.createElement('rect', { x: 3, y: 3, width: 7, height: 7, rx: 1.5 }),
295
+ React.createElement('rect', { x: 14, y: 3, width: 7, height: 7, rx: 1.5 }),
296
+ React.createElement('rect', { x: 3, y: 14, width: 7, height: 7, rx: 1.5 }),
297
+ React.createElement('rect', { x: 14, y: 14, width: 7, height: 7, rx: 1.5 })
298
+ ),
299
+ React.createElement('div', { style: { fontSize: 12.5, fontWeight: 600, color: T.text2 } }, tt('empty')),
300
+ React.createElement('div', { style: { fontSize: 11, lineHeight: 1.5 } }, tt('empty.hint'))
301
+ )
302
+ );
303
+ }
304
+ // ===== [panel-segment] batch-detail.js =====
305
+ function GateBadge({ gate }) {
306
+ if (!gate || !gate.layer) return null;
307
+ const items = [];
308
+ const seen = {};
309
+ const add = (kind, p) => { if (!seen[p]) { seen[p] = 1; items.push({ kind: kind, path: p }); } };
310
+ for (const p of gate.consumeMissing || []) add('consume', p);
311
+ for (const p of gate.outputsMissing || []) add('outputs', p);
312
+ for (const p of gate.produceMissing || []) add('produce', p);
313
+ for (const p of gate.contractProblems || []) {
314
+ const m = String(p).match(/^(.+?)\s+missing$/i);
315
+ if (m) add('contract', m[1]); else add('contract', String(p));
316
+ }
317
+ if (!items.length) return null;
318
+ const KIND = { consume: tt('gate.consume'), outputs: tt('gate.outputs'), produce: tt('gate.produce'), contract: tt('gate.contract') };
319
+ return React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 4 } },
320
+ items.map((it, i) => React.createElement('span', {
321
+ key: i,
322
+ title: KIND[it.kind] + ': ' + it.path,
323
+ style: { color: T.warn, background: pal().gateBg, borderRadius: 999, padding: '1px 7px', fontSize: 10, fontWeight: 600, fontFamily: T.mono, whiteSpace: 'nowrap' }
324
+ }, tt('gate.missing') + ' ' + it.path))
325
+ );
326
+ }
327
+
328
+ function AttemptBadge({ n, upgrade }) {
329
+ if (!n && !upgrade) return null;
330
+ const label = (upgrade ? tt('upgrade') : tt('attempt')) + ' ' + n;
331
+ return React.createElement('span', {
332
+ style: upgrade
333
+ ? { color: pal().escFg, background: pal().escBg, borderRadius: 999, padding: '1px 7px', fontSize: 10, fontWeight: 600, fontFamily: T.mono, whiteSpace: 'nowrap' }
334
+ : { color: T.text2, fontSize: 10.5, fontFamily: T.mono, whiteSpace: 'nowrap' }
335
+ }, label);
336
+ }
337
+
338
+ function LaneCard({ lane, state, attempt, upgrade, gate, meta }) {
339
+ const st = STATE[state] || STATE.pending;
340
+ const m = meta || {};
341
+ const deps = m.deps && m.deps.length ? m.deps : null;
342
+ return React.createElement('div', {
343
+ className: 'psw-card',
344
+ style: Object.assign({}, cardBase, {
345
+ flex: '1 1 168px', padding: '8px 10px', display: 'flex', flexDirection: 'column', gap: 6,
346
+ borderLeft: '3px solid ' + st.fg
347
+ })
348
+ },
349
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 } },
350
+ React.createElement('span', { style: { fontSize: 11.5, fontWeight: 600, fontFamily: T.mono, color: T.text, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 } }, lane),
351
+ React.createElement(Chip, { st: st }, state)
352
+ ),
353
+ m.cmd ? React.createElement('div', {
354
+ style: { fontSize: 11, color: T.text2, lineHeight: 1.45, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }
355
+ }, m.cmd) : null,
356
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' } },
357
+ React.createElement(GateBadge, { gate }),
358
+ React.createElement(AttemptBadge, { n: attempt, upgrade }),
359
+ m.layer ? React.createElement('span', { style: { fontSize: 10, color: T.text3, fontFamily: T.mono } }, tt('task.layer') + ' ' + m.layer) : null,
360
+ deps ? React.createElement('span', { style: { fontSize: 10, color: T.dim, fontFamily: T.mono } }, tt('task.deps') + ': ' + deps.join(', ')) : null
361
+ )
362
+ );
363
+ }
364
+
365
+ function EventRow({ e }) {
366
+ const kind = String(e.type || '').toLowerCase();
367
+ const color = kind.indexOf('running') >= 0 ? T.warn : kind.indexOf('fail') >= 0 || kind.indexOf('conflict') >= 0 ? T.error : kind.indexOf('merge') >= 0 ? T.success : kind.indexOf('review') >= 0 ? T.info : T.text3;
368
+ const label = e.type + (e.lane ? ':' + e.lane : '') + (e.from ? ' ' + e.from + '->' + e.to : '');
369
+ return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, padding: '3px 0' } },
370
+ React.createElement(Dot, { color: color }),
371
+ React.createElement('span', { style: { flex: 1, fontSize: 11.5, color: T.text2, fontFamily: T.mono, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, label),
372
+ React.createElement('span', { style: { fontSize: 10.5, color: T.dim, fontFamily: T.mono, fontVariantNumeric: 'tabular-nums' } }, (e.ts || '').slice(11, 19))
373
+ );
374
+ }
375
+ function BatchDetail({ d }) {
376
+ if (!d) {
377
+ return React.createElement('div', { style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 10, overflowY: 'auto' } },
378
+ React.createElement(Skeleton, { h: 64 }),
379
+ React.createElement(Skeleton, { h: 40 }),
380
+ React.createElement(Skeleton, { h: 120 })
381
+ );
382
+ }
383
+ const lanes = Object.keys(d.lanes || {});
384
+ const vals = Object.values(d.lanes || {});
385
+ const done = vals.filter((s) => TERMINAL.indexOf(s) >= 0).length;
386
+ const issues = vals.filter((s) => s === 'failed' || s === 'conflict').length;
387
+ const st = PHASE[d.phase] || PHASE.planning;
388
+ const evs = (d.recentEvents || []).slice().reverse();
389
+ const mail = d.mail || { inbox: [], broadcast: [] };
390
+ const laneMeta = {};
391
+ for (const w of d.wavePlan || []) {
392
+ for (const t of w.tasks || []) {
393
+ laneMeta[t.id] = { cmd: t.cmd || '', layer: t.layer || null, role: t.role || null, deps: t.deps || [] };
394
+ }
395
+ }
396
+ return React.createElement('div', { className: 'psw-scroll', style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 10, overflowY: 'auto', paddingRight: 4 } },
397
+ React.createElement('div', { className: 'psw-card', style: Object.assign({}, cardBase, { padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 8 }) },
398
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 } },
399
+ React.createElement(Chip, { st: st }, d.phase + (d.viewSettled ? ' ✓' : '')),
400
+ React.createElement('span', { style: { fontWeight: 700, fontSize: 14, fontFamily: T.mono, color: T.text, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, d.batchId),
401
+ React.createElement('span', { style: { flex: 1 } }),
402
+ d.autoReleaseable
403
+ ? React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 5, color: T.success, fontSize: 11, fontWeight: 600 } },
404
+ React.createElement(Dot, { color: T.success }), tt('batch.release'))
405
+ : null
406
+ ),
407
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
408
+ React.createElement(Progress, { value: lanes.length ? (done / lanes.length) * 100 : 0, color: issues ? T.error : (lanes.length && done === lanes.length) ? T.success : (d.phase === 'running' || d.phase === 'paused') ? T.warn : T.dim }),
409
+ React.createElement('span', { style: { fontSize: 10.5, color: T.text3, fontFamily: T.mono, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' } }, done + '/' + lanes.length)
410
+ ),
411
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, fontSize: 10.5, color: T.text3, flexWrap: 'wrap' } },
412
+ React.createElement('span', null, tt('lanes') + ' ' + lanes.length),
413
+ React.createElement('span', null, tt('events') + ' ' + (d.eventCount != null ? d.eventCount : (d.recentEvents || []).length)),
414
+ React.createElement('span', null, tt('concurrency') + '=' + d.concurrency)
415
+ )
416
+ ),
417
+ React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
418
+ lanes.map((lane) => React.createElement(LaneCard, {
419
+ key: lane, lane: lane, state: d.lanes[lane],
420
+ attempt: (d.laneAttempts || {})[lane], upgrade: (d.upgrades || {})[lane],
421
+ gate: (d.lanesGate || {})[lane], meta: laneMeta[lane]
422
+ }))
423
+ ),
424
+ React.createElement('div', { className: 'psw-card', style: Object.assign({}, cardBase, { padding: '10px 12px' }) },
425
+ React.createElement(SectionTitle, null, tt('event.timeline')),
426
+ evs.length
427
+ ? React.createElement('div', { style: { display: 'flex', flexDirection: 'column' } }, evs.map((e, i) => React.createElement(EventRow, { key: i, e })))
428
+ : React.createElement('div', { style: { fontSize: 11.5, color: T.text3, padding: '4px 0' } }, tt('empty'))
429
+ ),
430
+ React.createElement('div', { className: 'psw-card', style: Object.assign({}, cardBase, { padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 6 }) },
431
+ React.createElement(SectionTitle, null, tt('mailbox.title')),
432
+ React.createElement('div', { style: { display: 'flex', gap: 8 } },
433
+ React.createElement('div', { style: { flex: 1, display: 'flex', alignItems: 'center', gap: 6, background: T.skeleton, borderRadius: 8, padding: '6px 10px' } },
434
+ React.createElement(Dot, { color: T.info }),
435
+ React.createElement('span', { style: { fontSize: 11, color: T.text2 } }, tt('mailbox.inbox')),
436
+ React.createElement('span', { style: { marginLeft: 'auto', fontWeight: 700, fontSize: 13, fontFamily: T.mono, color: T.text, fontVariantNumeric: 'tabular-nums' } }, mail.inbox.length)
437
+ ),
438
+ React.createElement('div', { style: { flex: 1, display: 'flex', alignItems: 'center', gap: 6, background: T.skeleton, borderRadius: 8, padding: '6px 10px' } },
439
+ React.createElement(Dot, { color: T.warn }),
440
+ React.createElement('span', { style: { fontSize: 11, color: T.text2 } }, tt('mailbox.broadcast')),
441
+ React.createElement('span', { style: { marginLeft: 'auto', fontWeight: 700, fontSize: 13, fontFamily: T.mono, color: T.text, fontVariantNumeric: 'tabular-nums' } }, mail.broadcast.length)
442
+ )
443
+ ),
444
+ mail.inbox.length + mail.broadcast.length === 0
445
+ ? React.createElement('div', { style: { fontSize: 11, color: T.text3 } }, tt('mailbox.hint'))
446
+ : null
447
+ )
448
+ );
449
+ }
450
+ // ===== [panel-segment] main.js =====
451
+ const NS = 'dsh-punky-swarm';
452
+ const inject = ['slots', 'locale'];
453
+ async function api(path, session) {
454
+ const base = '/api/dsh-punky-swarm' + path;
455
+ const url = session
456
+ ? base + (base.includes('?') ? '&' : '?') + 'session=' + encodeURIComponent(session)
457
+ : base;
458
+ const res = await fetch(url);
459
+ if (!res.ok) throw new Error('HTTP ' + res.status);
460
+ return res.json();
461
+ }
462
+
463
+ // 跨会话聚合:/sessions 取全部有批次的会话 → 逐会话 /batches 合并为统一列表;
464
+ // 当前对话会话(sid)批次优先,其后按会话展示;列表项携带 session 归属字段
465
+ async function aggregateBatches(sid) {
466
+ let sessions = [];
467
+ try {
468
+ const r = await api('/sessions');
469
+ sessions = (r && r.sessions) || [];
470
+ } catch {}
471
+ const ids = [];
472
+ if (sid) ids.push(sid);
473
+ for (const s of sessions) {
474
+ if (s && s.sessionId && s.sessionId !== sid) ids.push(s.sessionId);
475
+ }
476
+ const out = [];
477
+ for (const id of ids) {
478
+ let items = [];
479
+ try {
480
+ const r = await api('/batches', id);
481
+ items = (r && r.batches) || [];
482
+ } catch {}
483
+ for (const b of items) {
484
+ out.push(Object.assign({}, b, {
485
+ session: id,
486
+ sessionShort: String(id).slice(0, 8),
487
+ isOwnSession: id === sid
488
+ }));
489
+ }
490
+ }
491
+ return out;
492
+ }
493
+
494
+ // 浏览器端 TERMINAL 副本:Node 端单点 = lib/state/constants.js(P1-07 收敛);
495
+ // 面板段经 window.__ModuleLoader__ 拼接执行(无 ESM import 能力),此处为手工同步副本,
496
+ // batch-list/batch-detail 段共享本作用域引用(渲染时求值)。
497
+ const TERMINAL = ['merged', 'failed', 'skipped', 'conflict'];
498
+ const cardBase = {
499
+ get background() { return T.card; },
500
+ get borderWidth() { return 1; },
501
+ get borderStyle() { return 'solid'; },
502
+ get borderColor() { return T.border; },
503
+ borderRadius: 10
504
+ };
505
+ function ClusterWorkbench({ sessionId }) {
506
+ const [batches, setBatches] = useState(null);
507
+ const [sel, setSel] = useState(null);
508
+ const [detail, setDetail] = useState(null);
509
+ const [updated, setUpdated] = useState(null);
510
+ const [mode, setMode] = useState('sse'); // 'sse' | 'poll'(R3 SSE 降级回轮询状态,D6 简化②)
511
+ const [, setThemeTick] = useState(0);
512
+ const sid = sessionId || '';
513
+
514
+ // 跟随 web UI 主题(body[data-ds-dark-theme])切换兜底色板并重渲染
515
+ useEffect(() => {
516
+ const apply = () => {
517
+ const t = detectTheme();
518
+ if (t !== CURRENT_THEME) { CURRENT_THEME = t; setThemeTick((x) => x + 1); }
519
+ };
520
+ let mo = null;
521
+ try {
522
+ if (typeof MutationObserver !== 'undefined' && document.body) {
523
+ mo = new MutationObserver(apply);
524
+ mo.observe(document.body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] });
525
+ }
526
+ } catch {}
527
+ let mq = null;
528
+ try {
529
+ if (typeof matchMedia !== 'undefined') {
530
+ mq = matchMedia('(prefers-color-scheme: light)');
531
+ if (mq.addEventListener) mq.addEventListener('change', apply);
532
+ }
533
+ } catch {}
534
+ return () => {
535
+ if (mo) mo.disconnect();
536
+ if (mq && mq.removeEventListener) mq.removeEventListener('change', apply);
537
+ };
538
+ }, []);
539
+
540
+ // R3 SSE 列表流(设计 §3.3.4):EventSource 主通道 + 3s 轮询降级兜底(D2 保留既有轮询路径不删)。
541
+ // 收到 batch 信号 → eventCount 去重(旧于当前忽略)→ 重跑既有聚合;onerror / 15s 无心跳 → 回退轮询;
542
+ // 重连成功(EventSource 自动重连 / 心跳恢复)→ 停轮询回 SSE。
543
+ useEffect(() => {
544
+ let alive = true;
545
+ let es = null;
546
+ let pollIv = null;
547
+ let lastBeat = 0;
548
+ let degraded = false;
549
+ const seen = new Map(); // batchId -> 已见 eventCount(服务端摘要去重,旧于当前忽略)
550
+ const tick = async () => {
551
+ try {
552
+ const agg = await aggregateBatches(sid);
553
+ if (!alive) return;
554
+ setBatches(agg); setUpdated(new Date());
555
+ for (const b of agg) {
556
+ if (typeof b.eventCount === 'number') seen.set(b.batchId, Math.max(seen.get(b.batchId) || 0, b.eventCount));
557
+ }
558
+ } catch {}
559
+ };
560
+ const startPoll = () => { if (degraded || pollIv) return; degraded = true; setMode('poll'); pollIv = setInterval(tick, 3000); };
561
+ const stopPoll = () => { degraded = false; if (pollIv) { clearInterval(pollIv); pollIv = null; } setMode('sse'); };
562
+ const onBatch = (ev) => {
563
+ lastBeat = Date.now();
564
+ try {
565
+ const d = JSON.parse(ev.data);
566
+ const bid = d.batchId, n = d.eventCount;
567
+ if (bid && typeof n === 'number' && seen.get(bid) != null && n <= seen.get(bid)) return; // 旧于当前忽略
568
+ if (bid && typeof n === 'number') seen.set(bid, n);
569
+ tick();
570
+ } catch { tick(); }
571
+ };
572
+ const onHeartbeat = () => { lastBeat = Date.now(); if (degraded) stopPoll(); }; // 心跳恢复 → 回 SSE
573
+ const connect = () => {
574
+ try {
575
+ if (typeof EventSource === 'undefined' || !sid) { startPoll(); return; }
576
+ es = new EventSource('/api/dsh-punky-swarm/stream?session=' + encodeURIComponent(sid));
577
+ es.addEventListener('batch', onBatch);
578
+ es.addEventListener('heartbeat', onHeartbeat);
579
+ es.onopen = () => { lastBeat = Date.now(); stopPoll(); };
580
+ es.onerror = () => { lastBeat = Date.now(); if (!degraded) startPoll(); }; // 断流 → 轮询兜底(EventSource 自动重连)
581
+ } catch { startPoll(); }
582
+ };
583
+ tick();
584
+ connect();
585
+ const stall = setInterval(() => {
586
+ if (!degraded && es && lastBeat && Date.now() - lastBeat > 15000) startPoll(); // 15s 无心跳 → 降级轮询
587
+ }, 5000);
588
+ return () => { alive = false; if (es) { try { es.close(); } catch {} } if (pollIv) clearInterval(pollIv); clearInterval(stall); };
589
+ }, [sid]);
590
+ // R3 SSE 详情流(设计 §3.3.4):信号 → 回拉 /batch + 双 /mailbox(复用既有逻辑);降级回轮询语义同列表流
591
+ useEffect(() => {
592
+ if (!sel) { setDetail(null); return; }
593
+ let alive = true;
594
+ let es = null;
595
+ let pollIv = null;
596
+ let lastBeat = 0;
597
+ let degraded = false;
598
+ let lastCount = null;
599
+ const tick = async () => {
600
+ try {
601
+ const d = await api('/batch?batchId=' + encodeURIComponent(sel.batchId), sel.session);
602
+ let mail = { inbox: [], broadcast: [] };
603
+ try { mail.inbox = (await api('/mailbox?batchId=' + encodeURIComponent(sel.batchId) + '&box=inbox', sel.session)).items; } catch {}
604
+ try { mail.broadcast = (await api('/mailbox?batchId=' + encodeURIComponent(sel.batchId) + '&box=broadcast', sel.session)).items; } catch {}
605
+ if (alive) { setDetail(Object.assign({}, d, { mail })); lastCount = d.eventCount; }
606
+ } catch {}
607
+ };
608
+ const startPoll = () => { if (degraded || pollIv) return; degraded = true; setMode('poll'); pollIv = setInterval(tick, 3000); };
609
+ const stopPoll = () => { degraded = false; if (pollIv) { clearInterval(pollIv); pollIv = null; } setMode('sse'); };
610
+ const onSignal = (ev) => {
611
+ lastBeat = Date.now();
612
+ try {
613
+ const d = JSON.parse(ev.data);
614
+ if (typeof d.eventCount === 'number' && lastCount != null && d.eventCount <= lastCount) return; // 旧于当前忽略
615
+ tick();
616
+ } catch { tick(); }
617
+ };
618
+ const onHeartbeat = () => { lastBeat = Date.now(); if (degraded) stopPoll(); };
619
+ const connect = () => {
620
+ try {
621
+ if (typeof EventSource === 'undefined' || !sel.session) { startPoll(); return; }
622
+ es = new EventSource('/api/dsh-punky-swarm/stream?session=' + encodeURIComponent(sel.session) + '&batchId=' + encodeURIComponent(sel.batchId));
623
+ es.addEventListener('batch', onSignal);
624
+ es.addEventListener('mailbox', onSignal); // 帧协议 event: mailbox(mailbox 目录变更信号,回拉双 /mailbox)
625
+ es.addEventListener('heartbeat', onHeartbeat);
626
+ es.onopen = () => { lastBeat = Date.now(); stopPoll(); };
627
+ es.onerror = () => { lastBeat = Date.now(); if (!degraded) startPoll(); };
628
+ } catch { startPoll(); }
629
+ };
630
+ tick();
631
+ connect();
632
+ const stall = setInterval(() => {
633
+ if (!degraded && es && lastBeat && Date.now() - lastBeat > 15000) startPoll();
634
+ }, 5000);
635
+ return () => { alive = false; if (es) { try { es.close(); } catch {} } if (pollIv) clearInterval(pollIv); clearInterval(stall); };
636
+ }, [sel]);
637
+
638
+ const list = batches || [];
639
+ const running = list.filter((b) => b.phase === 'running').length;
640
+ const doneCnt = list.filter((b) => b.phase === 'complete').length;
641
+ let issues = 0;
642
+ for (const b of list) {
643
+ const vals = Object.values(b.lanes || {});
644
+ issues += vals.filter((s) => s === 'failed' || s === 'conflict').length;
645
+ }
646
+ const liveColor = !batches ? T.dim : running ? T.warn : T.success;
647
+ const halo = !batches ? 'transparent' : running ? pal().haloWarn : pal().haloSuccess;
648
+ const updatedText = updated
649
+ ? updated.toTimeString().slice(0, 8)
650
+ : '--:--:--';
651
+
652
+ return React.createElement('div', {
653
+ 'aria-busy': !batches,
654
+ style: {
655
+ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', gap: 10,
656
+ padding: '14px 16px', color: T.text, fontFamily: T.font
657
+ }
658
+ },
659
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, minHeight: 28 } },
660
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
661
+ React.createElement('span', { style: { fontSize: 15, fontWeight: 700, letterSpacing: 0.2 } }, tt('view.cluster')),
662
+ React.createElement('span', { className: 'psw-pulse', style: { width: 7, height: 7, borderRadius: 999, background: liveColor, boxShadow: '0 0 0 3px ' + halo } }),
663
+ React.createElement('span', { style: { fontSize: 10, fontWeight: 600, letterSpacing: 0.8, color: liveColor } }, tt('live'))
664
+ ),
665
+ React.createElement('span', { style: { flex: 1 } }),
666
+ React.createElement('span', { style: { fontSize: 10.5, color: T.text3, fontFamily: T.mono } }, tt(mode === 'poll' ? 'stream.fallback' : 'stream.live')),
667
+ React.createElement('span', { style: { fontSize: 10.5, color: T.text3, fontFamily: T.mono, fontVariantNumeric: 'tabular-nums' } }, '· ' + tt('updated') + ' ' + updatedText)
668
+ ),
669
+ React.createElement('div', { role: 'status', 'aria-atomic': 'true', style: { display: 'flex', gap: 8 } },
670
+ React.createElement(Stat, { label: tt('stat.total'), value: list.length }),
671
+ React.createElement(Stat, { label: tt('stat.running'), value: running, color: T.warn }),
672
+ React.createElement(Stat, { label: tt('stat.done'), value: doneCnt, color: T.success }),
673
+ React.createElement(Stat, { label: tt('stat.issues'), value: issues, color: issues ? T.error : T.text3 })
674
+ ),
675
+ React.createElement('div', { className: 'psw-panes' },
676
+ React.createElement(BatchList, { batches: batches, selected: sel, onSelect: setSel, loading: true }),
677
+ React.createElement(BatchDetail, { d: detail })
678
+ )
679
+ );
680
+ }
681
+
682
+ function apply(ctx) {
683
+ if (typeof document !== 'undefined' && !document.getElementById('dsh-punky-swarm-ui')) {
684
+ const el = document.createElement('style');
685
+ el.id = 'dsh-punky-swarm-ui';
686
+ el.textContent = "body{--psw-shimmer-a:#eef2f7;--psw-shimmer-b:rgba(120,140,170,.16)}\nbody[data-ds-dark-theme]{--psw-shimmer-a:#1d2740;--psw-shimmer-b:rgba(148,163,184,.14)}\n.psw-scroll::-webkit-scrollbar{width:8px;height:8px}\n.psw-scroll::-webkit-scrollbar-thumb{background:var(--dsw-alias-scrollbar-bg-l1,rgba(148,163,184,.35));border-radius:999px}\n.psw-scroll::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-scrollbar-hover-l1,rgba(148,163,184,.5))}\n.psw-btn{transition:background .15s ease,border-color .15s ease,transform .15s ease}\n.psw-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(148,163,184,.09))}\n.psw-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4f8cff);outline-offset:2px}\n.psw-card{transition:border-color .15s ease,background .15s ease}\n.psw-pulse{animation:pswPulse 2s ease-in-out infinite}\n@keyframes pswPulse{0%,100%{opacity:1}50%{opacity:.3}}\n.psw-shimmer{background:linear-gradient(90deg,var(--psw-shimmer-a) 30%,var(--psw-shimmer-b) 50%,var(--psw-shimmer-a) 70%);background-size:200% 100%;animation:pswShimmer 1.4s linear infinite}\n@keyframes pswShimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}\n.psw-panes{display:flex;gap:12px;flex:1;min-height:0}\n@media (max-width:760px){.psw-panes{flex-direction:column}.psw-list{width:100%!important;max-height:240px}}\n@media (prefers-reduced-motion:reduce){.psw-pulse,.psw-shimmer{animation:none}.psw-btn,.psw-card{transition:none}}";
687
+ document.head.appendChild(el);
688
+ }
689
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-punky-swarm: dictionaries');
690
+ const t = ctx.locale.bind(NS);
691
+ ctx.slots.inject('conversation.view', () => ctx.slots.register({
692
+ name: 'conversation.view',
693
+ id: 'cluster',
694
+ order: 20,
695
+ locale: NS,
696
+ label: () => t('view.cluster'),
697
+ inject: (sessionId) => ({ sessionId })
698
+ }, ClusterWorkbench));
699
+ }
700
+
701
+ module.exports = { apply, inject };
702
+ return module.exports;
703
+ }
704
+ });