@acedatacloud/skills 2026.628.0 → 2026.628.2

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.628.0",
3
+ "version": "2026.628.2",
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",
@@ -0,0 +1,16 @@
1
+ # Unattended Scheduled-Task Confirmation
2
+
3
+ Write-capable Skill helpers may support unattended scheduled-task execution with
4
+ a common `--unattended-confirm` flag. The flag must only bypass the normal
5
+ dry-run/confirmation flow when all platform-provided environment checks pass:
6
+
7
+ - `AICHAT_UNATTENDED_MODE=true`
8
+ - `AICHAT_ACTIVE_SKILL` matches the helper's Skill slug
9
+ - `AICHAT_ACTIVE_SKILL` is present in `AICHAT_UNATTENDED_ALLOWED_SKILLS`
10
+ - `AICHAT_UNATTENDED_EXPIRES_AT`, when present, is still in the future
11
+
12
+ If any check fails, the helper must return a dry-run style response and must not
13
+ perform the write/send/publish/delete action. The current Skills packaging does
14
+ not automatically bundle `_shared` files into each Skill, so helpers that need
15
+ runtime code should vendor a small guard module in their own `scripts/` folder
16
+ until packaging grows shared-runtime support.
@@ -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
@@ -7,11 +7,12 @@ import argparse
7
7
  import json
8
8
  import os
9
9
  import sys
10
- import time
11
10
  import urllib.error
12
11
  import urllib.parse
13
12
  import urllib.request
14
13
 
14
+ from unattended import unattended_confirm_allowed
15
+
15
16
 
16
17
  BASE_URL = os.environ.get("PERSONALWECHAT_BASE_URL", "").rstrip("/")
17
18
  API_TOKEN = os.environ.get("PERSONALWECHAT_API_TOKEN", "")
@@ -28,34 +29,6 @@ def _json(data) -> None:
28
29
  print(json.dumps(data, ensure_ascii=False, default=str))
29
30
 
30
31
 
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
-
59
32
  def request(method: str, path: str, *, params: dict | None = None, body: dict | None = None):
60
33
  if not BASE_URL:
61
34
  _die("PERSONALWECHAT_BASE_URL is not set. Reconnect the Personal WeChat connector.")
@@ -93,6 +66,28 @@ def request(method: str, path: str, *, params: dict | None = None, body: dict |
93
66
  _die(f"Cannot reach Wisdom at {BASE_URL}: {reason}", code=3)
94
67
 
95
68
 
69
+ def wait_task(task_id: str, *, timeout: float = 600.0):
70
+ deadline = time.monotonic() + timeout
71
+ while time.monotonic() < deadline:
72
+ task = request("GET", f"/api/tasks/{task_id}")
73
+ status = task.get("status")
74
+ if status == "succeeded":
75
+ return task.get("result")
76
+ if status == "failed":
77
+ error = task.get("error") or {}
78
+ _die(f"Task failed: {error.get('message') or error}", code=2)
79
+ time.sleep(0.35)
80
+ _die(f"Task {task_id} did not finish within {timeout:g}s", code=2)
81
+
82
+
83
+ def request_task(method: str, path: str, *, params: dict | None = None, body: dict | None = None, timeout: float = 600.0):
84
+ task = request(method, path, params=params, body=body)
85
+ task_id = task.get("id")
86
+ if not task_id:
87
+ return task
88
+ return wait_task(task_id, timeout=timeout)
89
+
90
+
96
91
  def compact_conversation(item: dict) -> dict:
97
92
  return {
98
93
  "id": item.get("id") or item.get("strUsrName"),
@@ -195,17 +190,17 @@ def main() -> None:
195
190
  data = request("POST", "/api/messages/history/query", body={"db": args.db, "sql": args.sql})
196
191
  _json(data)
197
192
  elif args.cmd == "search":
198
- _json(request("POST", "/api/search", body={"query": args.query}))
193
+ _json(request_task("POST", "/api/search", body={"query": args.query}, timeout=120))
199
194
  elif args.cmd == "send":
200
195
  if args.unattended_confirm:
201
- allowed, reason = unattended_confirm_allowed()
196
+ allowed, reason = unattended_confirm_allowed(SKILL_SLUGS)
202
197
  if not allowed:
203
198
  _json({"dry_run": True, "target": args.target, "text": args.text, "error": "unattended_confirmation_denied", "reason": reason})
204
199
  return
205
200
  elif not args.confirm:
206
201
  _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."})
207
202
  return
208
- _json(request("POST", "/api/messages/send", body={"target": args.target, "type": "text", "text": args.text}))
203
+ _json(request_task("POST", "/api/messages/send", body={"target": args.target, "type": "text", "text": args.text}))
209
204
  elif args.cmd == "refresh-history":
210
205
  _json(request("POST", "/api/messages/history/refresh"))
211
206
 
@@ -0,0 +1,39 @@
1
+ """Unattended scheduled-task confirmation guard bundled with this Skill."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import time
8
+ from collections.abc import Iterable
9
+
10
+
11
+ def unattended_confirm_allowed(skill_slugs: Iterable[str]) -> tuple[bool, str]:
12
+ """Return whether the active Skill is pre-authorized for unattended writes."""
13
+
14
+ if os.environ.get("AICHAT_UNATTENDED_MODE") != "true":
15
+ return False, "not running in AceDataCloud unattended scheduled-task mode"
16
+
17
+ active_skill = os.environ.get("AICHAT_ACTIVE_SKILL", "")
18
+ allowed_skill_slugs = set(skill_slugs)
19
+ if active_skill not in allowed_skill_slugs:
20
+ return False, f"active skill {active_skill or '<empty>'!r} is not one of {sorted(allowed_skill_slugs)!r}"
21
+
22
+ raw_allowed = os.environ.get("AICHAT_UNATTENDED_ALLOWED_SKILLS", "[]")
23
+ try:
24
+ allowed = json.loads(raw_allowed)
25
+ except json.JSONDecodeError:
26
+ return False, "AICHAT_UNATTENDED_ALLOWED_SKILLS is not valid JSON"
27
+ if not isinstance(allowed, list) or active_skill not in allowed:
28
+ return False, f"skill {active_skill!r} is not pre-authorized for unattended confirmation"
29
+
30
+ expires_raw = os.environ.get("AICHAT_UNATTENDED_EXPIRES_AT", "")
31
+ if expires_raw:
32
+ try:
33
+ expires_at = int(expires_raw)
34
+ except ValueError:
35
+ return False, "AICHAT_UNATTENDED_EXPIRES_AT is invalid"
36
+ if expires_at < int(time.time()):
37
+ return False, "unattended authorization has expired"
38
+
39
+ return True, "ok"