dsh-cloudq 0.1.8 → 0.2.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/lib/client.js CHANGED
@@ -599,14 +599,17 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
599
599
  return !!sessionId && cloudqSessions.has(sessionId);
600
600
  }
601
601
  let cloudqHistorySync = null;
602
+ let reconcileQueued = false;
602
603
  function reconcileCloudqSessionsFromHost() {
603
- if (cloudqHistorySync) return cloudqHistorySync;
604
+ if (cloudqHistorySync) {
605
+ reconcileQueued = true;
606
+ return cloudqHistorySync;
607
+ }
604
608
  cloudqHistorySync = cloudqRequest(API_CLOUDQ_SESSIONS, void 0, 6e4).then((response) => {
605
609
  const detected = new Set(Array.isArray(response?.sessionIds) ? response.sessionIds : []);
606
- const snapshot = cloudqCtx?.sessions?.list?.getSnapshot?.();
607
- for (const sessionId of Array.from(cloudqSessions)) {
608
- const summary = snapshot?.byId?.[sessionId];
609
- if (summary && !summary.blank && !detected.has(sessionId)) cloudqSessions.delete(sessionId);
610
+ const known = new Set(Array.isArray(response?.knownIds) ? response.knownIds : []);
611
+ if (known.size > 0) {
612
+ for (const sessionId of Array.from(cloudqSessions)) if (!known.has(sessionId)) cloudqSessions.delete(sessionId);
610
613
  }
611
614
  for (const sessionId of detected) cloudqSessions.add(sessionId);
612
615
  saveCloudqSessions(cloudqSessions);
@@ -614,6 +617,10 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
614
617
  return detected;
615
618
  }).catch(() => /* @__PURE__ */ new Set()).finally(() => {
616
619
  cloudqHistorySync = null;
620
+ if (reconcileQueued) {
621
+ reconcileQueued = false;
622
+ reconcileCloudqSessionsFromHost();
623
+ }
617
624
  });
618
625
  return cloudqHistorySync;
619
626
  }
@@ -792,6 +799,17 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
792
799
  * order (manual and recent-order variants). Only if a collapsed/search
793
800
  * view prevents an exact sequence match do we fall back to title matching.
794
801
  */
802
+ function rowSessionId(row) {
803
+ const fiberKey = Object.keys(row).find((key) => key.startsWith("__reactFiber$"));
804
+ if (!fiberKey) return void 0;
805
+ let fiber = row[fiberKey];
806
+ for (let depth = 0; fiber && depth < 12; depth += 1) {
807
+ const props = fiber.memoizedProps;
808
+ const id = props?.node?.id ?? props?.sessionId;
809
+ if (typeof id === "string" && id.startsWith("session-")) return id;
810
+ fiber = fiber.return;
811
+ }
812
+ }
795
813
  function installSessionBadges() {
796
814
  let disposed = false;
797
815
  let frame = 0;
@@ -821,7 +839,8 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
821
839
  const exact = exactOrder?.[index] ?? (selected ? snapshot.byId?.[snapshot.current] : void 0);
822
840
  const candidates = exact ? [exact] : byTitle.get(title) ?? [];
823
841
  const states = new Set(candidates.map((summary) => isCloudqSession(summary.id)));
824
- const cloudq = candidates.length > 0 && states.size === 1 && states.has(true);
842
+ const fiberId = rowSessionId(row);
843
+ const cloudq = fiberId !== void 0 ? isCloudqSession(fiberId) : candidates.length > 0 && states.size === 1 && states.has(true);
825
844
  const existing = row.querySelector("[data-cloudq-session-badge=\"true\"]");
826
845
  if (!cloudq) {
827
846
  existing?.remove();
@@ -995,7 +1014,9 @@ body.dsh-cloudq-mode-active [class*=composerHero] [class*=root][class*=hero] {
995
1014
  }
996
1015
  .dsh-cloudq-convo-chip {
997
1016
  position: fixed;
998
- top: 20px;
1017
+ /* Below the conversation header divider (header bottom ≈76px) so the chip
1018
+ never overlaps the Session log affordance in the title row. */
1019
+ top: 88px;
999
1020
  right: 16px;
1000
1021
  display: inline-flex;
1001
1022
  align-items: center;
@@ -1586,12 +1607,24 @@ body.dsh-cloudq-mode-active [class*=composerHero] [class*=root][class*=hero] {
1586
1607
  }
1587
1608
  .dsh-cloudq-artifact__download {
1588
1609
  justify-self: end;
1610
+ padding: 0;
1611
+ border: 0;
1612
+ background: none;
1589
1613
  color: var(--dsw-alias-state-business-primary, #006eff);
1614
+ font: inherit;
1590
1615
  text-decoration: none;
1616
+ cursor: pointer;
1591
1617
  }
1592
1618
  .dsh-cloudq-artifact__download:hover {
1593
1619
  text-decoration: underline;
1594
1620
  }
1621
+ .dsh-cloudq-artifact__download:disabled {
1622
+ cursor: default;
1623
+ opacity: .6;
1624
+ }
1625
+ span.dsh-cloudq-artifact__download {
1626
+ cursor: default;
1627
+ }
1595
1628
  /* architecture library */
1596
1629
  .dsh-cloudq-arch__toolbar {
1597
1630
  position: relative;
@@ -2017,12 +2050,10 @@ display: none;
2017
2050
  let panelBody = null;
2018
2051
  let panelView = "usage";
2019
2052
  let panelExpanded = false;
2020
- let inspirationCache = null;
2021
2053
  let inspirationCategory = 0;
2022
- let artifactCache = null;
2023
- let artifactTotal = 0;
2024
- let architectureDirectoriesCache = null;
2025
- let architectureFirstFolderId = null;
2054
+ const artifactCollapsedSessions = /* @__PURE__ */ new Set();
2055
+ let artifactRefreshTimer = 0;
2056
+ let artifactRetryTimer = 0;
2026
2057
  let architectureRequestSeq = 0;
2027
2058
  let architecturePickerCleanup = null;
2028
2059
  function renderUsageView() {
@@ -2326,17 +2357,12 @@ display: none;
2326
2357
  list.appendChild(card);
2327
2358
  }
2328
2359
  };
2329
- if (inspirationCache) {
2330
- paint(inspirationCache);
2331
- return;
2332
- }
2333
2360
  const hint = document.createElement("div");
2334
2361
  hint.className = "dsh-cloudq-panel__hint";
2335
2362
  hint.textContent = "正在加载灵感…";
2336
2363
  list.appendChild(hint);
2337
2364
  requireCloudqCredential().then(() => cloudqRequest(API_INSPIRATIONS)).then((data) => {
2338
- inspirationCache = Array.isArray(data.inspirations) ? data.inspirations : [];
2339
- paint(inspirationCache);
2365
+ paint(Array.isArray(data.inspirations) ? data.inspirations : []);
2340
2366
  }).catch((error) => {
2341
2367
  if (!panelExpanded) return;
2342
2368
  list.textContent = "";
@@ -2346,140 +2372,181 @@ display: none;
2346
2372
  list.appendChild(err);
2347
2373
  });
2348
2374
  }
2349
- function renderArtifactView({ force = false } = {}) {
2375
+ /**
2376
+ * Refresh the artifact list in the background after a CloudQ conversation
2377
+ * turn completes. Never shows the loading hint: the visible list stays
2378
+ * untouched and is repainted synchronously once fresh data arrives. The
2379
+ * delayed retry covers artifacts archived with a lag.
2380
+ */
2381
+ function scheduleArtifactSilentRefresh() {
2382
+ window.clearTimeout(artifactRefreshTimer);
2383
+ window.clearTimeout(artifactRetryTimer);
2384
+ const run = () => {
2385
+ cloudqRequest(API_ARTIFACTS).then((data) => {
2386
+ if (panelView !== "artifact" || !panelExpanded) return;
2387
+ paintArtifactList(Array.isArray(data?.sessions) ? data.sessions : [], Number(data?.total) || 0);
2388
+ }).catch(() => {});
2389
+ };
2390
+ artifactRefreshTimer = window.setTimeout(run, 3e3);
2391
+ artifactRetryTimer = window.setTimeout(run, 15e3);
2392
+ }
2393
+ function paintArtifactList(sessions, total) {
2394
+ if (panelView !== "artifact" || !panelExpanded) return;
2350
2395
  panelBody.textContent = "";
2351
- const paint = (sessions, total) => {
2352
- if (panelView !== "artifact" || !panelExpanded) return;
2353
- panelBody.textContent = "";
2354
- const safeSessions = Array.isArray(sessions) ? sessions : [];
2355
- const artifactCount = safeSessions.reduce((sum, session) => {
2356
- return sum + (Array.isArray(session?.Artifacts) ? session.Artifacts : []).length;
2357
- }, 0);
2358
- const toolbar = document.createElement("div");
2359
- toolbar.className = "dsh-cloudq-artifact__toolbar";
2360
- const summary = document.createElement("span");
2361
- summary.textContent = `共 ${Number(total) || safeSessions.length} 组会话,${artifactCount} 个制品`;
2362
- const refresh = document.createElement("button");
2363
- refresh.type = "button";
2364
- refresh.className = "dsh-cloudq-artifact__refresh";
2365
- refresh.dataset.testid = "cloudq-artifact-refresh-btn";
2366
- refresh.textContent = "刷新";
2367
- refresh.addEventListener("click", () => {
2368
- artifactCache = null;
2369
- artifactTotal = 0;
2370
- renderArtifactView({ force: true });
2371
- });
2372
- toolbar.appendChild(summary);
2373
- toolbar.appendChild(refresh);
2374
- panelBody.appendChild(toolbar);
2375
- if (safeSessions.length === 0) {
2376
- const empty = document.createElement("div");
2377
- empty.className = "dsh-cloudq-usage__table__empty";
2378
- empty.textContent = "暂无制品";
2379
- panelBody.appendChild(empty);
2380
- return;
2381
- }
2382
- const list = document.createElement("div");
2383
- list.className = "dsh-cloudq-artifact__list";
2384
- const columns = document.createElement("div");
2385
- columns.className = "dsh-cloudq-artifact__columns";
2386
- for (const label of [
2387
- "报告名称",
2388
- "用户名称",
2389
- "生成时间",
2390
- "操作"
2391
- ]) {
2392
- const cell = document.createElement("span");
2393
- cell.textContent = label;
2394
- columns.appendChild(cell);
2395
- }
2396
- list.appendChild(columns);
2397
- for (const session of safeSessions) {
2398
- const artifacts = Array.isArray(session?.Artifacts) ? session.Artifacts : [];
2399
- const group = document.createElement("section");
2400
- group.className = "dsh-cloudq-artifact__session";
2401
- const sessionHead = document.createElement("button");
2402
- sessionHead.type = "button";
2403
- sessionHead.className = "dsh-cloudq-artifact__session-head";
2404
- sessionHead.dataset.testid = `cloudq-artifact-session-${session?.SessionID ?? "unknown"}`;
2405
- sessionHead.setAttribute("aria-expanded", "true");
2406
- const chevron = document.createElement("span");
2407
- chevron.className = "dsh-cloudq-artifact__chevron";
2408
- chevron.textContent = "▼";
2409
- const title = document.createElement("span");
2410
- title.className = "dsh-cloudq-artifact__session-title";
2411
- title.textContent = session?.SessionTitle || "未命名会话";
2412
- title.title = title.textContent;
2413
- const latestTime = artifacts.reduce((latest, artifact) => {
2414
- const time = String(artifact?.ArchiveTime ?? "");
2415
- return time > latest ? time : latest;
2416
- }, "");
2417
- const meta = document.createElement("span");
2418
- meta.className = "dsh-cloudq-artifact__session-meta";
2419
- meta.textContent = `${artifacts.length} 个制品${latestTime ? ` · 最近 ${latestTime.slice(0, 10)}` : ""}`;
2420
- sessionHead.appendChild(chevron);
2421
- sessionHead.appendChild(title);
2422
- sessionHead.appendChild(meta);
2423
- const files = document.createElement("div");
2424
- files.className = "dsh-cloudq-artifact__files";
2425
- for (const artifact of artifacts) {
2426
- const row = document.createElement("div");
2427
- row.className = "dsh-cloudq-artifact__file";
2428
- const main = document.createElement("div");
2429
- main.className = "dsh-cloudq-artifact__file-main";
2430
- const type = document.createElement("span");
2431
- type.className = "dsh-cloudq-artifact__file-type";
2432
- type.textContent = artifact?.FileType || "file";
2433
- const name = document.createElement("span");
2434
- name.className = "dsh-cloudq-artifact__file-name";
2435
- name.textContent = artifact?.FileName || "未命名文件";
2436
- name.title = `${name.textContent} · ${formatFileSize(artifact?.SizeBytes)}`;
2437
- main.appendChild(type);
2438
- main.appendChild(name);
2439
- const owner = document.createElement("span");
2440
- owner.className = "dsh-cloudq-artifact__owner";
2441
- owner.textContent = artifact?.UserName || "—";
2442
- const time = document.createElement("span");
2443
- time.className = "dsh-cloudq-artifact__time";
2444
- time.textContent = formatDateTime(artifact?.ArchiveTime);
2445
- const downloadUrl = safeDownloadUrl(artifact?.DownloadURL);
2446
- const action = document.createElement(downloadUrl ? "a" : "span");
2447
- action.className = "dsh-cloudq-artifact__download";
2448
- action.textContent = downloadUrl ? "下载" : "不可用";
2449
- if (downloadUrl) {
2450
- action.href = downloadUrl;
2451
- action.target = "_blank";
2452
- action.rel = "noopener noreferrer";
2453
- action.dataset.testid = `cloudq-artifact-download-${artifact?.ArtifactID ?? "unknown"}`;
2454
- }
2455
- row.appendChild(main);
2456
- row.appendChild(owner);
2457
- row.appendChild(time);
2458
- row.appendChild(action);
2459
- files.appendChild(row);
2396
+ const safeSessions = Array.isArray(sessions) ? sessions : [];
2397
+ const artifactCount = safeSessions.reduce((sum, session) => {
2398
+ return sum + (Array.isArray(session?.Artifacts) ? session.Artifacts : []).length;
2399
+ }, 0);
2400
+ const toolbar = document.createElement("div");
2401
+ toolbar.className = "dsh-cloudq-artifact__toolbar";
2402
+ const summary = document.createElement("span");
2403
+ summary.textContent = `共 ${Number(total) || safeSessions.length} 组会话,${artifactCount} 个制品`;
2404
+ const refresh = document.createElement("button");
2405
+ refresh.type = "button";
2406
+ refresh.className = "dsh-cloudq-artifact__refresh";
2407
+ refresh.dataset.testid = "cloudq-artifact-refresh-btn";
2408
+ refresh.textContent = "刷新";
2409
+ refresh.addEventListener("click", () => renderArtifactView());
2410
+ toolbar.appendChild(summary);
2411
+ toolbar.appendChild(refresh);
2412
+ panelBody.appendChild(toolbar);
2413
+ if (safeSessions.length === 0) {
2414
+ const empty = document.createElement("div");
2415
+ empty.className = "dsh-cloudq-usage__table__empty";
2416
+ empty.textContent = "暂无制品";
2417
+ panelBody.appendChild(empty);
2418
+ return;
2419
+ }
2420
+ const list = document.createElement("div");
2421
+ list.className = "dsh-cloudq-artifact__list";
2422
+ const columns = document.createElement("div");
2423
+ columns.className = "dsh-cloudq-artifact__columns";
2424
+ for (const label of [
2425
+ "报告名称",
2426
+ "用户名称",
2427
+ "生成时间",
2428
+ "操作"
2429
+ ]) {
2430
+ const cell = document.createElement("span");
2431
+ cell.textContent = label;
2432
+ columns.appendChild(cell);
2433
+ }
2434
+ list.appendChild(columns);
2435
+ for (const session of safeSessions) {
2436
+ const artifacts = Array.isArray(session?.Artifacts) ? session.Artifacts : [];
2437
+ const sessionKey = String(session?.SessionID ?? "");
2438
+ const group = document.createElement("section");
2439
+ group.className = "dsh-cloudq-artifact__session";
2440
+ const startCollapsed = sessionKey !== "" && artifactCollapsedSessions.has(sessionKey);
2441
+ if (startCollapsed) group.classList.add("is-collapsed");
2442
+ const sessionHead = document.createElement("button");
2443
+ sessionHead.type = "button";
2444
+ sessionHead.className = "dsh-cloudq-artifact__session-head";
2445
+ sessionHead.dataset.testid = `cloudq-artifact-session-${session?.SessionID ?? "unknown"}`;
2446
+ sessionHead.setAttribute("aria-expanded", String(!startCollapsed));
2447
+ const chevron = document.createElement("span");
2448
+ chevron.className = "dsh-cloudq-artifact__chevron";
2449
+ chevron.textContent = "";
2450
+ const title = document.createElement("span");
2451
+ title.className = "dsh-cloudq-artifact__session-title";
2452
+ title.textContent = session?.SessionTitle || "未命名会话";
2453
+ title.title = title.textContent;
2454
+ const latestTime = artifacts.reduce((latest, artifact) => {
2455
+ const time = String(artifact?.ArchiveTime ?? "");
2456
+ return time > latest ? time : latest;
2457
+ }, "");
2458
+ const meta = document.createElement("span");
2459
+ meta.className = "dsh-cloudq-artifact__session-meta";
2460
+ meta.textContent = `${artifacts.length} 个制品${latestTime ? ` · 最近 ${latestTime.slice(0, 10)}` : ""}`;
2461
+ sessionHead.appendChild(chevron);
2462
+ sessionHead.appendChild(title);
2463
+ sessionHead.appendChild(meta);
2464
+ const files = document.createElement("div");
2465
+ files.className = "dsh-cloudq-artifact__files";
2466
+ for (const artifact of artifacts) {
2467
+ const row = document.createElement("div");
2468
+ row.className = "dsh-cloudq-artifact__file";
2469
+ const main = document.createElement("div");
2470
+ main.className = "dsh-cloudq-artifact__file-main";
2471
+ const type = document.createElement("span");
2472
+ type.className = "dsh-cloudq-artifact__file-type";
2473
+ type.textContent = artifact?.FileType || "file";
2474
+ const name = document.createElement("span");
2475
+ name.className = "dsh-cloudq-artifact__file-name";
2476
+ name.textContent = artifact?.FileName || "未命名文件";
2477
+ name.title = `${name.textContent} · ${formatFileSize(artifact?.SizeBytes)}`;
2478
+ main.appendChild(type);
2479
+ main.appendChild(name);
2480
+ const owner = document.createElement("span");
2481
+ owner.className = "dsh-cloudq-artifact__owner";
2482
+ owner.textContent = artifact?.UserName || "—";
2483
+ const time = document.createElement("span");
2484
+ time.className = "dsh-cloudq-artifact__time";
2485
+ time.textContent = formatDateTime(artifact?.ArchiveTime);
2486
+ const downloadUrl = safeDownloadUrl(artifact?.DownloadURL);
2487
+ const fileName = artifact?.FileName || "未命名文件";
2488
+ const action = document.createElement(downloadUrl ? "button" : "span");
2489
+ action.className = "dsh-cloudq-artifact__download";
2490
+ action.textContent = downloadUrl ? "下载" : "不可用";
2491
+ if (downloadUrl) {
2492
+ action.type = "button";
2493
+ action.dataset.testid = `cloudq-artifact-download-${artifact?.ArtifactID ?? "unknown"}`;
2494
+ action.addEventListener("click", () => {
2495
+ if (action.disabled) return;
2496
+ action.disabled = true;
2497
+ action.textContent = "下载中…";
2498
+ const restore = () => {
2499
+ action.disabled = false;
2500
+ action.textContent = "下载";
2501
+ };
2502
+ fetch(downloadUrl).then((res) => {
2503
+ if (!res.ok) throw new Error(`download failed: ${res.status}`);
2504
+ return res.blob();
2505
+ }).then((blob) => {
2506
+ const objectUrl = URL.createObjectURL(blob);
2507
+ const link = document.createElement("a");
2508
+ link.href = objectUrl;
2509
+ link.download = fileName;
2510
+ link.rel = "noopener noreferrer";
2511
+ link.click();
2512
+ window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1e4);
2513
+ }).catch(() => {
2514
+ const link = document.createElement("a");
2515
+ link.href = downloadUrl;
2516
+ link.download = fileName;
2517
+ link.rel = "noopener noreferrer";
2518
+ link.click();
2519
+ }).finally(restore);
2520
+ });
2460
2521
  }
2461
- sessionHead.addEventListener("click", () => {
2462
- const collapsed = group.classList.toggle("is-collapsed");
2463
- sessionHead.setAttribute("aria-expanded", String(!collapsed));
2464
- });
2465
- group.appendChild(sessionHead);
2466
- group.appendChild(files);
2467
- list.appendChild(group);
2522
+ row.appendChild(main);
2523
+ row.appendChild(owner);
2524
+ row.appendChild(time);
2525
+ row.appendChild(action);
2526
+ files.appendChild(row);
2468
2527
  }
2469
- panelBody.appendChild(list);
2470
- };
2471
- if (artifactCache !== null && !force) {
2472
- paint(artifactCache, artifactTotal);
2473
- return;
2528
+ sessionHead.addEventListener("click", () => {
2529
+ const collapsed = group.classList.toggle("is-collapsed");
2530
+ sessionHead.setAttribute("aria-expanded", String(!collapsed));
2531
+ if (sessionKey) {
2532
+ if (collapsed) artifactCollapsedSessions.add(sessionKey);
2533
+ else artifactCollapsedSessions.delete(sessionKey);
2534
+ }
2535
+ });
2536
+ group.appendChild(sessionHead);
2537
+ group.appendChild(files);
2538
+ list.appendChild(group);
2474
2539
  }
2540
+ panelBody.appendChild(list);
2541
+ }
2542
+ function renderArtifactView() {
2543
+ panelBody.textContent = "";
2475
2544
  const hint = document.createElement("div");
2476
2545
  hint.className = "dsh-cloudq-panel__hint";
2477
2546
  hint.textContent = "正在加载制品…";
2478
2547
  panelBody.appendChild(hint);
2479
2548
  requireCloudqCredential().then(() => cloudqRequest(API_ARTIFACTS)).then((data) => {
2480
- artifactCache = Array.isArray(data?.sessions) ? data.sessions : [];
2481
- artifactTotal = Number(data?.total) || artifactCache.length;
2482
- paint(artifactCache, artifactTotal);
2549
+ paintArtifactList(Array.isArray(data?.sessions) ? data.sessions : [], Number(data?.total) || 0);
2483
2550
  }).catch((error) => {
2484
2551
  if (panelView !== "artifact" || !panelExpanded) return;
2485
2552
  panelBody.textContent = "";
@@ -2714,14 +2781,16 @@ display: none;
2714
2781
  }
2715
2782
  for (const architecture of architectures) {
2716
2783
  const imageUrl = safeDownloadUrl(architecture?.SvgURL);
2717
- const card = document.createElement(imageUrl ? "a" : "article");
2784
+ const archId = typeof architecture?.ArchId === "string" ? architecture.ArchId.trim() : "";
2785
+ const consoleUrl = archId ? `https://console.cloud.tencent.com/advisor?archId=${encodeURIComponent(archId)}` : "";
2786
+ const card = document.createElement(consoleUrl ? "a" : "article");
2718
2787
  card.className = "dsh-cloudq-arch__card";
2719
2788
  card.dataset.testid = `cloudq-architecture-card-${architecture?.ArchId ?? "unknown"}`;
2720
- if (imageUrl) {
2721
- card.href = imageUrl;
2789
+ if (consoleUrl) {
2790
+ card.href = consoleUrl;
2722
2791
  card.target = "_blank";
2723
2792
  card.rel = "noopener noreferrer";
2724
- card.setAttribute("aria-label", `查看架构图:${architecture?.ArchName || "未命名系统"}`);
2793
+ card.setAttribute("aria-label", `打开架构图:${architecture?.ArchName || "未命名系统"}`);
2725
2794
  }
2726
2795
  const preview = document.createElement("div");
2727
2796
  preview.className = "dsh-cloudq-arch__preview";
@@ -2740,7 +2809,7 @@ display: none;
2740
2809
  image.addEventListener("load", () => placeholder.remove());
2741
2810
  image.addEventListener("error", () => {
2742
2811
  image.remove();
2743
- placeholder.textContent = "预览加载失败,点击可打开原图";
2812
+ placeholder.textContent = consoleUrl ? "预览加载失败,点击打开架构图" : "预览加载失败";
2744
2813
  });
2745
2814
  preview.appendChild(image);
2746
2815
  }
@@ -2772,14 +2841,8 @@ display: none;
2772
2841
  };
2773
2842
  loadArchitectures(selectedFolderId);
2774
2843
  };
2775
- if (architectureDirectoriesCache !== null) {
2776
- paintDirectories(architectureDirectoriesCache, architectureFirstFolderId);
2777
- return;
2778
- }
2779
2844
  requireCloudqCredential().then(() => cloudqRequest(API_ARCH_DIRECTORIES)).then((data) => {
2780
- architectureDirectoriesCache = Array.isArray(data?.folders) ? data.folders : [];
2781
- architectureFirstFolderId = Number(data?.firstFolderId) || null;
2782
- paintDirectories(architectureDirectoriesCache, architectureFirstFolderId);
2845
+ paintDirectories(Array.isArray(data?.folders) ? data.folders : [], Number(data?.firstFolderId) || null);
2783
2846
  }).catch((error) => {
2784
2847
  if (panelView !== "architecture" || !panelExpanded || viewRequest !== architectureRequestSeq) return;
2785
2848
  panelBody.textContent = "";
@@ -3054,12 +3117,7 @@ display: none;
3054
3117
  panelEl = null;
3055
3118
  panelBody = null;
3056
3119
  }
3057
- inspirationCache = null;
3058
3120
  inspirationCategory = 0;
3059
- artifactCache = null;
3060
- artifactTotal = 0;
3061
- architectureDirectoriesCache = null;
3062
- architectureFirstFolderId = null;
3063
3121
  architectureRequestSeq += 1;
3064
3122
  panelView = "usage";
3065
3123
  };
@@ -3615,12 +3673,51 @@ display: none;
3615
3673
  window.dispatchEvent(new CustomEvent("dsh-cloudq:sessions-changed"));
3616
3674
  };
3617
3675
  window.addEventListener("storage", onStorage);
3676
+ const backstop = window.setInterval(() => {
3677
+ if (!disposed) reconcileCloudqSessionsFromHost();
3678
+ }, 3e4);
3618
3679
  syncHistory();
3619
3680
  return () => {
3620
3681
  disposed = true;
3682
+ window.clearInterval(backstop);
3621
3683
  window.removeEventListener("storage", onStorage);
3622
3684
  };
3623
3685
  }, "dsh-cloudq: reconcile durable session identities");
3686
+ ctx.effect(() => {
3687
+ const list = ctx?.sessions?.list;
3688
+ if (!list?.subscribe || !list?.getSnapshot) return void 0;
3689
+ const prevRunning = /* @__PURE__ */ new Map();
3690
+ return list.subscribe(() => {
3691
+ const byId = list.getSnapshot()?.byId ?? {};
3692
+ for (const [id, summary] of Object.entries(byId)) {
3693
+ const running = summary?.running === true;
3694
+ if (prevRunning.get(id) === true && !running && cloudqSessions.has(id)) scheduleArtifactSilentRefresh();
3695
+ prevRunning.set(id, running);
3696
+ }
3697
+ });
3698
+ }, "dsh-cloudq: refresh artifacts on turn completion");
3699
+ ctx.effect(() => {
3700
+ const markIfCloudqClaim = () => {
3701
+ const textarea = document.querySelector("textarea[class*=input]");
3702
+ const draft = typeof textarea?.value === "string" ? textarea.value : "";
3703
+ if (!/^\s*\/cloudq(?:\s|$)/i.test(draft)) return;
3704
+ const sessionId = cloudqCtx?.sessions?.selection?.getSnapshot?.()?.sessionId ?? cloudqCtx?.sessions?.list?.getSnapshot?.()?.current;
3705
+ if (sessionId) markCloudqSession(sessionId);
3706
+ };
3707
+ const onKeyDown = (event) => {
3708
+ if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
3709
+ if (event.target instanceof HTMLTextAreaElement) markIfCloudqClaim();
3710
+ };
3711
+ const onClick = (event) => {
3712
+ if (event.target?.closest?.("button[class*=primary]")) markIfCloudqClaim();
3713
+ };
3714
+ document.addEventListener("keydown", onKeyDown, true);
3715
+ document.addEventListener("click", onClick, true);
3716
+ return () => {
3717
+ document.removeEventListener("keydown", onKeyDown, true);
3718
+ document.removeEventListener("click", onClick, true);
3719
+ };
3720
+ }, "dsh-cloudq: mark session on claim submit");
3624
3721
  ctx.effect(installStyles, "dsh-cloudq: styles");
3625
3722
  ctx.effect(() => {
3626
3723
  const slots = ctx.slots;
package/lib/index.js CHANGED
@@ -589,18 +589,23 @@ async function detectCloudqSessionIds(ctx) {
589
589
  if (query === void 0) throw new HttpError(503, "session-query-unavailable", "当前 DSH 未启用会话查询服务。");
590
590
  const records = await query.listSessions();
591
591
  const ids = [];
592
+ const knownIds = [];
592
593
  const queue = [...records];
593
594
  const worker = async () => {
594
595
  while (queue.length > 0) {
595
596
  const sessionId = queue.shift()?.header?.id;
596
597
  if (!sessionId) continue;
598
+ knownIds.push(sessionId);
597
599
  try {
598
600
  if (hasCloudqEvidence((await query.readSession(sessionId))?.events)) ids.push(sessionId);
599
601
  } catch {}
600
602
  }
601
603
  };
602
604
  await Promise.all(Array.from({ length: Math.min(6, queue.length) }, () => worker()));
603
- return ids;
605
+ return {
606
+ ids,
607
+ knownIds
608
+ };
604
609
  }
605
610
  /** Settings namespace for the browser card (must equal client card `key`). */
606
611
  const SETTINGS_NAMESPACE = "dsh-cloudq";
@@ -825,9 +830,11 @@ function apply(ctx) {
825
830
  handler: async (request, response) => {
826
831
  try {
827
832
  assertSafeRequest(request, "GET");
833
+ const { ids, knownIds } = await detectCloudqSessionIds(ctx);
828
834
  sendJson(response, 200, {
829
835
  ok: true,
830
- sessionIds: await detectCloudqSessionIds(ctx)
836
+ sessionIds: ids,
837
+ knownIds
831
838
  });
832
839
  } catch (error) {
833
840
  sendError(response, error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cloudq",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "CloudQ integration for DeepSeek Harness with secure credential, workspace, and plugin-management surfaces",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -104,7 +104,7 @@ def find_available_role_name() -> tuple:
104
104
 
105
105
 
106
106
  def save_config(account_uin: str, role_arn: str, role_id: str = "",
107
- auto_created: bool = True):
107
+ auto_created: bool = True, role_name: str = ""):
108
108
  """保存配置到文件"""
109
109
  CONFIG_DIR.mkdir(parents=True, exist_ok=True)
110
110
 
@@ -114,15 +114,25 @@ def save_config(account_uin: str, role_arn: str, role_id: str = "",
114
114
  except OSError:
115
115
  pass
116
116
 
117
+ # 保留 DSH 插件写入的免密开关偏好,避免重建角色时被重置。
118
+ existing = {}
119
+ if CONFIG_FILE.exists():
120
+ try:
121
+ existing = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
122
+ except (json.JSONDecodeError, IOError):
123
+ existing = {}
124
+
117
125
  config = {
118
126
  "accountUin": account_uin,
119
- "roleName": ROLE_NAME,
127
+ "roleName": role_name or ROLE_NAME,
120
128
  "roleArn": role_arn,
121
129
  "roleId": role_id,
122
130
  "configuredAt": datetime.now(timezone.utc).isoformat(),
123
131
  "autoCreated": auto_created,
124
132
  "version": "1.0",
125
133
  }
134
+ if isinstance(existing.get("enabled"), bool):
135
+ config["enabled"] = existing["enabled"]
126
136
 
127
137
  CONFIG_FILE.write_text(
128
138
  json.dumps(config, indent=2, ensure_ascii=False),
@@ -153,6 +163,21 @@ def main():
153
163
  secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID", "")
154
164
  secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY", "")
155
165
 
166
+ # 环境变量缺失时回退到凭证文件(与 login_url.py 一致),
167
+ # 使 DSH 插件等宿主可直接调用而无需注入环境变量。
168
+ if not secret_id or not secret_key:
169
+ try:
170
+ from credential_manager import get_credential
171
+ cred = get_credential()
172
+ secret_id = cred["secretId"]
173
+ secret_key = cred["secretKey"]
174
+ if cred.get("token"):
175
+ os.environ["TENCENTCLOUD_TOKEN"] = cred["token"]
176
+ except ImportError:
177
+ pass
178
+ except Exception:
179
+ pass
180
+
156
181
  if not secret_id or not secret_key:
157
182
  print(output_json(output_error(
158
183
  "MissingCredentials",
@@ -210,7 +235,7 @@ def main():
210
235
  attach_warnings.append(f"策略 {policy_name} 关联失败: {err_msg}")
211
236
  print(f"WARNING: {attach_warnings[-1]}", file=sys.stderr)
212
237
 
213
- save_config(account_uin, role_arn, role_id, auto_created=False)
238
+ save_config(account_uin, role_arn, role_id, auto_created=False, role_name=target_role)
214
239
  result = output_success(role_arn, account_uin, role_id)
215
240
  result["data"]["roleName"] = target_role
216
241
  if attach_warnings:
@@ -266,7 +291,7 @@ def main():
266
291
 
267
292
  # ============== 6. 保存配置 ==============
268
293
  role_arn = f"qcs::cam::uin/{account_uin}:roleName/{target_role}"
269
- save_config(account_uin, role_arn, role_id, auto_created=True)
294
+ save_config(account_uin, role_arn, role_id, auto_created=True, role_name=target_role)
270
295
 
271
296
  # ============== 7. 输出结果 ==============
272
297
  result = output_success(role_arn, account_uin, role_id)