oc-browser-relay 1.0.4 → 1.0.6

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 (34) hide show
  1. package/bundled-relay/index.js +304 -8
  2. package/bundled-relay/public/configs/pages/douyin-creator-publish.json +473 -0
  3. package/bundled-relay/public/configs/pages/index.json +2 -1
  4. package/bundled-relay/public/configs/pages/sycm-taobao-home.json +387 -1
  5. package/bundled-relay/public/configs/pages/taobao-item-detail.json +117 -83
  6. package/bundled-relay/public/configs/pages/xiaohongshu-creator-publish.json +2 -5
  7. package/bundled-relay/public/configs/sites/douyin-creator.json +29 -0
  8. package/bundled-relay/public/configs/sites/index.json +2 -1
  9. package/bundled-relay/public/configs/task-templates/douyin-creator-publish.json +131 -0
  10. package/bundled-relay/public/configs/task-templates/douyin-creator-save-draft.json +99 -0
  11. package/bundled-relay/public/configs/task-templates/index.json +5 -1
  12. package/bundled-relay/public/configs/task-templates/sycm-taobao-api-group.json +170 -0
  13. package/bundled-relay/public/configs/task-templates/sycm-taobao-home-live-metrics.json +17 -0
  14. package/bundled-relay/public/configs/task-templates/taobao-item-detail-comments.json +61 -21
  15. package/bundled-relay/public/configs/task-templates/wechat-get-article.json +44 -0
  16. package/bundled-relay/public/configs/task-templates/xiaohongshu-creator-publish.json +15 -0
  17. package/bundled-relay/public/configs/task-templates/xiaohongshu-creator-save-draft.json +15 -0
  18. package/bundled-relay/public/configs/task-templates/xiaohongshu-web-search-notes.json +16 -0
  19. package/bundled-relay/public/icons/icon-active-128.png +0 -0
  20. package/bundled-relay/public/icons/icon-active-48.png +0 -0
  21. package/bundled-relay/public/icons/icon-active-96.png +0 -0
  22. package/bundled-relay/public/icons/icon-default-128.png +0 -0
  23. package/bundled-relay/public/icons/icon-default-48.png +0 -0
  24. package/bundled-relay/public/icons/icon-default-96.png +0 -0
  25. package/bundled-relay/public/icons/icon-inactive-128.png +0 -0
  26. package/bundled-relay/public/icons/icon-inactive-48.png +0 -0
  27. package/bundled-relay/public/icons/icon-inactive-96.png +0 -0
  28. package/index.js +1 -1
  29. package/openclaw.plugin.json +0 -12
  30. package/package.json +1 -1
  31. package/skills/bianjie-browser-relay/SKILL.md +562 -0
  32. package/skills/oc-browser-relay-taobao/SKILL.md +0 -213
  33. package/skills/oc-browser-relay-xiaohongshu/SKILL.md +0 -310
  34. package/skills/oc-sycm-live-metrics/SKILL.md +0 -121
