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/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
- function advisorGroupToken() {
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
- function authHeaders() {
290
- const token = advisorGroupToken();
291
- return token ? { "x-advisor-group-token": token } : {};
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
- return {
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 ? "YOU" : isSystem ? "SYSTEM" : `${label} · R${message.round ?? 1}`;
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(advisorGroupToken())}`);
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 fetch("/advisor-group/stop", {
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 fetch("/advisor-group/resume", {
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([fetch("/advisor-group/config", { headers: authHeaders() }).then((r) => r.json()), fetch("/advisor-group/providers", { headers: authHeaders() }).then((r) => r.json())]);
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 fetch("/advisor-group/models", {
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 fetch("/advisor-group/test-connection", {
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 fetch("/advisor-group/config", {
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