初始化

This commit is contained in:
gozeros committed 2026-09-07 18:24:55 +08:00
1 parent 34474bd5fc
commit b638b541d0
3008 files changed
+837699

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
---
name: xdfx-split-controller
description: xdfx 深链接单智能体拆分版总控技能,按顺序调度 step1-step6 技能完成分析与入库。
---
# xdfx 拆分版总控 Skill
总控也必须优先运行内置 Python 执行器,不要让模型手写接口流程。执行器位于:
```text
scripts/xdfx_flow.py
```
## 输入文件
准备 `xdfx-input.json`:
```json
{
"api_base": "https://ch1.b.uat.4.cn",
"taskId": "真实任务id",
"actionId": "真实xdfx行动id",
"mubiao": "传播...叙事,驱动...行为体产生...预期行为,实现...作战目的。",
"direction": "U方向",
"platform": "fb"
}
```
## 推荐执行顺序
默认会自动处理登录态:脚本启动时先 `import scripts/xdfx_auth.py`,用固定账号 `admin`、口令 `ch@user` 调用 `POST /api/auth/login`,请求体为 `{"username":"admin","password":"ch@user"}`。拿到 `access_token` 后写入内部 `Authorization: Bearer ...`,再调用第一步业务接口;不要通过任何命令行参数传 token。401 时仍由 `xdfx_auth.py` 刷新 token 并重试原接口一次。默认关闭 HTTPS 证书校验以兼容 UAT/内网自签证书;如果环境证书链正常,可以追加 `--verify-ssl`。
总控按顺序执行下面 6 条命令,所有步骤共用同一个 `xdfx-flow-state.json`:
```bash
python scripts/xdfx_flow.py --mode step1 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md
python scripts/xdfx_flow.py --mode step2 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md
python scripts/xdfx_flow.py --mode step3 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md
python scripts/xdfx_flow.py --mode step4 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md
python scripts/xdfx_flow.py --mode step5 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md
python scripts/xdfx_flow.py --mode step6 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md
```
每条命令结束都会输出一行 `步骤反馈 {...}`。总控必须在每个 step 命令结束后立即反馈用户,再继续下一步。
每次接口调用前会输出 `操作反馈 {...}`。总控必须告诉用户当前正在做什么、调用哪个接口。
每一步如果生成了可选择候选,会输出 `用户协助 {...}`。总控必须暂停后续步骤,把 `选项` 发给用户,并在用户选择后重跑当前 step:
```bash
python scripts/xdfx_flow.py --mode step1 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md --select-audiences 1,2,3
python scripts/xdfx_flow.py --mode step2 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md --select-pathways 1,2
python scripts/xdfx_flow.py --mode step3 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md --select-audience-vulns 1,2,4
python scripts/xdfx_flow.py --mode step4 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md --select-pathway-vulns 1,3
python scripts/xdfx_flow.py --mode step5 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md --select-strategy-audiences 1,2
python scripts/xdfx_flow.py --mode step6 --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md --select-strategy-pathways 1,2
```
Step5 只能选择受众生成策略;Step6 只能选择关键传播节点生成策略。总控不能在同一步混传 `--select-strategy-audiences` 和 `--select-strategy-pathways`。
策略归属必须由当前步骤强制决定,不能相信模型返回的 `账号类型/accountType`:Step5 固定按“目标受众账号”保存,只写 `designId + strategyId` 关系;Step6 固定按“关键传播节点账号”保存,只写 `pathwayId + strategyId` 关系,严禁把受众策略写到关键传播节点或反向串写。
每次保存接口成功拿到返回 ID 后会输出 `入库反馈 {...}`。总控必须告诉用户已入库的对象类型、名称和返回 ID,例如“已入库关键传播节点:xxx,pathwayAccountId=123”。
如果接口或模型处理报错,会立即输出 `错误反馈 {...}`。总控必须立刻把失败阶段、URL、状态码、请求摘要和响应摘要告诉用户。
如果模型返回文本经过自动修复仍无法解析,会输出 `JSON解析复核 {...}`。总控必须查看响应片段,判断是否需要重跑当前 step 或重新调用对应接口。
## 失败重试规则
- 任意 step 出现 `错误反馈`、`JSON解析复核`,或 `步骤反馈` 中 `状态` 为 `部分完成` / `已跳过` / `本步骤新增错误数` 大于 0 时,总控必须先重跑当前失败的同一条 `--mode stepN` 命令。
- 重试前必须告诉用户失败阶段、接口 URL、状态码、响应摘要和“正在重试当前步骤”,不能静默重试。
- 当前 step 未重试成功前,不允许继续执行下一 step;不要因为某一步失败就跳过进入后续分析或入库。
- 同一 step 重试后仍失败时,停止自动推进,读取 `xdfx-flow-state.json.errors` 汇报失败细节,并等待用户处理或确认。
- 只有用户明确要求忽略该失败,才可以继续后续 step;回复中必须说明这是用户确认后的带风险继续。
如果只想一条命令跑完,也可以使用下面命令做无人值守兜底,但不建议作为默认方式,因为外层工具通常会等命令结束后才返回输出,无法保证用户看到实时反馈:
```bash
python scripts/xdfx_flow.py --mode full --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md
```
更推荐的一条命令方式是使用包装脚本;它内部仍按 step1-step6 分步调用,并逐行转发反馈:
```bash
python scripts/xdfx_run_full_steps.py --input xdfx-input.json --state xdfx-flow-state.json --summary xdfx-flow-summary.md
```
## 断点续跑
- Step1 成功后会写入 `topAudiences`。
- Step1 的受众目标抽取结果里,`用户编号列表` / `编号id` / `id` 必须对应上一轮 `zhanghao` 账号数组的索引值;脚本会按这个索引依次找到账号并保存受众账号关系。
- Step2 成功后会写入 `topPathways`。
- Step1 默认入库最优 3 个受众;Step2 默认入库最优 3 个传播节点。
- 默认开启用户协助选择:受众、关键传播节点、受众脆弱点、传播节点脆弱点、策略生成对象都必须由用户选择后才继续。只有排障或无人值守时才追加 `--no-user-assist` 走自动排序。
- Step3/Step5 的受众下游分析只能从“已在 Step2 分析出并关联关键传播节点”的受众中选择,默认取前 2 个;没有产生传播节点的受众不得继续分析脆弱点和策略。
- Step4/Step6 的传播节点下游分析默认从已入库传播节点中选择前 2 个继续;可用 `--downstream-audiences` / `--downstream-pathways` 调成 1 或 2。
- Step3-Step6 对上述选中的每个受众/传播节点默认生成最多 3 个脆弱点或策略。
- 各步骤会同步前端 `task/task-action.step`;需要禁用时每条命令追加 `--no-step-status`。
- 策略步骤会同步写入 channel、narrate、message、策略与受众/传播节点关系表,保证原前端策略表格/编排页可继续读取;Step5 只写受众 `designId` 关系,Step6 只写传播节点 `pathwayId` 关系,归属不使用模型返回的 `accountType` 判断。
- 策略步骤保存 `信息形式/模态` 时,必须使用脚本读取 `sys/sys-dict-data` 的 `modelType` 字典并把文字转成 value 逗号串,不能把“图片,视频”等文字直接写入 `task-action-message.modelType`。
- 每个步骤完成都会输出 `步骤反馈`,总控必须逐步转述给用户。
- 每次操作和接口调用都会输出 `操作反馈`,总控必须逐条转述当前操作。
- 每次保存成功都会输出 `入库反馈`,总控必须逐条转述入库对象。
- 每次错误都会输出 `错误反馈`,总控必须立即报告错误细节。
- 每次 JSON 彻底解析失败都会输出 `JSON解析复核`,总控必须复核响应片段并判断是否重试当前 step。
- 每个步骤里的流式模型接口第一次空响应、返回 `y模型没有响应`/`模型没有响应` 或超时类字符串时,会自动再调用一次;仍失败才写入 `errors`。
- 某一步失败后,必须先重跑同一个 `--mode stepN`;重试仍失败再等待用户处理或确认,不能自动跳到后续步骤。
## 汇报规则
执行过程中按 `操作反馈` 告诉用户当前操作和接口;按 `入库反馈` 告诉用户已入库什么对象和返回 ID;按 `错误反馈` 立即报告错误;每个步骤结束后再根据 `步骤反馈` 回复用户一次。全部步骤结束后,读取 `xdfx-flow-summary.md` 和 `xdfx-flow-state.json.errors` 再做最终汇总。只要存在错误,就明确说“部分完成”,并列出失败阶段、接口 URL、状态码和响应摘要。
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""Authentication helper for xdfx skill scripts.
The caller should not pass tokens through command-line arguments. This module
owns the fixed admin login flow and returns a Bearer token before business
interfaces are called.
"""
from __future__ import annotations
import json
import re
import ssl
import urllib.error
import urllib.request
from dataclasses import dataclass
from typing import Any
@dataclass
class AuthResult:
token: str
url: str
status: int | None
raw: str
class AuthError(RuntimeError):
def __init__(
self,
message: str,
*,
url: str,
status: int | None = None,
response: str = "",
exception: BaseException | None = None,
) -> None:
super().__init__(message)
self.url = url
self.status = status
self.response = response
self.exception = exception
def auth_header_value(token: str) -> str:
token = token.strip()
return token if token.lower().startswith("bearer ") else f"Bearer {token}"
def redact_url(url: str) -> str:
return re.sub(r"([?&](?:password|token|authToken|access_token)=)[^&]+", r"\1***", url, flags=re.IGNORECASE)
def ssl_context(*, verify_ssl: bool) -> ssl.SSLContext | None:
if verify_ssl:
return None
return ssl._create_unverified_context()
def build_login_url(api_base: str, path: str) -> str:
return path if re.match(r"^https?://", path, flags=re.IGNORECASE) else api_base.rstrip("/") + "/" + path.lstrip("/")
def extract_access_token(data: Any) -> str | None:
if isinstance(data, dict):
for key in ("access_token", "accessToken", "token", "jwt", "id_token", "authorization", "Authorization"):
value = data.get(key)
if isinstance(value, str) and value.strip():
return value.strip()
for value in data.values():
nested = extract_access_token(value)
if nested:
return nested
elif isinstance(data, list):
for value in data:
nested = extract_access_token(value)
if nested:
return nested
return None
def parse_json_loose(text: str) -> Any:
try:
return json.loads(text)
except Exception:
pass
match = re.search(r"[\[{]", text or "")
if not match:
return {}
start = match.start()
for end in range(len(text), start, -1):
try:
return json.loads(text[start:end])
except Exception:
continue
return {}
def fetch_access_token(
*,
api_base: str,
username: str = "admin",
password: str = "ch@user",
login_path: str = "/api/auth/login",
timeout: float = 90,
verify_ssl: bool = False,
) -> AuthResult:
"""Login once and return the extracted access token."""
url = build_login_url(api_base, login_path)
payload = {"username": username, "password": password}
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
headers = {"Content-Type": "application/json", "Accept": "application/json"}
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
try:
with urllib.request.urlopen(req, timeout=timeout, context=ssl_context(verify_ssl=verify_ssl)) as resp:
raw = resp.read().decode("utf-8", errors="replace")
parsed = parse_json_loose(raw)
token = extract_access_token(parsed)
if not token:
raise AuthError(
"登录接口已响应但未找到 access_token",
url=redact_url(url),
status=getattr(resp, "status", None),
response=raw,
)
return AuthResult(token=token, url=redact_url(url), status=getattr(resp, "status", None), raw=raw)
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace") if exc.fp else ""
raise AuthError(
"登录接口返回错误",
url=redact_url(url),
status=exc.code,
response=body,
exception=exc,
) from exc
except AuthError:
raise
except Exception as exc: # noqa: BLE001 - auth errors must preserve detail for user feedback.
raise AuthError(
"登录接口调用异常",
url=redact_url(url),
response=str(exc),
exception=exc,
) from exc
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Run the xdfx flow one step at a time.
The heavy business logic intentionally stays in xdfx_flow.py. This wrapper only
keeps "full flow" orchestration readable and preserves per-step feedback: every
subprocess line is flushed immediately, so OP/SAVE/ERROR/STEP feedback remains
visible to the calling agent.
"""
from __future__ import annotations
import argparse
import os
import subprocess
import sys
from pathlib import Path
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
STEPS = ("step1", "step2", "step3", "step4", "step5", "step6")
STEP_LABELS = {
"step1": "受众目标分析与入库",
"step2": "关键传播节点分析与入库",
"step3": "受众脆弱点分析与入库",
"step4": "传播节点脆弱点分析与入库",
"step5": "受众策略分析与入库",
"step6": "传播节点策略分析与入库",
}
def build_parser() -> argparse.ArgumentParser:
"""Parse only wrapper-owned flags; pass all other flags to xdfx_flow.py."""
parser = argparse.ArgumentParser(
description="按六个步骤运行 xdfx_flow.py,并实时转发中文反馈日志。",
add_help=True,
)
parser.add_argument("--from-step", choices=STEPS, default="step1")
parser.add_argument("--to-step", choices=STEPS, default="step6")
parser.add_argument(
"--continue-on-error",
action="store_true",
help="某一步返回非零退出码时仍继续执行后续步骤。",
)
return parser
def selected_steps(first: str, last: str) -> list[str]:
"""Return an inclusive step slice for resume/partial reruns."""
start = STEPS.index(first)
end = STEPS.index(last)
if start > end:
raise SystemExit("起始步骤必须小于或等于结束步骤")
return list(STEPS[start : end + 1])
def stream_command(step: str, cmd: list[str]) -> tuple[int, bool]:
"""Run one step and forward stdout/stderr lines as they are produced."""
print(f"运行步骤:{STEP_LABELS.get(step, step)};执行命令:" + " ".join(cmd), flush=True)
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding="utf-8",
errors="replace",
env=env,
)
assert process.stdout is not None
waiting_user = False
for line in process.stdout:
if line.startswith("用户协助 "):
waiting_user = True
sys.stdout.buffer.write(line.encode("utf-8", errors="replace"))
sys.stdout.buffer.flush()
return process.wait(), waiting_user
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args, passthrough = parser.parse_known_args(argv)
flow_script = Path(__file__).with_name("xdfx_flow.py")
exit_code = 0
for step in selected_steps(args.from_step, args.to_step):
# Keep all business flags unchanged so the wrapper behaves like a thin
# streaming version of repeated xdfx_flow.py --mode stepN calls.
cmd = [sys.executable, str(flow_script), "--mode", step, *passthrough]
code, waiting_user = stream_command(step, cmd)
if waiting_user:
print("已发起用户协助,暂停后续步骤;请根据用户选择重跑当前步骤。", flush=True)
return code
if code:
exit_code = code
if not args.continue_on_error:
return code
return exit_code
if __name__ == "__main__":
raise SystemExit(main())