dsh-cloudq 0.1.9 → 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
@@ -1014,7 +1014,9 @@ body.dsh-cloudq-mode-active [class*=composerHero] [class*=root][class*=hero] {
1014
1014
  }
1015
1015
  .dsh-cloudq-convo-chip {
1016
1016
  position: fixed;
1017
- 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;
1018
1020
  right: 16px;
1019
1021
  display: inline-flex;
1020
1022
  align-items: center;
@@ -1605,12 +1607,24 @@ body.dsh-cloudq-mode-active [class*=composerHero] [class*=root][class*=hero] {
1605
1607
  }
1606
1608
  .dsh-cloudq-artifact__download {
1607
1609
  justify-self: end;
1610
+ padding: 0;
1611
+ border: 0;
1612
+ background: none;
1608
1613
  color: var(--dsw-alias-state-business-primary, #006eff);
1614
+ font: inherit;
1609
1615
  text-decoration: none;
1616
+ cursor: pointer;
1610
1617
  }
1611
1618
  .dsh-cloudq-artifact__download:hover {
1612
1619
  text-decoration: underline;
1613
1620
  }
1621
+ .dsh-cloudq-artifact__download:disabled {
1622
+ cursor: default;
1623
+ opacity: .6;
1624
+ }
1625
+ span.dsh-cloudq-artifact__download {
1626
+ cursor: default;
1627
+ }
1614
1628
  /* architecture library */
1615
1629
  .dsh-cloudq-arch__toolbar {
1616
1630
  position: relative;
@@ -2036,12 +2050,10 @@ display: none;
2036
2050
  let panelBody = null;
2037
2051
  let panelView = "usage";
2038
2052
  let panelExpanded = false;
2039
- let inspirationCache = null;
2040
2053
  let inspirationCategory = 0;
2041
- let artifactCache = null;
2042
- let artifactTotal = 0;
2043
- let architectureDirectoriesCache = null;
2044
- let architectureFirstFolderId = null;
2054
+ const artifactCollapsedSessions = /* @__PURE__ */ new Set();
2055
+ let artifactRefreshTimer = 0;
2056
+ let artifactRetryTimer = 0;
2045
2057
  let architectureRequestSeq = 0;
2046
2058
  let architecturePickerCleanup = null;
2047
2059
  function renderUsageView() {
@@ -2345,17 +2357,12 @@ display: none;
2345
2357
  list.appendChild(card);
2346
2358
  }
2347
2359
  };
2348
- if (inspirationCache) {
2349
- paint(inspirationCache);
2350
- return;
2351
- }
2352
2360
  const hint = document.createElement("div");
2353
2361
  hint.className = "dsh-cloudq-panel__hint";
2354
2362
  hint.textContent = "正在加载灵感…";
2355
2363
  list.appendChild(hint);
2356
2364
  requireCloudqCredential().then(() => cloudqRequest(API_INSPIRATIONS)).then((data) => {
2357
- inspirationCache = Array.isArray(data.inspirations) ? data.inspirations : [];
2358
- paint(inspirationCache);
2365
+ paint(Array.isArray(data.inspirations) ? data.inspirations : []);
2359
2366
  }).catch((error) => {
2360
2367
  if (!panelExpanded) return;
2361
2368
  list.textContent = "";
@@ -2365,140 +2372,181 @@ display: none;
2365
2372
  list.appendChild(err);
2366
2373
  });
2367
2374
  }
2368
- 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;
2369
2395
  panelBody.textContent = "";
2370
- const paint = (sessions, total) => {
2371
- if (panelView !== "artifact" || !panelExpanded) return;
2372
- panelBody.textContent = "";
2373
- const safeSessions = Array.isArray(sessions) ? sessions : [];
2374
- const artifactCount = safeSessions.reduce((sum, session) => {
2375
- return sum + (Array.isArray(session?.Artifacts) ? session.Artifacts : []).length;
2376
- }, 0);
2377
- const toolbar = document.createElement("div");
2378
- toolbar.className = "dsh-cloudq-artifact__toolbar";
2379
- const summary = document.createElement("span");
2380
- summary.textContent = `共 ${Number(total) || safeSessions.length} 组会话,${artifactCount} 个制品`;
2381
- const refresh = document.createElement("button");
2382
- refresh.type = "button";
2383
- refresh.className = "dsh-cloudq-artifact__refresh";
2384
- refresh.dataset.testid = "cloudq-artifact-refresh-btn";
2385
- refresh.textContent = "刷新";
2386
- refresh.addEventListener("click", () => {
2387
- artifactCache = null;
2388
- artifactTotal = 0;
2389
- renderArtifactView({ force: true });
2390
- });
2391
- toolbar.appendChild(summary);
2392
- toolbar.appendChild(refresh);
2393
- panelBody.appendChild(toolbar);
2394
- if (safeSessions.length === 0) {
2395
- const empty = document.createElement("div");
2396
- empty.className = "dsh-cloudq-usage__table__empty";
2397
- empty.textContent = "暂无制品";
2398
- panelBody.appendChild(empty);
2399
- return;
2400
- }
2401
- const list = document.createElement("div");
2402
- list.className = "dsh-cloudq-artifact__list";
2403
- const columns = document.createElement("div");
2404
- columns.className = "dsh-cloudq-artifact__columns";
2405
- for (const label of [
2406
- "报告名称",
2407
- "用户名称",
2408
- "生成时间",
2409
- "操作"
2410
- ]) {
2411
- const cell = document.createElement("span");
2412
- cell.textContent = label;
2413
- columns.appendChild(cell);
2414
- }
2415
- list.appendChild(columns);
2416
- for (const session of safeSessions) {
2417
- const artifacts = Array.isArray(session?.Artifacts) ? session.Artifacts : [];
2418
- const group = document.createElement("section");
2419
- group.className = "dsh-cloudq-artifact__session";
2420
- const sessionHead = document.createElement("button");
2421
- sessionHead.type = "button";
2422
- sessionHead.className = "dsh-cloudq-artifact__session-head";
2423
- sessionHead.dataset.testid = `cloudq-artifact-session-${session?.SessionID ?? "unknown"}`;
2424
- sessionHead.setAttribute("aria-expanded", "true");
2425
- const chevron = document.createElement("span");
2426
- chevron.className = "dsh-cloudq-artifact__chevron";
2427
- chevron.textContent = "▼";
2428
- const title = document.createElement("span");
2429
- title.className = "dsh-cloudq-artifact__session-title";
2430
- title.textContent = session?.SessionTitle || "未命名会话";
2431
- title.title = title.textContent;
2432
- const latestTime = artifacts.reduce((latest, artifact) => {
2433
- const time = String(artifact?.ArchiveTime ?? "");
2434
- return time > latest ? time : latest;
2435
- }, "");
2436
- const meta = document.createElement("span");
2437
- meta.className = "dsh-cloudq-artifact__session-meta";
2438
- meta.textContent = `${artifacts.length} 个制品${latestTime ? ` · 最近 ${latestTime.slice(0, 10)}` : ""}`;
2439
- sessionHead.appendChild(chevron);
2440
- sessionHead.appendChild(title);
2441
- sessionHead.appendChild(meta);
2442
- const files = document.createElement("div");
2443
- files.className = "dsh-cloudq-artifact__files";
2444
- for (const artifact of artifacts) {
2445
- const row = document.createElement("div");
2446
- row.className = "dsh-cloudq-artifact__file";
2447
- const main = document.createElement("div");
2448
- main.className = "dsh-cloudq-artifact__file-main";
2449
- const type = document.createElement("span");
2450
- type.className = "dsh-cloudq-artifact__file-type";
2451
- type.textContent = artifact?.FileType || "file";
2452
- const name = document.createElement("span");
2453
- name.className = "dsh-cloudq-artifact__file-name";
2454
- name.textContent = artifact?.FileName || "未命名文件";
2455
- name.title = `${name.textContent} · ${formatFileSize(artifact?.SizeBytes)}`;
2456
- main.appendChild(type);
2457
- main.appendChild(name);
2458
- const owner = document.createElement("span");
2459
- owner.className = "dsh-cloudq-artifact__owner";
2460
- owner.textContent = artifact?.UserName || "—";
2461
- const time = document.createElement("span");
2462
- time.className = "dsh-cloudq-artifact__time";
2463
- time.textContent = formatDateTime(artifact?.ArchiveTime);
2464
- const downloadUrl = safeDownloadUrl(artifact?.DownloadURL);
2465
- const action = document.createElement(downloadUrl ? "a" : "span");
2466
- action.className = "dsh-cloudq-artifact__download";
2467
- action.textContent = downloadUrl ? "下载" : "不可用";
2468
- if (downloadUrl) {
2469
- action.href = downloadUrl;
2470
- action.target = "_blank";
2471
- action.rel = "noopener noreferrer";
2472
- action.dataset.testid = `cloudq-artifact-download-${artifact?.ArtifactID ?? "unknown"}`;
2473
- }
2474
- row.appendChild(main);
2475
- row.appendChild(owner);
2476
- row.appendChild(time);
2477
- row.appendChild(action);
2478
- 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
+ });
2479
2521
  }
2480
- sessionHead.addEventListener("click", () => {
2481
- const collapsed = group.classList.toggle("is-collapsed");
2482
- sessionHead.setAttribute("aria-expanded", String(!collapsed));
2483
- });
2484
- group.appendChild(sessionHead);
2485
- group.appendChild(files);
2486
- list.appendChild(group);
2522
+ row.appendChild(main);
2523
+ row.appendChild(owner);
2524
+ row.appendChild(time);
2525
+ row.appendChild(action);
2526
+ files.appendChild(row);
2487
2527
  }
