@acedatacloud/skills 2026.627.2 → 2026.628.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acedatacloud/skills",
3
- "version": "2026.627.2",
3
+ "version": "2026.628.1",
4
4
  "description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -27,7 +27,9 @@ Credentials are injected by the `personalwechat` BYOC connector:
27
27
  - `PERSONALWECHAT_API_TOKEN` — Wisdom `API_TOKEN`. Secret — never echo, print, or log it.
28
28
 
29
29
  The helper sends the token as `Authorization: Bearer ...`; it never puts the
30
- token in the URL.
30
+ token in the URL. For queued UI operations such as search and send, the helper
31
+ submits the task and waits for `/api/tasks/{id}` before printing the final
32
+ result.
31
33
 
32
34
  This is the user's **real personal WeChat account**. Read operations can run
33
35
  directly. Sending messages or files must be dry-run first, then performed only
@@ -135,7 +137,9 @@ Search drives the WeChat UI, so it may be slower than local DB history reads.
135
137
 
136
138
  ## Sending Messages — GATED
137
139
 
138
- `send` dry-runs by default. It never sends unless `--confirm` is present.
140
+ `send` dry-runs by default. It never sends unless `--confirm` is present, or
141
+ unless an AceDataCloud scheduled task pre-authorized this Skill and you use
142
+ `--unattended-confirm`.
139
143
 
