evolcore 0.0.31 → 0.0.33
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/CHANGELOG.md +35 -0
- package/bin/install-codex-managed-hooks.mjs +124 -37
- package/dist/agents/claude-runner.js +29 -2
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +76 -44
- package/dist/agents/codex-runner.js +51 -24
- package/dist/agents/ecagent-runner.js +9 -13
- package/dist/agents/gemini-runner.js +4 -0
- package/dist/aun/aid/store.js +0 -81
- package/dist/aun/group/fs-download.js +1 -10
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/managed-operation.js +130 -32
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/aun/service-proxy.js +12 -18
- package/dist/aun/storage/index.js +29 -0
- package/dist/channels/aun.js +38 -137
- package/dist/channels/daemon.js +257 -2
- package/dist/cli/agent-command.js +64 -31
- package/dist/cli/agent-create.js +43 -24
- package/dist/cli/agent.js +16 -70
- package/dist/cli/aun-commands.js +512 -32
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +1 -2
- package/dist/cli/daemon-commands.js +82 -13
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/diag.js +2 -2
- package/dist/cli/handoff-command.js +18 -5
- package/dist/cli/index.js +81 -64
- package/dist/cli/init.js +2 -2
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +93 -12
- package/dist/cli/model.js +14 -138
- package/dist/cli/queue-command.js +51 -0
- package/dist/cli/response.js +5 -54
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +85 -3
- package/dist/cli/stats.js +10 -4
- package/dist/cli/task-command.js +1 -0
- package/dist/cli/task-context.js +16 -0
- package/dist/cli/thread-command.js +620 -0
- package/dist/cli/trigger-command.js +76 -50
- package/dist/config/builtin-roles.js +9 -7
- package/dist/config/config-field-policy.js +2 -3
- package/dist/config/config-manager.js +7 -2
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +15 -0
- package/dist/core/agent-ipc-registration.js +65 -22
- package/dist/core/auth/agent-delegation.js +42 -2
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-authorization.js +1 -0
- package/dist/core/auth/operation-authorizer.js +24 -1
- package/dist/core/auth/operation-catalog.js +44 -3
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/command-handler.js +224 -45
- package/dist/core/command/diag-service.js +5 -1
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +48 -1
- package/dist/core/command/menu-handler.js +92 -16
- package/dist/core/command/menu-protocol.js +24 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +193 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/handoff/ipc-registration.js +38 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +210 -9
- package/dist/core/memory/consolidation-context.js +49 -16
- package/dist/core/memory/consolidation-ops-records.js +35 -0
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +174 -180
- package/dist/core/message/aun-ipc-sender.js +207 -8
- package/dist/core/message/im-renderer.js +149 -6
- package/dist/core/message/message-bridge.js +13 -3
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +131 -3
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +379 -69
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +126 -7
- package/dist/core/permission/sandbox-runtime.js +14 -1
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +54 -9
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +4 -1
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/task/task-recovery-service.js +2 -0
- package/dist/core/task/task-resume-operation.js +11 -0
- package/dist/core/thread/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +777 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +10 -7
- package/dist/ipc.js +234 -17
- package/dist/paths.js +23 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/trigger/authorization.js +59 -29
- package/dist/trigger/feedback.js +7 -1
- package/dist/trigger/ipc-service.js +49 -35
- package/dist/trigger/parser.js +63 -1
- package/dist/trigger/script-sandbox.js +58 -77
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/error-utils.js +22 -1
- package/kits/docs/AUTHORING.md +1 -1
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +39 -28
- package/kits/docs/evolcore/agent.md +19 -4
- package/kits/docs/evolcore/ec-capabilities.md +4 -0
- package/kits/docs/evolcore/group-collaboration.md +3 -5
- package/kits/docs/evolcore/group.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +1 -1
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/msg.md +5 -8
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/evolcore/trigger.md +2 -2
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/eck_manifest.json +61 -26
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +0 -9
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +7 -1
- package/kits/rules/07-memory-and-thread.md +202 -0
- package/kits/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +5 -0
- package/kits/templates/memory-consolidation/generalize.md +5 -0
- package/kits/templates/memory-consolidation/profile.md +5 -0
- package/kits/templates/memory-consolidation/promote.md +5 -0
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +66 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +2 -1
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/reply.md +3 -2
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +3 -1
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
wechat-article-media-download
|
|
4
|
+
Download all videos and images embedded in a WeChat official account article.
|
|
5
|
+
|
|
6
|
+
Works WITHOUT a WeChat client and WITHOUT mp.weixin.qq.com login:
|
|
7
|
+
video direct links (signed mpvideo.qpic.cn URLs) are extracted from the
|
|
8
|
+
inline JS data blocks of the article HTML itself.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python3 download.py <article_url> [output_dir]
|
|
12
|
+
|
|
13
|
+
Requires: Python 3.7+ (stdlib only)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import html as htmllib
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
import threading
|
|
22
|
+
import urllib.request
|
|
23
|
+
|
|
24
|
+
UA = (
|
|
25
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
|
26
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
|
|
27
|
+
)
|
|
28
|
+
HEADERS = {"User-Agent": UA, "Referer": "https://mp.weixin.qq.com/"}
|
|
29
|
+
|
|
30
|
+
# preferred video formats, best first (10002 = 超清 H.264)
|
|
31
|
+
FORMAT_PRIORITY = ["10002", "10102", "10004", "10104"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def fetch(url, dest=None):
|
|
35
|
+
req = urllib.request.Request(url, headers=HEADERS)
|
|
36
|
+
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
37
|
+
data = resp.read()
|
|
38
|
+
if dest:
|
|
39
|
+
with open(dest, "wb") as f:
|
|
40
|
+
f.write(data)
|
|
41
|
+
return data
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def unescape_js(html_text):
|
|
45
|
+
"""Undo the JS escaping used in article data blocks.
|
|
46
|
+
|
|
47
|
+
Video URLs are stored like `url: 'http://mpvideo.qpic.cn/...\\x26amp;dis_t=...'`.
|
|
48
|
+
Missing this step truncates every URL at its first `&`.
|
|
49
|
+
"""
|
|
50
|
+
txt = htmllib.unescape(html_text)
|
|
51
|
+
txt = txt.replace("\\x26amp;", "&").replace("\\x26;", "&").replace("\\x26", "&")
|
|
52
|
+
return txt
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def extract_images(html_text):
|
|
56
|
+
seen, out = set(), []
|
|
57
|
+
for u in re.findall(r'data-src="(https://mmbiz\.qpic\.cn/[^"]+)"', html_text):
|
|
58
|
+
if u not in seen:
|
|
59
|
+
seen.add(u)
|
|
60
|
+
fmt_m = re.search(r"wx_fmt=(\w+)", u)
|
|
61
|
+
out.append((u, fmt_m.group(1) if fmt_m else "png"))
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def extract_videos(html_text):
|
|
66
|
+
"""Return an ordered list of (best_format, url) — one entry per article video.
|
|
67
|
+
|
|
68
|
+
Each video appears as ~4 format variants sharing the same base path;
|
|
69
|
+
order of first appearance matches article order.
|
|
70
|
+
"""
|
|
71
|
+
txt = unescape_js(html_text)
|
|
72
|
+
blocks = re.findall(
|
|
73
|
+
r"format_id:\s*'(\d+)'\s*\*[^}]*?url:\s*'(http://mpvideo\.qpic\.cn/[^']+)'",
|
|
74
|
+
txt,
|
|
75
|
+
re.S,
|
|
76
|
+
)
|
|
77
|
+
groups, order = {}, []
|
|
78
|
+
for fid, url in blocks:
|
|
79
|
+
m = re.match(r"(http://mpvideo\.qpic\.cn/[a-z0-9]+)\.f\d+\.mp4", url)
|
|
80
|
+
if not m:
|
|
81
|
+
continue
|
|
82
|
+
base = m.group(1)
|
|
83
|
+
if base not in groups:
|
|
84
|
+
groups[base] = {}
|
|
85
|
+
order.append(base)
|
|
86
|
+
groups[base][fid] = url
|
|
87
|
+
plan = []
|
|
88
|
+
for base in order:
|
|
89
|
+
for fid in FORMAT_PRIORITY:
|
|
90
|
+
if fid in groups[base]:
|
|
91
|
+
plan.append((fid, groups[base][fid]))
|
|
92
|
+
break
|
|
93
|
+
return plan
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def download_all(items, outdir, prefix, ext_default="bin"):
|
|
97
|
+
"""items: list of (name_hint, url, ext). Returns list of (path, size)."""
|
|
98
|
+
os.makedirs(outdir, exist_ok=True)
|
|
99
|
+
results, lock = [], threading.Lock()
|
|
100
|
+
|
|
101
|
+
def work(i, url, ext):
|
|
102
|
+
path = os.path.join(outdir, f"{prefix}_{i:02d}.{ext}")
|
|
103
|
+
try:
|
|
104
|
+
fetch(url, path)
|
|
105
|
+
size = os.path.getsize(path)
|
|
106
|
+
except Exception as e: # noqa: BLE001
|
|
107
|
+
size = 0
|
|
108
|
+
with lock:
|
|
109
|
+
print(f" [FAIL] {path}: {e}")
|
|
110
|
+
with lock:
|
|
111
|
+
print(f" [OK] {path} ({size / 1024:.0f} KB)" if size else f" [FAIL] {path}")
|
|
112
|
+
results.append((path, size))
|
|
113
|
+
|
|
114
|
+
threads = [
|
|
115
|
+
threading.Thread(target=work, args=(i, url, ext))
|
|
116
|
+
for i, (url, ext) in enumerate(items, 1)
|
|
117
|
+
]
|
|
118
|
+
for t in threads:
|
|
119
|
+
t.start()
|
|
120
|
+
for t in threads:
|
|
121
|
+
t.join()
|
|
122
|
+
return sorted(results)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main():
|
|
126
|
+
if len(sys.argv) < 2:
|
|
127
|
+
print(__doc__)
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
article_url = sys.argv[1]
|
|
130
|
+
outdir = sys.argv[2] if len(sys.argv) > 2 else "output"
|
|
131
|
+
|
|
132
|
+
print(f"[1/4] Fetching article: {article_url}")
|
|
133
|
+
html_text = fetch(article_url).decode("utf-8", errors="ignore")
|
|
134
|
+
|
|
135
|
+
title_m = re.search(r'<meta property="og:title" content="([^"]*)"', html_text)
|
|
136
|
+
print(f" Title: {title_m.group(1) if title_m else 'N/A'}")
|
|
137
|
+
|
|
138
|
+
print("[2/4] Extracting media links ...")
|
|
139
|
+
images = extract_images(html_text)
|
|
140
|
+
videos = extract_videos(html_text)
|
|
141
|
+
print(f" Found {len(videos)} videos, {len(images)} images")
|
|
142
|
+
|
|
143
|
+
print(f"[3/4] Downloading to {outdir}/ ...")
|
|
144
|
+
vres = download_all([(u, "mp4") for _, u in videos], os.path.join(outdir, "videos"), "video")
|
|
145
|
+
ires = download_all([(u, e) for u, e in images], os.path.join(outdir, "images"), "image")
|
|
146
|
+
|
|
147
|
+
print("[4/4] Verification")
|
|
148
|
+
ok_v = sum(1 for _, s in vres if s > 100_000)
|
|
149
|
+
ok_i = sum(1 for _, s in ires if s > 0)
|
|
150
|
+
print(f" videos: {ok_v}/{len(vres)} valid (>100KB)")
|
|
151
|
+
print(f" images: {ok_i}/{len(ires)} valid")
|
|
152
|
+
|
|
153
|
+
json.dump(
|
|
154
|
+
{"article": article_url, "videos": [u for _, u in videos], "images": [u for u, _ in images]},
|
|
155
|
+
open(os.path.join(outdir, "sources.json"), "w"),
|
|
156
|
+
ensure_ascii=False,
|
|
157
|
+
indent=1,
|
|
158
|
+
)
|
|
159
|
+
print("Done. Link sources saved to sources.json")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
main()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wechat-article-reader
|
|
3
|
+
description: >-
|
|
4
|
+
从微信公众号文章链接提取正文、图片、视频,保存为 Markdown 和本地媒体文件。
|
|
5
|
+
当用户提供 mp.weixin.qq.com/s 格式的链接,并要求"阅读""保存""下载""提取正文"
|
|
6
|
+
"抓取文章"时使用。输出 Markdown 正文(图片/视频引用本地相对路径)、
|
|
7
|
+
下载好的图片/视频文件、统计信息。零外部依赖,只需 Node.js ≥22。
|
|
8
|
+
不适用于需要登录才能查看的付费文章、视频号来源的视频、已删除的文章。
|
|
9
|
+
license: MIT
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# 微信公众号文章阅读器
|
|
13
|
+
|
|
14
|
+
从公众号文章链接提取完整内容并保存到本地,包含正文 Markdown、图片、视频。
|
|
15
|
+
|
|
16
|
+
## 功能
|
|
17
|
+
|
|
18
|
+
- 提取文章标题、作者、发布时间、正文
|
|
19
|
+
- 下载所有图片(自动取原图)
|
|
20
|
+
- 下载所有视频(选最高清晰度)
|
|
21
|
+
- 输出 Markdown,图片/视频引用本地相对路径
|
|
22
|
+
- 生成统计信息(文字数、图片数、视频数、成功/失败数)
|
|
23
|
+
- 原始链接存档到 sources.json
|
|
24
|
+
|
|
25
|
+
## 依赖
|
|
26
|
+
|
|
27
|
+
- Node.js ≥ 22
|
|
28
|
+
- 零外部依赖(只用 Node 标准库)
|
|
29
|
+
|
|
30
|
+
## 用法
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node "<技能目录>/scripts/fetch.mjs" "<文章URL>" [输出目录]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**示例:**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
node "<技能目录>/scripts/fetch.mjs" \
|
|
40
|
+
"https://mp.weixin.qq.com/s/xxxxxx"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
默认输出到当前目录下的 `wechat-articles/日期_公众号_标题/`。
|
|
44
|
+
|
|
45
|
+
输出结构:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
wechat-articles/
|
|
49
|
+
└── 2026-10-08_机器之心_豆包大模型…/
|
|
50
|
+
├── article.md # 正文 Markdown
|
|
51
|
+
├── images/ # 图片文件
|
|
52
|
+
│ ├── cover.png
|
|
53
|
+
│ └── 001.jpg
|
|
54
|
+
├── videos/ # 视频文件
|
|
55
|
+
│ └── 001.mp4
|
|
56
|
+
└── sources.json # 原始链接存档
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 原理
|
|
60
|
+
|
|
61
|
+
### 图片
|
|
62
|
+
- 从 `data-src="https://mmbiz.qpic.cn/..."` 提取 URL
|
|
63
|
+
- URL 中的 `/640?` 改成 `/0?` 取原图
|
|
64
|
+
- 无需 Referer,直接下载
|
|
65
|
+
|
|
66
|
+
### 视频
|
|
67
|
+
- 从页面内嵌 JS 数据块提取签名直链
|
|
68
|
+
- 每个视频有多种清晰度(format_id):优先 10002(超清 H.264),
|
|
69
|
+
依次回退 10102 → 10004 → 10104
|
|
70
|
+
- 签名 URL 短期有效,必须在提取后立即下载
|
|
71
|
+
- 视频号(Channels)来源无直链,无法下载
|
|
72
|
+
|
|
73
|
+
### 正文
|
|
74
|
+
- 定位 `#js_content` 容器
|
|
75
|
+
- 转换为 Markdown:保留段落、加粗、图片、视频占位
|
|
76
|
+
- 图片/视频路径改写为本地相对路径
|
|
77
|
+
|
|
78
|
+
## 统计
|
|
79
|
+
|
|
80
|
+
文章末尾追加:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
---
|
|
84
|
+
📊 统计: 3120 字 | 图片 8/8 | 视频 5/9 (4 个失败: 403 签名失效)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
格式:正文字数 | 图片成功/总数 | 视频成功/总数 (失败原因)
|
|
88
|
+
|
|
89
|
+
## 边界与限制
|
|
90
|
+
|
|
91
|
+
- **视频限频**:微信 CDN 对同一出口 IP 短时间内重复请求同一视频会返回 403。
|
|
92
|
+
脚本已加入随机延迟和重试,但无法完全避免。失败的视频会在统计中明确标注。
|
|
93
|
+
- **视频号视频**:无签名直链,跳过并标注。
|
|
94
|
+
- **付费/仅粉丝文章**:页面返回验证页或空内容,脚本报错退出。
|
|
95
|
+
- **已删除文章**:同上。
|
|
96
|
+
- **零依赖**:只用 Node.js 标准库,无需 npm install。
|
|
97
|
+
- **路径规则**:执行时必须用脚本的绝对路径,不依赖 cwd。
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { createWriteStream } from 'node:fs';
|
|
4
|
+
import { basename, join } from 'node:path';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
|
|
7
|
+
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36';
|
|
8
|
+
const BS = String.fromCharCode(92);
|
|
9
|
+
|
|
10
|
+
function usage() {
|
|
11
|
+
console.error('用法: node fetch.mjs <文章URL> [输出根目录]');
|
|
12
|
+
console.error('示例: node fetch.mjs "https://mp.weixin.qq.com/s/xxxxx" ./my-articles');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// HTML 实体解码(处理微信视频 URL 里的 \x26amp; \x26)
|
|
17
|
+
function decodeVideoUrl(raw) {
|
|
18
|
+
return raw.split(BS + 'x26amp;').join('&')
|
|
19
|
+
.split(BS + 'x26').join('&')
|
|
20
|
+
.split('&').join('&');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 微信图片 URL:把路径里的尺寸段换成 0 拿原图,并统一解实体
|
|
24
|
+
function toOriginal(src) {
|
|
25
|
+
return src.replace(/&/g, '&').replace(/\/\d+\?/, '/0?');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 安全文件名(跨平台)
|
|
29
|
+
function sanitize(s, maxLen = 80) {
|
|
30
|
+
if (!s) return '';
|
|
31
|
+
return s.replace(/[\[\]()/\\:*?"<>|]/g, ' ')
|
|
32
|
+
.replace(/\s+/g, ' ')
|
|
33
|
+
.trim()
|
|
34
|
+
.replace(/\.$/, '')
|
|
35
|
+
.slice(0, maxLen)
|
|
36
|
+
.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 从 js_content 开标签处切出正文:跳到标签闭合的 '>',再按 div 深度配对
|
|
40
|
+
function sliceJsContent(html, from) {
|
|
41
|
+
const openEnd = html.indexOf('>', from);
|
|
42
|
+
if (openEnd < 0) throw new Error('正文容器标签未闭合');
|
|
43
|
+
const rest = html.slice(openEnd + 1);
|
|
44
|
+
|
|
45
|
+
// 微信正文是 <div id="js_content">…</div>,内部还有大量嵌套 div
|
|
46
|
+
let depth = 1, pos = 0;
|
|
47
|
+
const re = /<(\/?)div\b[^>]*>/gi;
|
|
48
|
+
re.lastIndex = 0;
|
|
49
|
+
let m;
|
|
50
|
+
while ((m = re.exec(rest))) {
|
|
51
|
+
depth += m[1] ? -1 : 1;
|
|
52
|
+
if (depth === 0) return rest.slice(0, m.index);
|
|
53
|
+
pos = re.lastIndex;
|
|
54
|
+
}
|
|
55
|
+
// 配对失败时退回旧策略:截到第一个 <script>
|
|
56
|
+
const s = rest.indexOf('<script');
|
|
57
|
+
return s > 0 ? rest.slice(0, s) : rest;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 下载文件。403 是签名失效或 CDN 限频,重试无意义,直接失败;
|
|
61
|
+
// 网络抖动与 5xx/429 才退避重试。
|
|
62
|
+
async function download(url, dest) {
|
|
63
|
+
let lastErr;
|
|
64
|
+
for (const wait of [0, 1200, 3000]) {
|
|
65
|
+
if (wait) await new Promise(r => setTimeout(r, wait));
|
|
66
|
+
try {
|
|
67
|
+
const r = await fetch(url, { headers: { 'User-Agent': UA } });
|
|
68
|
+
if (!r.ok) {
|
|
69
|
+
const err = new Error(`HTTP ${r.status}`);
|
|
70
|
+
if (r.status === 403) throw Object.assign(err, { fatal: true });
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
await pipeline(r.body, createWriteStream(dest));
|
|
74
|
+
return dest;
|
|
75
|
+
} catch (e) {
|
|
76
|
+
if (e.fatal) throw e;
|
|
77
|
+
lastErr = e;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw lastErr;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function main() {
|
|
84
|
+
if (process.argv.length < 3) usage();
|
|
85
|
+
const url = process.argv[2];
|
|
86
|
+
const outRoot = process.argv[3] || 'wechat-articles';
|
|
87
|
+
|
|
88
|
+
console.log('[1/5] 抓取文章页面...');
|
|
89
|
+
const pageResp = await fetch(url, { headers: { 'User-Agent': UA } });
|
|
90
|
+
const html = await pageResp.text();
|
|
91
|
+
|
|
92
|
+
if (html.includes('secitptpage') || !html.includes('id="js_content"')) {
|
|
93
|
+
throw new Error('页面异常:可能遇到验证页、付费墙或文章已删除');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 提取元信息
|
|
97
|
+
const titleMatch = html.match(/<meta property="og:title" content="([^"]*)"/);
|
|
98
|
+
const title = titleMatch ? titleMatch[1].replace(/"/g, '"').replace(/&/g, '&') : '未命名文章';
|
|
99
|
+
|
|
100
|
+
const nicknameMatch = html.match(/var nickname\s*=\s*htmlDecode\("([^"]*)"\)/);
|
|
101
|
+
const nickname = nicknameMatch ? nicknameMatch[1] : '未知公众号';
|
|
102
|
+
|
|
103
|
+
const createTimeMatch = html.match(/var create_time\s*=\s*"(\d+)"/);
|
|
104
|
+
const timestamp = createTimeMatch ? Number(createTimeMatch[1]) * 1000 : Date.now();
|
|
105
|
+
const date = new Date(timestamp).toISOString().slice(0, 10);
|
|
106
|
+
|
|
107
|
+
console.log(` 标题: ${title}`);
|
|
108
|
+
console.log(` 公众号: ${nickname}`);
|
|
109
|
+
console.log(` 日期: ${date}`);
|
|
110
|
+
|
|
111
|
+
// 创建输出目录
|
|
112
|
+
const dirname = `${date}_${sanitize(nickname, 20)}_${sanitize(title, 40)}`;
|
|
113
|
+
const outDir = join(outRoot, dirname);
|
|
114
|
+
await mkdir(join(outDir, 'images'), { recursive: true });
|
|
115
|
+
await mkdir(join(outDir, 'videos'), { recursive: true });
|
|
116
|
+
|
|
117
|
+
console.log(`[2/5] 提取媒体链接...`);
|
|
118
|
+
|
|
119
|
+
// 定位正文:先跳到 js_content 开标签结束的 >,再按 div 深度配对找收尾
|
|
120
|
+
const containerStart = html.indexOf('id="js_content"');
|
|
121
|
+
if (containerStart < 0) throw new Error('未找到正文容器 #js_content');
|
|
122
|
+
const body = sliceJsContent(html, containerStart);
|
|
123
|
+
|
|
124
|
+
// 图片:整页去重,URL 里的尺寸段换成 /0 取原图
|
|
125
|
+
const imgMatches = [...body.matchAll(/data-src="(https:\/\/mmbiz\.qpic\.cn\/[^"]+)"/g)];
|
|
126
|
+
const images = [...new Set(imgMatches.map(m => toOriginal(m[1])))];
|
|
127
|
+
|
|
128
|
+
// 视频:videoPageInfos 里每个 video_id 自带一组清晰度,
|
|
129
|
+
// 按 video_id 分块,避免相邻视频的清晰度互相串台。
|
|
130
|
+
const formatsByVid = new Map();
|
|
131
|
+
const chunks = html.split(/video_id:\s*'(wxv_\d+)'/);
|
|
132
|
+
for (let i = 1; i < chunks.length; i += 2) {
|
|
133
|
+
const vid = chunks[i];
|
|
134
|
+
const block = chunks[i + 1] || '';
|
|
135
|
+
if (formatsByVid.has(vid)) continue;
|
|
136
|
+
// 以 format_id 切段,每段里第一个 mpvideo url 就是该清晰度的直链。
|
|
137
|
+
// 段长随页面模板变化(实测 format_id 到 url 有 550+ 字符),不能用固定距离。
|
|
138
|
+
const formats = {};
|
|
139
|
+
const parts = block.split(/format_id:\s*'(10002|10102|10004|10104)'/);
|
|
140
|
+
for (let k = 1; k < parts.length; k += 2) {
|
|
141
|
+
const fid = parts[k];
|
|
142
|
+
if (formats[fid]) continue;
|
|
143
|
+
const u = (parts[k + 1] || '').match(/url:\s*\(?'(http:\/\/mpvideo\.qpic\.cn\/[^']+)'/);
|
|
144
|
+
if (u) formats[fid] = decodeVideoUrl(u[1]);
|
|
145
|
+
}
|
|
146
|
+
formatsByVid.set(vid, formats);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const pickBestUrl = formats => {
|
|
150
|
+
for (const fid of ['10002', '10102', '10004', '10104']) {
|
|
151
|
+
if (formats[fid]) return { format: fid, url: formats[fid] };
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// 正文里视频按 iframe 出现顺序,用 data-mpvid 关联;同一视频只算一次
|
|
157
|
+
const orderedVids = [...new Set([...body.matchAll(/data-mpvid="(wxv_\d+)"/g)].map(m => m[1]))];
|
|
158
|
+
const videos = [];
|
|
159
|
+
for (const vid of orderedVids) {
|
|
160
|
+
const best = pickBestUrl(formatsByVid.get(vid) || {});
|
|
161
|
+
if (best) videos.push({ vid, ...best });
|
|
162
|
+
else videos.push({ vid, format: null, url: null, reason: '无直链(视频号或已下架)' });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log(` 图片: ${images.length} 个`);
|
|
166
|
+
console.log(` 视频: ${videos.length} 个`);
|
|
167
|
+
|
|
168
|
+
console.log('[3/5] 下载图片...');
|
|
169
|
+
const imageResults = [];
|
|
170
|
+
for (let i = 0; i < images.length; i++) {
|
|
171
|
+
const url = images[i];
|
|
172
|
+
const ext = url.match(/wx_fmt=(\w+)/)?.[1] || (url.includes('_gif') ? 'gif' : 'jpg');
|
|
173
|
+
const filename = `${String(i + 1).padStart(3, '0')}.${ext}`;
|
|
174
|
+
const dest = join(outDir, 'images', filename);
|
|
175
|
+
try {
|
|
176
|
+
await download(url, dest);
|
|
177
|
+
imageResults.push({ ok: true, url, path: `images/${filename}` });
|
|
178
|
+
process.stdout.write(` ✓ ${filename}\r`);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
imageResults.push({ ok: false, url, error: e.message });
|
|
181
|
+
process.stdout.write(` ✗ ${filename} (${e.message})\n`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
console.log(` 完成: ${imageResults.filter(r => r.ok).length}/${images.length}`);
|
|
185
|
+
|
|
186
|
+
console.log('[4/5] 下载视频...');
|
|
187
|
+
const videoResults = [];
|
|
188
|
+
for (let i = 0; i < videos.length; i++) {
|
|
189
|
+
const { vid, format, url, reason } = videos[i];
|
|
190
|
+
const filename = `${String(i + 1).padStart(3, '0')}.mp4`;
|
|
191
|
+
const dest = join(outDir, 'videos', filename);
|
|
192
|
+
if (!url) {
|
|
193
|
+
videoResults.push({ ok: false, vid, format, url: null, error: reason });
|
|
194
|
+
console.log(` ✗ ${filename}: ${reason}`);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
await download(url, dest);
|
|
199
|
+
videoResults.push({ ok: true, vid, format, url, path: `videos/${filename}` });
|
|
200
|
+
console.log(` ✓ ${filename} (f${format})`);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
videoResults.push({ ok: false, vid, format, url, error: e.message });
|
|
203
|
+
console.log(` ✗ ${filename} (f${format}, ${e.message})`);
|
|
204
|
+
}
|
|
205
|
+
if (i < videos.length - 1) await new Promise(r => setTimeout(r, 800 + Math.random() * 400));
|
|
206
|
+
}
|
|
207
|
+
console.log(` 完成: ${videoResults.filter(r => r.ok).length}/${videos.length}`);
|
|
208
|
+
|
|
209
|
+
// 远端链接 → 本地路径,供正文改写使用
|
|
210
|
+
const imageMap = new Map(imageResults.filter(r => r.ok).map(r => [r.url, r.path]));
|
|
211
|
+
const videoMap = new Map(videoResults.filter(r => r.ok).map(r => [r.vid, r.path]));
|
|
212
|
+
|
|
213
|
+
console.log('[5/5] 生成 Markdown...');
|
|
214
|
+
|
|
215
|
+
// 简化 HTML 转 Markdown
|
|
216
|
+
let md = body
|
|
217
|
+
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
218
|
+
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
219
|
+
.replace(/<iframe[^>]*data-mpvid="([^"]+)"[^>]*>(?:<\/iframe>)?/g, (_, vid) => {
|
|
220
|
+
const local = videoMap.get(vid);
|
|
221
|
+
return local ? `\n\n[▶ 视频](${local})\n\n` : `\n\n[▶ 视频: ${vid}(未能下载)]\n\n`;
|
|
222
|
+
})
|
|
223
|
+
.replace(/<img[^>]*data-src="([^"]+)"[^>]*>/g, (full, src) => {
|
|
224
|
+
const local = imageMap.get(toOriginal(src));
|
|
225
|
+
return local ? `\n\n\n\n` : `\n\n[图片缺失]\n\n`;
|
|
226
|
+
})
|
|
227
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
228
|
+
.replace(/<\/p>/gi, '\n\n')
|
|
229
|
+
.replace(/<[^>]+>/g, '')
|
|
230
|
+
.replace(/ /g, ' ')
|
|
231
|
+
.replace(/"/g, '"')
|
|
232
|
+
.replace(/</g, '<')
|
|
233
|
+
.replace(/>/g, '>')
|
|
234
|
+
.replace(/&/g, '&')
|
|
235
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
236
|
+
.trim();
|
|
237
|
+
|
|
238
|
+
const wordCount = md.replace(/[^一-龥a-zA-Z]/g, '').length;
|
|
239
|
+
const imgOk = imageResults.filter(r => r.ok).length;
|
|
240
|
+
const vidOk = videoResults.filter(r => r.ok).length;
|
|
241
|
+
const vidFail = videos.length - vidOk;
|
|
242
|
+
const failReason = vidFail > 0 ? ` (${vidFail} 个失败: 403 签名失效/限频)` : '';
|
|
243
|
+
|
|
244
|
+
const article = `# ${title}\n\n**作者**: ${nickname} \n**日期**: ${date} \n**原文**: ${url}\n\n---\n\n${md}\n\n---\n\n📊 统计: ${wordCount} 字 | 图片 ${imgOk}/${images.length} | 视频 ${vidOk}/${videos.length}${failReason}\n`;
|
|
245
|
+
|
|
246
|
+
await writeFile(join(outDir, 'article.md'), article, 'utf8');
|
|
247
|
+
await writeFile(join(outDir, 'sources.json'), JSON.stringify({
|
|
248
|
+
url, title, nickname, date, timestamp,
|
|
249
|
+
images: imageResults,
|
|
250
|
+
videos: videoResults
|
|
251
|
+
}, null, 2), 'utf8');
|
|
252
|
+
|
|
253
|
+
console.log(`\n✅ 完成! 输出目录: ${outDir}/`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
main().catch(e => { console.error('\n❌', e.message); process.exit(1); });
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
你好,我是 {{agentName}}。
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
我刚刚完成连接,先补齐基础信息就可以开始工作。创建时已经填写的有效信息会直接沿用,不需要重复提供或确认。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
1.
|
|
7
|
-
2.
|
|
8
|
-
3.
|
|
5
|
+
如果还有缺失,或你想修改,可以一次补充相应项:
|
|
6
|
+
1. 显示名:希望使用的名称。
|
|
7
|
+
2. 简介:主要职责或定位,一到两句话即可。
|
|
8
|
+
3. 标签:3 到 6 个检索标签,例如 `代码审查`、`客服助手`、`项目管理`。
|
|
9
9
|
|
|
10
10
|
当前信息:
|
|
11
11
|
- AID: {{agentAid}}
|
|
12
12
|
- 渠道: {{channel}}
|
|
13
13
|
- Baseagent: {{baseagent}}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
也可以顺便补充人格属性、话术风格、工作流程、任务偏好(persona,支持文本或 `.md`/`.txt` 文件),以及可选头像(HTTPS 图片地址,或放在当前项目目录/本会话临时目录中的图片文件)。这些都不必现在处理,未提供不影响激活,之后也可以修改。
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
如果希望沿用现有信息,直接回复“沿用现有信息”即可;我只会追问缺失或有歧义的部分。
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
上下文文件里列出的候选:mtime 超过 `memory.forgetAfterDays` 的记忆。**候选只给路径 + 描述。**
|
|
12
12
|
|
|
13
|
+
| 来源 | 位置 | 说明 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| 候选记忆 | 上下文文件里列出的候选清单 | mtime 超过 `forgetAfterDays` 的条目,只给路径+描述 |
|
|
16
|
+
| 对话历史 | 上下文文件里列出的 `threads/` 快照文件(若有,仅 relation 层) | 上次整理以来的增量对话,辅助判断超期记忆是否仍然有效;agent/owner 层无此文件 |
|
|
17
|
+
|
|
13
18
|
> **打了 `important` / `urgent` / `profile` 的记忆同样会出现在候选里。**
|
|
14
19
|
> 「永不淘汰」指的是索引截断时不会被截掉(只要它在,就保证进索引),
|
|
15
20
|
> **不代表它免于遗忘审查**。审查的是「这条内容还成不成立」,与它在不在索引里是两个维度。
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
上下文文件里列出的候选:关系级记忆里带 `to-generalize` 标签的条目。**候选只给路径 + 描述**,先只读描述判断;某条描述太简、拿不准的,再读那一条正文。
|
|
12
12
|
|
|
13
|
+
| 来源 | 位置 | 说明 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| 候选记忆 | 上下文文件里列出的候选清单 | 关系级 consolidated/ 里带 `to-generalize` 的条目,只给路径+描述 |
|
|
16
|
+
| 对话历史 | 上下文文件里列出的 `threads/` 快照文件(若有) | 上次本模式整理以来的增量对话,辅助判断泛化结论是否成立;无增量时为空,可忽略 |
|
|
17
|
+
|
|
13
18
|
## 产出
|
|
14
19
|
|
|
15
20
|
写到 **agent 作用域**(`--scope agent`)。
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
**按需读正文**:某条描述太简、拿不准它具体是什么意思时,再读那一条 consolidated 文件。**不要逐条读所有正文。**
|
|
14
14
|
|
|
15
|
+
| 来源 | 位置 | 说明 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| 已有记忆 | 该关系作用域的 `MEMORY.md` 索引 | 归纳画像的主要来源 |
|
|
18
|
+
| 对话历史 | 上下文文件里列出的 `threads/` 快照文件(若有) | 上次本模式整理以来的增量对话,辅助判断画像是否需要更新;无增量时为空,可忽略 |
|
|
19
|
+
|
|
15
20
|
## 产出
|
|
16
21
|
|
|
17
22
|
一条带 `profile` 标签的 **relation 级**记忆,且**一个关系只能有一条**。
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
上下文文件里列出的候选:关系级记忆里带 `to-promote` 标签的条目。**候选只给路径 + 描述**,先只读描述判断;某条描述太简、拿不准的,再读那一条正文。
|
|
12
12
|
|
|
13
|
+
| 来源 | 位置 | 说明 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| 候选记忆 | 上下文文件里列出的候选清单 | 关系级 consolidated/ 里带 `to-promote` 的条目,只给路径+描述 |
|
|
16
|
+
| 对话历史 | 上下文文件里列出的 `threads/` 快照文件(若有) | 上次本模式整理以来的增量对话,辅助判断 owner 的明确要求是否在对话里体现;无增量时为空,可忽略 |
|
|
17
|
+
|
|
13
18
|
## 产出
|
|
14
19
|
|
|
15
20
|
写到 **owner 作用域**(`--scope owner`)。
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
‹{{now}} · from:{{peerId}}{{?peerName}}({{peerName}}){{/}}{{?peerRole}} · role:{{peerRole}}{{/}}{{?chatType=group}} → 群:{{groupLabel}} · self:{{selfAid}}{{/}}{{?chatType=private}} → self:{{selfAid}}{{/}}{{?encrypted=true}} · 🔒密文{{/}}{{?encrypted=false}} · ✉️明文{{/}}{{?sameDevice}} · 📍同设备{{/}}{{?sameNetwork}} · 🌐同网络{{/}}{{?sameEgressIp}} · 🔀同出口IP{{/}}{{?mentionAids}} · @:{{mentionAids}}{{/}}›
|
|
2
|
-
{{?
|
|
2
|
+
{{?lifecycle=bootstrapping}}(本轮只推进首次设定。若是普通任务请求,说明阶段边界后回到 name / description / tags 即可。)
|
|
3
|
+
{{/}}{{?isHandoff!=true}}{{content}}{{/}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
‹跨会话上下文 · 来源话题:{{threadContextOriginThreadId}}{{?threadContextOriginPeer}} · 对端:{{threadContextOriginPeer}}{{/}}{{?threadContextTime}} · 发送于:{{threadContextTime}}{{/}}›
|
|
2
|
+
{{?threadContextGapHasNewMessages}}⚠️ 来源话题在发送后已有 {{threadContextGapCount}} 条新消息{{?threadContextGapRange}}({{threadContextGapRange}}){{/}} —— 处理前建议先回查该话题的空洞期消息。
|
|
3
|
+
{{/}}{{?threadContextGapError}}⚠️ 无法检测来源话题的空洞期消息({{threadContextGapError}})—— 如需确认,请手动执行 ec thread history 回查该话题。
|
|
4
|
+
{{/}}{{?threadContextTriggerId}}🔔 复查 Trigger:{{threadContextTriggerId}}(尚未触发/待处理)
|
|
5
|
+
{{/}}
|
|
6
|
+
{{content}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
‹Trigger 复查 · 话题:{{triggerMsgLabel_targetThreadId}} ← 来源:{{triggerMsgLabel_originThreadId}}{{?triggerMsgLabel_originPeer}}({{triggerMsgLabel_originPeer}}){{/}}{{?triggerMsgLabel_checkEvery}} · 周期:{{triggerMsgLabel_checkEvery}}{{/}}{{?triggerMsgFireTime}} · 触发于:{{triggerMsgFireTime}}{{/}} → self:{{selfAid}}›
|
|
2
|
+
{{content}}
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
"agent.*": { "allow": false },
|
|
31
31
|
"observable.current": { "allow": false },
|
|
32
32
|
"session.current": { "allow": true, "scopes": ["relation"], "constraints": { "ownPeerOnly": true, "targetCurrentAgentOnly": true } },
|
|
33
|
+
"session.loop.current": { "allow": true, "scopes": ["relation"], "constraints": { "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
34
|
+
"session.loop.update": { "allow": true, "scopes": ["relation"], "constraints": { "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
33
35
|
"session.topic.current": { "allow": true, "scopes": ["relation"], "constraints": { "ownPeerOnly": true, "targetCurrentAgentOnly": true } },
|
|
34
36
|
"session.topic.list": { "allow": true, "scopes": ["relation"], "constraints": { "ownPeerOnly": true, "targetCurrentAgentOnly": true } },
|
|
35
37
|
"project.current": { "allow": true, "scopes": ["relation"], "constraints": { "ownPeerOnly": true, "targetCurrentAgentOnly": true } },
|
|
@@ -50,6 +50,8 @@
|
|
|
50
50
|
"trigger.run": { "allow": false },
|
|
51
51
|
"session.list": { "allow": true, "scopes": ["relation"], "constraints": { "ownPeerOnly": true } },
|
|
52
52
|
"session.current": { "allow": true, "scopes": ["relation"], "constraints": { "ownPeerOnly": true, "targetCurrentAgentOnly": true } },
|
|
53
|
+
"session.loop.current": { "allow": true, "scopes": ["relation"], "constraints": { "currentRelationOnly": true, "targetCurrentAgentOnly": true } },
|
|
54
|
+
"session.loop.update": { "allow": false },
|
|
53
55
|
"session.topic.current": { "allow": true, "scopes": ["relation"], "constraints": { "ownPeerOnly": true } },
|
|
54
56
|
"session.topic.list": { "allow": true, "scopes": ["relation"], "constraints": { "ownPeerOnly": true } },
|
|
55
57
|
"project.current": { "allow": true, "scopes": ["relation"], "constraints": { "ownPeerOnly": true, "targetCurrentAgentOnly": true } },
|