@klarkxy/dsh-fusion 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.
@@ -0,0 +1,959 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let react = require("react");
3
+ let react_jsx_runtime = require("react/jsx-runtime");
4
+ //#region ../dsh-ai-services/src/contracts.ts
5
+ const CHAT_EVENTS_SLOT = "dsh-editor.chat.events";
6
+ //#endregion
7
+ //#region ../dsh-ai-services/src/client-utils.ts
8
+ /** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
9
+ function selectedSessionId(client) {
10
+ return client.uiWorkspace.current ? client.uiWorkspace.current.getSnapshot()?.sessionId ?? "" : client.uiSession.adapter.current.getSnapshot().key ?? "";
11
+ }
12
+ /** Owner props win; native settings only supplies close, so resolve its selected session. */
13
+ function useNativeSeat(client, props) {
14
+ const current = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => (client.uiWorkspace.current ?? client.uiSession.adapter.current).subscribe(fn), [client]), (0, react.useCallback)(() => selectedSessionId(client), [client]), () => "");
15
+ const language = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => client.locale.subscribe(fn), [client]), (0, react.useCallback)(() => client.locale.getSnapshot().active, [client]), () => "en");
16
+ const row = props && typeof props === "object" ? props : {};
17
+ const owner = row.owner && typeof row.owner === "object" ? row.owner : {};
18
+ const sessionId = typeof row.sessionId === "string" ? row.sessionId : typeof owner.sessionId === "string" ? owner.sessionId : current;
19
+ const requestedLocale = row.locale ?? owner.locale ?? language;
20
+ return {
21
+ sessionId,
22
+ locale: String(requestedLocale).startsWith("zh") ? "zh" : "en",
23
+ hidden: row.hidden === true || owner.hidden === true
24
+ };
25
+ }
26
+ //#endregion
27
+ //#region src/contracts.ts
28
+ const FUSION_RPC_CHANNEL = "/dsh-fusion";
29
+ const isWorking = (state) => [
30
+ "dispatching",
31
+ "working",
32
+ "decision",
33
+ "review"
34
+ ].includes(state);
35
+ //#endregion
36
+ //#region src/client-projection.ts
37
+ /** The last task is the only one the Lead may still act on. Older tasks remain in native history. */
38
+ function currentTask(pair) {
39
+ return pair?.tasks.at(-1);
40
+ }
41
+ /** An accepted review identifies an exact Writer revision; the newest draft alone grants no adoption. */
42
+ function acceptedCandidate(task) {
43
+ if (task.state !== "accepted") return void 0;
44
+ const review = task.reviews.at(-1);
45
+ if (review?.verdict !== "accept") return void 0;
46
+ return task.candidates.find((candidate) => candidate.id === review.candidateId && candidate.hash === review.candidateHash && candidate.taskRevision === task.revision);
47
+ }
48
+ function canAdopt(pair, task) {
49
+ const candidate = acceptedCandidate(task);
50
+ return pair.profile === "writing" && Boolean(task.target && candidate && task.candidates.at(-1)?.id === candidate.id && task.adoption !== "applied" && task.adoption !== "dismissed" && task.application?.state !== "pending");
51
+ }
52
+ /** Anchor a durable task near its latest Lead turn instead of repeating it after every turn. */
53
+ function taskAnchorTurn(task, entries) {
54
+ let eligible;
55
+ let latest;
56
+ for (const { event } of entries) {
57
+ const turn = event.data?.turn;
58
+ if (!Number.isSafeInteger(turn) || (turn ?? 0) < 0) continue;
59
+ latest = turn;
60
+ if (event.time <= task.updatedAt) eligible = turn;
61
+ }
62
+ return eligible ?? latest;
63
+ }
64
+ function needsRefresh(task) {
65
+ return Boolean(task && ([
66
+ "dispatching",
67
+ "working",
68
+ "decision",
69
+ "review"
70
+ ].includes(task.state) || task.cleanup === "pending"));
71
+ }
72
+ /** Capture every authority field that can invalidate an in-flight author action. */
73
+ function actionIdentity(sessionId, task, candidate) {
74
+ return [
75
+ sessionId,
76
+ task.id,
77
+ task.revision,
78
+ candidate?.id ?? "",
79
+ candidate?.hash ?? ""
80
+ ].join("\0");
81
+ }
82
+ function isCurrentAction(mounted, captured, current) {
83
+ return mounted && captured === current;
84
+ }
85
+ /** Only a Host receipt for the exact preview may request an Editor view refresh. */
86
+ function appliedReceipt(value, task, candidate, preview) {
87
+ if (!value || typeof value !== "object") return void 0;
88
+ const row = value;
89
+ const application = row.application;
90
+ if (row.id !== task.id || row.revision !== task.revision || row.adoption !== "applied" || application?.state !== "applied" || typeof application.id !== "string" || !application.id || application.candidateId !== candidate.id || application.candidateHash !== candidate.hash || application.path !== preview.path || application.beforeVersion !== preview.version || preview.candidateId !== candidate.id || preview.hash !== candidate.hash) return void 0;
91
+ return {
92
+ id: application.id,
93
+ path: application.path
94
+ };
95
+ }
96
+ /** A late response has no authority over the newly selected Session's view. */
97
+ function notifyAppliedReceipt(input) {
98
+ if (!input.isCurrent()) return false;
99
+ const receipt = appliedReceipt(input.value, input.task, input.candidate, input.preview);
100
+ if (!receipt) return false;
101
+ input.notify(receipt);
102
+ return true;
103
+ }
104
+ /** Cold-start applied history cannot navigate the author; only a seen pending intent may refresh. */
105
+ function reconciledReceipt(observed, sessionId, task) {
106
+ const application = task?.application;
107
+ if (!observed || !task || task.adoption !== "applied" || application?.state !== "applied") return void 0;
108
+ if (observed.sessionId !== sessionId || observed.taskId !== task.id || observed.id !== application.id || observed.path !== application.path || observed.candidateId !== application.candidateId || observed.candidateHash !== application.candidateHash) return void 0;
109
+ return {
110
+ id: application.id,
111
+ path: application.path
112
+ };
113
+ }
114
+ /** Native Agent.status has only idle/running on the pinned runtime; retain future values verbatim. */
115
+ function activityLabel(value, locale) {
116
+ if (locale === "en") return value;
117
+ if (value === "idle") return "待命";
118
+ if (value === "running") return "运行中";
119
+ return value;
120
+ }
121
+ //#endregion
122
+ //#region src/client-store.ts
123
+ const EMPTY = Object.freeze({});
124
+ /** One read-only status projection per viewed Session; no task or native state is copied back to DSH. */
125
+ var FusionClientStore = class {
126
+ entries = /* @__PURE__ */ new Map();
127
+ announcedApplied = /* @__PURE__ */ new Set();
128
+ client;
129
+ constructor(client) {
130
+ this.client = client;
131
+ }
132
+ snapshot(sessionId) {
133
+ return this.entries.get(sessionId)?.snapshot ?? EMPTY;
134
+ }
135
+ subscribe(sessionId, listener) {
136
+ let entry = this.entries.get(sessionId);
137
+ if (!entry) {
138
+ entry = {
139
+ snapshot: EMPTY,
140
+ listeners: /* @__PURE__ */ new Set(),
141
+ epoch: 0
142
+ };
143
+ this.entries.set(sessionId, entry);
144
+ const queue = () => {
145
+ if (typeof document === "undefined" || document.visibilityState === "visible") this.refresh(sessionId);
146
+ };
147
+ const stopEvents = this.client.sessions.binding?.(sessionId)?.eventSource.subscribe(queue);
148
+ const stopConnection = this.client.connection.generation?.subscribe(queue);
149
+ if (typeof window !== "undefined") window.addEventListener("focus", queue);
150
+ if (typeof document !== "undefined") document.addEventListener("visibilitychange", queue);
151
+ entry.dispose = () => {
152
+ stopEvents?.();
153
+ stopConnection?.();
154
+ if (typeof window !== "undefined") window.removeEventListener("focus", queue);
155
+ if (typeof document !== "undefined") document.removeEventListener("visibilitychange", queue);
156
+ };
157
+ this.refresh(sessionId);
158
+ }
159
+ entry.listeners.add(listener);
160
+ return () => {
161
+ entry.listeners.delete(listener);
162
+ if (entry.listeners.size) return;
163
+ entry.epoch++;
164
+ clearTimeout(entry.timer);
165
+ entry.dispose?.();
166
+ this.entries.delete(sessionId);
167
+ };
168
+ }
169
+ /** Supersede older reads before a user mutation so a late status cannot erase its result. */
170
+ invalidate(sessionId) {
171
+ const entry = this.entries.get(sessionId);
172
+ if (!entry) return;
173
+ entry.epoch++;
174
+ entry.inFlight = void 0;
175
+ clearTimeout(entry.timer);
176
+ }
177
+ async refresh(sessionId) {
178
+ const entry = this.entries.get(sessionId);
179
+ if (!entry) return;
180
+ if (entry.inFlight) return entry.inFlight;
181
+ const epoch = ++entry.epoch;
182
+ const work = (async () => {
183
+ try {
184
+ const result = await this.client.connection.rpc.call(FUSION_RPC_CHANNEL, "status", { sessionId });
185
+ if (this.entries.get(sessionId) !== entry || entry.epoch !== epoch) return;
186
+ if (!result.ok) throw new Error(result.error.message);
187
+ entry.snapshot = { status: result.value };
188
+ } catch (error) {
189
+ if (this.entries.get(sessionId) !== entry || entry.epoch !== epoch) return;
190
+ entry.snapshot = {
191
+ ...entry.snapshot,
192
+ error: error instanceof Error ? error.message : String(error)
193
+ };
194
+ } finally {
195
+ if (this.entries.get(sessionId) !== entry || entry.epoch !== epoch) return;
196
+ entry.inFlight = void 0;
197
+ for (const listener of entry.listeners) listener();
198
+ this.schedule(sessionId, entry);
199
+ }
200
+ })();
201
+ entry.inFlight = work;
202
+ return work;
203
+ }
204
+ schedule(sessionId, entry) {
205
+ clearTimeout(entry.timer);
206
+ if (!needsRefresh(currentTask(entry.snapshot.status?.pair))) return;
207
+ entry.timer = setTimeout(() => {
208
+ if (typeof document === "undefined" || document.visibilityState === "visible") this.refresh(sessionId);
209
+ }, 2500);
210
+ }
211
+ /** A Host-confirmed application refreshes one Editor view once per UI lifetime. */
212
+ announceApplied(sessionId, receiptId, path, notify) {
213
+ const key = `${sessionId}\u0000${receiptId}`;
214
+ if (this.announcedApplied.has(key)) return false;
215
+ this.announcedApplied.add(key);
216
+ try {
217
+ notify(path);
218
+ } catch (error) {
219
+ this.announcedApplied.delete(key);
220
+ throw error;
221
+ }
222
+ return true;
223
+ }
224
+ dispose() {
225
+ this.announcedApplied.clear();
226
+ for (const [sessionId, entry] of this.entries) {
227
+ entry.epoch++;
228
+ clearTimeout(entry.timer);
229
+ entry.dispose?.();
230
+ this.entries.delete(sessionId);
231
+ }
232
+ }
233
+ };
234
+ //#endregion
235
+ //#region src/presentation.ts
236
+ const labels = {
237
+ zh: {
238
+ dispatching: "正在交接",
239
+ working: "执行中",
240
+ decision: "等待主代理决策",
241
+ review: "主代理审查中",
242
+ accepted: "审查通过",
243
+ cancelled: "已停止",
244
+ failed: "执行失败",
245
+ interrupted: "执行已中断"
246
+ },
247
+ en: {
248
+ dispatching: "Handing off",
249
+ working: "Executing",
250
+ decision: "Waiting for Lead decision",
251
+ review: "Lead review",
252
+ accepted: "Review passed",
253
+ cancelled: "Stopped",
254
+ failed: "Failed",
255
+ interrupted: "Interrupted"
256
+ }
257
+ };
258
+ /** Pure task projection. No percentages, idle-to-success inference, or provider-cost estimates. */
259
+ function taskView(pair, task, locale) {
260
+ const zh = locale === "zh", writing = pair.profile === "writing";
261
+ let status = labels[locale][task.state];
262
+ if (zh && writing && task.state === "working") status = "执笔中";
263
+ if (zh && writing && task.state === "decision") status = "等待统筹决策";
264
+ if (zh && writing && task.state === "review") status = "统筹审阅中";
265
+ let detail = task.error ?? "";
266
+ if (task.state === "accepted") {
267
+ if (writing && task.target) {
268
+ status = task.adoption === "applied" ? zh ? "已采用" : "Applied" : task.adoption === "conflict" ? zh ? "原文已变化" : "Source changed" : task.adoption === "dismissed" ? zh ? "候选已放弃" : "Candidate dismissed" : zh ? "待你采用" : "Awaiting author adoption";
269
+ detail = zh ? "审阅通过不等于已写入稿件。" : "Model review does not apply changes to the manuscript.";
270
+ if (task.adoption === "applied") detail = zh ? "宿主已确认应用。" : "Application confirmed by the Host.";
271
+ } else detail = zh ? "结果已通过模型审查;文件操作以原生执行记录为准。" : "Review passed. Native execution records remain authoritative for file changes.";
272
+ }
273
+ if (task.cleanup === "pending") status = zh ? "正在停止" : "Stopping";
274
+ if (task.cleanup === "failed") {
275
+ status = zh ? "停止未完全完成" : "Stop incomplete";
276
+ detail = zh ? "旧结果已失效,但资源清理需要重试。" : "Old results are invalid, but resource cleanup needs retry.";
277
+ }
278
+ const candidate = task.candidates.at(-1);
279
+ return {
280
+ id: task.id,
281
+ title: task.brief.title,
282
+ status,
283
+ detail,
284
+ working: isWorking(task.state) || task.cleanup === "pending",
285
+ canStop: task.adoption !== "applied" && (isWorking(task.state) || task.cleanup === "failed" || task.state === "interrupted"),
286
+ canPreview: Boolean(candidate),
287
+ ...candidate ? { candidateLabel: zh ? `候选第 ${candidate.revision} 版` : `Candidate v${candidate.revision}` } : {},
288
+ executionAddress: `dsh-resource://subagentchat/session/${encodeURIComponent(pair.childSessionId)}?parent=${encodeURIComponent(pair.leadSessionId)}&mode=continuable`
289
+ };
290
+ }
291
+ //#endregion
292
+ //#region src/client-styles.ts
293
+ /** Scoped plugin styling. Editor tokens are inherited; native DSH uses its own gray/accent scales. */
294
+ const fusionClientStyles = `
295
+ .dsh-fusion-card{min-width:0;margin-block:var(--dsh-ui-space-3,12px);padding:var(--dsh-ui-space-3,12px);border:1px solid var(--dsh-ui-line,var(--dsw-alias-border-l1));border-radius:var(--dsh-ui-radius-md,12px);background:var(--dsh-ui-raised,var(--dsw-alias-bg-module-platform));color:var(--dsh-ui-text,var(--dsw-alias-label-primary));font:inherit;font-size:var(--dsh-ui-text-base,var(--dsh-content-font-size,14px));overflow-wrap:anywhere}
296
+ .dsh-fusion-card.is-native{margin-inline:var(--dsh-ui-space-3,12px)}
297
+ .dsh-fusion-head{display:flex;align-items:center;flex-wrap:wrap;gap:var(--dsh-ui-space-2,8px);min-width:0}
298
+ .dsh-fusion-head strong{min-width:0;flex:1 1 8rem;font-size:inherit;line-height:1.45}
299
+ .dsh-fusion-kicker,.dsh-fusion-state{white-space:nowrap;color:var(--dsh-ui-muted,var(--dsw-alias-label-tertiary))}
300
+ .dsh-fusion-kicker{font-weight:600}
301
+ .dsh-fusion-state{font-size:var(--dsh-ui-text-sm,var(--dsh-content-font-size-secondary,13px))}
302
+ .dsh-fusion-card p{margin:var(--dsh-ui-space-2,8px) 0 0;line-height:1.55}
303
+ .dsh-fusion-detail,.dsh-fusion-activity,.dsh-fusion-usage,.dsh-fusion-hint{color:var(--dsh-ui-muted,var(--dsw-alias-label-tertiary))}
304
+ .dsh-fusion-card button,.dsh-fusion-dialog button{min-height:32px;padding:var(--dsh-ui-space-1,4px) var(--dsh-ui-space-2,8px);border:1px solid var(--dsh-ui-line-strong,var(--dsw-alias-border-l3));border-radius:var(--dsh-ui-radius-sm,6px);background:var(--dsh-ui-bg,var(--dsw-alias-bg-base));color:inherit;font:inherit;cursor:pointer}
305
+ .dsh-fusion-card button:hover:not(:disabled),.dsh-fusion-dialog button:hover:not(:disabled){background:var(--dsh-ui-hover,var(--dsw-alias-interactive-bg-hover))}
306
+ .dsh-fusion-card button:focus-visible,.dsh-fusion-dialog button:focus-visible,.dsh-fusion-card textarea:focus-visible{outline:2px solid var(--dsh-ui-accent,var(--dsw-alias-button-info-fill));outline-offset:2px}
307
+ .dsh-fusion-card button:disabled,.dsh-fusion-dialog button:disabled{opacity:.55;cursor:not-allowed}
308
+ .dsh-fusion-details{margin-inline-start:auto}
309
+ .dsh-fusion-actions{display:flex;flex-wrap:wrap;gap:var(--dsh-ui-space-2,8px);margin-top:var(--dsh-ui-space-3,12px)}
310
+ .dsh-fusion-body{margin-top:var(--dsh-ui-space-3,12px);padding-top:var(--dsh-ui-space-3,12px);border-top:1px solid var(--dsh-ui-line,var(--dsw-alias-border-l1))}
311
+ .dsh-fusion-body details{margin-top:var(--dsh-ui-space-2,8px)}
312
+ .dsh-fusion-body summary{cursor:pointer;min-height:32px}
313
+ .dsh-fusion-body ul{margin:var(--dsh-ui-space-2,8px) 0;padding-inline-start:1.5em}
314
+ .dsh-fusion-report{white-space:pre-wrap}
315
+ .dsh-fusion-candidate{max-height:18rem;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;padding:var(--dsh-ui-space-2,8px);background:var(--dsh-ui-bg,var(--dsw-alias-bg-base));border:1px solid var(--dsh-ui-line,var(--dsw-alias-border-l1))}
316
+ .dsh-fusion-error,.dsh-fusion-warning{color:var(--dsh-ui-text,var(--dsw-alias-label-primary));font-weight:600}
317
+ .dsh-fusion-resume{display:grid;gap:var(--dsh-ui-space-2,8px);margin-top:var(--dsh-ui-space-3,12px)}
318
+ .dsh-fusion-resume textarea{box-sizing:border-box;width:100%;min-height:3.5rem;padding:var(--dsh-ui-space-2,8px);border:1px solid var(--dsh-ui-line-strong,var(--dsw-alias-border-l3));border-radius:var(--dsh-ui-radius-sm,6px);background:var(--dsh-ui-bg,var(--dsw-alias-bg-base));color:inherit;font:inherit;resize:vertical}
319
+ .dsh-fusion-resume button{justify-self:start}
320
+ .dsh-fusion-dialog{box-sizing:border-box;width:min(92vw,80rem);max-width:92vw;max-height:90vh;padding:var(--dsh-ui-space-4,16px);border:1px solid var(--dsh-ui-line-strong,var(--dsw-alias-border-l3));border-radius:var(--dsh-ui-radius-lg,16px);background:var(--dsh-ui-raised,var(--dsw-alias-bg-module-platform));color:var(--dsh-ui-text,var(--dsw-alias-label-primary));font:inherit;box-shadow:var(--dsh-ui-shadow-lg,var(--dsw-elevation-panel))}
321
+ .dsh-fusion-dialog::backdrop{background:var(--dsh-ui-scrim,var(--dsw-alias-bg-mask-drop))}
322
+ .dsh-fusion-dialog-head{display:flex;align-items:center;justify-content:space-between;gap:var(--dsh-ui-space-3,12px)}
323
+ .dsh-fusion-dialog h3,.dsh-fusion-dialog h4{margin:0;font:inherit;font-weight:600}
324
+ .dsh-fusion-path{overflow-wrap:anywhere}
325
+ .dsh-fusion-compare{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--dsh-ui-space-3,12px)}
326
+ .dsh-fusion-compare section{min-width:0}
327
+ .dsh-fusion-compare h4{margin-bottom:var(--dsh-ui-space-2,8px)}
328
+ .dsh-fusion-compare pre{box-sizing:border-box;max-height:55vh;min-height:12rem;overflow:auto;margin:0;padding:var(--dsh-ui-space-3,12px);border:1px solid var(--dsh-ui-line,var(--dsw-alias-border-l1));border-radius:var(--dsh-ui-radius-sm,6px);background:var(--dsh-ui-bg,var(--dsw-alias-bg-base));color:inherit;font:inherit;white-space:pre-wrap;overflow-wrap:anywhere}
329
+ .dsh-fusion-dialog .dsh-fusion-actions{justify-content:flex-end}
330
+ .dsh-fusion-dialog .dsh-fusion-primary{background:var(--dsh-ui-accent-solid,var(--dsw-alias-button-primary-fill));border-color:var(--dsh-ui-accent-solid,var(--dsw-alias-button-primary-fill));color:var(--dsh-ui-on-accent,var(--dsw-alias-label-primary-foreground))}
331
+ @media(max-width:700px){.dsh-fusion-compare{grid-template-columns:minmax(0,1fr)}.dsh-fusion-compare pre{max-height:30vh;min-height:6rem}.dsh-fusion-dialog{max-height:95vh}}
332
+ @media(forced-colors:active){.dsh-fusion-card,.dsh-fusion-dialog,.dsh-fusion-compare pre{border:1px solid CanvasText}.dsh-fusion-card button:focus-visible,.dsh-fusion-dialog button:focus-visible{outline:2px solid Highlight}}
333
+ `;
334
+ //#endregion
335
+ //#region src/client.tsx
336
+ const name = "dsh-fusion-client";
337
+ const inject = [
338
+ "slots",
339
+ "connection",
340
+ "sessions",
341
+ "locale",
342
+ "uiWorkspace",
343
+ "uiSession"
344
+ ];
345
+ const NATIVE_TURN_TAIL_SLOT = "conversation.chat.turnTail";
346
+ function label(locale, zh, en) {
347
+ return locale === "zh" ? zh : en;
348
+ }
349
+ function errorText(error) {
350
+ return error instanceof Error ? error.message : String(error);
351
+ }
352
+ function candidateAction(sessionId, task, candidate) {
353
+ return {
354
+ sessionId,
355
+ taskId: task.id,
356
+ taskRevision: task.revision,
357
+ candidateId: candidate.id,
358
+ hash: candidate.hash
359
+ };
360
+ }
361
+ function canRecover(task) {
362
+ return [
363
+ "interrupted",
364
+ "review",
365
+ "decision"
366
+ ].includes(task.state) && Boolean(task.reportIds.at(-1)?.startsWith(`${task.revision}:`));
367
+ }
368
+ function canResume(task) {
369
+ return [
370
+ "decision",
371
+ "interrupted",
372
+ "failed"
373
+ ].includes(task.state) && task.delivery !== "pending" && task.cleanup !== "pending";
374
+ }
375
+ function useStatus(store, sessionId) {
376
+ const subscribe = (0, react.useCallback)((listener) => sessionId ? store.subscribe(sessionId, listener) : () => {}, [store, sessionId]);
377
+ const snapshot = (0, react.useCallback)(() => store.snapshot(sessionId), [store, sessionId]);
378
+ return (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot);
379
+ }
380
+ function ExactPreview({ preview, locale }) {
381
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
382
+ className: "dsh-fusion-preview-content",
383
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
384
+ className: "dsh-fusion-path",
385
+ title: preview.path,
386
+ children: preview.path
387
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
388
+ className: "dsh-fusion-compare",
389
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: label(locale, "当前原文", "Current file") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: preview.before })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: label(locale, "采用后全文", "Exact result") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: preview.after })] })]
390
+ })]
391
+ });
392
+ }
393
+ function PreviewDialog(props) {
394
+ const ref = (0, react.useRef)(null);
395
+ const titleId = (0, react.useId)();
396
+ (0, react.useEffect)(() => {
397
+ const dialog = ref.current;
398
+ dialog?.showModal();
399
+ return () => {
400
+ dialog?.close();
401
+ props.returnTo?.focus();
402
+ };
403
+ }, [props.returnTo]);
404
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
405
+ ref,
406
+ className: "dsh-fusion-dialog",
407
+ "aria-labelledby": titleId,
408
+ onCancel: (event) => {
409
+ event.preventDefault();
410
+ if (!props.busy) props.onClose();
411
+ },
412
+ onClose: () => {
413
+ if (!props.busy) props.onClose();
414
+ },
415
+ children: [
416
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
417
+ className: "dsh-fusion-dialog-head",
418
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
419
+ id: titleId,
420
+ children: label(props.locale, "审阅写入内容", "Review exact change")
421
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
422
+ type: "button",
423
+ onClick: props.onClose,
424
+ disabled: props.busy,
425
+ "aria-label": label(props.locale, "关闭预览", "Close preview"),
426
+ children: "×"
427
+ })]
428
+ }),
429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExactPreview, {
430
+ preview: props.preview,
431
+ locale: props.locale
432
+ }),
433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
434
+ className: "dsh-fusion-hint",
435
+ children: label(props.locale, "确认后将按当前文件版本写入;原文变化会阻止应用。", "Confirming writes against this file version; changes to the source will block application.")
436
+ }),
437
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
438
+ className: "dsh-fusion-actions",
439
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
440
+ type: "button",
441
+ onClick: props.onClose,
442
+ disabled: props.busy,
443
+ children: label(props.locale, "返回", "Back")
444
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
445
+ type: "button",
446
+ className: "dsh-fusion-primary",
447
+ onClick: props.onApply,
448
+ disabled: props.busy,
449
+ children: props.busy ? label(props.locale, "正在应用…", "Applying…") : label(props.locale, "确认采用", "Apply change")
450
+ })]
451
+ })
452
+ ]
453
+ });
454
+ }
455
+ function FusionTaskCard(props) {
456
+ const { client, store, status, sessionId, locale, native } = props;
457
+ const pair = status.pair;
458
+ const task = currentTask(pair);
459
+ const [expanded, setExpanded] = (0, react.useState)(false);
460
+ const [busy, setBusy] = (0, react.useState)("");
461
+ const [error, setError] = (0, react.useState)("");
462
+ const [preview, setPreview] = (0, react.useState)();
463
+ const [feedback, setFeedback] = (0, react.useState)("");
464
+ const returnTo = (0, react.useRef)(null);
465
+ const mounted = (0, react.useRef)(true);
466
+ const current = (0, react.useRef)("");
467
+ const busyRef = (0, react.useRef)(false);
468
+ const candidate = task?.candidates.at(-1);
469
+ current.current = task ? actionIdentity(sessionId, task, candidate) : "";
470
+ (0, react.useLayoutEffect)(() => {
471
+ mounted.current = true;
472
+ return () => {
473
+ mounted.current = false;
474
+ };
475
+ }, []);
476
+ (0, react.useEffect)(() => {
477
+ setError("");
478
+ setPreview(void 0);
479
+ setFeedback("");
480
+ setBusy("");
481
+ busyRef.current = false;
482
+ }, [
483
+ sessionId,
484
+ task?.id,
485
+ task?.revision,
486
+ task?.state,
487
+ task?.updatedAt,
488
+ task?.adoption,
489
+ candidate?.hash
490
+ ]);
491
+ (0, react.useEffect)(() => {
492
+ if (!task || !candidate || !preview) return;
493
+ if (preview.candidateId !== candidate.id || preview.hash !== candidate.hash || !pair || !canAdopt(pair, task) || props.loadError || status.error) setPreview(void 0);
494
+ }, [
495
+ task,
496
+ candidate,
497
+ pair,
498
+ preview,
499
+ props.loadError,
500
+ status.error
501
+ ]);
502
+ if (!pair || !task || pair.leadSessionId !== sessionId) return null;
503
+ const view = taskView(pair, task, locale);
504
+ const staleStatus = Boolean(props.loadError || status.error);
505
+ const allowed = !staleStatus && canAdopt(pair, task);
506
+ const accepted = acceptedCandidate(task);
507
+ const key = actionIdentity(sessionId, task, candidate);
508
+ const isCurrent = () => isCurrentAction(mounted.current, key, current.current);
509
+ const identity = {
510
+ sessionId,
511
+ taskId: task.id,
512
+ taskRevision: task.revision
513
+ };
514
+ async function call(endpoint, payload) {
515
+ const result = await client.connection.rpc.call(FUSION_RPC_CHANNEL, endpoint, payload);
516
+ if (!result.ok) throw new Error(result.error.message);
517
+ return result.value;
518
+ }
519
+ async function act(endpoint, payload, success) {
520
+ if (!isCurrent() || busyRef.current || staleStatus) return;
521
+ busyRef.current = true;
522
+ setBusy(endpoint);
523
+ setError("");
524
+ store.invalidate(sessionId);
525
+ try {
526
+ const value = await call(endpoint, payload);
527
+ if (isCurrent()) success?.(value);
528
+ } catch (cause) {
529
+ if (isCurrent()) setError(errorText(cause));
530
+ } finally {
531
+ if (isCurrent()) {
532
+ busyRef.current = false;
533
+ setBusy("");
534
+ }
535
+ await store.refresh(sessionId);
536
+ }
537
+ }
538
+ async function loadPreview(event) {
539
+ if (!allowed || !candidate || candidate.id !== accepted?.id || busyRef.current || !isCurrent()) return;
540
+ returnTo.current = event.currentTarget;
541
+ busyRef.current = true;
542
+ setBusy("preview");
543
+ setError("");
544
+ try {
545
+ const value = await call("preview", candidateAction(sessionId, task, candidate));
546
+ if (isCurrent() && value.candidateId === candidate.id && value.hash === candidate.hash) setPreview(value);
547
+ } catch (cause) {
548
+ if (isCurrent()) setError(errorText(cause));
549
+ } finally {
550
+ if (isCurrent()) {
551
+ busyRef.current = false;
552
+ setBusy("");
553
+ }
554
+ }
555
+ }
556
+ async function applyPreview() {
557
+ if (!preview || staleStatus || !allowed || !candidate || preview.candidateId !== candidate.id || preview.hash !== candidate.hash) return;
558
+ const exactPreview = preview;
559
+ await act("apply", {
560
+ ...candidateAction(sessionId, task, candidate),
561
+ expectedVersion: exactPreview.version
562
+ }, (value) => {
563
+ if (!notifyAppliedReceipt({
564
+ value,
565
+ task,
566
+ candidate,
567
+ preview: exactPreview,
568
+ isCurrent,
569
+ notify: (receipt) => {
570
+ if (props.onApplied) store.announceApplied(sessionId, receipt.id, receipt.path, props.onApplied);
571
+ }
572
+ })) throw new Error(label(locale, "宿主未确认当前预览已应用,请刷新状态核对。", "The Host did not confirm this exact preview. Refresh status to inspect the result."));
573
+ setPreview(void 0);
574
+ });
575
+ }
576
+ function openChild() {
577
+ try {
578
+ if (!client.sidebarRight) throw new Error(label(locale, "原生侧栏不可用。", "Native sidebar unavailable."));
579
+ client.sidebarRight.openResource(view.executionAddress, {
580
+ kind: "subagentchat",
581
+ preferNewPane: true
582
+ });
583
+ } catch (cause) {
584
+ setError(errorText(cause));
585
+ }
586
+ }
587
+ function onFeedbackKey(event) {
588
+ if (event.key === "Escape") {
589
+ setFeedback("");
590
+ event.currentTarget.blur();
591
+ }
592
+ }
593
+ const recoverable = !staleStatus && canRecover(task) && (task.delivery === "uncertain" || task.state === "interrupted" || !task.notifiedReportId);
594
+ const resumable = !staleStatus && canResume(task);
595
+ const latestReview = task.reviews.at(-1);
596
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
597
+ className: `dsh-fusion-card${native ? " is-native" : ""}`,
598
+ "data-session": sessionId,
599
+ "aria-label": label(locale, "协作任务", "Collaboration task"),
600
+ children: [
601
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
602
+ className: "dsh-fusion-head",
603
+ children: [
604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
605
+ className: "dsh-fusion-kicker",
606
+ children: label(locale, "协作", "Collaboration")
607
+ }),
608
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
609
+ title: task.brief.title,
610
+ children: task.brief.title
611
+ }),
612
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
613
+ className: "dsh-fusion-state",
614
+ role: "status",
615
+ children: view.status
616
+ }),
617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
618
+ type: "button",
619
+ className: "dsh-fusion-details",
620
+ "aria-expanded": expanded,
621
+ onClick: () => setExpanded((value) => !value),
622
+ children: expanded ? label(locale, "收起", "Collapse") : label(locale, "详情", "Details")
623
+ })
624
+ ]
625
+ }),
626
+ view.detail ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
627
+ className: "dsh-fusion-detail",
628
+ children: view.detail
629
+ }) : null,
630
+ status.activity ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
631
+ className: "dsh-fusion-activity",
632
+ children: [
633
+ label(locale, "统筹", "Lead"),
634
+ ": ",
635
+ activityLabel(status.activity.lead, locale),
636
+ " · ",
637
+ label(locale, pair.profile === "writing" ? "执笔" : "搭档", "Sidekick"),
638
+ ": ",
639
+ activityLabel(status.activity.sidekick, locale)
640
+ ]
641
+ }) : null,
642
+ status.usage && (status.usage.leadTokens !== null || status.usage.sidekickTokens !== null) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
643
+ className: "dsh-fusion-usage",
644
+ children: [
645
+ label(locale, "原生会话累计 token", "Native session tokens"),
646
+ " ·",
647
+ status.usage.leadTokens !== null ? ` ${label(locale, "统筹", "Lead")} ${status.usage.leadTokens.toLocaleString()}` : "",
648
+ status.usage.sidekickTokens !== null ? ` ${label(locale, pair.profile === "writing" ? "执笔" : "搭档", "Sidekick")} ${status.usage.sidekickTokens.toLocaleString()}` : ""
649
+ ]
650
+ }) : null,
651
+ error || props.loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
652
+ className: "dsh-fusion-error",
653
+ role: "alert",
654
+ children: error || props.loadError
655
+ }) : null,
656
+ props.loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
657
+ type: "button",
658
+ onClick: () => void store.refresh(sessionId),
659
+ children: label(locale, "重试读取", "Retry status")
660
+ }) : null,
661
+ expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
662
+ className: "dsh-fusion-body",
663
+ children: [
664
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
665
+ className: "dsh-fusion-goal",
666
+ children: task.brief.goal
667
+ }),
668
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
669
+ className: "dsh-fusion-route",
670
+ children: [
671
+ label(locale, "搭档模型", "Sidekick model"),
672
+ ": ",
673
+ pair.route.provider,
674
+ " / ",
675
+ pair.route.model,
676
+ pair.route.reasoningEffort ? ` · ${pair.route.reasoningEffort}` : ""
677
+ ]
678
+ }),
679
+ task.brief.acceptance.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: label(locale, "验收要求", "Acceptance") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: task.brief.acceptance.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: item }, index)) })] }) : null,
680
+ candidate ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [
681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: view.candidateLabel ?? label(locale, "候选", "Candidate") }),
682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
683
+ className: "dsh-fusion-report",
684
+ children: candidate.report
685
+ }),
686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
687
+ className: "dsh-fusion-candidate",
688
+ children: candidate.text
689
+ })
690
+ ] }) : task.decision ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: task.decision }) : null,
691
+ latestReview ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
692
+ className: "dsh-fusion-review",
693
+ children: [
694
+ label(locale, "统筹审查", "Lead review"),
695
+ ": ",
696
+ latestReview.verdict === "accept" ? label(locale, "通过", "Accepted") : latestReview.verdict === "revise" ? label(locale, "要求修改", "Revision requested") : label(locale, "未通过", "Rejected"),
697
+ latestReview.feedback ? ` · ${latestReview.feedback}` : ""
698
+ ]
699
+ }) : null,
700
+ task.delivery === "uncertain" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
701
+ className: "dsh-fusion-warning",
702
+ role: "status",
703
+ children: task.reportIds.length ? label(locale, "报告通知结果不确定。请核对原生记录,再明确重送已保存的报告。", "Report notification is uncertain. Inspect native history, then explicitly re-notify the Lead of the saved report.") : label(locale, "初次交接结果不确定。请核对搭档会话,勿直接重发同一任务。", "Initial admission is uncertain. Inspect the Sidekick session before starting another task.")
704
+ }) : null,
705
+ task.application?.state === "pending" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
706
+ className: "dsh-fusion-warning",
707
+ children: label(locale, "写入结果待宿主核对,请刷新状态后再操作。", "The Host is reconciling the write. Refresh status before acting.")
708
+ }) : null,
709
+ native ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
710
+ type: "button",
711
+ onClick: openChild,
712
+ children: label(locale, pair.profile === "writing" ? "查看执笔会话" : "查看搭档会话", "Open Sidekick session")
713
+ }) : null
714
+ ]
715
+ }) : null,
716
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
717
+ className: "dsh-fusion-actions",
718
+ children: [
719
+ allowed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
720
+ type: "button",
721
+ disabled: Boolean(busy),
722
+ onClick: (event) => void loadPreview(event),
723
+ children: label(locale, "预览采用", "Preview adoption")
724
+ }) : null,
725
+ allowed && candidate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
726
+ type: "button",
727
+ disabled: Boolean(busy),
728
+ onClick: () => void act("dismiss", candidateAction(sessionId, task, candidate)),
729
+ children: label(locale, "放弃候选", "Dismiss candidate")
730
+ }) : null,
731
+ view.canStop ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
732
+ type: "button",
733
+ disabled: Boolean(busy) || staleStatus,
734
+ onClick: () => void act("cancel", identity),
735
+ children: label(locale, "停止协作任务", "Stop task")
736
+ }) : null,
737
+ recoverable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
738
+ type: "button",
739
+ disabled: Boolean(busy),
740
+ onClick: () => void act("recover", identity),
741
+ children: label(locale, "重送已保存报告", "Re-notify saved report")
742
+ }) : null,
743
+ task.cleanup === "failed" || task.adoption === "conflict" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
744
+ type: "button",
745
+ disabled: Boolean(busy),
746
+ onClick: () => void store.refresh(sessionId),
747
+ children: label(locale, "刷新状态", "Refresh status")
748
+ }) : null
749
+ ]
750
+ }),
751
+ resumable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
752
+ className: "dsh-fusion-resume",
753
+ children: [
754
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
755
+ htmlFor: `dsh-fusion-feedback-${task.id}`,
756
+ children: label(locale, "给统筹的后续要求", "Feedback to Lead")
757
+ }),
758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
759
+ id: `dsh-fusion-feedback-${task.id}`,
760
+ value: feedback,
761
+ onChange: (event) => setFeedback(event.target.value),
762
+ onKeyDown: onFeedbackKey,
763
+ rows: 2,
764
+ maxLength: 4e3
765
+ }),
766
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
767
+ type: "button",
768
+ disabled: Boolean(busy) || !feedback.trim(),
769
+ onClick: () => void act("resume", {
770
+ ...identity,
771
+ feedback: feedback.trim()
772
+ }, () => setFeedback("")),
773
+ children: label(locale, "继续协作", "Continue collaboration")
774
+ })
775
+ ]
776
+ }) : null,
777
+ preview ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreviewDialog, {
778
+ preview,
779
+ locale,
780
+ busy: busy === "apply",
781
+ returnTo: returnTo.current,
782
+ onClose: () => setPreview(void 0),
783
+ onApply: () => void applyPreview()
784
+ }) : null
785
+ ]
786
+ });
787
+ }
788
+ function appliedOwnerCallback(owner) {
789
+ if (!owner || typeof owner !== "object") return void 0;
790
+ const row = owner;
791
+ const nested = row.owner && typeof row.owner === "object" ? row.owner : void 0;
792
+ const callback = row.onApplied ?? nested?.onApplied;
793
+ return typeof callback === "function" ? callback : void 0;
794
+ }
795
+ function FusionSeat(props) {
796
+ const { client, store, native, owner } = props;
797
+ const seat = useNativeSeat(client, owner);
798
+ const snapshot = useStatus(store, seat.sessionId);
799
+ const task = currentTask(snapshot.status?.pair);
800
+ const onApplied = native ? void 0 : appliedOwnerCallback(owner);
801
+ const observedPending = (0, react.useRef)();
802
+ (0, react.useEffect)(() => {
803
+ if (!onApplied || !seat.sessionId || seat.hidden || snapshot.error) {
804
+ observedPending.current = void 0;
805
+ return;
806
+ }
807
+ const application = task?.application;
808
+ if (application?.state === "pending" && task) {
809
+ observedPending.current = {
810
+ sessionId: seat.sessionId,
811
+ taskId: task.id,
812
+ id: application.id,
813
+ path: application.path,
814
+ candidateId: application.candidateId,
815
+ candidateHash: application.candidateHash
816
+ };
817
+ return;
818
+ }
819
+ const receipt = reconciledReceipt(observedPending.current, seat.sessionId, task);
820
+ observedPending.current = void 0;
821
+ if (receipt) store.announceApplied(seat.sessionId, receipt.id, receipt.path, onApplied);
822
+ }, [
823
+ onApplied,
824
+ seat.sessionId,
825
+ seat.hidden,
826
+ snapshot.error,
827
+ task?.id,
828
+ task?.adoption,
829
+ task?.application,
830
+ store
831
+ ]);
832
+ if (!seat.sessionId || seat.hidden) return null;
833
+ if (native && client.uiWorkspace.current) return null;
834
+ if (!native && !client.uiWorkspace.current) return null;
835
+ if (native) {
836
+ const row = owner && typeof owner === "object" ? owner : {};
837
+ const turn = row.turn && typeof row.turn === "object" ? row.turn.turn : void 0;
838
+ const entries = client.sessions.binding?.(seat.sessionId)?.eventSource.getSnapshot().entries ?? [];
839
+ const anchor = task ? taskAnchorTurn(task, entries) : entries.map((item) => item.event.data?.turn).filter((value) => typeof value === "number").at(-1);
840
+ if (turn === void 0 || anchor !== turn) return null;
841
+ }
842
+ if (!snapshot.status) return snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
843
+ className: "dsh-fusion-card",
844
+ role: "alert",
845
+ children: [
846
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: label(seat.locale, "协作状态读取失败", "Collaboration status unavailable") }),
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: snapshot.error }),
848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
849
+ type: "button",
850
+ onClick: () => void store.refresh(seat.sessionId),
851
+ children: label(seat.locale, "重试", "Retry")
852
+ })
853
+ ]
854
+ }) : null;
855
+ if (!snapshot.status.available) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
856
+ className: "dsh-fusion-card",
857
+ role: "status",
858
+ children: [
859
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: label(seat.locale, "协作暂不可用", "Collaboration unavailable") }),
860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: snapshot.status.error ?? label(seat.locale, "检查插件或模型配置后刷新。", "Check plugin or model configuration, then refresh.") }),
861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
862
+ type: "button",
863
+ onClick: () => void store.refresh(seat.sessionId),
864
+ children: label(seat.locale, "刷新状态", "Refresh status")
865
+ })
866
+ ]
867
+ });
868
+ if (!task) return native ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
869
+ className: "dsh-fusion-card",
870
+ "aria-label": label(seat.locale, "协作", "Collaboration"),
871
+ children: [
872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: label(seat.locale, "协作", "Collaboration") }),
873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
874
+ className: "dsh-fusion-detail",
875
+ children: snapshot.status.error ?? (snapshot.status.configured ? label(seat.locale, "与统筹继续对话;需要执笔时由统筹交接。", "Continue with the Lead; the Lead delegates writing when needed.") : label(seat.locale, "协作模型尚未配置,请在模型设置中完成配置。", "Configure the collaboration model in model settings."))
876
+ }),
877
+ snapshot.status.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
878
+ type: "button",
879
+ onClick: () => void store.refresh(seat.sessionId),
880
+ children: label(seat.locale, "刷新状态", "Refresh status")
881
+ }) : null
882
+ ]
883
+ });
884
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FusionTaskCard, {
885
+ client,
886
+ store,
887
+ status: snapshot.status,
888
+ loadError: snapshot.error,
889
+ onApplied,
890
+ sessionId: seat.sessionId,
891
+ locale: seat.locale,
892
+ native
893
+ });
894
+ }
895
+ /** The native turn-tail slot is session scoped. Its own SessionSnapshot identifies subagent children. */
896
+ function NativeFusionSeat(props) {
897
+ const useSession = (props.owner && typeof props.owner === "object" ? props.owner : {}).useSession;
898
+ if (!useSession) return null;
899
+ if (useSession((snapshot) => Boolean(snapshot.subagent))) return null;
900
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FusionSeat, {
901
+ client: props.client,
902
+ store: props.store,
903
+ native: true,
904
+ owner: props.owner
905
+ });
906
+ }
907
+ function apply(ctx) {
908
+ const client = ctx;
909
+ const store = new FusionClientStore(client);
910
+ ctx.effect(() => () => store.dispose(), "dsh-fusion-client.store");
911
+ ctx.effect(() => {
912
+ if (typeof document === "undefined") return () => {};
913
+ const style = document.createElement("style");
914
+ style.setAttribute("data-plugin", "@klarkxy/dsh-fusion");
915
+ style.textContent = fusionClientStyles;
916
+ document.head.appendChild(style);
917
+ return () => style.remove();
918
+ }, "dsh-fusion-client.styles");
919
+ ctx.effect(() => client.slots.inject(CHAT_EVENTS_SLOT, () => client.slots.register({
920
+ name: CHAT_EVENTS_SLOT,
921
+ id: "fusion",
922
+ order: 30,
923
+ label: "协作"
924
+ }, (owner) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FusionSeat, {
925
+ client,
926
+ store,
927
+ native: false,
928
+ owner
929
+ }, seatKey(owner)))), "dsh-fusion-client.editor");
930
+ ctx.inject(["sidebarRight"], (scope) => {
931
+ const nativeClient = scope;
932
+ return nativeClient.slots.inject(NATIVE_TURN_TAIL_SLOT, () => nativeClient.slots.register({
933
+ name: NATIVE_TURN_TAIL_SLOT,
934
+ id: "fusion",
935
+ order: 30,
936
+ label: "Collaboration"
937
+ }, (owner) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NativeFusionSeat, {
938
+ client: nativeClient,
939
+ store,
940
+ owner
941
+ })));
942
+ });
943
+ }
944
+ function seatKey(owner) {
945
+ if (!owner || typeof owner !== "object") return "";
946
+ const row = owner;
947
+ const source = row.owner && typeof row.owner === "object" ? row.owner : row;
948
+ return typeof source.sessionId === "string" ? source.sessionId : "";
949
+ }
950
+ //#endregion
951
+ exports.NATIVE_TURN_TAIL_SLOT = NATIVE_TURN_TAIL_SLOT;
952
+ exports.apply = apply;
953
+ exports.canRecover = canRecover;
954
+ exports.canResume = canResume;
955
+ exports.candidateAction = candidateAction;
956
+ exports.inject = inject;
957
+ exports.name = name;
958
+
959
+ //# sourceMappingURL=client.inner.cjs.map