@@ -23052,7 +23052,7 @@ var TemplateExecutor = class _TemplateExecutor {
23052
23052
  if (lengthMatch) {
23053
23053
  const [, fieldName, operator, value] = lengthMatch;
23054
23054
  const fieldValue = data[fieldName];
23055
- const length = Array.isArray(fieldValue) ? fieldValue.length : 0;
23055
+ const length = Array.isArray(fieldValue) || typeof fieldValue === "string" ? fieldValue.length : 0;
23056
23056
  const targetValue = parseInt(value);
23057
23057
  switch (operator) {
23058
23058
  case ">=":
@@ -23250,6 +23250,7 @@ function mapToolNameToTaskOperation(tool) {
23250
23250
  case "present_page_activity":
23251
23251
  return "unknown";
23252
23252
  case "invoke_page_request":
23253
+ case "invoke_page_request_group":
23253
23254
  return "extract_api_payload";
23254
23255
  case "collect_search_list":
23255
23256
  case "collect_search_item_details":
@@ -23430,6 +23431,7 @@ function resolveTaskCapability(task, captureCapability) {
23430
23431
  case "save_note_draft":
23431
23432
  case "browser_action":
23432
23433
  case "search_notes":
23434
+ case "wechat_get_article":
23433
23435
  return "browser";
23434
23436
  default:
23435
23437
  return void 0;
@@ -23805,6 +23807,264 @@ function buildTaskFinalResult(task) {
23805
23807
  };
23806
23808
  }
23807
23809
 
23810
+ // processors/taobao/comments.ts
23811
+ function asRecord(value) {
23812
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
23813
+ }
23814
+ function asArray(value) {
23815
+ return Array.isArray(value) ? value : [];
23816
+ }
23817
+ function toNumberOrNull(value) {
23818
+ if (typeof value === "number" && Number.isFinite(value)) {
23819
+ return value;
23820
+ }
23821
+ if (typeof value === "string") {
23822
+ const trimmed = value.trim();
23823
+ if (!trimmed) return null;
23824
+ const parsed = Number(trimmed);
23825
+ return Number.isFinite(parsed) ? parsed : null;
23826
+ }
23827
+ return null;
23828
+ }
23829
+ function toStringOrNull(value) {
23830
+ return typeof value === "string" && value.trim() ? value.trim() : null;
23831
+ }
23832
+ var processTaobaoComments = async (input) => {
23833
+ const targetCount = resolveTargetCommentCount(input.stepArgs?.targetCommentCount);
23834
+ const pages = asArray(input.sources.product_comments_pages).map((page) => asRecord(page)).filter((page) => Boolean(page));
23835
+ const firstPage = pages[0];
23836
+ const firstData = asRecord(firstPage?.data) ?? {};
23837
+ const lastDataWithHasNext = findLastPageDataWithField(pages, "hasNext");
23838
+ const comments = [];
23839
+ const seenIds = /* @__PURE__ */ new Set();
23840
+ for (const page of pages) {
23841
+ const data = asRecord(page.data);
23842
+ const rateList = asArray(data?.rateList);
23843
+ for (const comment of rateList) {
23844
+ const key = toStringOrNull(comment.id) || JSON.stringify(comment);
23845
+ if (seenIds.has(key)) {
23846
+ continue;
23847
+ }
23848
+ seenIds.add(key);
23849
+ comments.push({
23850
+ id: toStringOrNull(comment.id),
23851
+ feedback: toStringOrNull(comment.feedback),
23852
+ feedbackDate: toStringOrNull(comment.feedbackDate),
23853
+ rateType: toStringOrNull(comment.rateType),
23854
+ skuId: toStringOrNull(comment.skuId),
23855
+ skuMap: asRecord(comment.skuMap),
23856
+ decision: comment.decision ?? null,
23857
+ interactInfo: {
23858
+ likeCount: toNumberOrNull(asRecord(comment.interactInfo)?.likeCount),
23859
+ commentCount: toNumberOrNull(asRecord(comment.interactInfo)?.commentCount),
23860
+ readCount: toNumberOrNull(asRecord(comment.interactInfo)?.readCount)
23861
+ },
23862
+ pictures: asArray(comment.feedPicPathList).map((url) => ({
23863
+ url: toStringOrNull(url)
23864
+ })).filter((entry) => entry.url),
23865
+ userNick: toStringOrNull(comment.userNick),
23866
+ creditLevel: toStringOrNull(comment.creditLevel)
23867
+ });
23868
+ if (targetCount > 0 && comments.length >= targetCount) {
23869
+ break;
23870
+ }
23871
+ }
23872
+ if (targetCount > 0 && comments.length >= targetCount) {
23873
+ break;
23874
+ }
23875
+ }
23876
+ return {
23877
+ data: {
23878
+ feedAllCount: toNumberOrNull(firstData.feedAllCount),
23879
+ feedGoodCount: toNumberOrNull(firstData.feedGoodCount),
23880
+ feedBadCount: toNumberOrNull(firstData.feedBadCount),
23881
+ feedNormalCount: toNumberOrNull(firstData.feedNormalCount),
23882
+ feedMediaCount: toNumberOrNull(firstData.feedMediaCount),
23883
+ feedVideoCount: toNumberOrNull(firstData.feedVideoCount),
23884
+ hasNext: lastDataWithHasNext?.hasNext ?? firstData.hasNext ?? null,
23885
+ total: toNumberOrNull(firstData.total),
23886
+ totalPage: toNumberOrNull(firstData.totalPage),
23887
+ timePeriodDesc: toStringOrNull(firstData.timePeriodDesc),
23888
+ comments
23889
+ },
23890
+ meta: {
23891
+ pageCount: pages.length,
23892
+ commentCount: comments.length,
23893
+ targetCount: targetCount > 0 ? targetCount : null
23894
+ }
23895
+ };
23896
+ };
23897
+ function resolveTargetCommentCount(value) {
23898
+ const parsed = Number(value);
23899
+ if (!Number.isFinite(parsed) || parsed <= 0) {
23900
+ return 0;
23901
+ }
23902
+ return Math.floor(parsed);
23903
+ }
23904
+ function findLastPageDataWithField(pages, key) {
23905
+ for (let index = pages.length - 1; index >= 0; index -= 1) {
23906
+ const data = asRecord(pages[index]?.data);
23907
+ if (data && data[key] !== void 0) {
23908
+ return data;
23909
+ }
23910
+ }
23911
+ return null;
23912
+ }
23913
+
23914
+ // processors/taobao/ask-answers.ts
23915
+ function asRecord2(value) {
23916
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
23917
+ }
23918
+ function asArray2(value) {
23919
+ return Array.isArray(value) ? value : [];
23920
+ }
23921
+ function toNumberOrNull2(value) {
23922
+ if (typeof value === "number" && Number.isFinite(value)) {
23923
+ return value;
23924
+ }
23925
+ if (typeof value === "string") {
23926
+ const trimmed = value.trim();
23927
+ if (!trimmed) return null;
23928
+ const parsed = Number(trimmed);
23929
+ return Number.isFinite(parsed) ? parsed : null;
23930
+ }
23931
+ return null;
23932
+ }
23933
+ function toStringOrNull2(value) {
23934
+ return typeof value === "string" && value.trim() ? value.trim() : null;
23935
+ }
23936
+ var processTaobaoAskAnswers = async (input) => {
23937
+ const targetCount = resolveTargetAskAnswerCount(input.stepArgs?.targetAskAnswerCount);
23938
+ const pages = asArray2(input.sources.ask_answers_pages);
23939
+ const normalizedPages = pages.map((page) => asRecord2(page)).filter((page) => Boolean(page));
23940
+ const firstPage = normalizedPages[0];
23941
+ const firstData = asRecord2(firstPage?.data) ?? {};
23942
+ const questions = [];
23943
+ const seenQuestionKeys = /* @__PURE__ */ new Set();
23944
+ for (const page of normalizedPages) {
23945
+ const data = asRecord2(page.data);
23946
+ const questionList = asArray2(data?.questionList);
23947
+ for (const question of questionList) {
23948
+ const key = toStringOrNull2(question.questionId) || toStringOrNull2(question.id) || toStringOrNull2(question.questionTitle) || JSON.stringify(question);
23949
+ if (seenQuestionKeys.has(key)) {
23950
+ continue;
23951
+ }
23952
+ seenQuestionKeys.add(key);
23953
+ questions.push({
23954
+ questionId: toStringOrNull2(question.questionId) || toStringOrNull2(question.id),
23955
+ questionTitle: toStringOrNull2(question.questionTitle),
23956
+ answerCount: toNumberOrNull2(question.answerCount),
23957
+ features: asArray2(question.features),
23958
+ topAnswerList: asArray2(question.topAnswerList)
23959
+ });
23960
+ if (targetCount > 0 && questions.length >= targetCount) {
23961
+ break;
23962
+ }
23963
+ }
23964
+ if (targetCount > 0 && questions.length >= targetCount) {
23965
+ break;
23966
+ }
23967
+ }
23968
+ const tags = asArray2(firstData.tags).map((tag) => ({
23969
+ keyword: toStringOrNull2(tag.keyword),
23970
+ count: toNumberOrNull2(tag.count)
23971
+ }));
23972
+ return {
23973
+ data: {
23974
+ questionTotal: toNumberOrNull2(firstData.questionTotal),
23975
+ hasNext: firstData.hasNext,
23976
+ tags,
23977
+ questions
23978
+ },
23979
+ meta: {
23980
+ pageCount: normalizedPages.length,
23981
+ questionCount: questions.length,
23982
+ targetCount: targetCount > 0 ? targetCount : null
23983
+ }
23984
+ };
23985
+ };
23986
+ function resolveTargetAskAnswerCount(value) {
23987
+ const parsed = Number(value);
23988
+ if (!Number.isFinite(parsed) || parsed <= 0) {
23989
+ return 0;
23990
+ }
23991
+ return Math.floor(parsed);
23992
+ }
23993
+
23994
+ // processors/registry.ts
23995
+ var PROCESSORS = {
23996
+ "taobao.comments": processTaobaoComments,
23997
+ "taobao.ask_answers": processTaobaoAskAnswers
23998
+ };
23999
+ function resolveStructuredDataProcessor(processorId) {
24000
+ return PROCESSORS[processorId] ?? null;
24001
+ }
24002
+
24003
+ // relay/src/structured-data-processor.ts
24004
+ function isProcessorEnvelope(value) {
24005
+ return Boolean(
24006
+ value && typeof value === "object" && value.mode === "relay_processor" && typeof value.processorId === "string" && value.processorId.length > 0 && typeof value.input === "object"
24007
+ );
24008
+ }
24009
+ async function applyStructuredDataProcessorToNodeResult(result) {
24010
+ if (!result.ok || result.tool !== "collect_structured_data") {
24011
+ return result;
24012
+ }
24013
+ const processing = result.observation?.processing;
24014
+ if (!isProcessorEnvelope(processing)) {
24015
+ return result;
24016
+ }
24017
+ const processor = resolveStructuredDataProcessor(processing.processorId);
24018
+ if (!processor) {
24019
+ return {
24020
+ ...result,
24021
+ ok: false,
24022
+ status: "failed",
24023
+ observation: {
24024
+ extractor: processing.input.extractor,
24025
+ processorId: processing.processorId
24026
+ },
24027
+ error: {
24028
+ code: ERROR_CODES.ERR_STEP_EXECUTION_FAILED,
24029
+ message: `Structured data processor not found: ${processing.processorId}`
24030
+ }
24031
+ };
24032
+ }
24033
+ try {
24034
+ const output = await processor(processing.input);
24035
+ return {
24036
+ ...result,
24037
+ observation: {
24038
+ ...result.observation,
24039
+ data: output.data,
24040
+ meta: {
24041
+ ...result.observation.meta ?? {},
24042
+ processor: {
24043
+ id: processing.processorId,
24044
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
24045
+ ...output.meta ? { meta: output.meta } : {}
24046
+ }
24047
+ },
24048
+ processing: void 0
24049
+ }
24050
+ };
24051
+ } catch (error) {
24052
+ return {
24053
+ ...result,
24054
+ ok: false,
24055
+ status: "failed",
24056
+ observation: {
24057
+ extractor: processing.input.extractor,
24058
+ processorId: processing.processorId
24059
+ },
24060
+ error: {
24061
+ code: ERROR_CODES.ERR_STEP_EXECUTION_FAILED,
24062
+ message: error instanceof Error ? error.message : `Processor failed: ${processing.processorId}`
24063
+ }
24064
+ };
24065
+ }
24066
+ }
24067
+
23808
24068
  // shared/node-template-compiler.ts
23809
24069
  function deriveOnFailurePolicyFromRetry(node) {
23810
24070
  if (node.onFailure) {
@@ -24139,7 +24399,7 @@ app.use((req, res, next) => {
24139
24399
  }
24140
24400
  next();
24141
24401
  });
24142
- app.use(import_express.default.json());
24402
+ app.use(import_express.default.json({ limit: "100mb" }));
24143
24403
  function logRelayFlow(message, details) {
24144
24404
  if (details) {
24145
24405
  console.log(`[Relay][Flow] ${message}`, details);
@@ -24147,6 +24407,37 @@ function logRelayFlow(message, details) {
24147
24407
  }
24148
24408
  console.log(`[Relay][Flow] ${message}`);
24149
24409
  }
24410
+ function previewRelayText(value, maxLength = 160) {
24411
+ if (value.length <= maxLength) {
24412
+ return value;
24413
+ }
24414
+ return `${value.slice(0, maxLength)}...`;
24415
+ }
24416
+ function buildRelayTaskPayloadSummary(payload) {
24417
+ const data = payload || {};
24418
+ const payloadKeys = Object.keys(data).filter((key) => key !== "nodes" && key !== "nodeResults");
24419
+ const contentValue = data.content;
24420
+ const titleValue = data.title;
24421
+ const topicsValue = data.topics;
24422
+ const imagesValue = data.images;
24423
+ const imageRefsValue = data.imageRefs;
24424
+ const videoValue = data.video;
24425
+ return {
24426
+ payloadKeys,
24427
+ hasTitle: typeof titleValue === "string" && titleValue.length > 0,
24428
+ titleLength: typeof titleValue === "string" ? titleValue.length : 0,
24429
+ titlePreview: typeof titleValue === "string" ? previewRelayText(titleValue, 80) : void 0,
24430
+ hasContentField: Object.prototype.hasOwnProperty.call(data, "content"),
24431
+ contentType: contentValue == null ? typeof contentValue : Array.isArray(contentValue) ? "array" : typeof contentValue,
24432
+ contentLength: typeof contentValue === "string" ? contentValue.length : 0,
24433
+ contentPreview: typeof contentValue === "string" ? previewRelayText(contentValue) : void 0,
24434
+ topicCount: Array.isArray(topicsValue) ? topicsValue.length : 0,
24435
+ imageCount: Array.isArray(imagesValue) ? imagesValue.length : 0,
24436
+ imageRefCount: Array.isArray(imageRefsValue) ? imageRefsValue.length : 0,
24437
+ hasVideoField: typeof videoValue === "string" && videoValue.length > 0,
24438
+ videoPreview: typeof videoValue === "string" ? previewRelayText(videoValue, 120) : void 0
24439
+ };
24440
+ }
24150
24441
  function createRelayError(message, code = ERROR_CODES.ERR_INVALID_TASK, status = 400) {
24151
24442
  const error = new Error(message);
24152
24443
  error.code = code;
@@ -24558,7 +24849,8 @@ async function dispatchTask(task) {
24558
24849
  siteId: task.context?.siteId,
24559
24850
  batchJobId: task.context?.batchJobId,
24560
24851
  batchItemIndex: task.context?.batchItemIndex,
24561
- nodeTools: task.payload?.nodes?.map((node) => `${node.nodeId}:${node.tool}`) || []
24852
+ nodeTools: task.payload?.nodes?.map((node) => `${node.nodeId}:${node.tool}`) || [],
24853
+ inputSummary: buildRelayTaskPayloadSummary(task.payload)
24562
24854
  });
24563
24855
  const taskMessage = {
24564
24856
  type: "task.submit",
@@ -24681,7 +24973,8 @@ async function startNextBatchItem(batchJobId) {
24681
24973
  message: error?.message || "Runtime not available"
24682
24974
  };
24683
24975
  updateTaskStatusView(task);
24684
- throw error;
24976
+ console.error(`Batch task ${task.taskId} failed:`, error);
24977
+ await handleBatchTaskTerminal(task.taskId, "failed", task.lastError);
24685
24978
  }
24686
24979
  }
24687
24980
  async function handleBatchTaskTerminal(taskId, status, lastError) {
@@ -24753,7 +25046,7 @@ async function registerAndDispatchTask(task) {
24753
25046
  message: error?.message || "Runtime not available"
24754
25047
  };
24755
25048
  updateTaskStatusView(task);
24756
- throw error;
25049
+ console.error(`Task ${task.taskId} failed:`, error);
24757
25050
  }
24758
25051
  return task;
24759
25052
  }
@@ -24794,7 +25087,7 @@ async function handleRuntimeMessage(msg) {
24794
25087
  handleArtifactCreated(msg);
24795
25088
  break;
24796
25089
  case "node.result":
24797
- handleNodeResult(msg);
25090
+ void handleNodeResult(msg);
24798
25091
  break;
24799
25092
  case "runtime.query.result":
24800
25093
  handleRuntimeQueryResult(msg);
@@ -24903,8 +25196,9 @@ function handleStepProgress(msg) {
24903
25196
  }
24904
25197
  applyStepProgressToTask(task, payload);
24905
25198
  }
24906
- function handleNodeResult(msg) {
24907
- const rawResult = msg.payload;
25199
+ async function handleNodeResult(msg) {
25200
+ const initialResult = msg.payload;
25201
+ const rawResult = await applyStructuredDataProcessorToNodeResult(initialResult);
24908
25202
  logRelayFlow("Received node result", {
24909
25203
  taskId: rawResult.taskId,
24910
25204
  nodeId: rawResult.nodeId,
@@ -25430,7 +25724,9 @@ app.get("/api/tools", async (req, res) => {
25430
25724
  });
25431
25725
  app.post("/api/tools/:toolName/execute", async (req, res) => {
25432
25726
  const { toolName } = req.params;
25727
+ console.log(`\u{1F680} ~ index.ts:1697 ~ toolName:`, toolName);
25433
25728
  const { input = {}, runtimeId } = req.body || {};
25729
+ console.log(`\u{1F680} ~ index.ts:1698 ~ req.body:`, req.body);
25434
25730
  const effectiveRuntimeId = resolveRuntimeIdOrDefault(runtimeId);
25435
25731
  try {
25436
25732
  if (toolName === "list_runtimes") {