@cicctencent/agent-server 0.2.83 → 0.2.84

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 (53) hide show
  1. package/dist/cjs/automation/index.d.ts +3 -0
  2. package/dist/cjs/automation/index.d.ts.map +1 -1
  3. package/dist/cjs/automation/index.js +6 -1
  4. package/dist/cjs/automation/index.js.map +1 -1
  5. package/dist/cjs/automation/node-task-runtime.d.ts +2 -0
  6. package/dist/cjs/automation/node-task-runtime.d.ts.map +1 -1
  7. package/dist/cjs/automation/node-task-runtime.js +42 -1
  8. package/dist/cjs/automation/node-task-runtime.js.map +1 -1
  9. package/dist/cjs/automation/node-task-validator.d.ts.map +1 -1
  10. package/dist/cjs/automation/node-task-validator.js +17 -2
  11. package/dist/cjs/automation/node-task-validator.js.map +1 -1
  12. package/dist/cjs/automation/python-prelude.d.ts +6 -0
  13. package/dist/cjs/automation/python-prelude.d.ts.map +1 -0
  14. package/dist/cjs/automation/python-prelude.js +290 -0
  15. package/dist/cjs/automation/python-prelude.js.map +1 -0
  16. package/dist/cjs/automation/python-task-runtime.d.ts +17 -0
  17. package/dist/cjs/automation/python-task-runtime.d.ts.map +1 -0
  18. package/dist/cjs/automation/python-task-runtime.js +244 -0
  19. package/dist/cjs/automation/python-task-runtime.js.map +1 -0
  20. package/dist/cjs/index.d.ts +2 -2
  21. package/dist/cjs/index.d.ts.map +1 -1
  22. package/dist/cjs/index.js +5 -2
  23. package/dist/cjs/index.js.map +1 -1
  24. package/dist/cjs/sandbox/local-sandbox.d.ts.map +1 -1
  25. package/dist/cjs/sandbox/local-sandbox.js +5 -8
  26. package/dist/cjs/sandbox/local-sandbox.js.map +1 -1
  27. package/dist/esm/automation/index.d.ts +3 -0
  28. package/dist/esm/automation/index.d.ts.map +1 -1
  29. package/dist/esm/automation/index.js +2 -0
  30. package/dist/esm/automation/index.js.map +1 -1
  31. package/dist/esm/automation/node-task-runtime.d.ts +2 -0
  32. package/dist/esm/automation/node-task-runtime.d.ts.map +1 -1
  33. package/dist/esm/automation/node-task-runtime.js +42 -1
  34. package/dist/esm/automation/node-task-runtime.js.map +1 -1
  35. package/dist/esm/automation/node-task-validator.d.ts.map +1 -1
  36. package/dist/esm/automation/node-task-validator.js +17 -2
  37. package/dist/esm/automation/node-task-validator.js.map +1 -1
  38. package/dist/esm/automation/python-prelude.d.ts +6 -0
  39. package/dist/esm/automation/python-prelude.d.ts.map +1 -0
  40. package/dist/esm/automation/python-prelude.js +287 -0
  41. package/dist/esm/automation/python-prelude.js.map +1 -0
  42. package/dist/esm/automation/python-task-runtime.d.ts +17 -0
  43. package/dist/esm/automation/python-task-runtime.d.ts.map +1 -0
  44. package/dist/esm/automation/python-task-runtime.js +206 -0
  45. package/dist/esm/automation/python-task-runtime.js.map +1 -0
  46. package/dist/esm/index.d.ts +2 -2
  47. package/dist/esm/index.d.ts.map +1 -1
  48. package/dist/esm/index.js +1 -1
  49. package/dist/esm/index.js.map +1 -1
  50. package/dist/esm/sandbox/local-sandbox.d.ts.map +1 -1
  51. package/dist/esm/sandbox/local-sandbox.js +5 -8
  52. package/dist/esm/sandbox/local-sandbox.js.map +1 -1
  53. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"node-task-validator.d.ts","sourceRoot":"","sources":["../../../src/automation/node-task-validator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,yBAAyB,EAEzB,8BAA8B,EAC/B,MAAM,yBAAyB,CAAC;AAUjC,wBAAgB,iCAAiC,CAC/C,WAAW,EAAE,yBAAyB,GACrC,8BAA8B,CAwChC"}
