@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.
- package/LICENSE +41 -0
- package/README.md +33 -0
- package/cordis.patch.yml +4 -0
- package/docs/README.zh-CN.md +13 -0
- package/lib/client.inner.cjs +959 -0
- package/lib/client.inner.cjs.map +1 -0
- package/lib/client.js +1129 -0
- package/lib/contracts.d.ts +173 -0
- package/lib/contracts.js +28 -0
- package/lib/contracts.js.map +1 -0
- package/lib/host-contracts.d.ts +38 -0
- package/lib/host-contracts.js +1 -0
- package/lib/index.d.ts +134 -0
- package/lib/index.js +1670 -0
- package/lib/index.js.map +1 -0
- package/package.json +128 -0
|
@@ -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
|