@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.
|
|
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
|
|
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(
|
|
220
|
+
_json(request_task("POST", "/api/search", body={"query": args.query}, timeout=120))
|
|
168
221
|
elif args.cmd == "send":
|
|
169
|
-
if
|
|
170
|
-
|
|
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(
|
|
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
|
|