1
+ {"version":3,"file":"node-task-validator.d.ts","sourceRoot":"","sources":["../../../src/automation/node-task-validator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,yBAAyB,EAEzB,8BAA8B,EAC/B,MAAM,yBAAyB,CAAC;AAcjC,wBAAgB,iCAAiC,CAC/C,WAAW,EAAE,yBAAyB,GACrC,8BAA8B,CAqDhC"}
@@ -7,6 +7,10 @@ const FORBIDDEN_PATTERNS = [
7
7
  { code: 'forbidden-fs-absolute-write', pattern: /writeFile(?:Sync)?\s*\(\s*['"]\//, message: '任务代码不能写入绝对路径,请使用 context.artifacts 输出产物。' },
8
8
  { code: 'forbidden-process-env-dump', pattern: /JSON\.stringify\s*\(\s*process\.env\s*\)/, message: '任务代码不能完整输出环境变量。' },
9
9
  { code: 'forbidden-npm-install', pattern: /npm\s+install|pnpm\s+add|yarn\s+add/, message: '任务代码不能在运行时安装依赖,请通过容器基础库注册依赖。' },
10
+ // Python 安全规则
11
+ { code: 'forbidden-python-subprocess', pattern: /\b(?:subprocess|os\.system|os\.popen|os\.exec\w*)\s*\(/, message: 'Python 任务代码不能使用 subprocess/os.system 等执行外部命令,请通过受控基础库暴露能力。' },
12
+ { code: 'forbidden-python-eval', pattern: /\b(?:eval|exec)\s*\(/, message: 'Python 任务代码不能使用 eval/exec 执行动态代码。' },
13
+ { code: 'forbidden-python-absolute-write', pattern: /open\s*\(\s*['"]\/(?!(?:tmp|sandbox))/, message: 'Python 任务代码不能写入沙箱外的绝对路径,请使用相对路径输出产物。' },
10
14
  ];
11
15
  export function validateNodeAutomationTaskPackage(taskPackage) {
12
16
  const issues = [];
@@ -14,13 +18,24 @@ export function validateNodeAutomationTaskPackage(taskPackage) {
14
18
  addIssue(issues, 'missing-id', '任务包缺少 id。');
15
19
  if (!taskPackage.version)
16
20
  addIssue(issues, 'missing-version', '任务包缺少 version。');
17
- if (taskPackage.runtime !== 'nodejs')
18
- addIssue(issues, 'invalid-runtime', '代码任务 runtime 必须为 nodejs。');
21
+ if (taskPackage.runtime !== 'nodejs' && taskPackage.runtime !== 'python') {
22
+ addIssue(issues, 'invalid-runtime', '代码任务 runtime 必须为 nodejs 或 python。');
23
+ }
19
24
  if (!taskPackage.entry)
20
25
  addIssue(issues, 'missing-entry', '任务包缺少入口文件。');
21
26
  if (!taskPackage.sourceFiles || Object.keys(taskPackage.sourceFiles).length === 0) {
22
27
  addIssue(issues, 'missing-source', '任务包缺少源码文件。');
23
28
  }
29
+ // 入口文件扩展名校验(与 runtime 匹配)
30
+ if (taskPackage.entry && taskPackage.runtime) {
31
+ const ext = taskPackage.entry.split('.').pop() || '';
32
+ if (taskPackage.runtime === 'nodejs' && ext !== 'js' && ext !== 'cjs' && ext !== 'mjs') {
33
+ addIssue(issues, 'invalid-entry-ext', `nodejs 任务入口应为 .js/.cjs/.mjs,当前为 ${taskPackage.entry}`);
34
+ }
35
+ if (taskPackage.runtime === 'python' && ext !== 'py') {
36
+ addIssue(issues, 'invalid-entry-ext', `python 任务入口应为 .py,当前为 ${taskPackage.entry}`);
37
+ }
38
+ }
24
39
  if (taskPackage.entry && taskPackage.sourceFiles && !taskPackage.sourceFiles[taskPackage.entry]) {
25
40
  addIssue(issues, 'entry-not-found', `入口文件不存在: ${taskPackage.entry}`);
26
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"node-task-validator.js","sourceRoot":"","sources":["../../../src/automation/node-task-validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node-task-validator.js","sourceRoot":"","sources":["../../../src/automation/node-task-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Python 基础能力库源码。
3
+ * 运行时会将 __AGENT_ENDPOINT__ 替换为真实的 A2A JSON-RPC 端点 URL。
4
+ */
5
+ export declare const PYTHON_PRELUDE_SOURCE = "# -*- coding: utf-8 -*-\n\"\"\"\nSandbox Prelude for Python automation tasks.\nAuto-imported as `sb` in every task script.\nMirrors the Node.js global.sb capabilities.\n\"\"\"\nimport os\nimport sys\nimport json\nimport csv\nimport io\nimport time\nimport datetime as _dt\nimport urllib.request\nimport urllib.error\nimport urllib.parse\nfrom typing import Any, Dict, List, Optional\n\n# Agent \u8C03\u7528\u7AEF\u70B9\uFF08\u7531\u8FD0\u884C\u65F6\u6CE8\u5165\uFF09\n_AGENT_ENDPOINT = \"__AGENT_ENDPOINT__\"\n_AGENT_CONFIGURED = _AGENT_ENDPOINT != \"__AGENT_ENDPOINT__\"\n\n\nclass _Logger:\n def __init__(self, sink):\n self._sink = sink\n\n def debug(self, message, meta=None):\n self._sink(\"debug\", message, meta)\n\n def info(self, message, meta=None):\n self._sink(\"info\", message, meta)\n\n def warn(self, message, meta=None):\n self._sink(\"warn\", message, meta)\n\n def error(self, message, meta=None):\n self._sink(\"error\", message, meta)\n\n\n# ===== HTTP =====\n\ndef fetch(url, options=None, timeout=30):\n \"\"\"HTTP \u8BF7\u6C42\uFF0C\u8FD4\u56DE {status, headers, data, text}\u3002\"\"\"\n options = options or {}\n method = options.get(\"method\", \"GET\").upper()\n headers = dict(options.get(\"headers\") or {})\n headers.setdefault(\"User-Agent\", \"workai-sandbox/1.0\")\n params = options.get(\"params\")\n if params:\n query = urllib.parse.urlencode(params)\n url = url + (\"&\" if \"?\" in url else \"?\") + query\n body = options.get(\"body\")\n json_body = options.get(\"json\")\n data = None\n if json_body is not None:\n data = json.dumps(json_body).encode(\"utf-8\")\n headers.setdefault(\"Content-Type\", \"application/json\")\n elif body is not None:\n if isinstance(body, str):\n data = body.encode(\"utf-8\")\n elif isinstance(body, (bytes, bytearray)):\n data = bytes(body)\n else:\n data = json.dumps(body).encode(\"utf-8\")\n headers.setdefault(\"Content-Type\", \"application/json\")\n req = urllib.request.Request(url, data=data, headers=headers, method=method)\n try:\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n status = resp.status\n resp_headers = dict(resp.headers)\n text = resp.read().decode(\"utf-8\", errors=\"replace\")\n except urllib.error.HTTPError as e:\n status = e.code\n resp_headers = dict(e.headers) if e.headers else {}\n text = e.read().decode(\"utf-8\", errors=\"replace\") if e.fp else \"\"\n parsed = text\n ct = (resp_headers.get(\"Content-Type\") or \"\").lower()\n if \"application/json\" in ct or \"+json\" in ct:\n try:\n parsed = json.loads(text)\n except Exception:\n pass\n return {\"status\": status, \"headers\": resp_headers, \"data\": parsed, \"text\": text}\n\n\ndef get(url, options=None, timeout=30):\n return fetch(url, dict(options or {}, method=\"GET\"), timeout)\n\n\ndef post(url, body=None, options=None, timeout=30):\n opts = dict(options or {}, method=\"POST\")\n if isinstance(body, (dict, list)):\n opts[\"json\"] = body\n else:\n opts[\"body\"] = body\n return fetch(url, opts, timeout)\n\n\n# ===== File I/O =====\n\ndef read_text(file_path, encoding=\"utf-8\"):\n with open(file_path, \"r\", encoding=encoding) as f:\n return f.read()\n\n\ndef write_text(file_path, content, encoding=\"utf-8\"):\n _ensure_parent(file_path)\n with open(file_path, \"w\", encoding=encoding) as f:\n f.write(content)\n\n\ndef append_text(file_path, content, encoding=\"utf-8\"):\n _ensure_parent(file_path)\n with open(file_path, \"a\", encoding=encoding) as f:\n f.write(content)\n\n\ndef read_json(file_path):\n with open(file_path, \"r\", encoding=\"utf-8\") as f:\n return json.load(f)\n\n\ndef write_json(file_path, data, indent=2):\n _ensure_parent(file_path)\n with open(file_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(data, f, ensure_ascii=False, indent=indent)\n\n\ndef read_csv(file_path):\n with open(file_path, \"r\", encoding=\"utf-8\", newline=\"\") as f:\n return list(csv.DictReader(f))\n\n\ndef write_csv(file_path, rows):\n _ensure_parent(file_path)\n if not rows:\n with open(file_path, \"w\", encoding=\"utf-8\", newline=\"\") as f:\n f.write(\"\")\n return\n with open(file_path, \"w\", encoding=\"utf-8\", newline=\"\") as f:\n writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))\n writer.writeheader()\n writer.writerows(rows)\n\n\ndef exists(file_path):\n return os.path.exists(file_path)\n\n\ndef list_dir(dir_path=\".\"):\n return os.listdir(dir_path)\n\n\ndef _ensure_parent(file_path):\n parent = os.path.dirname(os.path.abspath(file_path))\n os.makedirs(parent, exist_ok=True)\n\n\n# ===== Data helpers =====\n\ndef stats(values):\n values = [v for v in values if v is not None]\n n = len(values)\n if n == 0:\n return {\"count\": 0, \"sum\": 0, \"avg\": 0, \"min\": None, \"max\": None}\n s = sum(values)\n return {\"count\": n, \"sum\": s, \"avg\": s / n, \"min\": min(values), \"max\": max(values)}\n\n\ndef sum_of(values):\n return sum(v for v in values if v is not None)\n\n\ndef avg(values):\n vals = [v for v in values if v is not None]\n return sum(vals) / len(vals) if vals else 0\n\n\ndef group_by(rows, key):\n result = {}\n for row in rows:\n k = row.get(key) if isinstance(row, dict) else getattr(row, key, None)\n result.setdefault(k, []).append(row)\n return result\n\n\ndef sort_by(rows, key, reverse=False):\n return sorted(rows, key=lambda r: (r.get(key) if isinstance(r, dict) else getattr(r, key, None)), reverse=reverse)\n\n\ndef unique(values):\n seen = []\n for v in values:\n if v not in seen:\n seen.append(v)\n return seen\n\n\ndef chunk(values, size):\n return [values[i:i + size] for i in range(0, len(values), size)]\n\n\n# ===== Date helpers =====\n\ndef now():\n return _dt.datetime.now()\n\n\ndef format_date(dt, fmt=\"%Y-%m-%d %H:%M:%S\"):\n if isinstance(dt, (int, float)):\n dt = _dt.datetime.fromtimestamp(dt)\n if isinstance(dt, str):\n dt = _dt.datetime.fromisoformat(dt)\n return dt.strftime(fmt)\n\n\ndef parse_date(text, fmt=None):\n if fmt:\n return _dt.datetime.strptime(text, fmt)\n return _dt.datetime.fromisoformat(text)\n\n\ndef days_go(n):\n return _dt.datetime.now() - _dt.timedelta(days=n)\n\n\ndef days_later(n):\n return _dt.datetime.now() + _dt.timedelta(days=n)\n\n\n# ===== Agent =====\n\nclass _Agent:\n \"\"\"\u8C03\u7528\u5E73\u53F0 Agent \u80FD\u529B\uFF08\u57FA\u4E8E A2A JSON-RPC\uFF09\u3002\"\"\"\n\n def ask(self, prompt, options=None):\n \"\"\"\u5411 Agent \u53D1\u9001\u63D0\u793A\u8BCD\u5E76\u540C\u6B65\u7B49\u5F85\u6587\u672C\u56DE\u590D\u3002\"\"\"\n if not _AGENT_CONFIGURED:\n raise RuntimeError(\"Agent endpoint not configured\")\n options = options or {}\n payload = {\n \"jsonrpc\": \"2.0\",\n \"id\": options.get(\"id\", 1),\n \"method\": \"tasks/send\",\n \"params\": {\n \"message\": {\n \"role\": \"user\",\n \"parts\": [{\"type\": \"text\", \"text\": str(prompt)}],\n },\n \"contextId\": options.get(\"contextId\"),\n \"profile\": options.get(\"profile\"),\n },\n }\n resp = fetch(\n _AGENT_ENDPOINT,\n {\"method\": \"POST\", \"json\": payload, \"headers\": {\"Accept\": \"application/json\"}},\n timeout=options.get(\"timeout\", 120),\n )\n if resp[\"status\"] >= 400:\n raise RuntimeError(\"Agent call failed (HTTP %s): %s\" % (resp[\"status\"], resp[\"text\"][:200]))\n body = resp[\"data\"] if isinstance(resp[\"data\"], dict) else {}\n if \"error\" in body:\n raise RuntimeError(\"Agent error: %s\" % body[\"error\"])\n task = (body.get(\"result\") or {}).get(\"task\") or {}\n state = task.get(\"state\")\n if state == \"failed\":\n raise RuntimeError(\"Agent task failed: %s\" % task.get(\"error\", \"unknown\"))\n parts = (task.get(\"result\") or {}).get(\"parts\") or []\n texts = [p.get(\"text\", \"\") for p in parts if p.get(\"kind\") == \"text\" or p.get(\"type\") == \"text\"]\n return \"\".join(texts)\n\n\nagent = _Agent()\n";
6
+ //# sourceMappingURL=python-prelude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-prelude.d.ts","sourceRoot":"","sources":["../../../src/automation/python-prelude.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,eAAO,MAAM,qBAAqB,mgRAkRjC,CAAC"}
@@ -0,0 +1,287 @@
1
+ // ============================================================
2
+ // Python Automation Prelude - Python 自动化任务基础能力库
3
+ // ============================================================
4
+ // 在沙箱初始化时写入 _scripts/_sb.py,由 Python 引导脚本 import。
5
+ // 提供与 Node 端 global.sb 对等的能力:HTTP / 文件 / 数据 / 日期 / 日志 / Agent 调用。
6
+ // Agent 调用端点由运行时通过 __AGENT_ENDPOINT__ 占位符注入。
7
+ // ============================================================
8
+ /**
9
+ * Python 基础能力库源码。
10
+ * 运行时会将 __AGENT_ENDPOINT__ 替换为真实的 A2A JSON-RPC 端点 URL。
11
+ */
12
+ export const PYTHON_PRELUDE_SOURCE = `# -*- coding: utf-8 -*-
13
+ """
14
+ Sandbox Prelude for Python automation tasks.
15
+ Auto-imported as \`sb\` in every task script.
16
+ Mirrors the Node.js global.sb capabilities.
17
+ """
18
+ import os
19
+ import sys
20
+ import json
21
+ import csv
22
+ import io
23
+ import time
24
+ import datetime as _dt
25
+ import urllib.request
26
+ import urllib.error
27
+ import urllib.parse
28
+ from typing import Any, Dict, List, Optional
29
+
30
+ # Agent 调用端点(由运行时注入)
31
+ _AGENT_ENDPOINT = "__AGENT_ENDPOINT__"
32
+ _AGENT_CONFIGURED = _AGENT_ENDPOINT != "__AGENT_ENDPOINT__"
33
+
34
+
35
+ class _Logger:
36
+ def __init__(self, sink):
37
+ self._sink = sink
38
+
39
+ def debug(self, message, meta=None):
40
+ self._sink("debug", message, meta)
41
+
42
+ def info(self, message, meta=None):
43
+ self._sink("info", message, meta)
44
+
45
+ def warn(self, message, meta=None):
46
+ self._sink("warn", message, meta)
47
+
48
+ def error(self, message, meta=None):
49
+ self._sink("error", message, meta)
50
+
51
+
52
+ # ===== HTTP =====
53
+
54
+ def fetch(url, options=None, timeout=30):
55
+ """HTTP 请求,返回 {status, headers, data, text}。"""
56
+ options = options or {}
57
+ method = options.get("method", "GET").upper()
58
+ headers = dict(options.get("headers") or {})
59
+ headers.setdefault("User-Agent", "workai-sandbox/1.0")
60
+ params = options.get("params")
61
+ if params:
62
+ query = urllib.parse.urlencode(params)
63
+ url = url + ("&" if "?" in url else "?") + query
64
+ body = options.get("body")
65
+ json_body = options.get("json")
66
+ data = None
67
+ if json_body is not None:
68
+ data = json.dumps(json_body).encode("utf-8")
69
+ headers.setdefault("Content-Type", "application/json")
70
+ elif body is not None:
71
+ if isinstance(body, str):
72
+ data = body.encode("utf-8")
73
+ elif isinstance(body, (bytes, bytearray)):
74
+ data = bytes(body)
75
+ else:
76
+ data = json.dumps(body).encode("utf-8")
77
+ headers.setdefault("Content-Type", "application/json")
78
+ req = urllib.request.Request(url, data=data, headers=headers, method=method)
79
+ try:
80
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
81
+ status = resp.status
82
+ resp_headers = dict(resp.headers)
83
+ text = resp.read().decode("utf-8", errors="replace")
84
+ except urllib.error.HTTPError as e:
85
+ status = e.code
86
+ resp_headers = dict(e.headers) if e.headers else {}
87
+ text = e.read().decode("utf-8", errors="replace") if e.fp else ""
88
+ parsed = text
89
+ ct = (resp_headers.get("Content-Type") or "").lower()
90
+ if "application/json" in ct or "+json" in ct:
91
+ try:
92
+ parsed = json.loads(text)
93
+ except Exception:
94
+ pass
95
+ return {"status": status, "headers": resp_headers, "data": parsed, "text": text}
96
+
97
+
98
+ def get(url, options=None, timeout=30):
99
+ return fetch(url, dict(options or {}, method="GET"), timeout)
100
+
101
+
102
+ def post(url, body=None, options=None, timeout=30):
103
+ opts = dict(options or {}, method="POST")
104
+ if isinstance(body, (dict, list)):
105
+ opts["json"] = body
106
+ else:
107
+ opts["body"] = body
108
+ return fetch(url, opts, timeout)
109
+
110
+
111
+ # ===== File I/O =====
112
+
113
+ def read_text(file_path, encoding="utf-8"):
114
+ with open(file_path, "r", encoding=encoding) as f:
115
+ return f.read()
116
+
117
+
118
+ def write_text(file_path, content, encoding="utf-8"):
119
+ _ensure_parent(file_path)
120
+ with open(file_path, "w", encoding=encoding) as f:
121
+ f.write(content)
122
+
123
+
124
+ def append_text(file_path, content, encoding="utf-8"):
125
+ _ensure_parent(file_path)
126
+ with open(file_path, "a", encoding=encoding) as f:
127
+ f.write(content)
128
+
129
+
130
+ def read_json(file_path):
131
+ with open(file_path, "r", encoding="utf-8") as f:
132
+ return json.load(f)
133
+
134
+
135
+ def write_json(file_path, data, indent=2):
136
+ _ensure_parent(file_path)
137
+ with open(file_path, "w", encoding="utf-8") as f:
138
+ json.dump(data, f, ensure_ascii=False, indent=indent)
139
+
140
+
141
+ def read_csv(file_path):
142
+ with open(file_path, "r", encoding="utf-8", newline="") as f:
143
+ return list(csv.DictReader(f))
144
+
145
+
146
+ def write_csv(file_path, rows):
147
+ _ensure_parent(file_path)
148
+ if not rows:
149
+ with open(file_path, "w", encoding="utf-8", newline="") as f:
150
+ f.write("")
151
+ return
152
+ with open(file_path, "w", encoding="utf-8", newline="") as f:
153
+ writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
154
+ writer.writeheader()
155
+ writer.writerows(rows)
156
+
157
+
158
+ def exists(file_path):
159
+ return os.path.exists(file_path)
160
+
161
+
162
+ def list_dir(dir_path="."):
163
+ return os.listdir(dir_path)
164
+
165
+
166
+ def _ensure_parent(file_path):
167
+ parent = os.path.dirname(os.path.abspath(file_path))
168
+ os.makedirs(parent, exist_ok=True)
169
+
170
+
171
+ # ===== Data helpers =====
172
+
173
+ def stats(values):
174
+ values = [v for v in values if v is not None]
175
+ n = len(values)
176
+ if n == 0:
177
+ return {"count": 0, "sum": 0, "avg": 0, "min": None, "max": None}
178
+ s = sum(values)
179
+ return {"count": n, "sum": s, "avg": s / n, "min": min(values), "max": max(values)}
180
+
181
+
182
+ def sum_of(values):
183
+ return sum(v for v in values if v is not None)
184
+
185
+
186
+ def avg(values):
187
+ vals = [v for v in values if v is not None]
188
+ return sum(vals) / len(vals) if vals else 0
189
+
190
+
191
+ def group_by(rows, key):
192
+ result = {}
193
+ for row in rows:
194
+ k = row.get(key) if isinstance(row, dict) else getattr(row, key, None)
195
+ result.setdefault(k, []).append(row)
196
+ return result
197
+
198
+
199
+ def sort_by(rows, key, reverse=False):
200
+ return sorted(rows, key=lambda r: (r.get(key) if isinstance(r, dict) else getattr(r, key, None)), reverse=reverse)
201
+
202
+
203
+ def unique(values):
204
+ seen = []
205
+ for v in values:
206
+ if v not in seen:
207
+ seen.append(v)
208
+ return seen
209
+
210
+
211
+ def chunk(values, size):
212
+ return [values[i:i + size] for i in range(0, len(values), size)]
213
+
214
+
215
+ # ===== Date helpers =====
216
+
217
+ def now():
218
+ return _dt.datetime.now()
219
+
220
+
221
+ def format_date(dt, fmt="%Y-%m-%d %H:%M:%S"):
222
+ if isinstance(dt, (int, float)):
223
+ dt = _dt.datetime.fromtimestamp(dt)
224
+ if isinstance(dt, str):
225
+ dt = _dt.datetime.fromisoformat(dt)
226
+ return dt.strftime(fmt)
227
+
228
+
229
+ def parse_date(text, fmt=None):
230
+ if fmt:
231
+ return _dt.datetime.strptime(text, fmt)
232
+ return _dt.datetime.fromisoformat(text)
233
+
234
+
235
+ def days_go(n):
236
+ return _dt.datetime.now() - _dt.timedelta(days=n)
237
+
238
+
239
+ def days_later(n):
240
+ return _dt.datetime.now() + _dt.timedelta(days=n)
241
+
242
+
243
+ # ===== Agent =====
244
+
245
+ class _Agent:
246
+ """调用平台 Agent 能力(基于 A2A JSON-RPC)。"""
247
+
248
+ def ask(self, prompt, options=None):
249
+ """向 Agent 发送提示词并同步等待文本回复。"""
250
+ if not _AGENT_CONFIGURED:
251
+ raise RuntimeError("Agent endpoint not configured")
252
+ options = options or {}
253
+ payload = {
254
+ "jsonrpc": "2.0",
255
+ "id": options.get("id", 1),
256
+ "method": "tasks/send",
257
+ "params": {
258
+ "message": {
259
+ "role": "user",
260
+ "parts": [{"type": "text", "text": str(prompt)}],
261
+ },
262
+ "contextId": options.get("contextId"),
263
+ "profile": options.get("profile"),
264
+ },
265
+ }
266
+ resp = fetch(
267
+ _AGENT_ENDPOINT,
268
+ {"method": "POST", "json": payload, "headers": {"Accept": "application/json"}},
269
+ timeout=options.get("timeout", 120),
270
+ )
271
+ if resp["status"] >= 400:
272
+ raise RuntimeError("Agent call failed (HTTP %s): %s" % (resp["status"], resp["text"][:200]))
273
+ body = resp["data"] if isinstance(resp["data"], dict) else {}
274
+ if "error" in body:
275
+ raise RuntimeError("Agent error: %s" % body["error"])
276
+ task = (body.get("result") or {}).get("task") or {}
277
+ state = task.get("state")
278
+ if state == "failed":
279
+ raise RuntimeError("Agent task failed: %s" % task.get("error", "unknown"))
280
+ parts = (task.get("result") or {}).get("parts") or []
281
+ texts = [p.get("text", "") for p in parts if p.get("kind") == "text" or p.get("type") == "text"]
282
+ return "".join(texts)
283
+
284
+
285
+ agent = _Agent()
286
+ `;
287
+ //# sourceMappingURL=python-prelude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-prelude.js","sourceRoot":"","sources":["../../../src/automation/python-prelude.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,gDAAgD;AAChD,+DAA+D;AAC/D,kDAAkD;AAClD,kEAAkE;AAClE,6CAA6C;AAC7C,+DAA+D;AAE/D;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkRpC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { NodeAutomationExecutionResult, NodeAutomationRunOptions, NodeAutomationTaskPackage } from '@cicctencent/agent-core';
2
+ export interface PythonAutomationRuntimeOptions {
3
+ baseDir?: string;
4
+ }
5
+ export declare class PythonAutomationRuntimeRunner {
6
+ private readonly baseDir;
7
+ constructor(options?: PythonAutomationRuntimeOptions);
8
+ execute(taskPackage: NodeAutomationTaskPackage, options: NodeAutomationRunOptions): Promise<NodeAutomationExecutionResult>;
9
+ /** 剥离 stdout 中的内部结果/错误标记行,保留用户 print 输出 */
10
+ private stripMarkerLines;
11
+ private buildEnv;
12
+ private buildBootstrapScript;
13
+ private parseRuntimeResult;
14
+ private parseRuntimeError;
15
+ }
16
+ export declare function getPythonAutomationRuntimeRunner(): PythonAutomationRuntimeRunner;
17
+ //# sourceMappingURL=python-task-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-task-runtime.d.ts","sourceRoot":"","sources":["../../../src/automation/python-task-runtime.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,6BAA6B,EAE7B,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,yBAAyB,CAAC;AAMjC,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAYD,qBAAa,6BAA6B;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,GAAE,8BAAmC;IAIlD,OAAO,CACX,WAAW,EAAE,yBAAyB,EACtC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,6BAA6B,CAAC;IAmEzC,2CAA2C;IAC3C,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,QAAQ;IAahB,OAAO,CAAC,oBAAoB;IAoF5B,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,iBAAiB;CAU1B;AAID,wBAAgB,gCAAgC,IAAI,6BAA6B,CAGhF"}
@@ -0,0 +1,206 @@
1
+ // ============================================================
2
+ // Python Automation Runtime - Python 自动化任务代码容器
3
+ // ============================================================
4
+ // 与 NodeAutomationRuntimeRunner 对等:构建 Python 引导脚本、注入 _sb.py
5
+ // 基础能力库(含 agent.ask)、执行并解析结果。
6
+ // ============================================================
7
+ import * as path from 'path';
8
+ import * as fs from 'fs/promises';
9
+ import { ProcessCodeSandbox } from "../sandbox/process-code-sandbox.js";
10
+ import { getProjectDataPath } from "../utils/data-dir.js";
11
+ import { PYTHON_PRELUDE_SOURCE } from "./python-prelude.js";
12
+ import { validateNodeAutomationTaskPackage } from "./node-task-validator.js";
13
+ export class PythonAutomationRuntimeRunner {
14
+ baseDir;
15
+ constructor(options = {}) {
16
+ this.baseDir = options.baseDir || getProjectDataPath('automation-code-runs');
17
+ }
18
+ async execute(taskPackage, options) {
19
+ const validation = validateNodeAutomationTaskPackage(taskPackage);
20
+ if (!validation.valid) {
21
+ return {
22
+ stdout: '',
23
+ stderr: validation.issues.map(issue => `[${issue.code}] ${issue.message}`).join('\n'),
24
+ exitCode: 1,
25
+ duration: 0,
26
+ files: [],
27
+ logs: [],
28
+ };
29
+ }
30
+ const runId = options.runId || Date.now();
31
+ const sandboxId = `automation_${options.taskId}_${runId}`;
32
+ const sandbox = new ProcessCodeSandbox(sandboxId, {
33
+ rootDir: path.join(this.baseDir, sandboxId),
34
+ commandRestriction: true,
35
+ });
36
+ await sandbox.init();
37
+ // 写入 Python 基础能力库 _sb.py(注入真实 agent 端点)
38
+ const agentEndpoint = options.agentEndpoint || process.env.A2A_BASE_URL || '';
39
+ const preludeSource = PYTHON_PRELUDE_SOURCE.replace(/__AGENT_ENDPOINT__/g, agentEndpoint);
40
+ const scriptsDir = path.join(sandbox.rootDir, '_scripts');
41
+ await fs.writeFile(path.join(scriptsDir, '_sb.py'), preludeSource, 'utf-8');
42
+ const logs = [];
43
+ const payload = {
44
+ taskPackage,
45
+ inputs: options.inputs || {},
46
+ env: this.buildEnv(taskPackage, options.env || {}),
47
+ taskId: options.taskId,
48
+ runId,
49
+ workspace: options.workspace,
50
+ agentEndpoint,
51
+ };
52
+ const script = this.buildBootstrapScript(payload);
53
+ const result = await sandbox.execute(script, 'python', options.timeoutMs || taskPackage.runtimeConfig?.timeoutMs || 120000);
54
+ const parsed = this.parseRuntimeResult(result.stdout);
55
+ // 检测错误标记(脚本抛异常时会输出 __NODE_AUTOMATION_ERROR__)
56
+ const errorInfo = this.parseRuntimeError(result.stdout);
57
+ const exitCode = errorInfo ? 1 : result.exitCode;
58
+ const stderr = errorInfo
59
+ ? errorInfo.message + (result.stderr ? '\n' + result.stderr : '')
60
+ : result.stderr;
61
+ // 剥离内部结果/错误标记行,避免展示给用户
62
+ const cleanStdout = this.stripMarkerLines(result.stdout);
63
+ return {
64
+ stdout: cleanStdout,
65
+ stderr,
66
+ exitCode,
67
+ duration: result.duration,
68
+ files: result.files,
69
+ logs: [...logs, ...parsed.logs, ...(errorInfo?.logs || [])],
70
+ result: parsed.result,
71
+ artifacts: result.files,
72
+ };
73
+ }
74
+ /** 剥离 stdout 中的内部结果/错误标记行,保留用户 print 输出 */
75
+ stripMarkerLines(stdout) {
76
+ return stdout
77
+ .split('\n')
78
+ .filter(line => !line.startsWith('__NODE_AUTOMATION_RESULT__') && !line.startsWith('__NODE_AUTOMATION_ERROR__'))
79
+ .join('\n')
80
+ .replace(/\n{3,}/g, '\n\n')
81
+ .trim();
82
+ }
83
+ buildEnv(taskPackage, env) {
84
+ const allowed = new Set(taskPackage.runtimeConfig?.envKeys || []);
85
+ const safeEnv = {};
86
+ for (const key of allowed) {
87
+ if (env[key])
88
+ safeEnv[key] = env[key];
89
+ }
90
+ safeEnv.PYTHONUNBUFFERED = '1';
91
+ return safeEnv;
92
+ }
93
+ buildBootstrapScript(payload) {
94
+ // 将 payload 序列化为 JSON 字符串,在 Python 端用 json.loads 解析,
95
+ // 避免 JSON 的 null/true/false 与 Python 的 None/True/False 语法不兼容。
96
+ const payloadJson = JSON.stringify(JSON.stringify({
97
+ task: payload.taskPackage,
98
+ inputs: payload.inputs,
99
+ env: payload.env,
100
+ taskId: payload.taskId,
101
+ runId: payload.runId ?? null,
102
+ workspace: payload.workspace ?? null,
103
+ agentEndpoint: payload.agentEndpoint,
104
+ }));
105
+ return `# -*- coding: utf-8 -*-
106
+ import os
107
+ import sys
108
+ import json
109
+ import time
110
+ import traceback
111
+ import builtins
112
+
113
+ # 让入口模块(位于沙箱根目录)与 _scripts(_sb.py 所在)都可被 import
114
+ sys.path.insert(0, os.getcwd())
115
+ sys.path.insert(0, os.path.join(os.getcwd(), '_scripts'))
116
+
117
+ # 导入基础能力库(与引导脚本同在 _scripts/ 目录)
118
+ import _sb as sb
119
+
120
+ # 将 sb 注入 builtins,使所有用户模块均可直接使用 sb.xxx(与 Node 端 global.sb 一致)
121
+ builtins.sb = sb
122
+
123
+ # 解析运行时 payload(JSON 字符串 -> Python 字典)
124
+ __data = json.loads(${payloadJson})
125
+ __task = __data["task"]
126
+
127
+ __logs = []
128
+ def __log(level, message, meta=None):
129
+ __logs.append({"level": level, "message": str(message), "meta": meta, "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S")})
130
+
131
+ # 把用户源码文件写入工作目录
132
+ for __file, __content in (__task.get("sourceFiles") or {}).items():
133
+ __target = os.path.join(os.getcwd(), __file)
134
+ __rel = os.path.relpath(__target, os.getcwd())
135
+ if __rel.startswith("..") or os.path.isabs(__rel):
136
+ raise RuntimeError("Unsafe source path: " + __file)
137
+ os.makedirs(os.path.dirname(__target) or ".", exist_ok=True)
138
+ with open(__target, "w", encoding="utf-8") as __f:
139
+ __f.write(__content)
140
+
141
+ # 构造 context(与 Node 端结构对齐)
142
+ __context = {
143
+ "taskId": __data["taskId"],
144
+ "runId": __data["runId"],
145
+ "inputs": __data["inputs"],
146
+ "env": __data["env"],
147
+ "workspace": __data["workspace"],
148
+ "logger": sb._Logger(__log),
149
+ "artifacts": type("A", (), {
150
+ "writeText": staticmethod(lambda f, c: sb.write_text(f, str(c))),
151
+ "writeJson": staticmethod(lambda f, d: sb.write_json(f, d)),
152
+ })(),
153
+ "agent": sb.agent,
154
+ }
155
+
156
+ def __run():
157
+ __entry = __task["entry"]
158
+ __mod_name = os.path.splitext(__entry)[0].replace(os.sep, ".")
159
+ import importlib
160
+ __mod = importlib.import_module(__mod_name)
161
+ if not hasattr(__mod, "run"):
162
+ raise RuntimeError("Python automation entry must define run(context)")
163
+ return __mod.run(__context)
164
+
165
+ try:
166
+ __result = __run()
167
+ print("__NODE_AUTOMATION_RESULT__" + json.dumps({"result": __result, "logs": __logs}, ensure_ascii=False, default=str))
168
+ except Exception as e:
169
+ __log("error", str(e))
170
+ traceback.print_exc()
171
+ print("__NODE_AUTOMATION_ERROR__" + json.dumps({"message": str(e), "logs": __logs}, ensure_ascii=False, default=str))
172
+ sys.exit(1)
173
+ `;
174
+ }
175
+ parseRuntimeResult(stdout) {
176
+ const resultLine = stdout.split('\n').find(line => line.startsWith('__NODE_AUTOMATION_RESULT__'));
177
+ if (!resultLine)
178
+ return { logs: [] };
179
+ try {
180
+ const parsed = JSON.parse(resultLine.replace('__NODE_AUTOMATION_RESULT__', ''));
181
+ return { result: parsed.result, logs: parsed.logs || [] };
182
+ }
183
+ catch {
184
+ return { logs: [] };
185
+ }
186
+ }
187
+ parseRuntimeError(stdout) {
188
+ const errorLine = stdout.split('\n').find(line => line.startsWith('__NODE_AUTOMATION_ERROR__'));
189
+ if (!errorLine)
190
+ return null;
191
+ try {
192
+ const parsed = JSON.parse(errorLine.replace('__NODE_AUTOMATION_ERROR__', ''));
193
+ return { message: parsed.message || 'Python automation execution failed', logs: parsed.logs || [] };
194
+ }
195
+ catch {
196
+ return { message: 'Python automation execution failed', logs: [] };
197
+ }
198
+ }
199
+ }
200
+ let defaultRunner = null;
201
+ export function getPythonAutomationRuntimeRunner() {
202
+ if (!defaultRunner)
203
+ defaultRunner = new PythonAutomationRuntimeRunner();
204
+ return defaultRunner;
205
+ }
206
+ //# sourceMappingURL=python-task-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-task-runtime.js","sourceRoot":"","sources":["../../../src/automation/python-task-runtime.ts"],"names":[],"mappings":"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"}
@@ -62,8 +62,8 @@ export { ProcessCodeSandbox } from './sandbox/process-code-sandbox.js';
62
62
  export { SANDBOX_PRELUDE_SOURCE } from './sandbox/sandbox-prelude.js';
63
63
  export { registerSandboxPreludeExtension, getSandboxPreludeExtensions, clearSandboxPreludeExtensions, } from './sandbox/sandbox-prelude.js';
64
64
  export type { SandboxPreludeExtension } from './sandbox/sandbox-prelude.js';
65
- export { NodeAutomationRuntimeRunner, getNodeAutomationRuntimeRunner, NodeAutomationLibraryRegistry, getNodeAutomationLibraryRegistry, validateNodeAutomationTaskPackage, } from './automation/index.js';
66
- export type { NodeAutomationRuntimeOptions } from './automation/index.js';
65
+ export { NodeAutomationRuntimeRunner, getNodeAutomationRuntimeRunner, NodeAutomationLibraryRegistry, getNodeAutomationLibraryRegistry, validateNodeAutomationTaskPackage, PythonAutomationRuntimeRunner, getPythonAutomationRuntimeRunner, PYTHON_PRELUDE_SOURCE, } from './automation/index.js';
66
+ export type { NodeAutomationRuntimeOptions, PythonAutomationRuntimeOptions } from './automation/index.js';
67
67
  export { ApiBridgeClient } from './mcp/api-bridge-client.js';
68
68
  export type { ApiBridgeExtra } from './mcp/api-bridge-client.js';
69
69
  export type { RequestBuilderFn, BuildRequestContext, FetchRequestOptions, AuthConfig, ResponseConfig } from './mcp/api-bridge-types.js';