@web-auto/webauto 0.1.18 → 0.1.19

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.
Files changed (65) hide show
  1. package/README.md +122 -53
  2. package/apps/desktop-console/dist/main/index.mjs +227 -12
  3. package/apps/desktop-console/dist/renderer/index.js +237 -8
  4. package/apps/desktop-console/entry/ui-cli.mjs +282 -16
  5. package/apps/desktop-console/entry/ui-console.mjs +46 -15
  6. package/apps/webauto/entry/account.mjs +126 -27
  7. package/apps/webauto/entry/lib/account-detect.mjs +399 -9
  8. package/apps/webauto/entry/lib/account-store.mjs +201 -109
  9. package/apps/webauto/entry/lib/iflow-reply.mjs +194 -0
  10. package/apps/webauto/entry/lib/profile-policy.mjs +48 -0
  11. package/apps/webauto/entry/lib/profilepool.mjs +12 -0
  12. package/apps/webauto/entry/lib/schedule-store.mjs +29 -2
  13. package/apps/webauto/entry/lib/session-init.mjs +227 -0
  14. package/apps/webauto/entry/lib/upgrade-check.mjs +269 -0
  15. package/apps/webauto/entry/lib/xhs-unified-blocks.mjs +160 -0
  16. package/apps/webauto/entry/lib/xhs-unified-output-blocks.mjs +83 -0
  17. package/apps/webauto/entry/lib/xhs-unified-plan-blocks.mjs +55 -0
  18. package/apps/webauto/entry/lib/xhs-unified-profile-blocks.mjs +542 -0
  19. package/apps/webauto/entry/lib/xhs-unified-runtime-blocks.mjs +436 -0
  20. package/apps/webauto/entry/profilepool.mjs +56 -9
  21. package/apps/webauto/entry/smart-reply-cli.mjs +267 -0
  22. package/apps/webauto/entry/weibo-unified.mjs +84 -11
  23. package/apps/webauto/entry/xhs-orchestrate.mjs +43 -1
  24. package/apps/webauto/entry/xhs-unified.mjs +92 -997
  25. package/bin/webauto.mjs +22 -4
  26. package/dist/modules/camo-backend/src/index.js +33 -0
  27. package/dist/modules/camo-backend/src/internal/BrowserSession.js +232 -49
  28. package/dist/modules/camo-backend/src/internal/engine-manager.js +14 -13
  29. package/dist/modules/camo-backend/src/internal/ws-server.js +16 -19
  30. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +38 -6
  31. package/dist/modules/workflow/blocks/EnsureSession.js +0 -8
  32. package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +78 -6
  33. package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +266 -192
  34. package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +2 -0
  35. package/dist/modules/workflow/src/runner.js +2 -0
  36. package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +150 -37
  37. package/dist/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.js +491 -0
  38. package/modules/camo-backend/src/index.ts +31 -0
  39. package/modules/camo-backend/src/internal/BrowserSession.ts +224 -53
  40. package/modules/camo-backend/src/internal/engine-manager.ts +14 -15
  41. package/modules/camo-backend/src/internal/ws-server.ts +17 -17
  42. package/modules/camo-runtime/src/autoscript/action-providers/xhs/common.mjs +12 -2
  43. package/modules/camo-runtime/src/autoscript/action-providers/xhs/persistence.mjs +57 -0
  44. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +2475 -243
  45. package/modules/camo-runtime/src/autoscript/runtime.mjs +35 -30
  46. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +80 -443
  47. package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +39 -6
  48. package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +206 -39
  49. package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +0 -79
  50. package/modules/camo-runtime/src/container/runtime-core/operations/viewport.mjs +46 -0
  51. package/modules/camo-runtime/src/utils/browser-service.mjs +41 -6
  52. package/modules/camo-runtime/src/utils/js-policy.mjs +28 -0
  53. package/modules/workflow/blocks/EnsureSession.ts +0 -4
  54. package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +81 -6
  55. package/modules/workflow/blocks/WeiboCollectSearchLinksBlock.ts +316 -0
  56. package/modules/workflow/definitions/weibo-search-workflow-v1.ts +2 -0
  57. package/modules/workflow/src/runner.ts +2 -0
  58. package/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.ts +198 -53
  59. package/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.ts +706 -0
  60. package/package.json +2 -2
  61. package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +0 -498
  62. package/modules/camo-runtime/src/autoscript/action-providers/xhs/detail.mjs +0 -181
  63. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +0 -691
  64. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +0 -388
  65. package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +0 -135
