dsh-advisor-group 0.1.1 → 0.1.3
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/README.en.md +144 -0
- package/README.md +105 -86
- package/lib/client.js +97 -45
- package/lib/index.mjs +1266 -237
- package/package.json +1 -1
- package/README.zh.md +0 -131
package/lib/client.js
CHANGED
|
@@ -283,12 +283,60 @@ const DEFAULT_ADVISOR_PROMPT = `
|
|
|
283
283
|
* advisor-group/message -> update (live bubbles)
|
|
284
284
|
* advisor-group/end -> update (final synopsis)
|
|
285
285
|
*/
|
|
286
|
-
|
|
286
|
+
/** The per-boot token as injected into the page, when that injection arrived. */
|
|
287
|
+
function injectedAdvisorGroupToken() {
|
|
287
288
|
return globalThis.__ADVISOR_GROUP_TOKEN__ ?? "";
|
|
288
289
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
let advisorGroupTokenCache = "";
|
|
291
|
+
let advisorGroupTokenFetch = null;
|
|
292
|
+
/** Token known so far: the injected one wins, otherwise the last handshake. */
|
|
293
|
+
function currentAdvisorGroupToken() {
|
|
294
|
+
return injectedAdvisorGroupToken() || advisorGroupTokenCache;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The host normally injects the per-boot token into the served index. The
|
|
298
|
+
* desktop shell can compose its window before this plugin registers that
|
|
299
|
+
* injection, so the page may never receive it — fall back to fetching it from
|
|
300
|
+
* the plugin's own handshake route.
|
|
301
|
+
*/
|
|
302
|
+
async function resolveAdvisorGroupToken(force = false) {
|
|
303
|
+
if (!force) {
|
|
304
|
+
const known = currentAdvisorGroupToken();
|
|
305
|
+
if (known) return known;
|
|
306
|
+
}
|
|
307
|
+
if (!advisorGroupTokenFetch) advisorGroupTokenFetch = (async () => {
|
|
308
|
+
try {
|
|
309
|
+
const res = await fetch(`/advisor-group/boot-token`, { headers: { accept: "application/json" } });
|
|
310
|
+
if (!res.ok) return "";
|
|
311
|
+
const body = await res.json();
|
|
312
|
+
return typeof body.token === "string" ? body.token : "";
|
|
313
|
+
} catch {
|
|
314
|
+
return "";
|
|
315
|
+
}
|
|
316
|
+
})().then((token) => {
|
|
317
|
+
advisorGroupTokenCache = token;
|
|
318
|
+
advisorGroupTokenFetch = null;
|
|
319
|
+
return token;
|
|
320
|
+
});
|
|
321
|
+
return await advisorGroupTokenFetch || injectedAdvisorGroupToken();
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* `fetch` with the per-boot token attached. A 401 means the token rotated (a
|
|
325
|
+
* live plugin reload mints a new one) or that the injected copy was stale, so
|
|
326
|
+
* refresh it once and retry before surfacing the failure.
|
|
327
|
+
*/
|
|
328
|
+
async function authedFetch(path, init = {}) {
|
|
329
|
+
const send = (token) => fetch(path, {
|
|
330
|
+
...init,
|
|
331
|
+
headers: {
|
|
332
|
+
...init.headers,
|
|
333
|
+
...token ? { "x-advisor-group-token": token } : {}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
const res = await send(await resolveAdvisorGroupToken());
|
|
337
|
+
if (res.status !== 401) return res;
|
|
338
|
+
const refreshed = await resolveAdvisorGroupToken(true);
|
|
339
|
+
return refreshed ? send(refreshed) : res;
|
|
292
340
|
}
|
|
293
341
|
/**
|
|
294
342
|
* Browser-side twin of `sanitizeAdvisorContent` (host module cannot be
|
|
@@ -315,7 +363,8 @@ function viewData(state) {
|
|
|
315
363
|
...state.context === void 0 ? {} : { context: state.context },
|
|
316
364
|
advisors: state.advisors,
|
|
317
365
|
messages: state.messages,
|
|
318
|
-
...state.summary === void 0 ? {} : { summary: state.summary }
|
|
366
|
+
...state.summary === void 0 ? {} : { summary: state.summary },
|
|
367
|
+
...state.mode === void 0 ? {} : { mode: state.mode }
|
|
319
368
|
};
|
|
320
369
|
}
|
|
321
370
|
const advisorGroupDefinition = {
|
|
@@ -335,7 +384,7 @@ const advisorGroupDefinition = {
|
|
|
335
384
|
start: (_context, match) => {
|
|
336
385
|
if (match.event.type !== "advisor-group/start") throw new Error("advisor-group requires advisor-group/start");
|
|
337
386
|
const data = match.event.data;
|
|
338
|
-
|
|
387
|
+
const base = {
|
|
339
388
|
sessionId: data.sessionId,
|
|
340
389
|
turn: data.turn,
|
|
341
390
|
step: data.step,
|
|
@@ -351,6 +400,11 @@ const advisorGroupDefinition = {
|
|
|
351
400
|
}],
|
|
352
401
|
status: "running"
|
|
353
402
|
};
|
|
403
|
+
if (data.kind === "ask") return {
|
|
404
|
+
...base,
|
|
405
|
+
mode: "ask"
|
|
406
|
+
};
|
|
407
|
+
return base;
|
|
354
408
|
},
|
|
355
409
|
update: (context, match) => {
|
|
356
410
|
if (match.event.type === "advisor-group/message") {
|
|
@@ -710,11 +764,11 @@ function buildStepSequence(actionDescriptions, thinkingSegments, toolViews) {
|
|
|
710
764
|
* 💭 思考·1 → ⛭ 工具×1(输入/输出)→ 💭 思考·2 → ⛭ 工具×2 → … → 📄 正文
|
|
711
765
|
* Main/system messages stay a single collapsible row.
|
|
712
766
|
*/
|
|
713
|
-
function AdvisorSteps({ message }) {
|
|
767
|
+
function AdvisorSteps({ message, plain = false }) {
|
|
714
768
|
const isMain = message.role === "main";
|
|
715
769
|
const isSystem = message.role === "system";
|
|
716
770
|
const label = isMain ? "YOU" : isSystem ? "SYSTEM" : message.advisorName ?? message.advisorId ?? "ADVISOR";
|
|
717
|
-
const group = isMain
|
|
771
|
+
const group = isMain || isSystem || plain ? label : `${label} · R${message.round ?? 1}`;
|
|
718
772
|
const truncated = message.truncated;
|
|
719
773
|
const toolViews = buildToolStepViews(message.toolSteps);
|
|
720
774
|
const thinkingSegments = message.thinkingSegments && message.thinkingSegments.length > 0 ? message.thinkingSegments : message.thinking ? [message.thinking] : [];
|
|
@@ -767,7 +821,7 @@ function AdvisorSteps({ message }) {
|
|
|
767
821
|
if (liveToolPhase) continue;
|
|
768
822
|
children.push((0, react.createElement)(StepRow, {
|
|
769
823
|
key: `body-${index}`,
|
|
770
|
-
title: `${group} 📄 正文`,
|
|
824
|
+
title: plain ? group : `${group} 📄 正文`,
|
|
771
825
|
badges: truncated ? [(0, react.createElement)("span", { style: badgeStyle("#f87171") }, truncated.reason === "timeout" ? "⏱ 响应超时截断" : "⚠ 流中断")] : [],
|
|
772
826
|
defaultOpen: true,
|
|
773
827
|
color: "#4ade80",
|
|
@@ -793,6 +847,7 @@ const NOTICE_TTL_MS = 6e3;
|
|
|
793
847
|
function AdvisorGroupNodeView(props) {
|
|
794
848
|
const data = props.node.data;
|
|
795
849
|
const sessionId = data.sessionId;
|
|
850
|
+
const isAsk = data.mode === "ask";
|
|
796
851
|
const [live, setLive] = (0, react.useState)({});
|
|
797
852
|
const [contextCollapsed, setContextCollapsed] = (0, react.useState)(true);
|
|
798
853
|
const lastEventIdRef = (0, react.useRef)(0);
|
|
@@ -816,9 +871,20 @@ function AdvisorGroupNodeView(props) {
|
|
|
816
871
|
const clearStreamNotice = () => {
|
|
817
872
|
setTransientNotice((prev) => prev && prev.kind === "stream" ? null : prev);
|
|
818
873
|
};
|
|
874
|
+
const [streamTokenReady, setStreamTokenReady] = (0, react.useState)(() => currentAdvisorGroupToken() !== "");
|
|
875
|
+
(0, react.useEffect)(() => {
|
|
876
|
+
if (streamTokenReady) return;
|
|
877
|
+
let alive = true;
|
|
878
|
+
resolveAdvisorGroupToken().then((token) => {
|
|
879
|
+
if (alive && token) setStreamTokenReady(true);
|
|
880
|
+
});
|
|
881
|
+
return () => {
|
|
882
|
+
alive = false;
|
|
883
|
+
};
|
|
884
|
+
}, [streamTokenReady]);
|
|
819
885
|
(0, react.useEffect)(() => {
|
|
820
|
-
if (!sessionId) return;
|
|
821
|
-
const es = new EventSource(`/advisor-group/stream?sessionId=${encodeURIComponent(sessionId)}&lastEventId=${lastEventIdRef.current}&bootId=${encodeURIComponent(lastBootIdRef.current)}&token=${encodeURIComponent(
|
|
886
|
+
if (!sessionId || isAsk || !streamTokenReady) return;
|
|
887
|
+
const es = new EventSource(`/advisor-group/stream?sessionId=${encodeURIComponent(sessionId)}&lastEventId=${lastEventIdRef.current}&bootId=${encodeURIComponent(lastBootIdRef.current)}&token=${encodeURIComponent(currentAdvisorGroupToken())}`);
|
|
822
888
|
es.onopen = () => {
|
|
823
889
|
clearStreamNotice();
|
|
824
890
|
};
|
|
@@ -896,7 +962,7 @@ function AdvisorGroupNodeView(props) {
|
|
|
896
962
|
noticeTimerRef.current = null;
|
|
897
963
|
}
|
|
898
964
|
};
|
|
899
|
-
}, [sessionId]);
|
|
965
|
+
}, [sessionId, streamTokenReady]);
|
|
900
966
|
const mergedMessages = data.messages.map((message) => {
|
|
901
967
|
if (message.role !== "advisor") return message;
|
|
902
968
|
const advisorId = message.advisorId ?? "";
|
|
@@ -927,7 +993,7 @@ function AdvisorGroupNodeView(props) {
|
|
|
927
993
|
};
|
|
928
994
|
});
|
|
929
995
|
const shortId = data.sessionId.length > 8 ? data.sessionId.slice(0, 8) : data.sessionId;
|
|
930
|
-
const title = data.status === "completed" ? "ADVISOR GROUP · DONE" : data.status === "cancelled" ? "ADVISOR GROUP · STOPPED" : "ADVISOR GROUP · LIVE";
|
|
996
|
+
const title = isAsk ? data.status === "completed" ? "跨会话提问 · 已完成" : "跨会话提问 · 等待应答" : data.status === "completed" ? "ADVISOR GROUP · DONE" : data.status === "cancelled" ? "ADVISOR GROUP · STOPPED" : "ADVISOR GROUP · LIVE";
|
|
931
997
|
return (0, react.createElement)("div", {
|
|
932
998
|
style: rootStyle,
|
|
933
999
|
role: "log",
|
|
@@ -939,17 +1005,14 @@ function AdvisorGroupNodeView(props) {
|
|
|
939
1005
|
alignItems: "center",
|
|
940
1006
|
gap: 8
|
|
941
1007
|
}
|
|
942
|
-
}, data.status === "running" ? (0, react.createElement)("button", {
|
|
1008
|
+
}, data.status === "running" && !isAsk ? (0, react.createElement)("button", {
|
|
943
1009
|
type: "button",
|
|
944
1010
|
onClick: () => {
|
|
945
1011
|
(async () => {
|
|
946
1012
|
try {
|
|
947
|
-
const body = await (await
|
|
1013
|
+
const body = await (await authedFetch("/advisor-group/stop", {
|
|
948
1014
|
method: "POST",
|
|
949
|
-
headers: {
|
|
950
|
-
"content-type": "application/json",
|
|
951
|
-
...authHeaders()
|
|
952
|
-
},
|
|
1015
|
+
headers: { "content-type": "application/json" },
|
|
953
1016
|
body: JSON.stringify({ sessionId })
|
|
954
1017
|
})).json();
|
|
955
1018
|
if (body.ok === false) showTransientNotice(body.error ?? "请求未被服务端接受,请稍后重试。", "action", NOTICE_TTL_MS);
|
|
@@ -967,17 +1030,14 @@ function AdvisorGroupNodeView(props) {
|
|
|
967
1030
|
padding: "2px 8px",
|
|
968
1031
|
fontSize: 11
|
|
969
1032
|
}
|
|
970
|
-
}, "⏹ 停止") : data.status === "cancelled" ? (0, react.createElement)("button", {
|
|
1033
|
+
}, "⏹ 停止") : data.status === "cancelled" && !isAsk ? (0, react.createElement)("button", {
|
|
971
1034
|
type: "button",
|
|
972
1035
|
onClick: () => {
|
|
973
1036
|
(async () => {
|
|
974
1037
|
try {
|
|
975
|
-
const body = await (await
|
|
1038
|
+
const body = await (await authedFetch("/advisor-group/resume", {
|
|
976
1039
|
method: "POST",
|
|
977
|
-
headers: {
|
|
978
|
-
"content-type": "application/json",
|
|
979
|
-
...authHeaders()
|
|
980
|
-
},
|
|
1040
|
+
headers: { "content-type": "application/json" },
|
|
981
1041
|
body: JSON.stringify({ sessionId })
|
|
982
1042
|
})).json();
|
|
983
1043
|
if (body.ok === false) showTransientNotice(body.error ?? "请求未被服务端接受,请稍后重试。", "action", NOTICE_TTL_MS);
|
|
@@ -999,9 +1059,10 @@ function AdvisorGroupNodeView(props) {
|
|
|
999
1059
|
type: "button",
|
|
1000
1060
|
onClick: () => setContextCollapsed((value) => !value),
|
|
1001
1061
|
style: contextToggleStyle
|
|
1002
|
-
}, `📋 项目背景 ${contextCollapsed ? "▸" : "▾"}`), !contextCollapsed ? (0, react.createElement)("div", { style: contextBodyStyle }, data.context) : null) : null, (0, react.createElement)("div", { style: advisorsLineStyle }, `ADVISORS: ${data.advisors.map((a) => a.avatar ? `${a.avatar} ${a.name}` : a.name).join(" · ")}`), mergedMessages.map((message, index) => (0, react.createElement)(AdvisorSteps, {
|
|
1062
|
+
}, `📋 项目背景 ${contextCollapsed ? "▸" : "▾"}`), !contextCollapsed ? (0, react.createElement)("div", { style: contextBodyStyle }, data.context) : null) : null, (0, react.createElement)("div", { style: advisorsLineStyle }, isAsk ? `提问方: ${data.advisors[0]?.name ?? "?"} → 目标会话: ${data.advisors[1]?.name ?? "?"}` : `ADVISORS: ${data.advisors.map((a) => a.avatar ? `${a.avatar} ${a.name}` : a.name).join(" · ")}`), mergedMessages.map((message, index) => (0, react.createElement)(AdvisorSteps, {
|
|
1003
1063
|
key: index,
|
|
1004
|
-
message
|
|
1064
|
+
message,
|
|
1065
|
+
plain: isAsk
|
|
1005
1066
|
})), data.status === "completed" && data.summary?.conclusion ? (0, react.createElement)("div", { style: {
|
|
1006
1067
|
borderTop: "1px dashed #2f9e44",
|
|
1007
1068
|
marginTop: 8,
|
|
@@ -1010,7 +1071,7 @@ function AdvisorGroupNodeView(props) {
|
|
|
1010
1071
|
color: "#86efac",
|
|
1011
1072
|
fontWeight: 700,
|
|
1012
1073
|
marginBottom: 4
|
|
1013
|
-
} }, "📌 综合结论"), (0, react.createElement)("div", null, renderMarkdown(data.summary.conclusion))) : null, data.status === "completed" && (data.summary?.riskNotes ?? []).length > 0 ? (0, react.createElement)("div", { style: {
|
|
1074
|
+
} }, isAsk ? "📌 投递说明" : "📌 综合结论"), (0, react.createElement)("div", null, renderMarkdown(data.summary.conclusion))) : null, data.status === "completed" && (data.summary?.riskNotes ?? []).length > 0 ? (0, react.createElement)("div", { style: {
|
|
1014
1075
|
borderTop: "1px dashed #2f9e44",
|
|
1015
1076
|
marginTop: 8,
|
|
1016
1077
|
paddingTop: 8
|
|
@@ -1035,7 +1096,7 @@ function AdvisorGroupNodeView(props) {
|
|
|
1035
1096
|
fontSize: 12,
|
|
1036
1097
|
marginTop: 2
|
|
1037
1098
|
}
|
|
1038
|
-
}, note))) : null, data.status === "running" ? (0, react.createElement)("div", { style: waitingStyle }, "▊ AWAITING RESPONSES…") : null, transientNotice ? (0, react.createElement)("div", { style: noticeStyle }, transientNotice.text) : null);
|
|
1099
|
+
}, note))) : null, data.status === "running" ? (0, react.createElement)("div", { style: waitingStyle }, isAsk ? "▊ 已投递,等待对方回应…" : "▊ AWAITING RESPONSES…") : null, transientNotice ? (0, react.createElement)("div", { style: noticeStyle }, transientNotice.text) : null);
|
|
1039
1100
|
}
|
|
1040
1101
|
const settingsRootStyle = {
|
|
1041
1102
|
fontFamily: "'Inter', 'Segoe UI', system-ui, sans-serif",
|
|
@@ -1235,7 +1296,7 @@ function AdvisorGroupSettingsTab() {
|
|
|
1235
1296
|
setMsg("");
|
|
1236
1297
|
setError("");
|
|
1237
1298
|
try {
|
|
1238
|
-
const [configData, providerData] = await Promise.all([
|
|
1299
|
+
const [configData, providerData] = await Promise.all([authedFetch("/advisor-group/config").then((r) => r.json()), authedFetch("/advisor-group/providers").then((r) => r.json())]);
|
|
1239
1300
|
if (configData.ok && configData.config) {
|
|
1240
1301
|
setConfig(configData.config);
|
|
1241
1302
|
savedJsonRef.current = JSON.stringify(configData.config);
|
|
@@ -1291,12 +1352,9 @@ function AdvisorGroupSettingsTab() {
|
|
|
1291
1352
|
setError("");
|
|
1292
1353
|
setMsg("");
|
|
1293
1354
|
try {
|
|
1294
|
-
const data = await (await
|
|
1355
|
+
const data = await (await authedFetch("/advisor-group/models", {
|
|
1295
1356
|
method: "POST",
|
|
1296
|
-
headers: {
|
|
1297
|
-
"content-type": "application/json",
|
|
1298
|
-
...authHeaders()
|
|
1299
|
-
},
|
|
1357
|
+
headers: { "content-type": "application/json" },
|
|
1300
1358
|
body: JSON.stringify({
|
|
1301
1359
|
advisorId: advisor.id,
|
|
1302
1360
|
provider: advisor.provider,
|
|
@@ -1332,12 +1390,9 @@ function AdvisorGroupSettingsTab() {
|
|
|
1332
1390
|
setError("");
|
|
1333
1391
|
setMsg("");
|
|
1334
1392
|
try {
|
|
1335
|
-
const data = await (await
|
|
1393
|
+
const data = await (await authedFetch("/advisor-group/test-connection", {
|
|
1336
1394
|
method: "POST",
|
|
1337
|
-
headers: {
|
|
1338
|
-
"content-type": "application/json",
|
|
1339
|
-
...authHeaders()
|
|
1340
|
-
},
|
|
1395
|
+
headers: { "content-type": "application/json" },
|
|
1341
1396
|
body: JSON.stringify({
|
|
1342
1397
|
advisorId: advisor.id,
|
|
1343
1398
|
provider: advisor.provider,
|
|
@@ -1371,12 +1426,9 @@ function AdvisorGroupSettingsTab() {
|
|
|
1371
1426
|
},
|
|
1372
1427
|
advisors: config.advisors.map(({ apiKeyMetaByProvider: _meta, ...rest }) => rest)
|
|
1373
1428
|
};
|
|
1374
|
-
const data = await (await
|
|
1429
|
+
const data = await (await authedFetch("/advisor-group/config", {
|
|
1375
1430
|
method: "POST",
|
|
1376
|
-
headers: {
|
|
1377
|
-
"content-type": "application/json",
|
|
1378
|
-
...authHeaders()
|
|
1379
|
-
},
|
|
1431
|
+
headers: { "content-type": "application/json" },
|
|
1380
1432
|
body: JSON.stringify({
|
|
1381
1433
|
config: configPayload,
|
|
1382
1434
|
expectedRevision: revision
|