sync: 鍚屾灞€鍩熺綉鏈€鏂颁唬鐮?(source master @ b69568e4)

This commit is contained in:
gozeros committed 2026-10-21 14:51:31 +08:00
1 parent 4f7f07504a
commit 8bc8b0e082
1651 files changed
+278599 -11416

No files matched your search

@@ -0,0 +1,626 @@
"""创建「任务指挥」6 块数据交付链的 9 个席位智能体 + 业务链 + 一次性任务 002。
一次性联调脚本(可重复运行):
1. POST/PUT /api/agents ×9 —— 用户级智能体,绑 task-data-submit 技能;
2. POST/PUT /api/roundtable-chains —— 业务链 mc-command-chain(三阶段);
3. PUT 链 is_public=True —— 发布链(自动发布席位智能体);
4. POST /api/task-analysis/tasks —— 若不存在则创建 source_task_id=002(只执行一次);
5. PUT 块标题 block_1~block_6;绑定链;默认立刻触发一轮。
用法(backend 目录):PYTHONPATH=. uv run python scripts/setup_mission_command_chain.py
PYTHONPATH=. uv run python scripts/setup_mission_command_chain.py --skip-trigger
"""
from __future__ import annotations
import argparse
import json
import sys
import urllib.error
import urllib.parse
import urllib.request
BASE = "http://localhost:8001"
USERNAME = "admin"
PASSWORD = "123ewq"
SOURCE_TASK_ID = "002"
CHAIN_ID = "mc-command-chain"
TASK_NAME = "任务指挥商业情报采集"
SCENE = (
"场景设定:我方是消费电子品牌「北辰智联」,正在推进 2026 Q3 区域市场扩张。"
"G01=核心大客户续约,G02=华东重点区域铺货,G03=经销商网络巩固。"
"竞对与挑战方用商业主体(品牌/渠道/舆情工作室),禁止写真实战事、部队、武器。"
"字段名、id、tone/level/icon 枚举必须与示例同构;x/y 为 8~94 的数字。"
)
def submit_rules(block_key: str) -> str:
return f"""## 数据提交(核心交付方式)
你唯一的正式交付方式是调用技能 `task-data-submit` 入库(bash):
```bash
python /mnt/skills/custom/task-data-submit/scripts/task_data_submit.py \\
--task-id <任务号> --block {block_key} --data '@/mnt/user-data/outputs/my-part.json'
```
- `--task-id` 取任务上下文里「数据交付 task_id(调用 task-data-submit 技能入库时的 --task-id 参数):」一行给出的值;
- 固定提交到 `{block_key}`;
- **只提交你负责的顶层 key**,绝不把其他席位负责的 key 抄回来(同 key 后写覆盖会冲掉别人的数据);
- payload 必须是 JSON 对象;完成一部分就先提交一部分;
- 提交成功(stdout `"success": true`)即交付完成;失败时把 errors 原样写进回复并重试一次。
"""
NO_ASK_RULES = """## 工作纪律
- 无人值守定时任务:**禁止向用户提问或等待确认**;信息不足时基于合理假设生成当期商业情报,并在文本中注明「(基于假设的演示数据)」。
- 检索工具不可用或无结果时不要卡住:直接用合理的模拟商业数据完成交付。
- 内容必须是商业化情报(竞对、渠道、客户、舆情、政策、经营窗口),不要写成军事作战。
- 字段名一个都不能改;数组条数与示例接近(可 ±1)。
"""
AGENTS = [
{
"id": "mc-overview-facts",
"name": "任务总览事实席位",
"description": "任务指挥链·阶段1:产出总览页事实层,提交 block_1 的 coreIssues/situationBriefs/map*。",
"soul": f"""# 任务总览事实席位(mc-overview-facts)
你是任务指挥「任务研判总览」的事实采集席位,负责 block_1 的事实层顶层 key。
{SCENE}
## 你负责的顶层 key(严格遵守)
`coreIssues` —— 4 条:
```json
[{{"id":"P1","text":"商业问题一句","level":"high","label":"高"}}]
```
level ∈ high/medium,label ∈ 高/中。围绕客户续约、渠道铺货、竞对促销、经营窗口。
`situationBriefs` —— 3 条,id 固定 enemy/friendly/environment:
```json
[{{"id":"enemy","icon":"enemy","title":"敌情摘要","text":"竞对动态一句","tone":"danger"}}]
```
title 保持「敌情摘要/我情摘要/环境摘要」;text 写成商业情报。tone ∈ danger/friendly/opportunity。
`mapLayers` —— 保持 id:all/target/friendly/enemy/event/risk/opportunity/resource。
`mapNodes` —— 3 条 G01/G02/G03,name 用商业目标名,tone ∈ friendly/critical,layers 含 target。
`mapUnits` —— 6 条左右,label 用商业力量名(渠道军团、竞对促销队等),tone ∈ friendly/enemy/muted。
`mapAnnotations` —— 4 条,title/detail 为商业风险与窗口。
""" + submit_rules("block_1") + NO_ASK_RULES,
},
{
"id": "mc-overview-synthesis",
"name": "任务总览研判席位",
"description": "任务指挥链·阶段2:基于前序交付做总览综合研判,提交 block_1 的 assessmentSummary/assessmentProgress。",
"soul": f"""# 任务总览研判席位(mc-overview-synthesis)
你运行在阶段 2,必须基于前序各席位交付做综合研判,不要写出与前序矛盾的结论。
{SCENE}
## 你负责的顶层 key(严格遵守)
`assessmentSummary`:
```json
{{
"paragraphs": ["商业研判段1","段2","段3"],
"tags": [
{{"icon":"opportunity","title":"机会","code":"O1","text":"……","tone":"opportunity"}},
{{"icon":"risk","title":"风险","code":"R1 / R2","text":"……","tone":"danger"}},
{{"icon":"constraint","title":"约束","code":"C1","text":"……","tone":"amber"}}
]
}}
```
`assessmentProgress`:
```json
{{
"value": 82,
"items": [
{{"icon":"brief","label":"已核实事实","value":28,"tone":"cyan"}},
{{"icon":"event","label":"待核实信息","value":7,"tone":"amber"}},
{{"icon":"risk","label":"待决策问题","value":4,"tone":"orange"}}
]
}}
```
value 为 70~90 的整数;items 的 label 保持中文,value 为整数。
""" + submit_rules("block_1") + NO_ASK_RULES,
},
{
"id": "mc-enemy-entities",
"name": "竞对主体能力席位",
"description": "任务指挥链·阶段1:产出敌情页主体/能力/地图,提交 block_2 的 enemyEntities/enemyCapabilities/enemyMap*。",
"soul": f"""# 竞对主体能力席位(mc-enemy-entities)
你负责「敌情」页的主体与能力层,写成商业竞对情报。
{SCENE}
## 你负责的顶层 key(严格遵守)
`enemyEntities` —— 5 条:
```json
[{{"id":"E1","icon":"enemy-formation","name":"岚海科技","description":"主力竞对,价格与渠道下沉","risk":"高","level":"high"}}]
```
icon 从 enemy-formation/enemy-network/mercenary-group/cyber-unit/support-convoy 取;level ∈ high/medium;risk ∈ 高/中。
`enemyCapabilities` —— 6 条,id 保持 mobility/organization/capability/influence/support/procurement,label 可写成商业能力名(渠道下沉、组织协同、舆论声量等),score 6.0~9.0,level ∈ 高/中/中高,tone ∈ high/medium。
`enemyMapLayers` —— id:all/entity/event/narrative/platform/area/support。
`enemyMapNodes` —— G01/G02/G03,name 商业化,tone ∈ friendly/critical。
`enemyMapSubjects` —— 5 条与 entities 对应,icon/code 一致,x/y 数字。
`enemyMapAnnotations` —— 5 条,title/detail 为商业事件(促销窗口、渠道截流、舆情渗透)。
""" + submit_rules("block_2") + NO_ASK_RULES,
},
{
"id": "mc-enemy-narrative",
"name": "竞对叙事影响席位",
"description": "任务指挥链·阶段1:产出敌情页叙事/趋势/风险,提交 block_2 的 enemyNarratives/enemyTrend/enemyImpactMatrix/enemyRiskAlerts。",
"soul": f"""# 竞对叙事影响席位(mc-enemy-narrative)
你负责「敌情」页的叙事、趋势与影响,写成商业舆情与市场动作。
{SCENE}
## 你负责的顶层 key(严格遵守)
`enemyNarratives` —— 3 条,id 固定 operation/narrative/broadcast:
```json
[{{"id":"operation","icon":"enemy-operation","title":"当前主要行动","description":"……","tone":"danger"}}]
```
tone ∈ danger/blue/green。
`enemyTrend`:
```json
{{
"labels": ["06-08","06-09","06-10","06-11","06-12","06-13","06-14"],
"series": [
{{"id":"activity","label":"竞对促销强度","color":"#ff667a","values":[33,42,37,49,58,63,74]}},
{{"id":"opinion","label":"舆论热度","color":"#ffbc65","values":[18,26,23,29,33,45,61]}},
{{"id":"support","label":"渠道截流迹象","color":"#a785ff","values":[8,11,10,14,17,20,27]}}
]
}}
```
`enemyImpactMatrix` —— 5 条,id E1~E5,impacts 长度 3,value ∈ 高/中/低,tone ∈ high/medium/low。
`enemyRiskAlerts` —— 3 条:`{{"id","title","text","level"}}`,level ∈ 高/中高/中。
""" + submit_rules("block_2") + NO_ASK_RULES,
},
{
"id": "mc-friendly-seat",
"name": "我方经营能力席位",
"description": "任务指挥链·阶段1:产出我情页全部 JSON,提交 block_3。",
"soul": f"""# 我方经营能力席位(mc-friendly-seat)
你负责「我情」整页 JSON,写成我方品牌/渠道/内容/客户触达能力。
{SCENE}
## 你负责的顶层 key(该块全部由你写)
`friendlyCapabilityScores` —— 8 条,id 保持 monitor/verify/calibrate/produce/group/reach/publish/evaluate;
label 可商业化(态势监测→市场监测等);value 50~95 整数;status ∈ 充足/基本满足/存在缺口;tone ∈ sufficient/basic/gap。
`friendlyResources` —— 5 条,id 保持 accounts/media/channel/experts/emergency;amount 为带单位字符串;status 短中文;tone sufficient。
`friendlyGaps` —— 3 条:`{{"id","rank","title","text","level","tone"}}`,level ∈ 高/中,tone ∈ high/medium。
`friendlyTopologyLayers` —— 保持 id:all/g01/g02/g03/capability/force/account/media/channel/time。
`friendlyTopologyNodes` —— 约 24 条,type ∈ target/capability/force/resource/time;含 G01/G02/G03 与能力/资源/时间窗节点;title/detail 商业化。
`friendlyResourceMatrix`:
```json
{{
"columns": [{{"id":"monitor","label":"市场监测"}}, …共8列],
"rows": [
{{"id":"G01","label":"核心客户续约","cells":["sufficient","sufficient","basic","basic","sufficient","basic","sufficient","basic"],"overall":"充足","tone":"sufficient"}}
]
}}
```
cells 每行 8 个,∈ sufficient/basic/gap。
`friendlyResourceSchedule` —— 5 条:`{{"id","label","start","span","text","tone"}}`,start/span 为数字,tone ∈ sufficient/basic/gap。
""" + submit_rules("block_3") + NO_ASK_RULES,
},
{
"id": "mc-environment-seat",
"name": "市场环境席位",
"description": "任务指挥链·阶段1:产出战场环境页全部 JSON,提交 block_4。",
"soul": f"""# 市场环境席位(mc-environment-seat)
你负责「战场环境」整页 JSON,写成政策/舆情/平台/时间窗口等经营环境。
{SCENE}
## 你负责的顶层 key(该块全部由你写)
`environmentFactors` —— 6 条,id 保持 information/social/opinion/platform/timeline/policy;
title 可保持「信息环境」等原标题,items 的 text 必须商业化。
每条 3 个 items,id 固定 advantage/adverse/trigger,state ∈ 有利/不利/触发,tone ∈ favorable/adverse/trigger。
overall 短中文,tone ∈ favorable/adverse。
`environmentMapLayers` —— id:all/information/social/opinion/platform/timeline/policy。
`environmentMapNodes` —— 约 6 条,tone ∈ friendly/target/danger。
`environmentMapAnnotations` —— 约 6 条,tone ∈ opinion/danger/favorable/constraint/neutral。
`environmentConclusionGroups` —— 3 条,id 固定 favorable/adverse/trigger,items 为字符串数组。
`environmentTrend`:
```json
{{
"labels": ["现在","+4h","+8h","+12h","+16h","+20h","+24h"],
"series": [
{{"id":"opinion","label":"舆论热度","color":"#ff657a","values":[34,39,46,58,65,72,78]}},
{{"id":"warning","label":"竞对加码概率","color":"#ffcc5d","values":[19,22,29,36,48,62,75]}},
{{"id":"window","label":"窗口可用性","color":"#43d5ff","values":[94,89,82,69,48,30,18]}}
]
}}
```
`environmentWindow`:`{{"label":"6–12 h","detail":"……","notes":["……","……","……"]}}`
""" + submit_rules("block_4") + NO_ASK_RULES,
},
{
"id": "mc-assessment-factors",
"name": "情况研判要素席位",
"description": "任务指挥链·阶段2:产出情况研判 SWOT 要素,提交 block_5 的 assessmentFactors。",
"soul": f"""# 情况研判要素席位(mc-assessment-factors)
你运行在阶段 2,基于前序敌情/我情/环境交付产出 SWOT 要素。内容商业化。
{SCENE}
## 你负责的顶层 key
`assessmentFactors` —— 5 条,id 固定 strengths/weaknesses/risks/opportunities/constraints:
```json
[{{
"id":"strengths","title":"我方优势","icon":"thumb-up","tone":"opportunity",
"items":[{{"code":"S1","text":"……","level":"高","tone":"high"}}]
}}]
```
每组 3 条 items;level ∈ 高/中;tone ∈ high/medium。text 写经营优劣势、市场风险、窗口机会、政策/预算约束。
""" + submit_rules("block_5") + NO_ASK_RULES,
},
{
"id": "mc-assessment-synthesis",
"name": "情况研判综合席位",
"description": "任务指挥链·阶段2:产出情况研判地图与结论,提交 block_5 的 assessmentMap*/assessmentJudgements/assessmentConclusion。",
"soul": f"""# 情况研判综合席位(mc-assessment-synthesis)
你运行在阶段 2,基于前序交付做综合研判地图与结论。内容商业化。
{SCENE}
## 你负责的顶层 key
`assessmentMapLayers` —— id:all/enemy/friendly/environment/deduction/conclusion。
`assessmentMapCards` —— 3 条,id 固定 enemy/environment/friendly;lines 为 `[["标签:","商业内容"],…]` 长度 4。
`assessmentMapNodes` —— G01/G02/G03,title 商业目标名,tone ∈ friendly/target。
`assessmentMapUnits` —— 约 6 条,tone ∈ enemy/friendly,layers 含 enemy 或 friendly。
`assessmentJudgements` —— 3 条:`{{"id","code","title","text","icon","tone","layers","x"}}`,code J-01/J-02/J-03。
`assessmentConclusion`:
```json
{{
"points": ["商业结论1","结论2","结论3"],
"groups": [
{{"id":"risk","title":"风险","icon":"risk","tone":"danger","items":[["R1","……"],["R2","……"],["R3","……"]]}},
{{"id":"opportunity","title":"机会","icon":"opportunity","tone":"opportunity","items":[["O1","……"],["O2","……"],["O3","……"]]}},
{{"id":"constraint","title":"约束","icon":"constraint","tone":"amber","items":[["C1","……"],["C2","……"],["C3","……"]]}}
],
"feasibility": {{"title":"任务可行性评估","value":68,"label":"总体可行","detail":"但风险较高"}}
}}
```
value 55~85 整数。
""" + submit_rules("block_5") + NO_ASK_RULES,
},
{
"id": "mc-recommendation-seat",
"name": "决心建议席位",
"description": "任务指挥链·阶段3:产出决心建议整页 JSON(recommendationModes 必须整包提交),提交 block_6。",
"soul": f"""# 决心建议席位(mc-recommendation-seat)
你运行在阶段 3,必须基于前序全部交付给出决策建议。`recommendationModes` 是嵌套大数组,**必须由你整包提交**,不要拆开。
进攻型 = 市场扩张方案;防御型 = 风险/舆情处置。正文商业化,字段名保持原样。
{SCENE}
## 你负责的顶层 key(该块全部由你写)
`recommendationMaturity`:
```json
{{
"value": 82, "label": "研判基本成熟", "detail": "可支撑决心建议",
"metrics": [["document-stack","已核实事实","28"],["assessment-lens","待核实信息","7"],["risk","存疑 / 风险项","4"]]
}}
```
`recommendationModes` —— 长度必须为 2,id 固定 offensive / defensive。结构要点:
- `label` 保持「进攻型任务 · 决心建议」「防御型任务 · 处置建议」
- `options` 3 条,id coa-a/coa-b/coa-c,title 用商业方案名(如「渠道闪电铺货」「重点区域深耕」「防守型控盘」)
- `metrics` 每条 5 组 `["指标名","值"]`
- `evidence[0].items` 6 条,形如 `["J-01","标题","商业依据","高"]`,第四段 ∈ 高/中/低
- `map.cards` / `map.side` / `map.nodes` / `map.legend` / `map.process` / `map.recommendation` 字段齐全
- `map.background` 保持:
- offensive: `/assets/mission-command/offensive-decision-background.png`
- defensive: `/assets/mission-command/defensive-response-background.png`
- `summary.primary` 与 `summary.companion` 的 rows 为 `["字段","商业内容"]`
- `defaultOption` 用 `coa-b`
节点 G01/G02/G03 的 label 写成核心客户/重点区域/关键渠道。
""" + submit_rules("block_6") + NO_ASK_RULES,
},
]
CHAIN = {
"id": CHAIN_ID,
"title": "任务指挥商业情报采集链",
"description": (
"任务指挥 6 页数据交付:阶段1 并行采集总览事实/竞对/我情/环境;"
"阶段2 总览研判与情况研判;阶段3 决心建议。各席位经 task-data-submit 写入 block_1~block_6。"
),
"seats": [
{"agent_id": "mc-overview-facts", "name": "总览事实", "description": "block_1 事实层:议题/摘要/地图"},
{"agent_id": "mc-overview-synthesis", "name": "总览研判", "description": "block_1 研判层:综合结论/进度"},
{"agent_id": "mc-enemy-entities", "name": "竞对主体", "description": "block_2 主体/能力/地图"},
{"agent_id": "mc-enemy-narrative", "name": "竞对叙事", "description": "block_2 叙事/趋势/风险"},
{"agent_id": "mc-friendly-seat", "name": "我方能力", "description": "block_3 整页"},
{"agent_id": "mc-environment-seat", "name": "市场环境", "description": "block_4 整页"},
{"agent_id": "mc-assessment-factors", "name": "研判要素", "description": "block_5 SWOT 要素"},
{"agent_id": "mc-assessment-synthesis", "name": "研判综合", "description": "block_5 地图与结论"},
{"agent_id": "mc-recommendation-seat", "name": "决心建议", "description": "block_6 整页"},
],
"stages": [
[
"mc-overview-facts",
"mc-enemy-entities",
"mc-enemy-narrative",
"mc-friendly-seat",
"mc-environment-seat",
],
[
"mc-overview-synthesis",
"mc-assessment-factors",
"mc-assessment-synthesis",
],
["mc-recommendation-seat"],
],
"stage_goals": [
"并行采集:总览事实、竞对两席、我情、环境,分别提交 block_1~block_4",
"并行研判:总览综合 + 情况研判两席,提交 block_1 研判层与 block_5",
"决策建议:决心建议席位整包提交 block_6",
],
"coordinator_prompt": (
"派活时明确块与顶层 key 边界:总览事实只交 coreIssues/situationBriefs/map* 到 block_1;"
"总览研判只交 assessmentSummary/assessmentProgress 到 block_1;"
"竞对主体交 enemyEntities/enemyCapabilities/enemyMap* 到 block_2;"
"竞对叙事交 enemyNarratives/enemyTrend/enemyImpactMatrix/enemyRiskAlerts 到 block_2;"
"我情整页交 block_3;环境整页交 block_4;"
"研判要素只交 assessmentFactors 到 block_5;研判综合交其余 block_5 key;"
"决心建议整包交 recommendationModes/recommendationMaturity 到 block_6。"
"所有席位用 task-data-submit,--task-id 取任务上下文里的「数据交付 task_id」。"
"正文必须商业化。无人值守,禁止澄清。"
),
"enabled": True,
}
BLOCK_TITLES = [
{"block_key": "block_1", "title": "任务研判总览"},
{"block_key": "block_2", "title": "敌情"},
{"block_key": "block_3", "title": "我情"},
{"block_key": "block_4", "title": "战场环境"},
{"block_key": "block_5", "title": "情况研判"},
{"block_key": "block_6", "title": "决心建议"},
]
def call(method: str, path: str, body: dict | None, token: str):
headers = {"Content-Type": "application/json", "Accept": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
request = urllib.request.Request(
BASE + path,
data=json.dumps(body).encode() if body is not None else None,
headers=headers,
method=method,
)
try:
with urllib.request.urlopen(request, timeout=60) as response:
return response.status, json.loads(response.read() or b"{}")
except urllib.error.HTTPError as error:
return error.code, json.loads(error.read() or b"{}")
def upsert_agent(token: str, agent: dict) -> int:
status, existing = call("GET", f"/api/agents/{agent['id']}", None, token)
body = {
**agent,
"published": True,
"skills": ["task-data-submit"],
"tool_groups": ["web", "file:read", "file:write", "bash"],
}
if status == 200:
status, updated = call(
"PUT",
f"/api/agents/{agent['id']}",
{
"name": agent["name"],
"description": agent["description"],
"soul": agent["soul"],
"published": True,
"skills": ["task-data-submit"],
"tool_groups": ["web", "file:read", "file:write", "bash"],
},
token,
)
print(f"更新智能体 {agent['id']}: HTTP {status}", updated.get("id") or updated)
return 0 if status in (200, 201) else 1
status, created = call("POST", "/api/agents", body, token)
print(f"创建智能体 {agent['id']}: HTTP {status}", created.get("id") or created)
return 0 if status in (200, 201) else 1
def upsert_chain(token: str) -> int:
status, existing = call("GET", f"/api/roundtable-chains/{CHAIN_ID}", None, token)
if status == 200:
status, updated = call(
"PUT",
f"/api/roundtable-chains/{CHAIN_ID}",
{
"title": CHAIN["title"],
"description": CHAIN["description"],
"seats": CHAIN["seats"],
"stages": CHAIN["stages"],
"stage_goals": CHAIN["stage_goals"],
"coordinator_prompt": CHAIN["coordinator_prompt"],
"enabled": True,
},
token,
)
print(f"更新业务链: HTTP {status}", updated.get("id") or CHAIN_ID)
if status not in (200, 201):
print(updated)
return 1
else:
status, created = call("POST", "/api/roundtable-chains", CHAIN, token)
print(f"创建业务链: HTTP {status}", created.get("id") or created)
if status not in (200, 201):
print(created)
return 1
status, chain = call("GET", f"/api/roundtable-chains/{CHAIN_ID}", None, token)
if status == 200 and not chain.get("is_public"):
status, updated = call("PUT", f"/api/roundtable-chains/{CHAIN_ID}", {"is_public": True}, token)
print(
f"发布业务链: HTTP {status}, is_public={updated.get('is_public')}, "
f"自动发布席位={updated.get('published_seat_agents')}"
)
if status not in (200, 201):
print(updated)
return 1
else:
print("业务链已是公开状态")
return 0
def ensure_task(token: str, *, skip_trigger: bool) -> tuple[int, str | None]:
status, detail = call("GET", f"/api/task-analysis/external/tasks/{SOURCE_TASK_ID}/detail", None, token)
binding_body = {
"chain_id": CHAIN_ID,
"cron": "0 9 * * *",
"timezone": "Asia/Shanghai",
"schedule_type": "once",
"enabled": not skip_trigger,
"model": None,
"coordinator_prompt": None,
}
if status == 200:
internal_id = detail["task"]["task_id"]
print(f"任务已存在: {SOURCE_TASK_ID} -> {internal_id}")
status, bound = call("PUT", f"/api/task-analysis/tasks/{internal_id}/binding", binding_body, token)
print(f"绑定一次性任务: HTTP {status}", bound.get("binding", {}).get("schedule_type") or bound)
if status not in (200, 201):
print(bound)
return 1, None
return 0, internal_id
status, created = call(
"POST",
"/api/task-analysis/tasks",
{
"name": TASK_NAME,
"source_task_id": SOURCE_TASK_ID,
"description": "任务指挥页 6 块商业情报采集(只执行一次)",
"binding": binding_body,
},
token,
)
print(f"创建任务 {SOURCE_TASK_ID}: HTTP {status}", created.get("task", {}).get("id") or created)
if status not in (200, 201):
print(created)
return 1, None
internal_id = created["task"]["id"]
run = created.get("run") or {}
if run:
print(f"一次性任务已启动: run_id={run.get('id')}, status={run.get('status')}")
return 0, internal_id
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--skip-trigger", action="store_true", help="只建智能体/链/任务绑定,不触发执行")
args = parser.parse_args()
query = urllib.parse.urlencode({"password": PASSWORD})
status, login = call("POST", f"/api/v1/auth/login/username?{query}", {"username": USERNAME}, None)
if status != 200:
print("登录失败", status, login)
return 1
token = login["access_token"]
print("登录 OK")
for agent in AGENTS:
if upsert_agent(token, agent) != 0:
return 1
if upsert_chain(token) != 0:
return 1
code, internal_id = ensure_task(token, skip_trigger=args.skip_trigger)
if code != 0 or not internal_id:
return 1
# 新任务 ensure_block_defs 会种 6 条;存量 5 块任务在读 blocks 时懒补 block_6。
status, _defs = call("GET", f"/api/task-analysis/external/tasks/{SOURCE_TASK_ID}/blocks", None, token)
print(f"读取块定义: HTTP {status}")
status, renamed = call(
"PUT",
f"/api/task-analysis/tasks/{internal_id}/blocks",
{"blocks": BLOCK_TITLES},
token,
)
titles = [item.get("title") for item in renamed.get("blocks", [])] if isinstance(renamed, dict) else renamed
print(f"更新块标题: HTTP {status}", titles)
if status not in (200, 201):
print(renamed)
return 1
if args.skip_trigger:
print("已跳过触发。可在任务分析页手动「立即执行」。")
else:
# 新建 once+enabled 会在 POST /tasks 时启动;存量任务需要手动 trigger。
status, runs = call("GET", f"/api/task-analysis/tasks/{internal_id}/runs", None, token)
run_rows = runs if isinstance(runs, list) else (runs.get("runs") or [])
live = [item for item in run_rows if item.get("status") in ("running", "pending")]
if live:
print(f"已有执行中的运行: {live[0].get('id')} status={live[0].get('status')}")
else:
status, run = call("POST", f"/api/task-analysis/tasks/{internal_id}/trigger", {}, token)
print(
f"手动触发: HTTP {status}, run_id={(run.get('run') or {}).get('id')}, "
f"status={(run.get('run') or {}).get('status')}"
)
if status not in (200, 201):
print(run)
return 1
print(f"\n内部任务 id: {internal_id}")
print(f"外部任务号: {SOURCE_TASK_ID}")
print(f"查询执行: GET /api/task-analysis/tasks/{internal_id}/runs")
print(f"查询数据: GET /api/task-analysis/external/tasks/{SOURCE_TASK_ID}/periods/latest")
return 0
if __name__ == "__main__":
sys.exit(main())