@@ -2133,9 +2133,7 @@ var PLATFORM_TASKS = {
2133
2133
  { type: "xhs-unified", label: "\u641C\u7D22\u4EFB\u52A1", icon: "\u{1F4D5}", platform: "xiaohongshu" }
2134
2134
  ],
2135
2135
  weibo: [
2136
- { type: "weibo-timeline", label: "\u4E3B\u9875\u65F6\u95F4\u7EBF", icon: "\u{1F4F0}", platform: "weibo" },
2137
- { type: "weibo-search", label: "\u641C\u7D22\u4EFB\u52A1", icon: "\u{1F50D}", platform: "weibo" },
2138
- { type: "weibo-monitor", label: "\u76D1\u63A7\u4E2A\u4EBA\u4E3B\u9875", icon: "\u{1F441}\uFE0F", platform: "weibo" }
2136
+ { type: "weibo-search", label: "\u641C\u7D22\u4EFB\u52A1", icon: "\u{1F50D}", platform: "weibo" }
2139
2137
  ],
2140
2138
  "1688": [
2141
2139
  { type: "1688-search", label: "\u641C\u7D22\u4EFB\u52A1", icon: "\u{1F6D2}", platform: "1688" }
@@ -2249,6 +2247,16 @@ function parseSortableTime(value) {
2249
2247
  const ts = Date.parse(String(value || ""));
2250
2248
  return Number.isFinite(ts) ? ts : 0;
2251
2249
  }
2250
+ function normalizeCsvKeywords(value) {
2251
+ return String(value || "").split(",").map((item) => item.trim()).filter(Boolean).join(",");
2252
+ }
2253
+ function normalizeIsoOrNull(value) {
2254
+ const text = String(value || "").trim();
2255
+ if (!text) return null;
2256
+ const ts = Date.parse(text);
2257
+ if (!Number.isFinite(ts)) return text;
2258
+ return new Date(ts).toISOString();
2259
+ }
2252
2260
  function renderTasksPanel(root, ctx2) {
2253
2261
  root.innerHTML = "";
2254
2262
  const pageIndicator = createEl("div", { className: "page-indicator" }, [
@@ -2411,6 +2419,11 @@ function renderTasksPanel(root, ctx2) {
2411
2419
  <button id="task-history-edit-btn" class="secondary">\u8F7D\u5165\u7F16\u8F91</button>
2412
2420
  <button id="task-history-clone-btn" class="secondary">\u8F7D\u5165\u53E6\u5B58</button>
2413
2421
  <button id="task-history-run-btn">\u7ACB\u5373\u6267\u884C</button>
2422
+ <label style="display:flex;align-items:center;gap:6px;margin-left:6px;">
2423
+ <input id="task-select-all" type="checkbox" />
2424
+ <span style="font-size:12px;">\u5168\u9009</span>
2425
+ </label>
2426
+ <button id="task-history-delete-btn" class="secondary">\u6279\u91CF\u5220\u9664</button>
2414
2427
  <button id="task-history-refresh-btn" class="secondary">\u5237\u65B0</button>
2415
2428
  </div>
2416
2429
  <div class="muted" style="font-size:12px; margin-bottom:6px;">\u53CC\u51FB\u5217\u8868\u9879\u53EF\u76F4\u63A5\u5207\u6362\u4E3A\u5F53\u524D\u4EFB\u52A1\u3002</div>
@@ -2451,6 +2464,8 @@ function renderTasksPanel(root, ctx2) {
2451
2464
  const historyEditBtn = recentCard.querySelector("#task-history-edit-btn");
2452
2465
  const historyCloneBtn = recentCard.querySelector("#task-history-clone-btn");
2453
2466
  const historyRunBtn = recentCard.querySelector("#task-history-run-btn");
2467
+ const taskSelectAll = recentCard.querySelector("#task-select-all");
2468
+ const historyDeleteBtn = recentCard.querySelector("#task-history-delete-btn");
2454
2469
  const historyRefreshBtn = recentCard.querySelector("#task-history-refresh-btn");
2455
2470
  const recentTasksList = recentCard.querySelector("#recent-tasks-list");
2456
2471
  const statRunning = statsCard.querySelector("#stat-running");
@@ -2459,6 +2474,7 @@ function renderTasksPanel(root, ctx2) {
2459
2474
  let tasks = [];
2460
2475
  let accountRows = [];
2461
2476
  const activeRunIds = /* @__PURE__ */ new Set();
2477
+ const selectedTaskIds = /* @__PURE__ */ new Set();
2462
2478
  let unsubscribeActiveRuns = null;
2463
2479
  const joinPath2 = (...parts) => {
2464
2480
  if (typeof ctx2?.api?.pathJoin === "function") return ctx2.api.pathJoin(...parts);
@@ -2659,21 +2675,49 @@ function renderTasksPanel(root, ctx2) {
2659
2675
  historySelect.value = previous;
2660
2676
  }
2661
2677
  }
2678
+ function updateSelectionUi(rows) {
2679
+ const availableIds = new Set(rows.map((row) => row.id));
2680
+ for (const id of Array.from(selectedTaskIds)) {
2681
+ if (!availableIds.has(id)) selectedTaskIds.delete(id);
2682
+ }
2683
+ const total = rows.length;
2684
+ const selected = rows.filter((row) => selectedTaskIds.has(row.id)).length;
2685
+ taskSelectAll.checked = total > 0 && selected === total;
2686
+ taskSelectAll.indeterminate = selected > 0 && selected < total;
2687
+ historyDeleteBtn.disabled = selected === 0;
2688
+ historyDeleteBtn.textContent = selected > 0 ? `\u6279\u91CF\u5220\u9664(${selected})` : "\u6279\u91CF\u5220\u9664";
2689
+ }
2662
2690
  function renderRecentTasks() {
2663
2691
  const rows = sortedTasksByRecent();
2664
2692
  if (rows.length === 0) {
2665
2693
  recentTasksList.innerHTML = '<div class="muted" style="font-size:12px;">\u6682\u65E0\u4EFB\u52A1</div>';
2694
+ updateSelectionUi([]);
2666
2695
  return;
2667
2696
  }
2668
2697
  recentTasksList.innerHTML = rows.map((task) => `
2669
2698
  <div class="task-row task-item" data-id="${task.id}" style="display:flex;gap:var(--gap-sm);padding:var(--gap-xs)0;border-bottom:1px solid var(--border-subtle);align-items:center;cursor:pointer;">
2699
+ <input class="task-select-checkbox" data-id="${task.id}" type="checkbox" style="margin:0;" ${selectedTaskIds.has(task.id) ? "checked" : ""} />
2670
2700
  <span style="flex:1;font-size:12px;">${task.name || task.id}</span>
2671
2701
  <span style="font-size:11px;color:var(--text-tertiary);">${task.commandType}</span>
2672
2702
  <span style="font-size:11px;color:${task.enabled ? "var(--accent-success)" : "var(--text-muted)"};">${task.enabled ? "\u542F\u7528" : "\u7981\u7528"}</span>
2673
2703
  <button class="secondary edit-task-btn" data-id="${task.id}" style="padding:2px 6px;font-size:10px;height:auto;">\u7F16\u8F91</button>
2674
2704
  <button class="run-task-btn" data-id="${task.id}" style="padding:2px 6px;font-size:10px;height:auto;">\u7ACB\u5373\u6267\u884C</button>
2705
+ <button class="secondary delete-task-btn" data-id="${task.id}" style="padding:2px 6px;font-size:10px;height:auto;">\u5220\u9664</button>
2675
2706
  </div>
2676
2707
  `).join("");
2708
+ recentTasksList.querySelectorAll(".task-select-checkbox").forEach((checkbox) => {
2709
+ checkbox.addEventListener("click", (event) => {
2710
+ event.stopPropagation();
2711
+ });
2712
+ checkbox.addEventListener("change", () => {
2713
+ const input = checkbox;
2714
+ const taskId = String(input.dataset.id || "").trim();
2715
+ if (!taskId) return;
2716
+ if (input.checked) selectedTaskIds.add(taskId);
2717
+ else selectedTaskIds.delete(taskId);
2718
+ updateSelectionUi(rows);
2719
+ });
2720
+ });
2677
2721
  recentTasksList.querySelectorAll(".task-item").forEach((item) => {
2678
2722
  item.addEventListener("dblclick", () => {
2679
2723
  const taskId = item.dataset.id || "";
@@ -2700,6 +2744,14 @@ function renderTasksPanel(root, ctx2) {
2700
2744
  void runTaskImmediately(task);
2701
2745
  });
2702
2746
  });
2747
+ recentTasksList.querySelectorAll(".delete-task-btn").forEach((btn) => {
2748
+ btn.addEventListener("click", () => {
2749
+ const taskId = String(btn.dataset.id || "").trim();
2750
+ if (!taskId) return;
2751
+ void deleteTasks([taskId]);
2752
+ });
2753
+ });
2754
+ updateSelectionUi(rows);
2703
2755
  }
2704
2756
  function updateStats() {
2705
2757
  statSaved.textContent = String(tasks.length);
@@ -2771,6 +2823,8 @@ function renderTasksPanel(root, ctx2) {
2771
2823
  "max-notes": data.targetCount,
2772
2824
  target: data.targetCount,
2773
2825
  env: data.env,
2826
+ // Desktop-triggered runs must keep UI alive; service reset would call ui cli stop/start.
2827
+ "service-reset": false,
2774
2828
  "do-comments": data.collectComments,
2775
2829
  "fetch-body": data.collectBody,
2776
2830
  "do-likes": data.doLikes,
@@ -2839,7 +2893,7 @@ function renderTasksPanel(root, ctx2) {
2839
2893
  };
2840
2894
  }
2841
2895
  async function runSavedTask(taskId, data) {
2842
- const out = await invokeSchedule({ action: "run", taskId, timeoutMs: 0 });
2896
+ const out = await invokeSchedule({ action: "run", taskId, timeoutMs: 0, background: true });
2843
2897
  const runId = String(
2844
2898
  out?.result?.runResult?.lastRunId || out?.result?.runResult?.runId || out?.runResult?.runId || ""
2845
2899
  ).trim();
@@ -2868,24 +2922,140 @@ function renderTasksPanel(root, ctx2) {
2868
2922
  applyTaskToForm(task, "edit");
2869
2923
  await runSavedTask(taskId, taskToRunMeta(task));
2870
2924
  }
2925
+ function toTaskDedupFingerprintFromForm(data) {
2926
+ const scheduleMode = data.scheduleMode;
2927
+ const periodicType = scheduleMode === "periodic" ? data.periodicType : "interval";
2928
+ const intervalMinutes = scheduleMode === "periodic" ? Math.max(1, Number(data.intervalMinutes || 30) || 30) : 0;
2929
+ const runAt = scheduleMode === "scheduled" || scheduleMode === "periodic" && (periodicType === "daily" || periodicType === "weekly") ? normalizeIsoOrNull(data.runAt) : null;
2930
+ const maxRuns = scheduleMode === "periodic" ? Number.isFinite(Number(data.maxRuns || 0)) && Number(data.maxRuns) > 0 ? Math.max(1, Math.floor(Number(data.maxRuns))) : null : null;
2931
+ return {
2932
+ taskType: String(data.taskType || "xhs-unified").trim() || "xhs-unified",
2933
+ profileId: String(data.profileId || "").trim(),
2934
+ keyword: String(data.keyword || "").trim(),
2935
+ targetCount: Math.max(1, Number(data.targetCount || 50) || 50),
2936
+ env: String(data.env || "debug").trim() === "prod" ? "prod" : "debug",
2937
+ userId: String(data.userId || "").trim(),
2938
+ collectComments: data.collectComments !== false,
2939
+ collectBody: data.collectBody !== false,
2940
+ doLikes: data.doLikes === true,
2941
+ likeKeywords: normalizeCsvKeywords(data.likeKeywords),
2942
+ scheduleMode,
2943
+ periodicType,
2944
+ intervalMinutes,
2945
+ runAt,
2946
+ maxRuns
2947
+ };
2948
+ }
2949
+ function toTaskDedupFingerprintFromTask(task) {
2950
+ const uiSchedule = inferUiScheduleEditorState(task);
2951
+ const scheduleMode = uiSchedule.mode;
2952
+ const periodicType = uiSchedule.periodicType;
2953
+ const intervalMinutes = scheduleMode === "periodic" ? Math.max(1, Number(task.intervalMinutes || 30) || 30) : 0;
2954
+ const runAt = scheduleMode === "scheduled" || scheduleMode === "periodic" && (periodicType === "daily" || periodicType === "weekly") ? normalizeIsoOrNull(task.runAt) : null;
2955
+ const maxRuns = scheduleMode === "periodic" ? Number.isFinite(Number(task.maxRuns || 0)) && Number(task.maxRuns) > 0 ? Math.max(1, Math.floor(Number(task.maxRuns))) : null : null;
2956
+ return {
2957
+ taskType: String(task.commandType || "xhs-unified").trim() || "xhs-unified",
2958
+ profileId: String(task.commandArgv?.profile || task.commandArgv?.profileId || "").trim(),
2959
+ keyword: String(task.commandArgv?.keyword || task.commandArgv?.k || "").trim(),
2960
+ targetCount: Math.max(1, Number(task.commandArgv?.["max-notes"] ?? task.commandArgv?.target ?? 50) || 50),
2961
+ env: String(task.commandArgv?.env || "debug").trim() === "prod" ? "prod" : "debug",
2962
+ userId: String(task.commandArgv?.["user-id"] || task.commandArgv?.userId || "").trim(),
2963
+ collectComments: task.commandArgv?.["do-comments"] !== false,
2964
+ collectBody: task.commandArgv?.["fetch-body"] !== false,
2965
+ doLikes: task.commandArgv?.["do-likes"] === true,
2966
+ likeKeywords: normalizeCsvKeywords(String(task.commandArgv?.["like-keywords"] || "").trim()),
2967
+ scheduleMode,
2968
+ periodicType,
2969
+ intervalMinutes,
2970
+ runAt,
2971
+ maxRuns
2972
+ };
2973
+ }
2974
+ function toDedupKey(fingerprint) {
2975
+ return JSON.stringify(fingerprint);
2976
+ }
2977
+ function findDuplicateTaskByParams(data) {
2978
+ const editingId = String(data.id || "").trim();
2979
+ if (editingId) return getTaskById(editingId);
2980
+ const dedupKey = toDedupKey(toTaskDedupFingerprintFromForm(data));
2981
+ const rows = sortedTasksByRecent();
2982
+ for (const row of rows) {
2983
+ if (toDedupKey(toTaskDedupFingerprintFromTask(row)) === dedupKey) return row;
2984
+ }
2985
+ return null;
2986
+ }
2987
+ async function deleteTasks(taskIds) {
2988
+ const uniqueIds = Array.from(new Set(taskIds.map((item) => String(item || "").trim()).filter(Boolean)));
2989
+ if (uniqueIds.length === 0) {
2990
+ alert("\u8BF7\u5148\u9009\u62E9\u8981\u5220\u9664\u7684\u4EFB\u52A1");
2991
+ return;
2992
+ }
2993
+ const confirmed = window.confirm(`\u786E\u8BA4\u5220\u9664 ${uniqueIds.length} \u4E2A\u4EFB\u52A1\u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002`);
2994
+ if (!confirmed) return;
2995
+ historyDeleteBtn.disabled = true;
2996
+ try {
2997
+ const failed = [];
2998
+ const deleted = /* @__PURE__ */ new Set();
2999
+ for (const taskId of uniqueIds) {
3000
+ try {
3001
+ await invokeSchedule({ action: "delete", taskId });
3002
+ deleted.add(taskId);
3003
+ } catch {
3004
+ failed.push(taskId);
3005
+ }
3006
+ }
3007
+ for (const taskId of deleted) {
3008
+ selectedTaskIds.delete(taskId);
3009
+ }
3010
+ if (deleted.has(String(editingIdInput.value || "").trim())) {
3011
+ resetForm();
3012
+ }
3013
+ if (deleted.has(String(historySelect.value || "").trim())) {
3014
+ historySelect.value = "";
3015
+ }
3016
+ await loadTasks();
3017
+ if (failed.length > 0) {
3018
+ alert(`\u5DF2\u5220\u9664 ${deleted.size} \u4E2A\u4EFB\u52A1\uFF0C\u5931\u8D25 ${failed.length} \u4E2A`);
3019
+ }
3020
+ } finally {
3021
+ historyDeleteBtn.disabled = false;
3022
+ }
3023
+ }
2871
3024
  async function saveTask(runImmediately = false) {
3025
+ await loadTasks();
2872
3026
  const data = collectFormData();
3027
+ if (!String(data.id || "").trim()) {
3028
+ const duplicate = findDuplicateTaskByParams(data);
3029
+ if (duplicate) {
3030
+ data.id = duplicate.id;
3031
+ editingIdInput.value = duplicate.id;
3032
+ updateFormTitle("edit");
3033
+ }
3034
+ }
2873
3035
  saveBtn.disabled = true;
2874
3036
  runBtn.disabled = true;
2875
3037
  runEphemeralBtn.disabled = true;
3038
+ let savedTaskId = "";
3039
+ let resolvedProfile = String(data.profileId || "").trim();
2876
3040
  try {
2877
3041
  const out = await invokeSchedule({ action: "save", payload: toSchedulePayload(data) });
2878
3042
  const taskId = String(out?.task?.id || data.id || "").trim();
2879
3043
  if (!taskId) {
2880
3044
  throw new Error("task id missing after save");
2881
3045
  }
3046
+ savedTaskId = taskId;
2882
3047
  editingIdInput.value = taskId;
2883
3048
  updateFormTitle("edit");
2884
3049
  await loadTasks();
2885
3050
  historySelect.value = taskId;
3051
+ resolvedProfile = String(out?.task?.commandArgv?.profile || data.profileId || "").trim();
3052
+ } catch (err) {
3053
+ alert(`\u4FDD\u5B58\u5931\u8D25: ${err?.message || String(err)}`);
3054
+ return;
3055
+ }
3056
+ try {
2886
3057
  if (runImmediately) {
2887
- const resolvedProfile = String(out?.task?.commandArgv?.profile || data.profileId || "").trim();
2888
- await runSavedTask(taskId, {
3058
+ await runSavedTask(savedTaskId, {
2889
3059
  taskType: data.taskType,
2890
3060
  profileId: resolvedProfile,
2891
3061
  keyword: data.keyword,
@@ -2895,7 +3065,11 @@ function renderTasksPanel(root, ctx2) {
2895
3065
  alert("\u4EFB\u52A1\u5DF2\u4FDD\u5B58");
2896
3066
  }
2897
3067
  } catch (err) {
2898
- alert(`\u4FDD\u5B58\u5931\u8D25: ${err?.message || String(err)}`);
3068
+ if (runImmediately) {
3069
+ alert(`\u4EFB\u52A1\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u6267\u884C\u5931\u8D25: ${err?.message || String(err)}`);
3070
+ } else {
3071
+ alert(`\u6267\u884C\u5931\u8D25: ${err?.message || String(err)}`);
3072
+ }
2899
3073
  } finally {
2900
3074
  saveBtn.disabled = false;
2901
3075
  runBtn.disabled = false;
@@ -2979,6 +3153,18 @@ function renderTasksPanel(root, ctx2) {
2979
3153
  historyRefreshBtn.addEventListener("click", () => {
2980
3154
  void loadTasks();
2981
3155
  });
3156
+ taskSelectAll.addEventListener("change", () => {
3157
+ const rows = sortedTasksByRecent();
3158
+ if (taskSelectAll.checked) {
3159
+ for (const row of rows) selectedTaskIds.add(row.id);
3160
+ } else {
3161
+ selectedTaskIds.clear();
3162
+ }
3163
+ renderRecentTasks();
3164
+ });
3165
+ historyDeleteBtn.addEventListener("click", () => {
3166
+ void deleteTasks(Array.from(selectedTaskIds));
3167
+ });
2982
3168
  historyEditBtn.addEventListener("click", () => {
2983
3169
  const task = selectedHistoryTask();
2984
3170
  if (!task) {
@@ -3233,6 +3419,36 @@ function renderDashboard(root, ctx2) {
3233
3419
  const normalizeStatus = (value) => String(value || "").trim().toLowerCase();
3234
3420
  const isRunningStatus = (value) => ["running", "queued", "pending", "starting"].includes(normalizeStatus(value));
3235
3421
  const isTerminalStatus = (value) => ["completed", "done", "success", "succeeded", "failed", "error", "stopped", "canceled"].includes(normalizeStatus(value));
3422
+ const resolveUnifiedPhaseFromOp = (operationId, fallback = "\u8FD0\u884C\u4E2D") => {
3423
+ const op = String(operationId || "").trim();
3424
+ if (!op) return fallback;
3425
+ if (op === "sync_window_viewport" || op === "goto_home" || op === "fill_keyword" || op === "submit_search" || op === "xhs_assert_logged_in" || op === "abort_on_login_guard" || op === "abort_on_risk_guard") {
3426
+ return "\u767B\u5F55\u6821\u9A8C";
3427
+ }
3428
+ if (op === "ensure_tab_pool" || op === "verify_subscriptions_all_pages") {
3429
+ return "\u91C7\u96C6\u94FE\u63A5";
3430
+ }
3431
+ if (op === "open_first_detail" || op === "open_next_detail" || op === "wait_between_notes" || op === "switch_tab_round_robin") {
3432
+ return "\u6253\u5F00\u8BE6\u60C5";
3433
+ }
3434
+ if (op === "detail_harvest" || op === "expand_replies" || op === "comments_harvest" || op === "comment_match_gate" || op === "comment_like" || op === "comment_reply" || op === "close_detail") {
3435
+ return "\u8BE6\u60C5\u91C7\u96C6\u70B9\u8D5E";
3436
+ }
3437
+ return fallback;
3438
+ };
3439
+ const resolveUnifiedActionFromEvent = (eventName, payload, fallback = "-") => {
3440
+ const opId = String(payload?.operationId || "").trim();
3441
+ if (opId) {
3442
+ if (eventName === "autoscript:operation_error" || eventName === "autoscript:operation_recovery_failed") {
3443
+ const err = String(payload?.error || payload?.message || payload?.code || "").trim();
3444
+ return err ? `${opId}: ${err}` : `${opId}: failed`;
3445
+ }
3446
+ const stage = String(payload?.stage || "").trim();
3447
+ return stage ? `${opId}:${stage}` : opId;
3448
+ }
3449
+ const message = String(payload?.message || payload?.reason || "").trim();
3450
+ return message || fallback;
3451
+ };
3236
3452
  const isXhsCommandTitle = (title) => {
3237
3453
  const normalized = String(title || "").trim().toLowerCase();
3238
3454
  if (!normalized) return false;
@@ -3695,9 +3911,16 @@ function renderDashboard(root, ctx2) {
3695
3911
  renderRunSummary();
3696
3912
  return;
3697
3913
  }
3914
+ if (event === "autoscript:operation_start" || event === "autoscript:operation_progress") {
3915
+ const opId = String(payload?.operationId || "").trim();
3916
+ currentPhase.textContent = resolveUnifiedPhaseFromOp(opId, currentPhase.textContent || "\u8FD0\u884C\u4E2D");
3917
+ currentAction.textContent = resolveUnifiedActionFromEvent(event, payload, currentAction.textContent || "-");
3918
+ return;
3919
+ }
3698
3920
  if (event === "autoscript:operation_done") {
3699
3921
  const opId = String(payload.operationId || "").trim();
3700
- currentAction.textContent = opId || currentAction.textContent;
3922
+ currentPhase.textContent = resolveUnifiedPhaseFromOp(opId, currentPhase.textContent || "\u8FD0\u884C\u4E2D");
3923
+ currentAction.textContent = resolveUnifiedActionFromEvent(event, payload, currentAction.textContent || "-");
3701
3924
  const result = payload.result && typeof payload.result === "object" ? payload.result : {};
3702
3925
  const opResult = result && typeof result === "object" && "result" in result ? result.result : result;
3703
3926
  if (opId === "open_first_detail" || opId === "open_next_detail") {
@@ -3755,6 +3978,10 @@ function renderDashboard(root, ctx2) {
3755
3978
  const failed = Number(statFailed.textContent || "0") || 0;
3756
3979
  statFailed.textContent = String(failed + 1);
3757
3980
  const opId = String(payload?.operationId || "").trim();
3981
+ if (opId) {
3982
+ currentPhase.textContent = resolveUnifiedPhaseFromOp(opId, currentPhase.textContent || "\u8FD0\u884C\u4E2D");
3983
+ }
3984
+ currentAction.textContent = resolveUnifiedActionFromEvent(event, payload, currentAction.textContent || "-");
3758
3985
  const err = String(payload?.error || payload?.message || payload?.code || event).trim();
3759
3986
  pushRecentError(opId ? `${opId}: ${err}` : err, event, payload);
3760
3987
  return;
@@ -4370,6 +4597,8 @@ Profile ID: ${acc.profileId}
4370
4597
  ctx2.api.pathJoin("apps", "webauto", "entry", "account.mjs"),
4371
4598
  "sync",
4372
4599
  profileId,
4600
+ "--platform",
4601
+ normalizePlatform(account.platform || "xiaohongshu"),
4373
4602
  ...options.pendingWhileLogin ? ["--pending-while-login"] : [],
4374
4603
  ...options.resolveAlias ? ["--resolve-alias"] : [],
4375
4604
  "--json"