2488
- panelBody.appendChild(list);
2489
- };
2490
- if (artifactCache !== null && !force) {
2491
- paint(artifactCache, artifactTotal);
2492
- 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);
2493
2539
  }
2540
+ panelBody.appendChild(list);
2541
+ }
2542
+ function renderArtifactView() {
2543
+ panelBody.textContent = "";
2494
2544
  const hint = document.createElement("div");
2495
2545
  hint.className = "dsh-cloudq-panel__hint";
2496
2546
  hint.textContent = "正在加载制品…";
2497
2547
  panelBody.appendChild(hint);
2498
2548
  requireCloudqCredential().then(() => cloudqRequest(API_ARTIFACTS)).then((data) => {
2499
- artifactCache = Array.isArray(data?.sessions) ? data.sessions : [];
2500
- artifactTotal = Number(data?.total) || artifactCache.length;
2501
- paint(artifactCache, artifactTotal);
2549
+ paintArtifactList(Array.isArray(data?.sessions) ? data.sessions : [], Number(data?.total) || 0);
2502
2550
  }).catch((error) => {
2503
2551
  if (panelView !== "artifact" || !panelExpanded) return;
2504
2552
  panelBody.textContent = "";
@@ -2733,14 +2781,16 @@ display: none;
2733
2781
  }
2734
2782
  for (const architecture of architectures) {
2735
2783
  const imageUrl = safeDownloadUrl(architecture?.SvgURL);
2736
- 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");
2737
2787
  card.className = "dsh-cloudq-arch__card";
2738
2788
  card.dataset.testid = `cloudq-architecture-card-${architecture?.ArchId ?? "unknown"}`;
2739
- if (imageUrl) {
2740
- card.href = imageUrl;
2789
+ if (consoleUrl) {
2790
+ card.href = consoleUrl;
2741
2791
  card.target = "_blank";
2742
2792
  card.rel = "noopener noreferrer";
2743
- card.setAttribute("aria-label", `查看架构图:${architecture?.ArchName || "未命名系统"}`);
2793
+ card.setAttribute("aria-label", `打开架构图:${architecture?.ArchName || "未命名系统"}`);
2744
2794
  }
2745
2795
  const preview = document.createElement("div");
2746
2796
  preview.className = "dsh-cloudq-arch__preview";
@@ -2759,7 +2809,7 @@ display: none;
2759
2809
  image.addEventListener("load", () => placeholder.remove());
2760
2810
  image.addEventListener("error", () => {
2761
2811
  image.remove();
2762
- placeholder.textContent = "预览加载失败,点击可打开原图";
2812
+ placeholder.textContent = consoleUrl ? "预览加载失败,点击打开架构图" : "预览加载失败";
2763
2813
  });
2764
2814
  preview.appendChild(image);
2765
2815
  }
@@ -2791,14 +2841,8 @@ display: none;
2791
2841
  };
2792
2842
  loadArchitectures(selectedFolderId);
2793
2843
  };
2794
- if (architectureDirectoriesCache !== null) {
2795
- paintDirectories(architectureDirectoriesCache, architectureFirstFolderId);
2796
- return;
2797
- }
2798
2844
  requireCloudqCredential().then(() => cloudqRequest(API_ARCH_DIRECTORIES)).then((data) => {
2799
- architectureDirectoriesCache = Array.isArray(data?.folders) ? data.folders : [];
2800
- architectureFirstFolderId = Number(data?.firstFolderId) || null;
2801
- paintDirectories(architectureDirectoriesCache, architectureFirstFolderId);
2845
+ paintDirectories(Array.isArray(data?.folders) ? data.folders : [], Number(data?.firstFolderId) || null);
2802
2846
  }).catch((error) => {
2803
2847
  if (panelView !== "architecture" || !panelExpanded || viewRequest !== architectureRequestSeq) return;
2804
2848
  panelBody.textContent = "";
@@ -3073,12 +3117,7 @@ display: none;
3073
3117
  panelEl = null;
3074
3118
  panelBody = null;
3075
3119
  }
3076
- inspirationCache = null;
3077
3120
  inspirationCategory = 0;
3078
- artifactCache = null;
3079
- artifactTotal = 0;
3080
- architectureDirectoriesCache = null;
3081
- architectureFirstFolderId = null;
3082
3121
  architectureRequestSeq += 1;
3083
3122
  panelView = "usage";
3084
3123
  };
@@ -3644,6 +3683,19 @@ display: none;
3644
3683
  window.removeEventListener("storage", onStorage);
3645
3684
  };
3646
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");
3647
3699
  ctx.effect(() => {
3648
3700
  const markIfCloudqClaim = () => {
3649
3701
  const textarea = document.querySelector("textarea[class*=input]");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cloudq",
3
- "version": "0.1.9",
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)