140
144
  ```bash
141
145
  python3 $WX send "Alice" "今晚 8 点开会吗?"
@@ -152,11 +156,32 @@ python3 $WX send "Alice" "今晚 8 点开会吗?" --confirm
152
156
  Never add `--confirm` in the first attempt. Never infer consent from vague text.
153
157
  The user must clearly approve sending this exact message.
154
158
 
159
+ ### Scheduled-task unattended confirmation
160
+
161
+ When running inside an AceDataCloud scheduled task, the platform may pre-authorize
162
+ specific Skills for unattended execution. If all of these are true:
163
+
164
+ - `AICHAT_UNATTENDED_MODE=true`
165
+ - `AICHAT_ACTIVE_SKILL` is `personal-wechat` or `acedatacloud/personal-wechat`
166
+ - `AICHAT_ACTIVE_SKILL` appears in `AICHAT_UNATTENDED_ALLOWED_SKILLS`
167
+
168
+ then the user has pre-authorized this Skill for that scheduled task. In that
169
+ case, use:
170
+
171
+ ```bash
172
+ python3 $WX send "Alice" "今晚 8 点开会吗?" --unattended-confirm
173
+ ```
174
+
175
+ If the helper returns `unattended_confirmation_denied`, do not retry with
176
+ `--confirm`; report the dry-run and explain that the task needs this Skill to be
177
+ selected in its unattended authorization settings.
178
+
155
179
  ## Safety Rules
156
180
 
157
181
  - Never print `PERSONALWECHAT_API_TOKEN`.
158
182
  - Treat `PERSONALWECHAT_BASE_URL + API_TOKEN` as full remote control of the user's WeChat.
159
- - For any write/send operation: dry-run first, ask for explicit approval, then re-run with `--confirm`.
183
+ - For normal chat write/send operations: dry-run first, ask for explicit approval, then re-run with `--confirm`.
184
+ - For scheduled-task unattended writes: use `--unattended-confirm` only when the platform env says this Skill is pre-authorized.
160
185
  - Do not call logout/restart endpoints from the skill unless the user explicitly asks to repair the Wisdom service.
161
186
  - If Wisdom returns 503 for history, run `python3 $WX refresh-history` once, then retry the read.
162
187
  - If the server is unreachable, ask the user to check the Windows host / security group / port 8000.
@@ -176,4 +201,4 @@ The helper wraps these Wisdom endpoints:
176
201
  - `POST /api/messages/history/query`
177
202
  - `POST /api/messages/history/refresh`
178
203
  - `POST /api/search`
179
- - `POST /api/messages/send` (only after `--confirm`)
204
+ - `POST /api/messages/send` (only after `--confirm` or verified `--unattended-confirm`)
@@ -7,6 +7,7 @@ import argparse
7
7
  import json
8
8
  import os
9
9
  import sys
10
+ import time
10
11
  import urllib.error
11
12
  import urllib.parse
12
13
  import urllib.request
@@ -15,6 +16,7 @@ import urllib.request
15
16
  BASE_URL = os.environ.get("PERSONALWECHAT_BASE_URL", "").rstrip("/")
16
17
  API_TOKEN = os.environ.get("PERSONALWECHAT_API_TOKEN", "")
17
18
  MAX_TEXT_CHARS = 800
19
+ SKILL_SLUGS = {"personal-wechat", "acedatacloud/personal-wechat"}
18
20
 
19
21
 
20
22
  def _die(message: str, code: int = 1) -> None:
@@ -26,6 +28,34 @@ def _json(data) -> None:
26
28
  print(json.dumps(data, ensure_ascii=False, default=str))
27
29
 
28
30
 
31
+ def unattended_confirm_allowed() -> tuple[bool, str]:
32
+ if os.environ.get("AICHAT_UNATTENDED_MODE") != "true":
33
+ return False, "not running in AceDataCloud unattended scheduled-task mode"
34
+
35
+ active_skill = os.environ.get("AICHAT_ACTIVE_SKILL", "")
36
+ if active_skill not in SKILL_SLUGS:
37
+ return False, f"active skill {active_skill or '<empty>'!r} is not personal-wechat"
38
+
39
+ raw_allowed = os.environ.get("AICHAT_UNATTENDED_ALLOWED_SKILLS", "[]")
40
+ try:
41
+ allowed = json.loads(raw_allowed)
42
+ except json.JSONDecodeError:
43
+ return False, "AICHAT_UNATTENDED_ALLOWED_SKILLS is not valid JSON"
44
+ if not isinstance(allowed, list) or active_skill not in allowed:
45
+ return False, f"skill {active_skill!r} is not pre-authorized for unattended confirmation"
46
+
47
+ expires_raw = os.environ.get("AICHAT_UNATTENDED_EXPIRES_AT", "")
48
+ if expires_raw:
49
+ try:
50
+ expires_at = int(expires_raw)
51
+ except ValueError:
52
+ return False, "AICHAT_UNATTENDED_EXPIRES_AT is invalid"
53
+ if expires_at < int(time.time()):
54
+ return False, "unattended authorization has expired"
55
+
56
+ return True, "ok"
57
+
58
+
29
59
  def request(method: str, path: str, *, params: dict | None = None, body: dict | None = None):
30
60
  if not BASE_URL:
31
61
  _die("PERSONALWECHAT_BASE_URL is not set. Reconnect the Personal WeChat connector.")
@@ -63,6 +93,28 @@ def request(method: str, path: str, *, params: dict | None = None, body: dict |
63
93
  _die(f"Cannot reach Wisdom at {BASE_URL}: {reason}", code=3)
64
94
 
65
95
 
96
+ def wait_task(task_id: str, *, timeout: float = 600.0):
97
+ deadline = time.monotonic() + timeout
98
+ while time.monotonic() < deadline:
99
+ task = request("GET", f"/api/tasks/{task_id}")
100
+ status = task.get("status")
101
+ if status == "succeeded":
102
+ return task.get("result")
103
+ if status == "failed":
104
+ error = task.get("error") or {}
105
+ _die(f"Task failed: {error.get('message') or error}", code=2)
106
+ time.sleep(0.35)
107
+ _die(f"Task {task_id} did not finish within {timeout:g}s", code=2)
108
+
109
+
110
+ def request_task(method: str, path: str, *, params: dict | None = None, body: dict | None = None, timeout: float = 600.0):
111
+ task = request(method, path, params=params, body=body)
112
+ task_id = task.get("id")
113
+ if not task_id:
114
+ return task
115
+ return wait_task(task_id, timeout=timeout)
116
+
117
+
66
118
  def compact_conversation(item: dict) -> dict:
67
119
  return {
68
120
  "id": item.get("id") or item.get("strUsrName"),
@@ -127,6 +179,7 @@ def main() -> None:
127
179
  send.add_argument("target")
128
180
  send.add_argument("text")
129
181
  send.add_argument("--confirm", action="store_true")
182
+ send.add_argument("--unattended-confirm", action="store_true")
130
183
 
131
184
  refresh = sub.add_parser("refresh-history")
132
185
  refresh.set_defaults(cmd="refresh-history")
@@ -164,12 +217,17 @@ def main() -> None:
164
217
  data = request("POST", "/api/messages/history/query", body={"db": args.db, "sql": args.sql})
165
218
  _json(data)
166
219
  elif args.cmd == "search":
167
- _json(request("POST", "/api/search", body={"query": args.query}))
220
+ _json(request_task("POST", "/api/search", body={"query": args.query}, timeout=120))
168
221
  elif args.cmd == "send":
169
- if not args.confirm:
170
- _json({"dry_run": True, "target": args.target, "text": args.text, "note": "Re-run with --confirm only after the user explicitly approves sending this exact message."})
222
+ if args.unattended_confirm:
223
+ allowed, reason = unattended_confirm_allowed()
224
+ if not allowed:
225
+ _json({"dry_run": True, "target": args.target, "text": args.text, "error": "unattended_confirmation_denied", "reason": reason})
226
+ return
227
+ elif not args.confirm:
228
+ _json({"dry_run": True, "target": args.target, "text": args.text, "note": "Re-run with --confirm after explicit user approval, or --unattended-confirm when this Skill is pre-authorized for an AceDataCloud scheduled task."})
171
229
  return
172
- _json(request("POST", "/api/messages/send", body={"target": args.target, "type": "text", "text": args.text}))
230
+ _json(request_task("POST", "/api/messages/send", body={"target": args.target, "type": "text", "text": args.text}))
173
231
  elif args.cmd == "refresh-history":
174
232
  _json(request("POST", "/api/messages/history/refresh"))
175
233