Compare commits
46 Commits
latest
..
9ee197af20
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ee197af20 | |||
| 5f4ff0e57a | |||
| 8cd81900f8 | |||
| 8281c532f2 | |||
| 7b79c4034d | |||
| 2e6f4b0a10 | |||
| 65a1a11b39 | |||
| 90d591b7bb | |||
| aa8f801a04 | |||
| f1fae1819f | |||
| cdeba0a725 | |||
| 1c226afb69 | |||
| be5e4c87de | |||
| 9373a42841 | |||
| baec7940a5 | |||
| ebb0743fcb | |||
| 50c44ddc2d | |||
| ad34cbeab3 | |||
| 38644bf5a9 | |||
| 6547a87391 | |||
| 45435c8f1b | |||
| 3dfbc7c33e | |||
| 60e5b37913 | |||
| 160136014e | |||
| 4cbddb9e0c | |||
| 2f83add134 | |||
| 4ec6cbed16 | |||
| 2ba4f35a2d | |||
| 5315ff1902 | |||
| 59270b6b29 | |||
| 3021fc42ec | |||
| 319b3c8ea5 | |||
| 0dcb04ee89 | |||
| cbe13dd1df | |||
| 3efd2e2871 | |||
| c5801bbf41 | |||
| 37bee1e775 | |||
| 3c7e02f974 | |||
| f049712b68 | |||
| 883faa2d54 | |||
| fb2b4fad9f | |||
| 1afd0d9f3e | |||
| f20c131bec | |||
| ac2870a938 | |||
| 32babdd8a2 | |||
| f6c45f1ba5 |
@@ -1,4 +1,5 @@
|
|||||||
.git
|
.git
|
||||||
|
.gitignore
|
||||||
node_modules
|
node_modules
|
||||||
.opencode/node_modules
|
.opencode/node_modules
|
||||||
.local.env
|
.local.env
|
||||||
|
|||||||
@@ -18,11 +18,6 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Setup tools
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -qq && sudo apt-get install -y -qq jq
|
|
||||||
jq --version
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
env:
|
env:
|
||||||
SERVER_URL: ${{ github.server_url }}
|
SERVER_URL: ${{ github.server_url }}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
__pycache__/
|
|
||||||
.opencode/node_modules/
|
.opencode/node_modules/
|
||||||
.opencode/skills/
|
|
||||||
.local.env
|
.local.env
|
||||||
.vscode
|
.vscode
|
||||||
docker-compose.yml
|
|
||||||
data/
|
data/
|
||||||
logs/
|
logs/
|
||||||
AGENT_HARNESS_REPORT.md
|
|
||||||
HARNESS_INTRODUCTION.md
|
|
||||||
|
|||||||
@@ -4,96 +4,31 @@ mode: primary
|
|||||||
model: deepseek/deepseek-v4-pro
|
model: deepseek/deepseek-v4-pro
|
||||||
temperature: 0.2
|
temperature: 0.2
|
||||||
---
|
---
|
||||||
你是 TJWater 供水管网分析 Agent,运用水力专业知识,回复用户时使用简体中文,内容要求简洁准确。
|
您是运行在 opencode 上的默认 TJWater Agent,使用简体中文回复用户的问题。
|
||||||
|
|
||||||
## 工作流生命周期
|
按照以下规则操作:
|
||||||
|
|
||||||
Skills 树是**动态生长的**——工作流不是预置的,而是从实际任务中沉淀出来的:
|
1. 使用 `.opencode/skills/tjwater-skills-root-index` 作为 TJWater 技能树,仅在任务需要该领域知识时加载特定技能。对分析类问题,优先检查 `workflow` 域下是否已有固定工作流(例如 `bottleneck-analysis`);只有在 workflow 不存在、信息不足或需要补充原子能力时,才继续查询其他 API / action skills。
|
||||||
```
|
2. 当您需要后端数据用于推理、总结、诊断或分析时,优先使用 `dynamic_http_call`。
|
||||||
初次遇到问题 → tjwater_cli + Python 脚本拼装 → 验证有效 →
|
3. 当用户主要需要 UI 操作或可视化时,优先使用前端工具(`locate_features`、`view_history`、`view_scada`、`show_chart`)。
|
||||||
→ 立即调用 skill_manager 保存到 skills/workflow/<name>/
|
4. 仅将前端工具视为显示/交互工具,不要假设它们返回数据。
|
||||||
→ 下次遇到同类问题直接加载该 skill,按既定步骤执行
|
5. 保持回复准确、简洁,对供水网络用户在操作上有用。
|
||||||
```
|
6. 尊重用户授权和项目隔离,工具调用失败或无可用数据时,切勿编造后端结果。
|
||||||
|
7. 每次调用任意工具时,必须在工具参数 `reason` 字段中填写本次调用理由,理由需具体且与当前用户问题直接相关。
|
||||||
## 任务执行决策
|
8. 每次按需加载技能(skills)前,先明确说明加载理由,并只加载与当前任务直接相关的最小技能集合。默认遵循 **workflow-first**:先查固定工作流 skill,再按需回落到原子 API skills。
|
||||||
|
9. 当 `dynamic_http_call` 返回 `result_mode = referenced` 和 `result_ref` 时,说明当前只拿到了预览;如果后续推理仍需要完整结果,必须调用 `fetch_result_ref` 回读,不能把 preview 当成完整数据。
|
||||||
收到用户请求时,按以下顺序决策:
|
10. 对 `render_ref`、`result_ref` 或其他引用型结果,默认只使用 preview、摘要、局部字段,或直接把引用传给前端工具;如果引用仅用于渲染/展示(例如 `render_junctions`),直接传引用,不要先读取完整内容再重组。
|
||||||
|
11. 对任何可能很大的引用文件、结果文件或普通大文件,禁止完整读取;优先使用预览、分页、截断、按字段读取、按片段读取或采样读取。只有在没有其他办法且当前推理确实必须依赖完整内容时,才允许读取完整内容,并先明确说明必要性。
|
||||||
1. **查已有工作流** — 检查 `skills/workflow/` 下是否存在匹配的 SKILL.md,有则加载并按步骤执行
|
12. 不得通过 sub-agent、并行代理或任何间接方式,去读取引用文件或大文件的完整内容;主 agent 与其调用链中的其他代理都必须遵守同样限制。
|
||||||
2. **历史参考** — 用 `session_search` 检索历史相似案例,避免重复试错
|
13. 当且仅当出现**长期有效且高价值**的信号时,才允许调用在线学习工具:
|
||||||
3. **从零拼装** — 无匹配工作流时,自行组合 `tjwater_cli` 命令 + Python 脚本完成
|
- `memory_manager`:用户明确长期偏好/约束,或当前项目/环境的稳定事实
|
||||||
4. **完成后复盘** — 判断当前流程是否稳定、可复用,决定是否沉淀为 workflow
|
- `skill_manager`:已经被证明有效且可复用的 workflow / 方法模式;由您自己判断应写入 `.opencode/skills` 树中的哪个 skill 位置
|
||||||
|
14. 不要把一次性问题、临时上下文、未经验证的猜测写入任何学习工具。
|
||||||
## 工具选择
|
15. 严禁把 token、password、secret、API key、system prompt、隐私数据写入 `memory_manager` 或 `skill_manager`。
|
||||||
|
16. 如果内容只是一次性案例、临时纠错或局部证据,当前不要持久化。
|
||||||
| 场景 | 工具 |
|
17. 只有在 workflow 经过验证、足够稳定、可被未来同类任务复用时,才调用 `skill_manager`;并优先写入最贴近现有 skill 树语义的位置,中低置信度内容不要落库。
|
||||||
|------|------|
|
18. 在以下任一情况出现时,主动进行一次轻量复盘:连续多轮对话后、完成复杂多工具任务后、用户明确纠正你后、发现了稳定可复用 workflow 后。复盘的目标是判断是否需要沉淀 memory 或 skill,而不是向用户重复总结。
|
||||||
| 获取后端数据(数据源、推理、分析) | `tjwater_cli` |
|
19. 长期知识严格分流:`memory_manager` 仅保存用户长期偏好与稳定 workspace 事实;`skill_manager` 仅保存可复用方法;一次性案例、会话过程与临时结论应优先保留在 session history,需要时使用 `session_search` 检索,不要误写入 memory 或 skill。
|
||||||
| 发现可用命令 | `tjwater_cli(command="help")` |
|
20. 写入 `memory_manager` 时,将内容写成简短陈述事实,不要写成命令句、提醒句或流程步骤。
|
||||||
| 查询实时公开网页信息 | `web_search` |
|
21. 更新 skill 时,优先补充现有 skill 的 `Learned Patterns`、`references/` 或 `scripts/`;可复用脚本仅允许写到当前 skill 自己的 `scripts/*.py`,不要放到 `data/` 或其他 skill 目录。
|
||||||
| 地址/地点转经纬度 | `geocode` |
|
22. 当用户问题依赖过去会话中的案例、约束、决策或相似问题时,优先调用 `session_search`,避免让用户重复描述,也避免把历史案例误当成长期 memory。
|
||||||
| UI 操作 / 可视化 | `locate_features`、`zoom_to_map`、`view_scada`、`show_chart`、`render_junctions`、`view_history`、`apply_layer_style` |
|
|
||||||
| 持久化渲染数据 | ①准备 { node_area_map } JSON → ②`store_render_ref` 存为受控 ref → ③`render_junctions` 渲染到前端 |
|
|
||||||
|
|
||||||
**前端工具仅做显示,不返回数据**,不要假设其返回内容。
|
|
||||||
|
|
||||||
## 执行约束
|
|
||||||
|
|
||||||
1. 每次工具调用必须在 `reason` 字段填写具体理由
|
|
||||||
2. `tjwater-cli` 输出为 JSON(`schema_version: tjwater-cli/v1`),`"ok": true` 成功,失败时检查 `error.code`
|
|
||||||
3. 大结果集禁止完整读取,优先采样/截断/按字段读取
|
|
||||||
4. 避免直接用 `Read` 或 `cat` 读取结果文件,尤其是大文件;优先用 `head`/`tail`/`rg` 截断查看,或用 Python 只向 stdout 输出精简 JSON,避免大文件冲击 stdin/stdout
|
|
||||||
5. 无可用数据时不得编造结果
|
|
||||||
6. 尽量不使用 `task` 子代理,避免无法观测过程进行人为干预
|
|
||||||
|
|
||||||
## 工作流沉淀(skill_manager)
|
|
||||||
|
|
||||||
**写入条件**(必须同时满足):
|
|
||||||
- 经过当前对话验证有效
|
|
||||||
- 可被未来同类任务复用
|
|
||||||
- 非一次性/临时/猜测
|
|
||||||
|
|
||||||
**写入位置**:`skills/workflow/<name>/`,包含 SKILL.md(步骤说明)、references/*.md(参考材料)和 scripts/*.py(分析脚本)。
|
|
||||||
|
|
||||||
**工具动作**:`write_skill / remove_skill` 维护主 SKILL.md;`append_pattern / remove_pattern` 维护 `## Learned Patterns`;`write_reference / remove_reference` 维护 references/*.md;`write_script / remove_script` 维护 scripts/*.py。`write_skill` 可创建或覆盖完整 SKILL.md。
|
|
||||||
|
|
||||||
目录入口也通过 `skill_manager` 维护:更新 `skills/workflow/SKILL.md` 时使用 `write_skill(skill_path="workflow", ...)`,更新根入口 `skills/SKILL.md` 时使用 `write_skill(skill_path="__root__", ...)`。
|
|
||||||
|
|
||||||
**脚本编写要求——优先用 pipe 串联**:
|
|
||||||
|
|
||||||
workflow skill 脚本应尽量用 shell pipe 在一次 subprocess 调用中串联多个 CLI 命令。减少 tool calling 次数,提升执行效率。
|
|
||||||
|
|
||||||
```python
|
|
||||||
import subprocess, os
|
|
||||||
|
|
||||||
# env dict 仅用于当前子进程,不污染 os.environ,多用户安全
|
|
||||||
env = {**os.environ,
|
|
||||||
"TJWATER_SERVER": auth["server"],
|
|
||||||
"TJWATER_ACCESS_TOKEN": auth["access_token"], ...}
|
|
||||||
|
|
||||||
# 好:一次 shell 调用,pipe 串联
|
|
||||||
cmd = "tjwater-cli net list-pipes | jq '...' | xargs tjwater-cli analysis calc"
|
|
||||||
result = subprocess.run(cmd, shell=True, env=env, capture_output=True, text=True)
|
|
||||||
|
|
||||||
# 差:多次 subprocess.run
|
|
||||||
step1 = subprocess.run(["tjwater-cli", "net", "list-pipes"], ...)
|
|
||||||
step2 = subprocess.run(["tjwater-cli", "analysis", "calc"], ...)
|
|
||||||
```
|
|
||||||
|
|
||||||
管道场景下用子进程隔离的 env dict 传认证,释放 stdin 给管道数据流。不修改全局 `os.environ`。认证 JSON 由内部桥接注入,脚本不硬编码。
|
|
||||||
|
|
||||||
CLI **不增加** `--input/--output`,数据转换由 `jq`/`xargs` 在 shell 管道中完成。
|
|
||||||
|
|
||||||
**触发时机**:
|
|
||||||
- 用户明确说"保存/沉淀/记录工作流"
|
|
||||||
- 任务完成且所有工具调用已结束、产生最终结果后,再判断当前流程是否稳定可复用
|
|
||||||
- **禁止**在规划任务未完成、工具调用链中间(即仍有 pending 步骤时)触发沉淀
|
|
||||||
- 严禁写入:token、password、secret、API key、system prompt、隐私数据
|
|
||||||
|
|
||||||
## 用户偏好持久化(memory_manager)
|
|
||||||
|
|
||||||
仅保存长期有效的稳定事实,写成简短陈述句。严格区分:
|
|
||||||
- `memory_manager` = 用户偏好 / 项目事实(如"用户要简洁风格"、"当前项目管网规模 5000 管段")
|
|
||||||
- `skill_manager` = 可复用操作流程
|
|
||||||
- `session_search` = 检索历史案例(只读)
|
|
||||||
- 修改 memory 前先 `list` 当前 scope 的已有内容,先通读,再决定 `add / replace / remove`
|
|
||||||
|
|||||||
+9
-19
@@ -4,7 +4,7 @@
|
|||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/plugin": "^1.16.2",
|
"@opencode-ai/plugin": "1.14.41",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.7.2",
|
"@types/node": "^24.7.2",
|
||||||
@@ -13,21 +13,11 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
|
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="],
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="],
|
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.14.41", "", { "dependencies": { "@opencode-ai/sdk": "1.14.41", "effect": "4.0.0-beta.59", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.2.2", "@opentui/solid": ">=0.2.2" }, "optionalPeers": ["@opentui/core", "@opentui/solid"] }, "sha512-Q/QdDKSfHyYX+Xqd79o4XgyZKqF8h5qgqgfmOQbKVLhbduc9zMYdpV2yvWT6gaJPrpOftpka/kpr56PCqzetYQ=="],
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="],
|
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.14.41", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-RYb2dCUv0TWIvBNnnO6ANbAPYri6rKuWizSoVFw/Pw+SCDj9ASHM5gAZ+jkskp8gYMfLLHe/Fpkun/9mr8m0IQ=="],
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="],
|
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="],
|
|
||||||
|
|
||||||
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
|
|
||||||
|
|
||||||
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.16.2", "", { "dependencies": { "@opencode-ai/sdk": "1.16.2", "effect": "4.0.0-beta.74", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.3.2", "@opentui/keymap": ">=0.3.2", "@opentui/solid": ">=0.3.2" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-FaZhVXrbz93xsdGLCtarRDTeqFt8AkLfh8B34tFBj6G4HXVmKSgBwVXmtELKKC+08xMtawBC9hshiMbXryv6cg=="],
|
|
||||||
|
|
||||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.16.2", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-Z/xZ7q79dYeE0afqIk/yFEcRNGEQFcE+H8ssYivUiy+xGZ1mGwT72jpaQZKBwPn3JH4sRCu4KA2lcktBQfcOjg=="],
|
|
||||||
|
|
||||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
@@ -37,21 +27,21 @@
|
|||||||
|
|
||||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
"effect": ["effect@4.0.0-beta.74", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-Yx+Kh12U+i2FmjwEfKs+ePFmpMd43RPD1oGqc/VraSS9bYzvF0Ff3PojwEFEVEewp8xc92Uxu28gTspU4qyvHA=="],
|
"effect": ["effect@4.0.0-beta.59", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-xyUDLeHSe8d6lWGOvR6Fgn2HL6gYeTZ/S4Jzk9uc4ZUxMPPsNZlNXrvk0C7/utQFzeX7uAWcVnG2BjbA0SRoAA=="],
|
||||||
|
|
||||||
"fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="],
|
"fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="],
|
||||||
|
|
||||||
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
|
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
|
||||||
|
|
||||||
"ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="],
|
"ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
|
||||||
|
|
||||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
|
||||||
"kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
|
"kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
|
||||||
|
|
||||||
"msgpackr": ["msgpackr@2.0.2", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ=="],
|
"msgpackr": ["msgpackr@1.11.12", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg=="],
|
||||||
|
|
||||||
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
|
"msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="],
|
||||||
|
|
||||||
"multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
|
"multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
|
||||||
|
|
||||||
@@ -71,7 +61,7 @@
|
|||||||
|
|
||||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
"uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="],
|
"uuid": ["uuid@13.0.2", "", { "bin": "dist-node/bin/uuid" }, "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw=="],
|
||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"typecheck": "tsc --noEmit -p tsconfig.json"
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/plugin": "^1.16.2"
|
"@opencode-ai/plugin": "1.14.41"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.7.2",
|
"@types/node": "^24.7.2",
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-skills-root-index
|
||||||
|
description: TJWater Skills 分层索引(Domain -> Scenario -> Action)。
|
||||||
|
version: 1.2.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# TJWater Skills
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
按“领域 (Domain) -> 场景 (Scenario) -> 操作 (Action)”组织技能文档,逐层进入具体能力。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
|
||||||
|
- **analytics**: 见 `./analytics/SKILL.md`
|
||||||
|
- **business**: 见 `./business/SKILL.md`
|
||||||
|
- **data**: 见 `./data/SKILL.md`
|
||||||
|
- **platform**: 见 `./platform/SKILL.md`
|
||||||
|
- **workflow**: 见 `./workflow/SKILL.md`
|
||||||
|
|
||||||
|
## 加载策略
|
||||||
|
|
||||||
|
- 先按用户问题判断最可能的 Domain,再进入最小必要的 Scenario / Action。
|
||||||
|
- 对分析、诊断、建议类问题,优先检查 `workflow/` 下是否已有固定工作流 skill;若存在,可先按 workflow 执行,再回补所需原子 skills。
|
||||||
|
- 如果当前节点已经足以指导工具选择,不继续下钻到更多 skill。
|
||||||
|
- 如果 workflow 已覆盖主要步骤,则不要先从大量 API skills 开始拼装流程;仅在 workflow 缺失、步骤不全或需要额外原子能力时,才继续下钻。
|
||||||
|
- 优先更新已有 skill,而不是为一次性问题新增新的 skill 目录。
|
||||||
|
- learned pattern 应写成可复用的方法或坑点,不应写成某次会话的流水账。
|
||||||
|
- 某个 workflow 反复验证过的私有辅助脚本,应放在该 skill 目录下的 `scripts/*.py`,并随 skill 一起维护;不要写入 `data/`。
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
|
||||||
|
- 示例:`./examples.md`
|
||||||
|
- 运行手册:`./runbook.md`
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-domain-analytics
|
||||||
|
description: 负责仿真分析、SCADA 分析等计算类 API 能力。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Analytics Domain Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责仿真分析、SCADA 分析等计算类 API 能力。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **scada-operations**: 见 `./scada-operations/SKILL.md`
|
||||||
|
- **simulation-analysis**: 见 `./simulation-analysis/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-scenario-analytics-scada-operations
|
||||||
|
description: 负责 SCADA 设备与数据操作。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# scada-operations Scenario Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 SCADA 设备与数据操作。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **scada**: 见 `./scada/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-analytics-scada-operations-scada
|
||||||
|
description: analytics/scada-operations 下 scada 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# scada Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `analytics/scada-operations` 场景下 `scada` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addscadadevice/` | 添加SCADA设备 | network (query) | - |
|
||||||
|
| POST | `/api/v1/addscadadevicedata/` | 添加SCADA设备数据 | network (query) | - |
|
||||||
|
| POST | `/api/v1/addscadaelement/` | 添加SCADA元素映射 | network (query) | - |
|
||||||
|
| POST | `/api/v1/cleanscadadevice/` | 清空SCADA设备表 | network (query) | - |
|
||||||
|
| POST | `/api/v1/cleanscadadevicedata/` | 清空SCADA设备数据表 | network (query) | - |
|
||||||
|
| POST | `/api/v1/cleanscadaelement/` | 清空SCADA元素映射表 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deletescadadevice/` | 删除SCADA设备 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deletescadadevicedata/` | 删除SCADA设备数据 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deletescadaelement/` | 删除SCADA元素映射 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallscadadeviceids/` | 获取所有SCADA设备ID | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallscadadevices/` | 获取所有SCADA设备 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallscadainfo/` | 获取所有SCADA信息 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallscadaproperties/` | 获取所有SCADA属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getscadadevice/` | 获取SCADA设备 | network (query), id (query) | - |
|
||||||
|
| GET | `/api/v1/getscadadevicedata/` | 获取SCADA设备数据 | network (query), device_id (query) | - |
|
||||||
|
| GET | `/api/v1/getscadadevicedataschema/` | 获取SCADA设备数据架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getscadadeviceschema/` | 获取SCADA设备架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getscadaelement/` | 获取单个SCADA元素映射 | network (query), id (query) | - |
|
||||||
|
| GET | `/api/v1/getscadaelements/` | 获取所有SCADA元素映射 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getscadaelementschema/` | 获取SCADA元素架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getscadainfo/` | 获取SCADA信息 | network (query), id (query) | - |
|
||||||
|
| GET | `/api/v1/getscadainfoschema/` | 获取SCADA信息架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getscadaproperties/` | 获取SCADA属性 | network (query), scada (query) | - |
|
||||||
|
| POST | `/api/v1/scada/batch` | 批量插入SCADA监测数据 | data (body) | - |
|
||||||
|
| DELETE | `/api/v1/scada/by-id-time-range` | 按设备ID和时间范围删除SCADA数据 | device_id (query), start_time (query), end_time (query) | - |
|
||||||
|
| GET | `/api/v1/scada/by-ids-field-time-range` | 按设备ID、字段和时间范围查询SCADA数据 | start_time (query), end_time (query), field (query), device_ids (query) | - |
|
||||||
|
| GET | `/api/v1/scada/by-ids-time-range` | 按设备ID和时间范围查询SCADA数据 | start_time (query), end_time (query), device_ids (query) | - |
|
||||||
|
| PATCH | `/api/v1/scada/{device_id}/field` | 更新SCADA设备字段 | device_id (path), time (query), field (query), value (query) | - |
|
||||||
|
| POST | `/api/v1/setscadadevice/` | 更新SCADA设备 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setscadadevicedata/` | 更新SCADA设备数据 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setscadaelement/` | 更新SCADA元素映射 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`DELETE, GET, PATCH, POST`
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-scenario-analytics-simulation-analysis
|
||||||
|
description: 负责仿真、风险、漏损与爆管分析。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# simulation-analysis Scenario Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责仿真、风险、漏损与爆管分析。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **burst_detection**: 见 `./burst_detection/SKILL.md`
|
||||||
|
- **burst_location**: 见 `./burst_location/SKILL.md`
|
||||||
|
- **leakage**: 见 `./leakage/SKILL.md`
|
||||||
|
- **risk**: 见 `./risk/SKILL.md`
|
||||||
|
- **simulation**: 见 `./simulation/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-analytics-simulation-analysis-burst-detection
|
||||||
|
description: analytics/simulation-analysis 下 burst-detection 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# burst-detection Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `analytics/simulation-analysis` 场景下 `burst-detection` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/burst-detection/detect/` | 执行爆管检测 | data (body) | - |
|
||||||
|
| GET | `/api/v1/burst-detection/schemes/` | 查询爆管检测方案列表 | network (query) | query_date (query) |
|
||||||
|
| GET | `/api/v1/burst-detection/schemes/{scheme_name}` | 获取爆管检测方案详情 | network (query), scheme_name (path) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-analytics-simulation-analysis-burst-location
|
||||||
|
description: analytics/simulation-analysis 下 burst-location 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# burst-location Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `analytics/simulation-analysis` 场景下 `burst-location` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/burst-location/locate/` | 执行爆管定位 | data (body) | - |
|
||||||
|
| GET | `/api/v1/burst-location/schemes/` | 查询爆管定位方案列表 | network (query) | query_date (query) |
|
||||||
|
| GET | `/api/v1/burst-location/schemes/{scheme_name}` | 获取爆管定位方案详情 | network (query), scheme_name (path) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-analytics-simulation-analysis-leakage
|
||||||
|
description: analytics/simulation-analysis 下 leakage 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# leakage Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `analytics/simulation-analysis` 场景下 `leakage` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/leakage/identify/` | 执行漏损识别 | data (body) | - |
|
||||||
|
| GET | `/api/v1/leakage/schemes/` | 查询漏损识别方案列表 | network (query) | query_date (query) |
|
||||||
|
| GET | `/api/v1/leakage/schemes/{scheme_name}` | 获取漏损识别方案详情 | network (query), scheme_name (path) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-analytics-simulation-analysis-risk
|
||||||
|
description: analytics/simulation-analysis 下 risk 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# risk Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `analytics/simulation-analysis` 场景下 `risk` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/getnetworkpiperiskprobabilitynow/` | 获取整个网络的管道风险概率 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getpiperiskprobability/` | 获取管道风险概率历史 | network (query), pipe_id (query) | - |
|
||||||
|
| GET | `/api/v1/getpiperiskprobabilitygeometries/` | 获取管道风险几何信息 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getpiperiskprobabilitynow/` | 获取管道当前风险概率 | network (query), pipe_id (query) | - |
|
||||||
|
| GET | `/api/v1/getpipesriskprobability/` | 批量获取多条管道风险概率 | network (query), pipe_ids (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET`
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-analytics-simulation-analysis-simulation
|
||||||
|
description: analytics/simulation-analysis 下 simulation 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# simulation Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `analytics/simulation-analysis` 场景下 `simulation` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/age_analysis/` | 水龄分析(高级) | network (query), start_time (query), end_time (query), duration (query) | - |
|
||||||
|
| GET | `/api/v1/ageanalysis/` | 水龄分析(基础) | network (query) | - |
|
||||||
|
| GET | `/api/v1/burst_analysis/` | 爆管分析(高级) | network (query), modify_pattern_start_time (query), burst_ID (query), burst_size (query), modify_total_duration (query), scheme_name (query) | - |
|
||||||
|
| GET | `/api/v1/burstanalysis/` | 爆管分析(基础) | network (query), pipe_id (query), start_time (query), end_time (query), burst_flow (query) | - |
|
||||||
|
| GET | `/api/v1/contaminant_simulation/` | 污染物模拟 | network (query), start_time (query), source (query), concentration (query), duration (query) | scheme_name (query), pattern (query) |
|
||||||
|
| POST | `/api/v1/daily_scheduling_analysis/` | 日排程分析 | data (body) | - |
|
||||||
|
| GET | `/api/v1/dumpoutput/` | 导出模拟输出 | output (query) | - |
|
||||||
|
| GET | `/api/v1/flushing_analysis/` | 冲洗分析(高级) | network (query), start_time (query), valves (query), valves_k (query), drainage_node_ID (query) | flush_flow (query), duration (query), scheme_name (query) |
|
||||||
|
| GET | `/api/v1/flushinganalysis/` | 冲洗分析(基础) | network (query), pipe_id (query), start_time (query), duration (query), flow (query) | - |
|
||||||
|
| POST | `/api/v1/network_project/` | 导入网络项目 | file (file) | - |
|
||||||
|
| POST | `/api/v1/network_update/` | 管网更新(高级) | file (file) | - |
|
||||||
|
| GET | `/api/v1/networkupdate/` | 管网更新(基础) | network (query) | - |
|
||||||
|
| POST | `/api/v1/pressure_regulation/` | 压力调节(高级) | data (body) | - |
|
||||||
|
| POST | `/api/v1/pressure_sensor_placement_kmeans/` | 压力传感器放置-KMeans聚类分析(高级) | data (body) | - |
|
||||||
|
| POST | `/api/v1/pressure_sensor_placement_sensitivity/` | 压力传感器放置-灵敏度分析(高级) | data (body) | - |
|
||||||
|
| GET | `/api/v1/pressureregulation/` | 压力调节(基础) | network (query), target_node (query), target_pressure (query) | - |
|
||||||
|
| GET | `/api/v1/pressuresensorplacementkmeans/` | 压力传感器放置-KMeans聚类分析(基础) | name (query), scheme_name (query), sensor_number (query), min_diameter (query), username (query) | - |
|
||||||
|
| GET | `/api/v1/pressuresensorplacementsensitivity/` | 压力传感器放置-灵敏度分析(基础) | name (query), scheme_name (query), sensor_number (query), min_diameter (query), username (query) | - |
|
||||||
|
| POST | `/api/v1/project_management/` | 项目管理(高级) | data (body) | - |
|
||||||
|
| GET | `/api/v1/projectmanagement/` | 项目管理(基础) | network (query) | - |
|
||||||
|
| POST | `/api/v1/pump_failure/` | 泵故障管理 | data (body) | - |
|
||||||
|
| GET | `/api/v1/runinp/` | 运行INP文件 | network (query) | - |
|
||||||
|
| GET | `/api/v1/runproject/` | 运行项目模拟 | network (query) | - |
|
||||||
|
| GET | `/api/v1/runprojectreturndict/` | 运行项目模拟(返回字典) | network (query) | - |
|
||||||
|
| POST | `/api/v1/runsimulationmanuallybydate/` | 手动运行日期指定模拟 | data (body) | - |
|
||||||
|
| POST | `/api/v1/scheduling_analysis/` | 排程分析 | data (body) | - |
|
||||||
|
| POST | `/api/v1/sensorplacementscheme/create` | 传感器放置方案创建 | network (query), scheme_name (query), sensor_type (query), method (query), sensor_count (query), user_name (query) | min_diameter (query) |
|
||||||
|
| GET | `/api/v1/valve_close_analysis/` | 阀门关闭分析(高级) | network (query), start_time (query), valves (query) | duration (query) |
|
||||||
|
| GET | `/api/v1/valve_isolation_analysis/` | 阀门隔离分析 | network (query), accident_element (query) | disabled_valves (query) |
|
||||||
|
| GET | `/api/v1/valvecloseanalysis/` | 阀门关闭分析(基础) | network (query), valve_id (query), start_time (query), end_time (query) | - |
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-domain-business
|
||||||
|
description: 负责业务逻辑、业务对象与项目侧 API 能力。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Business Domain Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责业务逻辑、业务对象与项目侧 API 能力。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **component-config**: 见 `./component-config/SKILL.md`
|
||||||
|
- **identity-access**: 见 `./identity-access/SKILL.md`
|
||||||
|
- **network-assets**: 见 `./network-assets/SKILL.md`
|
||||||
|
- **project-workspace**: 见 `./project-workspace/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-scenario-business-component-config
|
||||||
|
description: 负责组件配置与参数管理。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# component-config Scenario Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责组件配置与参数管理。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **controls**: 见 `./controls/SKILL.md`
|
||||||
|
- **curves**: 见 `./curves/SKILL.md`
|
||||||
|
- **options**: 见 `./options/SKILL.md`
|
||||||
|
- **patterns**: 见 `./patterns/SKILL.md`
|
||||||
|
- **quality**: 见 `./quality/SKILL.md`
|
||||||
|
- **visuals**: 见 `./visuals/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-component-config-controls
|
||||||
|
description: business/component-config 下 controls 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# controls Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/component-config` 场景下 `controls` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/getcontrolproperties/` | 获取控制属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getcontrolschema/` | 获取控制架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getruleproperties/` | 获取规则属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getruleschema/` | 获取规则架构 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setcontrolproperties/` | 设置控制属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setruleproperties/` | 设置规则属性 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-component-config-curves
|
||||||
|
description: business/component-config 下 curves 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# curves Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/component-config` 场景下 `curves` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addcurve/` | 添加曲线 | network (query), curve (query) | - |
|
||||||
|
| POST | `/api/v1/deletecurve/` | 删除曲线 | network (query), curve (query) | - |
|
||||||
|
| GET | `/api/v1/getcurveproperties/` | 获取曲线属性 | network (query), curve (query) | - |
|
||||||
|
| GET | `/api/v1/getcurves/` | 获取所有曲线 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getcurveschema` | 获取曲线架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/iscurve/` | 检查曲线存在性 | network (query), curve (query) | - |
|
||||||
|
| POST | `/api/v1/setcurveproperties/` | 设置曲线属性 | network (query), curve (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-component-config-options
|
||||||
|
description: business/component-config 下 options 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# options Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/component-config` 场景下 `options` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/getenergyproperties/` | 获取能耗选项属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getenergyschema/` | 获取能耗选项架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getoptionproperties/` | 获取选项属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getoptionschema/` | 获取选项架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getpumpenergyproperties/` | 获取泵能耗属性 | - | - |
|
||||||
|
| GET | `/api/v1/getpumpenergyschema/` | 获取泵能耗选项架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/gettimeproperties/` | 获取时间选项属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/gettimeschema` | 获取时间选项架构 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setenergyproperties/` | 设置能耗选项属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setoptionproperties/` | 设置选项属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/setpumpenergyproperties/` | 设置泵能耗属性 | - | - |
|
||||||
|
| POST | `/api/v1/settimeproperties/` | 设置时间选项属性 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-component-config-patterns
|
||||||
|
description: business/component-config 下 patterns 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# patterns Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/component-config` 场景下 `patterns` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addpattern/` | 添加模式 | network (query), pattern (query) | - |
|
||||||
|
| POST | `/api/v1/deletepattern/` | 删除模式 | network (query), pattern (query) | - |
|
||||||
|
| GET | `/api/v1/getpatternproperties/` | 获取模式属性 | network (query), pattern (query) | - |
|
||||||
|
| GET | `/api/v1/getpatterns/` | 获取所有模式 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getpatternschema` | 获取模式架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/ispattern/` | 检查模式存在性 | network (query), pattern (query) | - |
|
||||||
|
| POST | `/api/v1/setpatternproperties/` | 设置模式属性 | network (query), pattern (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-component-config-quality
|
||||||
|
description: business/component-config 下 quality 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# quality Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/component-config` 场景下 `quality` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addmixing/` | 添加混合 | network (query) | - |
|
||||||
|
| POST | `/api/v1/addsource/` | 添加水源 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deletemixing/` | 删除混合 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deletesource/` | 删除水源 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/getemitterproperties/` | 获取发射器属性 | network (query), junction (query) | - |
|
||||||
|
| GET | `/api/v1/getemitterschema` | 获取发射器架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getmixing/` | 获取混合属性 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/getmixingschema/` | 获取混合架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getpipereaction/` | 获取管道反应属性 | network (query), pipe (query) | - |
|
||||||
|
| GET | `/api/v1/getpipereactionschema/` | 获取管道反应架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getqualityproperties/` | 获取水质属性 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/getqualityschema/` | 获取水质架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getreaction/` | 获取反应属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getreactionschema/` | 获取反应架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getsource/` | 获取水源属性 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/getsourcechema/` | 获取水源架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/gettankreaction/` | 获取水池反应属性 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankreactionschema/` | 获取水池反应架构 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setemitterproperties/` | 设置发射器属性 | network (query), junction (query) | - |
|
||||||
|
| POST | `/api/v1/setmixing/` | 设置混合属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setpipereaction/` | 设置管道反应属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setqualityproperties/` | 设置水质属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setreaction/` | 设置反应属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setsource/` | 设置水源属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/settankreaction/` | 设置水池反应属性 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-component-config-visuals
|
||||||
|
description: business/component-config 下 visuals 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# visuals Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/component-config` 场景下 `visuals` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addlabel/` | 添加标签 | network (query) | - |
|
||||||
|
| POST | `/api/v1/addvertex/` | 添加图形元素 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deletelabel/` | 删除标签 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deletevertex/` | 删除图形元素 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallvertexlinks/` | 获取所有图形元素链接 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallvertices/` | 获取所有图形元素 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getbackdropproperties/` | 获取背景属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getbackdropschema/` | 获取背景架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getlabelproperties/` | 获取标签属性 | network (query), x (query), y (query) | - |
|
||||||
|
| GET | `/api/v1/getlabelschema/` | 获取标签架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getvertexproperties/` | 获取图形元素属性 | network (query), link (query) | - |
|
||||||
|
| GET | `/api/v1/getvertexschema/` | 获取图形元素架构 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setbackdropproperties/` | 设置背景属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setlabelproperties/` | 设置标签属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setvertexproperties/` | 设置图形元素属性 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-scenario-business-identity-access
|
||||||
|
description: 负责认证、用户与权限相关操作。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# identity-access Scenario Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责认证、用户与权限相关操作。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **auth**: 见 `./auth/SKILL.md`
|
||||||
|
- **user_management**: 见 `./user_management/SKILL.md`
|
||||||
|
- **users**: 见 `./users/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-identity-access-auth
|
||||||
|
description: business/identity-access 下 auth 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# auth Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/identity-access` 场景下 `auth` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/auth/login` | login | form_data (body) | - |
|
||||||
|
| POST | `/api/v1/auth/login/simple` | login_simple | username (query), password (query) | - |
|
||||||
|
| GET | `/api/v1/auth/me` | get_current_user_info | - | - |
|
||||||
|
| POST | `/api/v1/auth/refresh` | refresh_token | refresh_token (query) | - |
|
||||||
|
| POST | `/api/v1/auth/register` | register | user_data (body) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-identity-access-user-management
|
||||||
|
description: business/identity-access 下 user-management 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# user-management Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/identity-access` 场景下 `user-management` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/users/` | 列出所有用户 | - | skip (query), limit (query) |
|
||||||
|
| DELETE | `/api/v1/users/{user_id}` | 删除用户 | user_id (path) | - |
|
||||||
|
| GET | `/api/v1/users/{user_id}` | 获取用户详情 | user_id (path) | - |
|
||||||
|
| PUT | `/api/v1/users/{user_id}` | 更新用户信息 | user_id (path) | user_update (body) |
|
||||||
|
| POST | `/api/v1/users/{user_id}/activate` | 激活用户 | user_id (path) | - |
|
||||||
|
| POST | `/api/v1/users/{user_id}/deactivate` | 停用用户 | user_id (path) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`DELETE, GET, POST, PUT`
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-identity-access-users
|
||||||
|
description: business/identity-access 下 users 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# users Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/identity-access` 场景下 `users` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/getallusers/` | 获取所有用户 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getuser/` | 获取单个用户 | network (query), user_name (query) | - |
|
||||||
|
| GET | `/api/v1/getuserschema/` | 获取用户模式 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET`
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-scenario-business-network-assets
|
||||||
|
description: 负责管网资产与拓扑对象操作。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# network-assets Scenario Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责管网资产与拓扑对象操作。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **demands**: 见 `./demands/SKILL.md`
|
||||||
|
- **general**: 见 `./general/SKILL.md`
|
||||||
|
- **geometry**: 见 `./geometry/SKILL.md`
|
||||||
|
- **junctions**: 见 `./junctions/SKILL.md`
|
||||||
|
- **pipes**: 见 `./pipes/SKILL.md`
|
||||||
|
- **pumps**: 见 `./pumps/SKILL.md`
|
||||||
|
- **regions**: 见 `./regions/SKILL.md`
|
||||||
|
- **reservoirs**: 见 `./reservoirs/SKILL.md`
|
||||||
|
- **tags**: 见 `./tags/SKILL.md`
|
||||||
|
- **tanks**: 见 `./tanks/SKILL.md`
|
||||||
|
- **valves**: 见 `./valves/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-demands
|
||||||
|
description: business/network-assets 下 demands 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# demands Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `demands` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/calculatedemandtonetwork/` | 计算需水量到整网分配 | network (query), demand (query) | - |
|
||||||
|
| GET | `/api/v1/calculatedemandtonodes/` | 计算需水量到节点分配 | network (query) | - |
|
||||||
|
| GET | `/api/v1/calculatedemandtoregion/` | 计算需水量到区域分配 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getdemandproperties/` | 获取需水量属性 | network (query), junction (query) | - |
|
||||||
|
| GET | `/api/v1/getdemandschema` | 获取需水量属性架构 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setdemandproperties/` | 设置需水量属性 | network (query), junction (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-general
|
||||||
|
description: business/network-assets 下 general 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# general Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `general` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/deletelink/` | 删除管线 | network (query), link (query) | - |
|
||||||
|
| POST | `/api/v1/deletenode/` | 删除节点 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/getallscadaproperties/` | 获取所有SCADA点属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getelementproperties/` | 获取元素属性 | network (query), element (query) | - |
|
||||||
|
| GET | `/api/v1/getelementpropertieswithtype/` | 获取指定类型元素属性 | network (query), elementtype (query), element (query) | - |
|
||||||
|
| GET | `/api/v1/getelementtype/` | 获取元素类型 | network (query), element (query) | - |
|
||||||
|
| GET | `/api/v1/getelementtypevalue/` | 获取元素类型值 | network (query), element (query) | - |
|
||||||
|
| GET | `/api/v1/getlinkproperties/` | 获取管线属性 | network (query), link (query) | - |
|
||||||
|
| GET | `/api/v1/getlinks/` | 获取所有管线 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getlinktype/` | 获取管线类型 | network (query), link (query) | - |
|
||||||
|
| GET | `/api/v1/getnodelinks/` | 获取节点的关联管线 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/getnodeproperties/` | 获取节点属性 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/getnodes/` | 获取所有节点 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getnodetype/` | 获取节点类型 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/getscadaproperties/` | 获取SCADA点属性 | network (query), scada (query) | - |
|
||||||
|
| GET | `/api/v1/getstatus/` | 获取管线状态 | network (query), link (query) | - |
|
||||||
|
| GET | `/api/v1/getstatusschema` | 获取状态属性架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/gettitle/` | 获取水网标题属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/gettitleschema/` | 获取标题属性架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/isjunction/` | 检查是否为接点 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/islink/` | 检查管线有效性 | network (query), link (query) | - |
|
||||||
|
| GET | `/api/v1/isnode/` | 检查节点有效性 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/ispipe/` | 检查是否为管道 | network (query), link (query) | - |
|
||||||
|
| GET | `/api/v1/ispump/` | 检查是否为泵 | network (query), link (query) | - |
|
||||||
|
| GET | `/api/v1/isreservoir/` | 检查是否为水源 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/istank/` | 检查是否为蓄水池 | network (query), node (query) | - |
|
||||||
|
| GET | `/api/v1/isvalve/` | 检查是否为阀门 | network (query), link (query) | - |
|
||||||
|
| POST | `/api/v1/setstatus/` | 设置管线状态 | network (query), link (query) | - |
|
||||||
|
| GET | `/api/v1/settitle/` | 设置水网标题属性 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-geometry
|
||||||
|
description: business/network-assets 下 geometry 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# geometry Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `geometry` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/getmajornodecoords/` | 获取主要节点坐标 | network (query), diameter (query) | - |
|
||||||
|
| GET | `/api/v1/getmajorpipenodes/` | 获取主要管道节点 | network (query), diameter (query) | - |
|
||||||
|
| GET | `/api/v1/getnetworkgeometries/` | 获取完整网络几何信息 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getnetworkinextent/` | 获取范围内的网络元素 | network (query), x1 (query), y1 (query), x2 (query), y2 (query) | - |
|
||||||
|
| GET | `/api/v1/getnetworklinknodes/` | 获取网络管线节点 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getnodecoord/` | 获取节点坐标 | network (query), node (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET`
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-junctions
|
||||||
|
description: business/network-assets 下 junctions 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# junctions Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `junctions` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addjunction/` | 添加节点 | network (query), junction (query), x (query), y (query), z (query) | - |
|
||||||
|
| POST | `/api/v1/deletejunction/` | 删除节点 | network (query), junction (query) | - |
|
||||||
|
| GET | `/api/v1/getalljunctionproperties/` | 获取所有节点属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getjunctioncoord/` | 获取节点坐标 | network (query), junction (query) | - |
|
||||||
|
| GET | `/api/v1/getjunctiondemand/` | 获取节点需水量 | network (query), junction (query) | - |
|
||||||
|
| GET | `/api/v1/getjunctionelevation/` | 获取节点标高 | network (query), junction (query) | - |
|
||||||
|
| GET | `/api/v1/getjunctionpattern/` | 获取节点需水模式 | network (query), junction (query) | - |
|
||||||
|
| GET | `/api/v1/getjunctionproperties/` | 获取节点属性 | network (query), junction (query) | - |
|
||||||
|
| GET | `/api/v1/getjunctionschema` | 获取节点架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getjunctionx/` | 获取节点 X 坐标 | network (query), junction (query) | - |
|
||||||
|
| GET | `/api/v1/getjunctiony/` | 获取节点 Y 坐标 | network (query), junction (query) | - |
|
||||||
|
| POST | `/api/v1/setjunctioncoord/` | 设置节点坐标 | network (query), junction (query), x (query), y (query) | - |
|
||||||
|
| POST | `/api/v1/setjunctiondemand/` | 设置节点需水量 | network (query), junction (query), demand (query) | - |
|
||||||
|
| POST | `/api/v1/setjunctionelevation/` | 设置节点标高 | network (query), junction (query), elevation (query) | - |
|
||||||
|
| POST | `/api/v1/setjunctionpattern/` | 设置节点需水模式 | network (query), junction (query), pattern (query) | - |
|
||||||
|
| POST | `/api/v1/setjunctionproperties/` | 批量设置节点属性 | network (query), junction (query) | - |
|
||||||
|
| POST | `/api/v1/setjunctionx/` | 设置节点 X 坐标 | network (query), junction (query), x (query) | - |
|
||||||
|
| POST | `/api/v1/setjunctiony/` | 设置节点 Y 坐标 | network (query), junction (query), y (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-pipes
|
||||||
|
description: business/network-assets 下 pipes 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# pipes Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `pipes` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addpipe/` | 添加管道 | network (query), pipe (query), node1 (query), node2 (query) | length (query), diameter (query), roughness (query), minor_loss (query), status (query) |
|
||||||
|
| POST | `/api/v1/deletepipe/` | 删除管道 | network (query), pipe (query) | - |
|
||||||
|
| GET | `/api/v1/getallpipeproperties/` | 获取所有管道属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getpipediameter/` | 获取管道管径 | network (query), pipe (query) | - |
|
||||||
|
| GET | `/api/v1/getpipelength/` | 获取管道长度 | network (query), pipe (query) | - |
|
||||||
|
| GET | `/api/v1/getpipeminorloss/` | 获取管道局部阻力系数 | network (query), pipe (query) | - |
|
||||||
|
| GET | `/api/v1/getpipenode1/` | 获取管道起始节点 | network (query), pipe (query) | - |
|
||||||
|
| GET | `/api/v1/getpipenode2/` | 获取管道终止节点 | network (query), pipe (query) | - |
|
||||||
|
| GET | `/api/v1/getpipeproperties/` | 获取管道属性 | network (query), pipe (query) | - |
|
||||||
|
| GET | `/api/v1/getpiperoughness/` | 获取管道粗糙度 | network (query), pipe (query) | - |
|
||||||
|
| GET | `/api/v1/getpipeschema` | 获取管道模式 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getpipestatus/` | 获取管道状态 | network (query), pipe (query) | - |
|
||||||
|
| POST | `/api/v1/setpipediameter/` | 设置管道管径 | network (query), pipe (query), diameter (query) | - |
|
||||||
|
| POST | `/api/v1/setpipelength/` | 设置管道长度 | network (query), pipe (query), length (query) | - |
|
||||||
|
| POST | `/api/v1/setpipeminorloss/` | 设置管道局部阻力系数 | network (query), pipe (query), minor_loss (query) | - |
|
||||||
|
| POST | `/api/v1/setpipenode1/` | 设置管道起始节点 | network (query), pipe (query), node1 (query) | - |
|
||||||
|
| POST | `/api/v1/setpipenode2/` | 设置管道终止节点 | network (query), pipe (query), node2 (query) | - |
|
||||||
|
| POST | `/api/v1/setpipeproperties/` | 设置管道属性 | network (query), pipe (query) | - |
|
||||||
|
| POST | `/api/v1/setpiperoughness/` | 设置管道粗糙度 | network (query), pipe (query), roughness (query) | - |
|
||||||
|
| POST | `/api/v1/setpipestatus/` | 设置管道状态 | network (query), pipe (query), status (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-pumps
|
||||||
|
description: business/network-assets 下 pumps 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# pumps Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `pumps` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addpump/` | 添加水泵 | network (query), pump (query), node1 (query), node2 (query) | power (query) |
|
||||||
|
| POST | `/api/v1/deletepump/` | 删除水泵 | network (query), pump (query) | - |
|
||||||
|
| GET | `/api/v1/getallpumpproperties/` | 获取所有水泵属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getpumpnode1/` | 获取水泵起始节点 | network (query), pump (query) | - |
|
||||||
|
| GET | `/api/v1/getpumpnode2/` | 获取水泵终止节点 | network (query), pump (query) | - |
|
||||||
|
| GET | `/api/v1/getpumpproperties/` | 获取水泵属性 | network (query), pump (query) | - |
|
||||||
|
| GET | `/api/v1/getpumpschema` | 获取水泵模式 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setpumpnode1/` | 设置水泵起始节点 | network (query), pump (query), node1 (query) | - |
|
||||||
|
| POST | `/api/v1/setpumpnode2/` | 设置水泵终止节点 | network (query), pump (query), node2 (query) | - |
|
||||||
|
| POST | `/api/v1/setpumpproperties/` | 设置水泵属性 | network (query), pump (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-regions
|
||||||
|
description: business/network-assets 下 regions 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# regions Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `regions` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/adddistrictmeteringarea/` | 添加新DMA | network (query) | - |
|
||||||
|
| POST | `/api/v1/addregion/` | 添加新区域 | network (query) | - |
|
||||||
|
| POST | `/api/v1/addservicearea/` | 添加新服务区 | network (query) | - |
|
||||||
|
| POST | `/api/v1/addvirtualdistrict/` | 添加新虚拟分区 | network (query) | - |
|
||||||
|
| GET | `/api/v1/calculatedistrictmeteringarea/` | 计算DMA分区 | network (query) | - |
|
||||||
|
| GET | `/api/v1/calculatedistrictmeteringareafornetwork/` | 计算整网DMA分区 | network (query) | - |
|
||||||
|
| GET | `/api/v1/calculatedistrictmeteringareafornodes/` | 计算节点DMA分区 | network (query) | - |
|
||||||
|
| GET | `/api/v1/calculatedistrictmeteringareaforregion/` | 计算区域内DMA分区 | network (query) | - |
|
||||||
|
| GET | `/api/v1/calculateregion/` | 计算区域 | network (query), time_index (query) | - |
|
||||||
|
| GET | `/api/v1/calculateservicearea/` | 计算服务区 | network (query), time_index (query) | - |
|
||||||
|
| GET | `/api/v1/calculatevirtualdistrict/` | 计算虚拟分区 | network (query), centers (query) | - |
|
||||||
|
| POST | `/api/v1/deletedistrictmeteringarea/` | 删除DMA | network (query) | - |
|
||||||
|
| POST | `/api/v1/deleteregion/` | 删除区域 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deleteservicearea/` | 删除服务区 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deletevirtualdistrict/` | 删除虚拟分区 | network (query) | - |
|
||||||
|
| POST | `/api/v1/generatedistrictmeteringarea/` | 生成DMA分区 | network (query), part_count (query), part_type (query), inflate_delta (query) | - |
|
||||||
|
| POST | `/api/v1/generateregion/` | 生成区域分区 | network (query), inflate_delta (query) | - |
|
||||||
|
| POST | `/api/v1/generateservicearea/` | 生成服务区分区 | network (query), inflate_delta (query) | - |
|
||||||
|
| POST | `/api/v1/generatesubdistrictmeteringarea/` | 生成DMA子分区 | network (query), dma (query), part_count (query), part_type (query), inflate_delta (query) | - |
|
||||||
|
| POST | `/api/v1/generatevirtualdistrict/` | 生成虚拟分区 | network (query), inflate_delta (query) | - |
|
||||||
|
| GET | `/api/v1/getalldistrictmeteringareaids/` | 获取所有DMA ID | network (query) | - |
|
||||||
|
| GET | `/api/v1/getalldistrictmeteringareas/` | 获取所有DMA | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallregions/` | 获取所有区域 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallserviceareas/` | 获取所有服务区 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallvirtualdistrict/` | 获取所有虚拟分区 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getdistrictmeteringarea/` | 获取DMA信息 | network (query), id (query) | - |
|
||||||
|
| GET | `/api/v1/getdistrictmeteringareaschema/` | 获取DMA属性架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getregion/` | 获取区域信息 | network (query), id (query) | - |
|
||||||
|
| GET | `/api/v1/getregionschema/` | 获取区域属性架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getservicearea/` | 获取服务区信息 | network (query), id (query) | - |
|
||||||
|
| GET | `/api/v1/getserviceareaschema/` | 获取服务区属性架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getvirtualdistrict/` | 获取虚拟分区信息 | network (query), id (query) | - |
|
||||||
|
| GET | `/api/v1/getvirtualdistrictschema/` | 获取虚拟分区属性架构 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setdistrictmeteringarea/` | 设置DMA属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setregion/` | 设置区域属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setservicearea/` | 设置服务区属性 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setvirtualdistrict/` | 设置虚拟分区属性 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-reservoirs
|
||||||
|
description: business/network-assets 下 reservoirs 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# reservoirs Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `reservoirs` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addreservoir/` | 添加水库 | network (query), reservoir (query), x (query), y (query), head (query) | - |
|
||||||
|
| POST | `/api/v1/deletereservoir/` | 删除水库 | network (query), reservoir (query) | - |
|
||||||
|
| GET | `/api/v1/getallreservoirproperties/` | 获取所有水库属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getreservoircoord/` | 获取水库坐标 | network (query), reservoir (query) | - |
|
||||||
|
| GET | `/api/v1/getreservoirhead/` | 获取水库水头 | network (query), reservoir (query) | - |
|
||||||
|
| GET | `/api/v1/getreservoirpattern/` | 获取水库模式 | network (query), reservoir (query) | - |
|
||||||
|
| GET | `/api/v1/getreservoirproperties/` | 获取水库属性 | network (query), reservoir (query) | - |
|
||||||
|
| GET | `/api/v1/getreservoirschema` | 获取水库模式 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getreservoirx/` | 获取水库X坐标 | network (query), reservoir (query) | - |
|
||||||
|
| GET | `/api/v1/getreservoiry/` | 获取水库Y坐标 | network (query), reservoir (query) | - |
|
||||||
|
| POST | `/api/v1/setreservoircoord/` | 设置水库坐标 | network (query), reservoir (query), x (query), y (query) | - |
|
||||||
|
| POST | `/api/v1/setreservoirhead/` | 设置水库水头 | network (query), reservoir (query), head (query) | - |
|
||||||
|
| POST | `/api/v1/setreservoirpattern/` | 设置水库模式 | network (query), reservoir (query), pattern (query) | - |
|
||||||
|
| POST | `/api/v1/setreservoirproperties/` | 设置水库属性 | network (query), reservoir (query) | - |
|
||||||
|
| POST | `/api/v1/setreservoirx/` | 设置水库X坐标 | network (query), reservoir (query), x (query) | - |
|
||||||
|
| POST | `/api/v1/setreservoiry/` | 设置水库Y坐标 | network (query), reservoir (query), y (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-tags
|
||||||
|
description: business/network-assets 下 tags 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# tags Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `tags` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/gettag/` | 获取标签信息 | network (query), t_type (query), id (query) | - |
|
||||||
|
| GET | `/api/v1/gettags/` | 获取所有标签 | network (query) | - |
|
||||||
|
| GET | `/api/v1/gettagschema/` | 获取标签属性架构 | network (query) | - |
|
||||||
|
| POST | `/api/v1/settag/` | 设置标签 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-tanks
|
||||||
|
description: business/network-assets 下 tanks 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# tanks Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `tanks` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addtank/` | 新增水箱 | network (query), tank (query), x (query), y (query), elevation (query) | init_level (query), min_level (query), max_level (query), diameter (query), min_vol (query) |
|
||||||
|
| POST | `/api/v1/deletetank/` | 删除水箱 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/getalltankproperties/` | 获取所有水箱属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/gettankcoord/` | 获取水箱坐标 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankdiameter/` | 获取水箱直径 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankelevation/` | 获取水箱标高 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankinitlevel/` | 获取水箱初始水位 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankmaxlevel/` | 获取水箱最大水位 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankminlevel/` | 获取水箱最小水位 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankminvol/` | 获取水箱最小体积 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankoverflow/` | 获取水箱溢流口 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankproperties/` | 获取水箱属性 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankschema` | 获取水箱模式 | network (query) | - |
|
||||||
|
| GET | `/api/v1/gettankvolcurve/` | 获取水箱容积曲线 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettankx/` | 获取水箱X坐标 | network (query), tank (query) | - |
|
||||||
|
| GET | `/api/v1/gettanky/` | 获取水箱Y坐标 | network (query), tank (query) | - |
|
||||||
|
| POST | `/api/v1/settankcoord/` | 设置水箱坐标 | network (query), tank (query), x (query), y (query) | - |
|
||||||
|
| POST | `/api/v1/settankdiameter/` | 设置水箱直径 | network (query), tank (query), diameter (query) | - |
|
||||||
|
| POST | `/api/v1/settankelevation/` | 设置水箱标高 | network (query), tank (query), elevation (query) | - |
|
||||||
|
| POST | `/api/v1/settankinitlevel/` | 设置水箱初始水位 | network (query), tank (query), init_level (query) | - |
|
||||||
|
| POST | `/api/v1/settankmaxlevel/` | 设置水箱最大水位 | network (query), tank (query), max_level (query) | - |
|
||||||
|
| POST | `/api/v1/settankminlevel/` | 设置水箱最小水位 | network (query), tank (query), min_level (query) | - |
|
||||||
|
| POST | `/api/v1/settankminvol/` | 设置水箱最小体积 | network (query), tank (query), min_vol (query) | - |
|
||||||
|
| POST | `/api/v1/settankoverflow/` | 设置水箱溢流口 | network (query), tank (query), overflow (query) | - |
|
||||||
|
| POST | `/api/v1/settankproperties/` | 设置水箱属性 | network (query), tank (query) | - |
|
||||||
|
| POST | `/api/v1/settankvolcurve/` | 设置水箱容积曲线 | network (query), tank (query), vol_curve (query) | - |
|
||||||
|
| POST | `/api/v1/settankx/` | 设置水箱X坐标 | network (query), tank (query), x (query) | - |
|
||||||
|
| POST | `/api/v1/settanky/` | 设置水箱Y坐标 | network (query), tank (query), y (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-network-assets-valves
|
||||||
|
description: business/network-assets 下 valves 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# valves Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/network-assets` 场景下 `valves` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/addvalve/` | 添加阀门 | network (query), valve (query), node1 (query), node2 (query) | diameter (query), v_type (query), setting (query), minor_loss (query) |
|
||||||
|
| POST | `/api/v1/deletevalve/` | 删除阀门 | network (query), valve (query) | - |
|
||||||
|
| GET | `/api/v1/getallvalveproperties/` | 获取所有阀门属性 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getvalvediameter/` | 获取阀门直径 | network (query), valve (query) | - |
|
||||||
|
| GET | `/api/v1/getvalveminorloss/` | 获取阀门损失系数 | network (query), valve (query) | - |
|
||||||
|
| GET | `/api/v1/getvalvenode1/` | 获取阀门起点节点 | network (query), valve (query) | - |
|
||||||
|
| GET | `/api/v1/getvalvenode2/` | 获取阀门终点节点 | network (query), valve (query) | - |
|
||||||
|
| GET | `/api/v1/getvalveproperties/` | 获取阀门所有属性 | network (query), valve (query) | - |
|
||||||
|
| GET | `/api/v1/getvalveschema` | 获取阀门架构 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getvalvesetting/` | 获取阀门开度 | network (query), valve (query) | - |
|
||||||
|
| GET | `/api/v1/getvalvetype/` | 获取阀门类型 | network (query), valve (query) | - |
|
||||||
|
| POST | `/api/v1/setvalvenode1/` | 设置阀门起点节点 | network (query), valve (query), node1 (query) | - |
|
||||||
|
| POST | `/api/v1/setvalvenode2/` | 设置阀门终点节点 | network (query), valve (query), node2 (query) | - |
|
||||||
|
| POST | `/api/v1/setvalvenodediameter/` | 设置阀门直径 | network (query), valve (query), diameter (query) | - |
|
||||||
|
| POST | `/api/v1/setvalveproperties/` | 批量设置阀门属性 | network (query), valve (query) | - |
|
||||||
|
| POST | `/api/v1/setvalvesetting/` | 设置阀门开度 | network (query), valve (query), setting (query) | - |
|
||||||
|
| POST | `/api/v1/setvalvetype/` | 设置阀门类型 | network (query), valve (query), type (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-scenario-business-project-workspace
|
||||||
|
description: 负责项目空间、快照与扩展操作。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# project-workspace Scenario Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责项目空间、快照与扩展操作。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **extension**: 见 `./extension/SKILL.md`
|
||||||
|
- **misc**: 见 `./misc/SKILL.md`
|
||||||
|
- **project**: 见 `./project/SKILL.md`
|
||||||
|
- **project_data**: 见 `./project_data/SKILL.md`
|
||||||
|
- **schemes**: 见 `./schemes/SKILL.md`
|
||||||
|
- **snapshots**: 见 `./snapshots/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-project-workspace-extension
|
||||||
|
description: business/project-workspace 下 extension 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# extension Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/project-workspace` 场景下 `extension` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/getallextensiondata/` | 获取所有扩展数据 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallextensiondatakeys/` | 获取所有扩展数据键 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getextensiondata/` | 获取指定扩展数据 | network (query), key (query) | - |
|
||||||
|
| POST | `/api/v1/setextensiondata/` | 设置扩展数据 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-project-workspace-misc
|
||||||
|
description: business/project-workspace 下 misc 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# misc Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/project-workspace` 场景下 `misc` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/getallburstlocateresults/` | 获取所有爆管定位结果 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getallsensorplacements/` | 获取所有传感器位置 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getjson/` | 获取JSON示例 | - | - |
|
||||||
|
| GET | `/api/v1/getrealtimedata/` | 获取实时数据 | - | - |
|
||||||
|
| GET | `/api/v1/getsimulationresult/` | 获取模拟结果 | - | - |
|
||||||
|
| POST | `/api/v1/test_dict/` | 测试字典处理 | data (body) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-project-workspace-project-data
|
||||||
|
description: business/project-workspace 下 project_data 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# project_data Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/project-workspace` 场景下 `project_data` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/burst-locate-result` | 获取爆管定位结果 | - | - |
|
||||||
|
| GET | `/api/v1/burst-locate-result/{burst_incident}` | 按事件查询爆管定位结果 | burst_incident (path) | - |
|
||||||
|
| GET | `/api/v1/scada-info` | 获取SCADA信息 | - | - |
|
||||||
|
| GET | `/api/v1/scheme-list` | 获取方案列表 | - | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET`
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-project-workspace-project
|
||||||
|
description: business/project-workspace 下 project 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# project Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/project-workspace` 场景下 `project` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/closeproject/` | 关闭项目 | network (query) | - |
|
||||||
|
| GET | `/api/v1/convertv3tov2/` | 转换 INP V3 为 V2 | - | - |
|
||||||
|
| GET | `/api/v1/convertv3tov2/` | 转换 INP V3 为 V2 | - | - |
|
||||||
|
| POST | `/api/v1/copyproject/` | 复制项目 | source (query), target (query) | - |
|
||||||
|
| POST | `/api/v1/createproject/` | 创建新项目 | network (query) | - |
|
||||||
|
| POST | `/api/v1/deleteproject/` | 删除项目 | network (query) | - |
|
||||||
|
| GET | `/api/v1/downloadinp/` | 下载 INP 文件 | name (query) | - |
|
||||||
|
| GET | `/api/v1/downloadinp/` | 下载 INP 文件 | name (query) | - |
|
||||||
|
| GET | `/api/v1/dumpinp/` | 导出项目到 INP 文件 | network (query), inp (query) | - |
|
||||||
|
| GET | `/api/v1/dumpinp/` | 导出项目到 INP 文件 | network (query), inp (query) | - |
|
||||||
|
| GET | `/api/v1/exportinp/` | 导出项目为 ChangeSet | network (query), version (query) | - |
|
||||||
|
| GET | `/api/v1/haveproject/` | 检查项目是否存在 | network (query) | - |
|
||||||
|
| POST | `/api/v1/importinp/` | 导入 INP 文件内容 | network (query) | - |
|
||||||
|
| GET | `/api/v1/isprojectlocked/` | 检查项目是否被锁定 | network (query) | - |
|
||||||
|
| GET | `/api/v1/isprojectlocked/` | 检查项目是否被锁定 | network (query) | - |
|
||||||
|
| GET | `/api/v1/isprojectlockedbyme/` | 检查项目是否被当前用户锁定 | network (query) | - |
|
||||||
|
| GET | `/api/v1/isprojectlockedbyme/` | 检查项目是否被当前用户锁定 | network (query) | - |
|
||||||
|
| GET | `/api/v1/isprojectopen/` | 检查项目是否已打开 | network (query) | - |
|
||||||
|
| GET | `/api/v1/listprojects/` | 获取项目列表 | - | - |
|
||||||
|
| POST | `/api/v1/lockproject/` | 锁定项目 | network (query) | - |
|
||||||
|
| POST | `/api/v1/lockproject/` | 锁定项目 | network (query) | - |
|
||||||
|
| POST | `/api/v1/openproject/` | 打开项目 | network (query) | - |
|
||||||
|
| GET | `/api/v1/project_info/` | 获取项目信息 | network (query) | - |
|
||||||
|
| POST | `/api/v1/readinp/` | 读取 INP 文件到项目 | network (query), inp (query) | - |
|
||||||
|
| POST | `/api/v1/readinp/` | 读取 INP 文件到项目 | network (query), inp (query) | - |
|
||||||
|
| POST | `/api/v1/unlockproject/` | 解锁项目 | network (query) | - |
|
||||||
|
| POST | `/api/v1/unlockproject/` | 解锁项目 | network (query) | - |
|
||||||
|
| POST | `/api/v1/uploadinp/` | 上传 INP 文件 | afile (body), name (query) | - |
|
||||||
|
| POST | `/api/v1/uploadinp/` | 上传 INP 文件 | afile (body), name (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-project-workspace-schemes
|
||||||
|
description: business/project-workspace 下 schemes 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# schemes Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/project-workspace` 场景下 `schemes` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/getallschemes/` | 获取所有方案 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getscheme/` | 获取单个方案 | network (query), schema_name (query) | - |
|
||||||
|
| GET | `/api/v1/getschemeschema/` | 获取方案模式 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET`
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-business-project-workspace-snapshots
|
||||||
|
description: business/project-workspace 下 snapshots 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# snapshots Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `business/project-workspace` 场景下 `snapshots` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/batch/` | 执行批量命令 | network (query) | - |
|
||||||
|
| POST | `/api/v1/compressedbatch/` | 执行压缩批量命令 | network (query) | - |
|
||||||
|
| GET | `/api/v1/getcurrentoperationid/` | 获取当前操作ID | network (query) | - |
|
||||||
|
| GET | `/api/v1/getrestoreoperation/` | 获取恢复操作ID | network (query) | - |
|
||||||
|
| GET | `/api/v1/getsnapshots/` | 获取快照列表 | network (query) | - |
|
||||||
|
| GET | `/api/v1/havesnapshot/` | 检查快照是否存在 | network (query), tag (query) | - |
|
||||||
|
| GET | `/api/v1/havesnapshotforcurrentoperation/` | 检查当前操作快照是否存在 | network (query) | - |
|
||||||
|
| GET | `/api/v1/havesnapshotforoperation/` | 检查操作快照是否存在 | network (query), operation (query) | - |
|
||||||
|
| POST | `/api/v1/pickoperation/` | 选择操作 | network (query), operation (query) | discard (query) |
|
||||||
|
| POST | `/api/v1/picksnapshot/` | 选择快照 | network (query), tag (query) | discard (query) |
|
||||||
|
| POST | `/api/v1/redo/` | 重做操作 | network (query) | - |
|
||||||
|
| POST | `/api/v1/setrestoreoperation/` | 设置恢复操作ID | network (query), operation (query) | - |
|
||||||
|
| GET | `/api/v1/syncwithserver/` | 与服务器同步 | network (query), operation (query) | - |
|
||||||
|
| POST | `/api/v1/takenapshotforcurrentoperation` | 为当前操作创建快照(兼容模式) | network (query), tag (query) | - |
|
||||||
|
| POST | `/api/v1/takesnapshot/` | 创建快照 | network (query), tag (query) | - |
|
||||||
|
| POST | `/api/v1/takesnapshotforcurrentoperation` | 为当前操作创建快照 | network (query), tag (query) | - |
|
||||||
|
| POST | `/api/v1/takesnapshotforoperation/` | 为操作创建快照 | network (query), operation (query), tag (query) | - |
|
||||||
|
| POST | `/api/v1/undo/` | 撤销操作 | network (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-domain-data
|
||||||
|
description: 负责时序数据访问与读写能力。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Data Domain Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责时序数据访问与读写能力。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **timeseries-access**: 见 `./timeseries-access/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-scenario-data-timeseries-access
|
||||||
|
description: 负责时序数据查询、写入与聚合。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# timeseries-access Scenario Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责时序数据查询、写入与聚合。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **composite**: 见 `./composite/SKILL.md`
|
||||||
|
- **realtime**: 见 `./realtime/SKILL.md`
|
||||||
|
- **scheme**: 见 `./scheme/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-data-timeseries-access-composite
|
||||||
|
description: data/timeseries-access 下 composite 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# composite Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `data/timeseries-access` 场景下 `composite` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/composite/clean-scada` | 清洗SCADA监测数据 | device_ids (query), start_time (query), end_time (query) | - |
|
||||||
|
| GET | `/api/v1/composite/element-scada` | 获取管网元素关联的SCADA监测数据 | element_id (query), start_time (query), end_time (query) | use_cleaned (query) |
|
||||||
|
| GET | `/api/v1/composite/element-simulation` | 获取管网元素的模拟数据 | start_time (query), end_time (query), feature_infos (query) | scheme_type (query), scheme_name (query) |
|
||||||
|
| GET | `/api/v1/composite/pipeline-health-prediction` | 预测管道健康状况 | query_time (query), network_name (query) | - |
|
||||||
|
| GET | `/api/v1/composite/scada-simulation` | 获取SCADA关联的模拟数据 | start_time (query), end_time (query), device_ids (query) | scheme_type (query), scheme_name (query) |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-data-timeseries-access-realtime
|
||||||
|
description: data/timeseries-access 下 realtime 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# realtime Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `data/timeseries-access` 场景下 `realtime` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| DELETE | `/api/v1/realtime/links` | 删除实时管道数据 | start_time (query), end_time (query) | - |
|
||||||
|
| GET | `/api/v1/realtime/links` | 查询实时管道数据 | start_time (query), end_time (query) | - |
|
||||||
|
| POST | `/api/v1/realtime/links/batch` | 批量插入实时管道数据 | data (body) | - |
|
||||||
|
| PATCH | `/api/v1/realtime/links/{link_id}/field` | 更新实时管道字段 | link_id (path), time (query), field (query), value (query) | - |
|
||||||
|
| DELETE | `/api/v1/realtime/nodes` | 删除实时节点数据 | start_time (query), end_time (query) | - |
|
||||||
|
| GET | `/api/v1/realtime/nodes` | 查询实时节点数据 | start_time (query), end_time (query) | - |
|
||||||
|
| POST | `/api/v1/realtime/nodes/batch` | 批量插入实时节点数据 | data (body) | - |
|
||||||
|
| GET | `/api/v1/realtime/query/by-id-time` | 按ID和时间查询实时模拟数据 | id (query), type (query), query_time (query) | - |
|
||||||
|
| GET | `/api/v1/realtime/query/by-time-property` | 按时间和属性查询实时数据 | query_time (query), type (query), property (query) | - |
|
||||||
|
| POST | `/api/v1/realtime/simulation/store` | 存储实时模拟结果 | node_result_list (body), link_result_list (body), result_start_time (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`DELETE, GET, PATCH, POST`
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-data-timeseries-access-scheme
|
||||||
|
description: data/timeseries-access 下 scheme 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# scheme Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `data/timeseries-access` 场景下 `scheme` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| DELETE | `/api/v1/scheme/links` | 删除方案管道数据 | scheme_type (query), scheme_name (query), start_time (query), end_time (query) | - |
|
||||||
|
| GET | `/api/v1/scheme/links` | 查询方案管道数据 | scheme_type (query), scheme_name (query), start_time (query), end_time (query) | - |
|
||||||
|
| POST | `/api/v1/scheme/links/batch` | 批量插入方案管道数据 | data (body) | - |
|
||||||
|
| GET | `/api/v1/scheme/links/{link_id}/field` | 查询方案管道字段数据 | link_id (path), scheme_type (query), scheme_name (query), start_time (query), end_time (query), field (query) | - |
|
||||||
|
| PATCH | `/api/v1/scheme/links/{link_id}/field` | 更新方案管道字段 | link_id (path), scheme_type (query), scheme_name (query), time (query), field (query), value (query) | - |
|
||||||
|
| DELETE | `/api/v1/scheme/nodes` | 删除方案节点数据 | scheme_type (query), scheme_name (query), start_time (query), end_time (query) | - |
|
||||||
|
| POST | `/api/v1/scheme/nodes/batch` | 批量插入方案节点数据 | data (body) | - |
|
||||||
|
| GET | `/api/v1/scheme/nodes/{node_id}/field` | 查询方案节点字段数据 | node_id (path), scheme_type (query), scheme_name (query), start_time (query), end_time (query), field (query) | - |
|
||||||
|
| PATCH | `/api/v1/scheme/nodes/{node_id}/field` | 更新方案节点字段 | node_id (path), scheme_type (query), scheme_name (query), time (query), field (query), value (query) | - |
|
||||||
|
| GET | `/api/v1/scheme/query/by-id-time` | 按ID和时间查询方案模拟数据 | scheme_type (query), scheme_name (query), id (query), type (query), query_time (query) | - |
|
||||||
|
| GET | `/api/v1/scheme/query/by-scheme-time-property` | 按方案、时间和属性查询数据 | scheme_type (query), scheme_name (query), query_time (query), type (query), property (query) | - |
|
||||||
|
| POST | `/api/v1/scheme/simulation/store` | 存储方案模拟结果 | scheme_type (query), scheme_name (query), node_result_list (body), link_result_list (body), result_start_time (query) | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`DELETE, GET, PATCH, POST`
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
# 示例(基于 opencode Agent chat/stream 工具调用链)
|
||||||
|
|
||||||
|
## 示例 1:前端发起对话,opencode agent 触发工具调用
|
||||||
|
|
||||||
|
用户意图:查询设备 `170490` 在时间范围内的 `monitored_value`。
|
||||||
|
|
||||||
|
前端调用 `POST /api/v1/agent/chat/stream`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "请查询设备170490在最近24小时的monitored_value历史数据",
|
||||||
|
"session_id": "agent-demo-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
请求头至少包含(由前端传入):
|
||||||
|
- `Authorization: Bearer <token>`
|
||||||
|
- `x-project-id: <project-id>`
|
||||||
|
|
||||||
|
服务端内部行为:
|
||||||
|
- 持续通过 SSE `progress` 输出处理阶段,例如“正在规划分析步骤”“正在调用后端数据查询”
|
||||||
|
- opencode agent 选择工具 `dynamic_http_call`
|
||||||
|
- 工具参数示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"path": "/api/v1/scada/by-ids-field-time-range",
|
||||||
|
"method": "GET",
|
||||||
|
"arguments": {
|
||||||
|
"device_ids": "170490",
|
||||||
|
"field": "monitored_value",
|
||||||
|
"start_time": "2026-03-29T07:57:47.338Z",
|
||||||
|
"end_time": "2026-03-30T07:57:47.338Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例 2:opencode agent 多步规划 + 多次工具调用
|
||||||
|
|
||||||
|
用户消息:
|
||||||
|
- “先查这个设备历史数据,再给我异常点摘要。”
|
||||||
|
|
||||||
|
典型链路:
|
||||||
|
- 第一步工具调用:查询历史数据接口。
|
||||||
|
- 第二步(可选)工具调用:查询补充数据接口。
|
||||||
|
- opencode agent 汇总工具结果,持续通过 SSE 输出 `progress` 与 `token`,最终返回 `done`。
|
||||||
|
|
||||||
|
`progress` 示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"session_id": "agent-demo-001",
|
||||||
|
"id": "tool-dynamic-http",
|
||||||
|
"phase": "tool",
|
||||||
|
"status": "running",
|
||||||
|
"title": "正在调用后端数据查询"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例 3:前端工具 — 定位要素
|
||||||
|
|
||||||
|
用户消息:
|
||||||
|
- "帮我找到管道 P-001 和 P-002"
|
||||||
|
|
||||||
|
opencode agent 调用工具 `locate_features`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ids": ["P-001", "P-002"],
|
||||||
|
"feature_type": "pipe"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端收到 SSE 事件后缩放地图并高亮管道。opencode agent 回复文字:"已在地图上定位到管道 P-001 和 P-002。"
|
||||||
|
|
||||||
|
## 示例 4:前端工具 — 对话内图表
|
||||||
|
|
||||||
|
用户消息:
|
||||||
|
- "展示节点 J-001 最近一天的压力变化曲线"
|
||||||
|
|
||||||
|
典型链路:
|
||||||
|
1. opencode agent 先调用 `dynamic_http_call` 查询数据
|
||||||
|
2. 拿到数据后,调用 `show_chart` 将处理好的数据传给前端渲染
|
||||||
|
|
||||||
|
第一步 — opencode agent 调用 `dynamic_http_call`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"path": "/api/v1/composite/element-simulation",
|
||||||
|
"method": "GET",
|
||||||
|
"arguments": {
|
||||||
|
"feature_infos": "[\"J-001\", \"node\"]",
|
||||||
|
"start_time": "2026-03-29T00:00:00Z",
|
||||||
|
"end_time": "2026-03-30T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
第二步 — opencode agent 处理数据后调用 `show_chart`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "节点 J-001 压力变化",
|
||||||
|
"chart_type": "line",
|
||||||
|
"x_data": ["03-29 00:00", "03-29 01:00", "03-29 02:00", "..."],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "J-001 压力",
|
||||||
|
"data": [32.5, 31.8, 30.2, "..."]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"y_axis_name": "压力 (m)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端直接用 AI 提供的数据渲染 ECharts 图表,不再请求后端。
|
||||||
|
|
||||||
|
## 示例 5:前端工具 — 查看 SCADA 监测面板
|
||||||
|
|
||||||
|
用户消息:
|
||||||
|
- "我想看看 J-001 的监测数据"
|
||||||
|
|
||||||
|
opencode agent 调用工具 `view_scada`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"device_ids": ["J-001"],
|
||||||
|
"start_time": "2026-03-29T00:00:00Z",
|
||||||
|
"end_time": "2026-03-30T00:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端打开 SCADA 监测面板,展示该节点的历史监测曲线。
|
||||||
|
|
||||||
|
## 示例 6:记住用户长期偏好
|
||||||
|
|
||||||
|
用户消息:
|
||||||
|
- "以后回答尽量简洁,先给结论再解释。"
|
||||||
|
|
||||||
|
opencode agent 调用工具 `memory_manager`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "add",
|
||||||
|
"reason": "用户明确给出了长期有效的回答风格偏好,后续会话也应遵守。",
|
||||||
|
"scope": "user",
|
||||||
|
"content": "用户偏好先给结论、再补必要解释,整体风格尽量简洁。"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例 7:检索历史案例而不是误写入 memory
|
||||||
|
|
||||||
|
用户消息:
|
||||||
|
- "我们之前是不是分析过类似的爆管定位问题?"
|
||||||
|
|
||||||
|
opencode agent 调用工具 `session_search`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"reason": "用户在询问过往会话中的类似案例,应先检索历史 transcript 而不是写入新的 memory。",
|
||||||
|
"query": "爆管定位 类似案例",
|
||||||
|
"max_results": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例 8:沉淀可复用 workflow 模式
|
||||||
|
|
||||||
|
用户消息:
|
||||||
|
- "这套瓶颈分析流程之后可以复用。"
|
||||||
|
|
||||||
|
opencode agent 调用工具 `skill_manager`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "append_pattern",
|
||||||
|
"reason": "本轮已验证一套稳定可复用的瓶颈分析 workflow,适合沉淀到已有 skill。",
|
||||||
|
"skill_path": "workflow/bottleneck-analysis",
|
||||||
|
"pattern": "当瓶颈分析依赖大体量属性数据和模拟结果时,先用 dynamic_http_call 获取 preview,再用 fetch_result_ref 回读完整数据后再做合并与排序。"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例 9:给单个 workflow skill 写入可复用脚本
|
||||||
|
|
||||||
|
当某个 workflow 的本地 Python 处理逻辑已经稳定、未来同类任务会重复使用时,可写入该 skill 自己的 `scripts/*.py`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "write_script",
|
||||||
|
"reason": "本轮已验证瓶颈分析中的合并与排序脚本,后续同类 workflow 可直接复用。",
|
||||||
|
"skill_path": "workflow/bottleneck-analysis",
|
||||||
|
"file_path": "scripts/merge_and_rank.py",
|
||||||
|
"content": "import json\n\n\ndef rank_links(rows):\n return sorted(rows, key=lambda row: row['composite_score'], reverse=True)\n"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本应只归属当前 `skill_path`,不要写到 `data/` 或其他 skill 目录。
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-domain-platform
|
||||||
|
description: 负责治理、审计、缓存与元数据能力。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Platform Domain Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责治理、审计、缓存与元数据能力。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **governance-observability**: 见 `./governance-observability/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-scenario-platform-governance-observability
|
||||||
|
description: 负责审计、缓存与平台元数据。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# governance-observability Scenario Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责审计、缓存与平台元数据。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **audit**: 见 `./audit/SKILL.md`
|
||||||
|
- **cache**: 见 `./cache/SKILL.md`
|
||||||
|
- **meta**: 见 `./meta/SKILL.md`
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-platform-governance-observability-audit
|
||||||
|
description: platform/governance-observability 下 audit 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# audit Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `platform/governance-observability` 场景下 `audit` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/audit/logs` | 查询审计日志 | - | user_id (query), project_id (query), action (query), resource_type (query), start_time (query), end_time (query), skip (query), limit (query) |
|
||||||
|
| GET | `/api/v1/audit/logs/count` | 获取审计日志总数 | - | user_id (query), project_id (query), action (query), resource_type (query), start_time (query), end_time (query) |
|
||||||
|
| GET | `/api/v1/audit/logs/my` | 查询我的审计日志 | - | action (query), start_time (query), end_time (query), skip (query), limit (query) |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET`
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-platform-governance-observability-cache
|
||||||
|
description: platform/governance-observability 下 cache 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# cache Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `platform/governance-observability` 场景下 `cache` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/v1/clearallredis/` | 清除所有缓存 | - | - |
|
||||||
|
| POST | `/api/v1/clearrediskey/` | 清除单个缓存键 | key (query) | - |
|
||||||
|
| POST | `/api/v1/clearrediskeys/` | 清除匹配的缓存键 | keys (query) | - |
|
||||||
|
| GET | `/api/v1/queryredis/` | 查询缓存键列表 | - | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET, POST`
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-action-platform-governance-observability-meta
|
||||||
|
description: platform/governance-observability 下 meta 操作技能。
|
||||||
|
version: 3.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# meta Action Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `platform/governance-observability` 场景下 `meta` 的具体接口调用。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- 当前为叶子节点,直接使用下方接口目录。
|
||||||
|
|
||||||
|
## 接口目录
|
||||||
|
| Method | Path | Summary | Required Params | Optional Params |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| GET | `/api/v1/meta/db/health` | 检查数据库健康状态 | - | - |
|
||||||
|
| GET | `/api/v1/meta/project` | 获取项目元数据 | - | - |
|
||||||
|
| GET | `/api/v1/meta/projects` | 列出用户项目 | - | - |
|
||||||
|
|
||||||
|
- 覆盖方法:`GET`
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
# API Skills 使用 Runbook(工具调用链)
|
||||||
|
|
||||||
|
## 1) 总体原则
|
||||||
|
|
||||||
|
- Skills 负责“告诉模型可做什么”。
|
||||||
|
- `chat/stream` 内部启动 opencode 会话,并注册工具 `dynamic_http_call`。
|
||||||
|
- opencode agent 通过工具调用后端能力,不直接发 HTTP。
|
||||||
|
- TJWaterAgent 执行器负责“代表当前用户调真实后端 API”(动态路径,无白名单)。
|
||||||
|
- 会话完成后,运行时会基于 transcript 做后台 learning review;这一步用于判断是否需要更新 memory 或 skill,而不是替代主任务回答。
|
||||||
|
|
||||||
|
## 1.1) 自我学习闭环
|
||||||
|
|
||||||
|
- **memory_manager**:保存用户长期偏好 / 约束,以及稳定 workspace 事实
|
||||||
|
- **skill_manager**:保存经过验证、可复用的 workflow / 方法 / pitfall
|
||||||
|
- **session_search**:检索当前用户 + 当前项目范围内的历史会话 transcript,用于回忆旧案例,避免把一次性案例写入 memory
|
||||||
|
|
||||||
|
推荐分流:
|
||||||
|
|
||||||
|
- 需要长期遵守的偏好 / 稳定事实 → `memory_manager`
|
||||||
|
- 可复用的方法、步骤、坑点 → `skill_manager`
|
||||||
|
- 某次分析过程、历史案例、临时结论 → `session_search`
|
||||||
|
|
||||||
|
## 2) 请求入口(前端)
|
||||||
|
|
||||||
|
- `POST /api/v1/agent/chat/stream`(唯一前端入口)
|
||||||
|
|
||||||
|
不提供 `/execute` 对外调用路径,统一通过 `chat/stream` + 工具调用链执行。
|
||||||
|
|
||||||
|
请求体:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "帮我分析当前管网中的水力瓶颈管道,并给出改造建议",
|
||||||
|
"session_id": "agent-demo-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
SSE 事件:
|
||||||
|
|
||||||
|
| event | 用途 | 关键字段 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `progress` | 展示 Agent 处理过程、规划和工具进度 | `session_id`, `id`, `phase`, `status`, `title`, `detail` |
|
||||||
|
| `token` | 渲染面向用户的最终回答文本 | `session_id`, `content` |
|
||||||
|
| `tool_call` | 驱动前端地图/面板/图表动作 | `session_id`, `tool`, `params` |
|
||||||
|
| `done` | 当前轮对话结束 | `session_id` |
|
||||||
|
| `error` | 当前轮失败 | `session_id`, `message`, `detail` |
|
||||||
|
|
||||||
|
`progress.status` 取值为 `running`、`completed`、`error`;前端应按相同 `id` 覆盖更新同一条进度,而不是重复追加。
|
||||||
|
|
||||||
|
## 3) 工具参数约定(opencode agent 调用工具时)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"path": "/api/v1/scada/by-ids-field-time-range",
|
||||||
|
"method": "GET",
|
||||||
|
"arguments": {
|
||||||
|
"device_ids": "170490",
|
||||||
|
"field": "monitored_value",
|
||||||
|
"start_time": "2026-03-29T07:57:47.338Z",
|
||||||
|
"end_time": "2026-03-30T07:57:47.338Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明(工具 `dynamic_http_call`):
|
||||||
|
- `path` 必须以 `/` 开头。
|
||||||
|
- `method` 支持:`GET/POST/PUT/PATCH/DELETE`。
|
||||||
|
- `arguments` 会编码为 query 参数(列表会转为逗号拼接)。
|
||||||
|
|
||||||
|
## 3.1) 学习工具约定
|
||||||
|
|
||||||
|
- 所有学习类工具都必须带 `reason`
|
||||||
|
- `memory_manager` 支持:`add / list / replace / remove`
|
||||||
|
- `skill_manager` 支持:`list / append_pattern / remove_pattern / write_reference / remove_reference / write_script / remove_script`
|
||||||
|
- `session_search` 只搜索当前用户 + 当前项目作用域,不接受跨项目检索
|
||||||
|
- `skill_manager` 的结构化写入优先落到:
|
||||||
|
1. `## Learned Patterns`
|
||||||
|
2. `references/*.md`
|
||||||
|
3. `scripts/*.py`
|
||||||
|
不应直接重写 skill frontmatter 或任意正文段落
|
||||||
|
- `scripts/*.py` 仅表示当前 `skill_path` 私有的可复用脚本资产;不要把运行时临时脚本写进 `data/`
|
||||||
|
|
||||||
|
## 4) 用户上下文注入(后端执行阶段)
|
||||||
|
|
||||||
|
- `Authorization`(Bearer Token)
|
||||||
|
- `x-project-id`
|
||||||
|
|
||||||
|
执行器会附带 `x-trace-id` 用于链路排查(可透传或自动生成)。
|
||||||
|
|
||||||
|
## 5) 排障要点
|
||||||
|
|
||||||
|
- `400`:检查工具参数 `path/method/arguments` 格式。
|
||||||
|
- `401/403`:检查 token 与项目权限。
|
||||||
|
- `404`:检查 `path` 是否正确。
|
||||||
|
- `422`:检查 `arguments` 字段名与类型。
|
||||||
|
- `5xx`:记录 `trace_id`,结合后端日志排查。
|
||||||
|
|
||||||
|
## 6) 前端工具调用链
|
||||||
|
|
||||||
|
前端工具(`locate_features`, `view_history`, `view_scada`, `show_chart`)的调用链与 `dynamic_http_call` 不同:
|
||||||
|
|
||||||
|
```
|
||||||
|
用户消息 → opencode agent → tool calling → 调用前端工具 (如 locate_features)
|
||||||
|
↓
|
||||||
|
tool handler:
|
||||||
|
1) 推送 SSE tool_call 事件到前端
|
||||||
|
2) 返回简短确认给 opencode agent("已定位到管道")
|
||||||
|
↓
|
||||||
|
前端同时收到:
|
||||||
|
- SSE event: progress → 展示规划/工具执行/完成状态
|
||||||
|
- SSE event: tool_call → 前端执行操作(定位地图/打开面板/渲染图表)
|
||||||
|
- SSE event: token → 渲染 opencode agent 文字回复
|
||||||
|
```
|
||||||
|
|
||||||
|
关键区别:
|
||||||
|
- `dynamic_http_call`:TJWaterAgent 代理 HTTP 请求,结果返回给 opencode agent 做后续分析。
|
||||||
|
- 前端工具:TJWaterAgent 仅推送 SSE 事件,前端直接执行,结果不返回 opencode agent。
|
||||||
|
- `show_chart`:opencode agent 先通过 `dynamic_http_call` 查询数据,处理为 x_data + series 格式后调用 `show_chart`,前端直接渲染图表,不再请求后端。
|
||||||
|
|
||||||
|
## 7) 复盘与沉淀建议
|
||||||
|
|
||||||
|
- 复杂多工具任务完成后,优先判断是否产生了稳定 workflow,可写入 `skill_manager`
|
||||||
|
- 用户明确纠正表达风格、输出格式或步骤时,优先判断是否需要写入 `memory_manager`
|
||||||
|
- 如果你只是想确认“以前是不是处理过类似问题”,先用 `session_search`
|
||||||
|
- 如果结果仍然只是 preview,不要基于 preview 做 learned pattern,总是先 `fetch_result_ref`
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-workflow
|
||||||
|
description: 负责分析类工作流能力。
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Workflow Domain Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责分析场景下的工作流组织与调用入口能力。
|
||||||
|
|
||||||
|
## 使用策略
|
||||||
|
|
||||||
|
- 当用户问题明显属于“多接口 + 本地分析 + 综合结论”的分析任务时,优先从本目录查找固定 workflow。
|
||||||
|
- 如果找到合适 workflow,应先按 workflow 执行主路径,再补充缺少的原子 skill。
|
||||||
|
- 如果没有匹配 workflow,或现有 workflow 缺少关键步骤、接口或输出约束,再回到其他 domain/scenario/action skills 组合能力。
|
||||||
|
|
||||||
|
## 子模块索引 (渐进式引导)
|
||||||
|
- **bottleneck-analysis**: 见 `./bottleneck-analysis/SKILL.md`
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
name: tjwater-workflow-bottleneck-analysis
|
||||||
|
description: workflow 下 bottleneck-analysis(水力瓶颈分析)工作流技能。
|
||||||
|
version: 1.1.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# bottleneck-analysis Workflow Skill
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
负责 `analytics/simulation-analysis` 场景下的水力瓶颈综合分析,通过结合管道属性与水力模拟结果,识别管网中超负荷、高流速、高水头损失的瓶颈管道,并给出分级改造建议。
|
||||||
|
|
||||||
|
## 前置依赖
|
||||||
|
本工作流依赖以下两个数据源,需按顺序并行或串行获取:
|
||||||
|
|
||||||
|
### 依赖 1:管道属性数据
|
||||||
|
- 接口:`GET /api/v1/getallpipeproperties/`
|
||||||
|
- 参数:`network`(query,如 `tjwater`)
|
||||||
|
- 用途:获取全部管道的 id、管径(diameter)、长度(length)、粗糙度(roughness)、起端(node1)、终端(node2) 等属性
|
||||||
|
- 注意:结果可能很大(数万条),需使用 `fetch_result_ref` 分批或全量获取
|
||||||
|
|
||||||
|
### 依赖 2:水力模拟结果
|
||||||
|
- 接口:`GET /api/v1/runprojectreturndict/`
|
||||||
|
- 参数:`network`(query,如 `tjwater`)
|
||||||
|
- 用途:运行管网水力模拟,返回各管段的 flow(LPS)、velocity(m/s)、headloss(m)、status,以及各节点的 demand、head、pressure(KPA)
|
||||||
|
- 注意:结果可达 30MB+,需用 Python 脚本批量处理或使用 `fetch_result_ref` 回读
|
||||||
|
|
||||||
|
## 工作流步骤
|
||||||
|
|
||||||
|
### 第 1 步:并行获取管道属性和运行水力模拟
|
||||||
|
同时调用 `getallpipeproperties` 和 `runprojectreturndict`,network 参数使用项目名称(如 `tjwater`)。
|
||||||
|
|
||||||
|
### 第 2 步:合并数据
|
||||||
|
用 Python 脚本将管道属性的 pipe_id 与模拟结果的 link_id 进行关联,构建含以下字段的合并数据集:
|
||||||
|
|
||||||
|
| 字段 | 来源 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | 两者关联键 | 管道/链路 ID |
|
||||||
|
| flow | 模拟 link_results | 流量 (LPS) |
|
||||||
|
| velocity | 模拟 link_results | 流速 (m/s) |
|
||||||
|
| headloss | 模拟 link_results | 水头损失 (m) |
|
||||||
|
| diameter | 管道属性 | 管径 (mm) |
|
||||||
|
| length | 管道属性 | 长度 (m) |
|
||||||
|
| roughness | 管道属性 | 粗糙度系数 |
|
||||||
|
| node1 / node2 | 管道属性 | 起端/终端节点 ID |
|
||||||
|
| unit_headloss | 计算 | headloss / length (m/m) |
|
||||||
|
| capacity_ratio | 计算 | |flow| / (π×(d/2000)²×1000),即实际流量与 1m/s 设计流量的比值 |
|
||||||
|
|
||||||
|
同时从模拟 node_results 提取各节点 pressure,关联到管段两端。
|
||||||
|
|
||||||
|
### 第 3 步:多维度瓶颈识别
|
||||||
|
按以下 5 个维度分别排序筛选,交叉印证:
|
||||||
|
|
||||||
|
| 维度 | 筛选条件 | 指示含义 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| 高流速 | velocity > 1.2 m/s | 管径不足 |
|
||||||
|
| 主干管高流量 | diameter ≥ 300mm 且 velocity > 0.5 m/s | 传输瓶颈 |
|
||||||
|
| 高水头损失 | headloss > 5m 且 0.3 < velocity < 1.5 m/s | 能耗瓶颈/粗糙度问题 |
|
||||||
|
| 高单位水头损失 | unit_headloss > 1.0 m/m | 严重局部瓶颈 |
|
||||||
|
| 超负荷 | capacity_ratio > 1.0 | 实际流量超过设计能力 |
|
||||||
|
|
||||||
|
排除极短管道(length < 0.5m)以减少噪声。
|
||||||
|
|
||||||
|
### 第 4 步:综合评分
|
||||||
|
对有效管道计算综合瓶颈分数:
|
||||||
|
|
||||||
|
```
|
||||||
|
composite_score = (velocity / max_velocity) × 0.4
|
||||||
|
+ (headloss / max_headloss) × 0.3
|
||||||
|
+ (capacity_ratio / max_capacity_ratio) × 0.3
|
||||||
|
```
|
||||||
|
|
||||||
|
取 TOP 10~20 作为最严重瓶颈管道。
|
||||||
|
|
||||||
|
### 第 5 步:前端可视化
|
||||||
|
- 使用 `show_chart` 展示流速分布柱状图
|
||||||
|
- 使用 `locate_features` 在地图上定位 TOP 瓶颈管道(feature_type=pipe)
|
||||||
|
- 可选:使用 `view_history` 查看瓶颈管道的历史运行数据
|
||||||
|
- 前端工具仅用于展示,分析结论必须来自 `dynamic_http_call` / `fetch_result_ref` 获得的数据
|
||||||
|
|
||||||
|
### 第 6 步:给出分级改造建议
|
||||||
|
按严重程度分为三级:
|
||||||
|
|
||||||
|
- **🚨 紧急**:综合评分 > 0.3,立即安排管径升级
|
||||||
|
- **⚡ 重点**:综合评分 0.15~0.3,纳入近期改造计划
|
||||||
|
- **📋 关注**:综合评分 0.05~0.15 或单维度超标,持续监测
|
||||||
|
|
||||||
|
每条建议含:当前管径 → 建议管径(基于目标流速 1.0~1.5 m/s 反推),并附改造理由。
|
||||||
|
|
||||||
|
## 改造管径计算公式
|
||||||
|
```
|
||||||
|
建议管径(mm) = 2 × 1000 × sqrt(|flow| / (π × target_velocity × 1000))
|
||||||
|
```
|
||||||
|
目标流速:DN<300 取 1.0 m/s,DN≥300 取 1.2 m/s。
|
||||||
|
|
||||||
|
## 证据约束
|
||||||
|
|
||||||
|
- 如果关键数据仍处于 preview 状态,不得直接输出最终瓶颈结论
|
||||||
|
- 如果模拟结果不完整或接口失败,应明确说明当前仅能做初步筛查
|
||||||
|
- 改造建议必须区分“数据直接支持的结论”和“工程经验推断”
|
||||||
|
|
||||||
|
## 推荐输出结构
|
||||||
|
|
||||||
|
1. 分析范围与数据来源
|
||||||
|
2. 主要瓶颈管段 Top N
|
||||||
|
3. 分级建议(紧急 / 重点 / 关注)
|
||||||
|
4. 假设与局限
|
||||||
|
5. 是否建议地图定位或图表展示
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
- 管道属性操作:`../business/network-assets/pipes/SKILL.md`
|
||||||
|
- 模拟操作:`./simulation/SKILL.md`
|
||||||
|
- 节点属性操作:`../business/network-assets/junctions/SKILL.md`
|
||||||
|
|
||||||
|
## Learned Patterns
|
||||||
|
- 先按“属性数据获取 → 模拟结果获取 → 本地关联 → 多指标筛选 → 分级建议”拆解工作流,再组织展示步骤,避免把一次分析过程写成会话流水账。
|
||||||
|
- 结果集较大时,优先使用 `fetch_result_ref` 或本地脚本批处理;只要数据仍是 preview、截断或未完整回读,就不能直接输出 Top N 瓶颈结论。
|
||||||
|
- 关联前先统一关键字段和单位:`pipe_id/link_id`、`diameter(mm)`、`length(m)`、`flow(LPS)`、`pressure(KPA)`;字段未对齐时,后续 ranking 和建议都会失真。
|
||||||
|
- `unit_headloss`、`capacity_ratio` 等衍生指标应在过滤异常数据(如 `length < 0.5m` 的短管)后再计算,否则容易被极端值放大。
|
||||||
|
- 阈值和评分权重应视为可调启发式,而不是唯一真理;输出时要区分“数据直接支持的结论”和“工程经验推断的建议”。
|
||||||
|
- 地图定位、图表展示属于证据呈现层,不能替代分析层;瓶颈判定必须基于后端原始结果或完整回读数据。
|
||||||
|
- 常见坑点:短管导致单位水头损失虚高、节点或链路映射缺失导致误判、模拟结果不完整时误把局部结果当全量结论。
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { tool } from "@opencode-ai/plugin";
|
|
||||||
|
|
||||||
export default tool({
|
|
||||||
description:
|
|
||||||
"在前端地图上对节点或管道图层应用样式,或重置为默认样式。样式参数应尽量与前端样式编辑器字段保持一致。",
|
|
||||||
args: {
|
|
||||||
reason: tool.schema
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"Why this style action is needed for the current user request.",
|
|
||||||
),
|
|
||||||
layer_id: tool.schema
|
|
||||||
.enum(["junctions", "pipes"])
|
|
||||||
.describe("Target layer id. Must be exactly 'junctions' or 'pipes'."),
|
|
||||||
reset_to_default: tool.schema
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Whether to reset the target layer to its default style."),
|
|
||||||
style_config: tool.schema
|
|
||||||
.object({
|
|
||||||
property: tool.schema
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Data property to render."),
|
|
||||||
classification_method: tool.schema
|
|
||||||
.enum(["pretty_breaks", "custom_breaks"])
|
|
||||||
.optional()
|
|
||||||
.describe("Classification method."),
|
|
||||||
segments: tool.schema
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Number of segments."),
|
|
||||||
min_size: tool.schema
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Minimum point radius."),
|
|
||||||
max_size: tool.schema
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Maximum point radius."),
|
|
||||||
min_stroke_width: tool.schema
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Minimum line width."),
|
|
||||||
max_stroke_width: tool.schema
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Maximum line width."),
|
|
||||||
fixed_stroke_width: tool.schema
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Fixed line width when width is not data-driven."),
|
|
||||||
color_type: tool.schema
|
|
||||||
.enum(["single", "gradient", "rainbow", "custom"])
|
|
||||||
.optional()
|
|
||||||
.describe("Color strategy."),
|
|
||||||
single_palette_index: tool.schema.number().optional(),
|
|
||||||
gradient_palette_index: tool.schema.number().optional(),
|
|
||||||
rainbow_palette_index: tool.schema.number().optional(),
|
|
||||||
show_labels: tool.schema
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Whether to show labels."),
|
|
||||||
show_id: tool.schema
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Whether to show ids."),
|
|
||||||
opacity: tool.schema.number().optional().describe("Opacity in [0, 1]."),
|
|
||||||
adjust_width_by_property: tool.schema
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Whether line width is driven by the rendered property."),
|
|
||||||
custom_breaks: tool.schema
|
|
||||||
.array(tool.schema.number())
|
|
||||||
.optional()
|
|
||||||
.describe("Custom break values."),
|
|
||||||
custom_colors: tool.schema
|
|
||||||
.array(tool.schema.string())
|
|
||||||
.optional()
|
|
||||||
.describe("Custom rgba colors."),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
"Optional style config overrides. Omit when reset_to_default is true.",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
async execute(args) {
|
|
||||||
const layerLabel = args.layer_id === "junctions" ? "节点" : "管道";
|
|
||||||
if (args.reset_to_default) {
|
|
||||||
return `已将${layerLabel}图层重置为默认样式。`;
|
|
||||||
}
|
|
||||||
return `已对${layerLabel}图层应用样式。`;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { tool } from "@opencode-ai/plugin";
|
||||||
|
|
||||||
|
const internalBaseUrl = process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
||||||
|
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
||||||
|
|
||||||
|
export default tool({
|
||||||
|
description:
|
||||||
|
"通过本地 Agent 桥接调用 TJWater 后端 API。需提供 API 路径、可选的请求方法以及查询参数。",
|
||||||
|
args: {
|
||||||
|
reason: tool.schema
|
||||||
|
.string()
|
||||||
|
.describe("Why this tool call is required for the current user request."),
|
||||||
|
path: tool.schema.string().describe("Target backend API path, starting with '/'."),
|
||||||
|
method: tool.schema
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("HTTP method. Defaults to GET."),
|
||||||
|
arguments: tool.schema
|
||||||
|
.record(tool.schema.string(), tool.schema.unknown())
|
||||||
|
.optional()
|
||||||
|
.describe("Query arguments object."),
|
||||||
|
},
|
||||||
|
async execute(args, context) {
|
||||||
|
// 工具本身不直接持有用户 token;通过 sessionID 回调 Agent 服务,由服务侧补齐用户上下文。
|
||||||
|
const response = await fetch(`${internalBaseUrl}/internal/tools/dynamic-http-call`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"x-agent-internal-token": internalToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
sessionId: context.sessionID,
|
||||||
|
reason: args.reason,
|
||||||
|
path: args.path,
|
||||||
|
method: args.method,
|
||||||
|
arguments: args.arguments,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(text);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { tool } from "@opencode-ai/plugin";
|
||||||
|
|
||||||
|
const internalBaseUrl = process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
||||||
|
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
||||||
|
|
||||||
|
export default tool({
|
||||||
|
description:
|
||||||
|
"回读由 dynamic_http_call 生成的持久化 result_ref。适用于大结果只返回 preview 时,再按需读取完整或截断后的数据。",
|
||||||
|
args: {
|
||||||
|
reason: tool.schema
|
||||||
|
.string()
|
||||||
|
.describe("Why the stored result needs to be read for the current user request."),
|
||||||
|
result_ref: tool.schema.string().describe("The result_ref returned by dynamic_http_call."),
|
||||||
|
max_items: tool.schema
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.positive()
|
||||||
|
.optional()
|
||||||
|
.describe("Optional maximum number of top-level items or fields to return."),
|
||||||
|
},
|
||||||
|
async execute(args, context) {
|
||||||
|
const response = await fetch(`${internalBaseUrl}/internal/tools/fetch-result-ref`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"x-agent-internal-token": internalToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
sessionId: context.sessionID,
|
||||||
|
result_ref: args.result_ref,
|
||||||
|
max_items: args.max_items,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(text);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { tool } from "@opencode-ai/plugin";
|
|
||||||
|
|
||||||
const internalBaseUrl =
|
|
||||||
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
|
||||||
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
|
||||||
|
|
||||||
export default tool({
|
|
||||||
description:
|
|
||||||
"调用 TJWater 后端的天地图地理编码服务,将中国境内结构化地址或地点名称转换为经纬度。若需缩放地图,把返回的 location.lon/location.lat 传给 zoom_to_map,并设置 source_crs='EPSG:4326'。",
|
|
||||||
args: {
|
|
||||||
reason: tool.schema
|
|
||||||
.string()
|
|
||||||
.describe("Why geocoding is required for the current user request."),
|
|
||||||
keyword: tool.schema
|
|
||||||
.string()
|
|
||||||
.describe("Address or place name to geocode, such as 北京市人民政府."),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
const response = await fetch(`${internalBaseUrl}/internal/tools/geocode`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"x-agent-internal-token": internalToken,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
session_id: context.sessionID,
|
|
||||||
keyword: args.keyword,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const text = await response.text();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(text);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -5,12 +5,8 @@ export default tool({
|
|||||||
args: {
|
args: {
|
||||||
reason: tool.schema
|
reason: tool.schema
|
||||||
.string()
|
.string()
|
||||||
.describe(
|
.describe("Why this map positioning action is needed for the user request."),
|
||||||
"Why this map positioning action is needed for the user request.",
|
ids: tool.schema.array(tool.schema.string()).describe("Feature ids to locate."),
|
||||||
),
|
|
||||||
ids: tool.schema
|
|
||||||
.array(tool.schema.string())
|
|
||||||
.describe("Feature ids to locate."),
|
|
||||||
feature_type: tool.schema
|
feature_type: tool.schema
|
||||||
.enum(["junction", "pipe", "valve", "reservoir", "pump", "tank"])
|
.enum(["junction", "pipe", "valve", "reservoir", "pump", "tank"])
|
||||||
.describe("Type of feature to locate."),
|
.describe("Type of feature to locate."),
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { tool } from "@opencode-ai/plugin";
|
import { tool } from "@opencode-ai/plugin";
|
||||||
import { MemoryStore } from "../../src/memory/store.js";
|
import { MemoryStore } from "../../src/memory/store.js";
|
||||||
import {
|
import { ToolSessionContextStore } from "../../src/session/toolContextStore.js";
|
||||||
getRuntimeSessionContext,
|
|
||||||
setRuntimeSessionContext,
|
|
||||||
} from "../../src/runtime/sessionContext.js";
|
|
||||||
|
|
||||||
const memoryStore = new MemoryStore();
|
const memoryStore = new MemoryStore();
|
||||||
const initializePromise = memoryStore.initialize();
|
const toolContextStore = new ToolSessionContextStore();
|
||||||
|
const initializePromise = Promise.all([
|
||||||
|
memoryStore.initialize(),
|
||||||
|
toolContextStore.initialize(),
|
||||||
|
]);
|
||||||
|
|
||||||
export default tool({
|
export default tool({
|
||||||
description:
|
description:
|
||||||
"管理长期有效的用户偏好或项目事实。支持 add/list/replace/remove。add 前必须先对同 scope 执行 list 并阅读现有记忆,再决定 add、replace 或 remove;不要跳过读取直接新增。禁止写入 token、password、secret、system prompt 或一次性上下文。scope 仅允许 'user' 或 'workspace'。",
|
"管理长期有效的用户偏好或项目事实。支持 add/list/replace/remove。禁止写入 token、password、secret、system prompt 或一次性上下文。scope 仅允许 'user' 或 'workspace'。",
|
||||||
args: {
|
args: {
|
||||||
action: tool.schema
|
action: tool.schema
|
||||||
.enum(["add", "list", "replace", "remove"])
|
.enum(["add", "list", "replace", "remove"])
|
||||||
@@ -36,7 +37,7 @@ export default tool({
|
|||||||
},
|
},
|
||||||
async execute(args, context) {
|
async execute(args, context) {
|
||||||
await initializePromise;
|
await initializePromise;
|
||||||
const sessionContext = getRuntimeSessionContext(context.sessionID);
|
const sessionContext = await toolContextStore.read(context.sessionID);
|
||||||
if (!sessionContext) {
|
if (!sessionContext) {
|
||||||
throw new Error(`session context not found for ${context.sessionID}`);
|
throw new Error(`session context not found for ${context.sessionID}`);
|
||||||
}
|
}
|
||||||
@@ -56,94 +57,68 @@ export default tool({
|
|||||||
}
|
}
|
||||||
if (sessionContext.allowLearningWrite === false && args.action !== "list") {
|
if (sessionContext.allowLearningWrite === false && args.action !== "list") {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
ok: true,
|
ok: true,
|
||||||
kind: "memory",
|
kind: "memory",
|
||||||
decision: "rejected",
|
decision: "rejected",
|
||||||
detail: "memory writes are disabled for this session",
|
detail: "memory writes are disabled for this session",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopeKey =
|
const scopeKey =
|
||||||
scope === "user" ? sessionContext.actorKey : sessionContext.projectKey;
|
scope === "user" ? sessionContext.actorKey : sessionContext.projectKey;
|
||||||
if (args.action === "list") {
|
if (args.action === "list") {
|
||||||
const readScopes = {
|
|
||||||
...(sessionContext.memoryListReadScopes ?? {}),
|
|
||||||
[scope]: true,
|
|
||||||
};
|
|
||||||
setRuntimeSessionContext({
|
|
||||||
...sessionContext,
|
|
||||||
memoryListReadScopes: readScopes,
|
|
||||||
});
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
ok: true,
|
ok: true,
|
||||||
kind: "memory",
|
kind: "memory",
|
||||||
decision: "accepted",
|
decision: "accepted",
|
||||||
detail: "memory listed",
|
detail: "memory listed",
|
||||||
items: await memoryStore.list(scope, scopeKey),
|
items: await memoryStore.list(scope, scopeKey),
|
||||||
target: scope,
|
target: scope,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.action === "add") {
|
if (args.action === "add") {
|
||||||
if (sessionContext.memoryListReadScopes?.[scope] !== true) {
|
|
||||||
return JSON.stringify({
|
|
||||||
ok: true,
|
|
||||||
kind: "memory",
|
|
||||||
decision: "rejected",
|
|
||||||
detail: `must list ${scope} memory and review existing entries before add`,
|
|
||||||
target: scope,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const result = await memoryStore.upsert(scope, scopeKey, {
|
const result = await memoryStore.upsert(scope, scopeKey, {
|
||||||
content: args.content ?? "",
|
content: args.content ?? "",
|
||||||
sessionId: sessionContext.clientSessionId,
|
sessionId: context.sessionID,
|
||||||
source: "tool",
|
source: "tool",
|
||||||
traceId: sessionContext.traceId,
|
traceId: sessionContext.traceId,
|
||||||
});
|
});
|
||||||
if (!result.entry) {
|
if (!result.entry) {
|
||||||
return JSON.stringify({
|
|
||||||
ok: true,
|
|
||||||
kind: "memory",
|
|
||||||
decision: "rejected",
|
|
||||||
detail: "content rejected by persistence policy",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
ok: true,
|
ok: true,
|
||||||
kind: "memory",
|
kind: "memory",
|
||||||
decision: result.changed ? "accepted" : "deduped",
|
decision: "rejected",
|
||||||
detail: result.detail,
|
detail: "content rejected by persistence policy",
|
||||||
entry: result.entry,
|
});
|
||||||
target: scope,
|
}
|
||||||
|
return JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
kind: "memory",
|
||||||
|
decision: result.changed ? "accepted" : "deduped",
|
||||||
|
detail: result.changed ? "memory stored" : "memory already existed",
|
||||||
|
entry: result.entry,
|
||||||
|
target: scope,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.action === "replace") {
|
if (args.action === "replace") {
|
||||||
const result = await memoryStore.replace(
|
const result = await memoryStore.replace(scope, scopeKey, args.target_id ?? "", {
|
||||||
scope,
|
content: args.content ?? "",
|
||||||
scopeKey,
|
sessionId: context.sessionID,
|
||||||
args.target_id ?? "",
|
source: "tool",
|
||||||
{
|
traceId: sessionContext.traceId,
|
||||||
content: args.content ?? "",
|
});
|
||||||
sessionId: sessionContext.clientSessionId,
|
|
||||||
source: "tool",
|
|
||||||
traceId: sessionContext.traceId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
ok: true,
|
ok: true,
|
||||||
kind: "memory",
|
kind: "memory",
|
||||||
decision: result.changed ? "accepted" : "rejected",
|
decision: result.changed ? "accepted" : "rejected",
|
||||||
detail: result.detail,
|
detail: result.detail,
|
||||||
target: scope,
|
target: scope,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await memoryStore.remove(
|
const result = await memoryStore.remove(scope, scopeKey, args.target_id ?? "");
|
||||||
scope,
|
|
||||||
scopeKey,
|
|
||||||
args.target_id ?? "",
|
|
||||||
);
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
ok: true,
|
ok: true,
|
||||||
kind: "memory",
|
kind: "memory",
|
||||||
|
|||||||
@@ -2,17 +2,15 @@ import { tool } from "@opencode-ai/plugin";
|
|||||||
|
|
||||||
export default tool({
|
export default tool({
|
||||||
description:
|
description:
|
||||||
"在前端地图上对 junctions 图层应用分区渲染。使用前必须完成两步:① 准备数据结构(JSON 文件,结构为 { node_area_map: Record<string, string>, area_ids?: string[], area_colors?: Record<string, string> },其中 node_area_map 的 key 是 junction/node id,value 是 area id);② 调用 store_render_ref 将 JSON 文件存储到受控路径,获取 render_ref(格式为 res-...);③ 将 render_ref 传入本工具完成前端渲染。注意:不要先把 ref 内容完整读出再传给前端,也不要直接传本地文件路径。",
|
"在前端地图上对 junctions 图层应用分区渲染。优先直接传入 render_ref(指向已持久化的渲染结果引用),不要先把 ref 内容完整读出再重组;前端会自行根据 render_ref 拉取完整 payload 并渲染,这样可以避免 LLM 读取大型 node_area_map。若必须自行构造供 render_ref 引用的 JSON,其 data 结构必须为 { node_area_map: Record<string, string>, area_ids?: string[], area_colors?: Record<string, string> },其中 node_area_map 的 key 是 junction/node id,value 是 area id。",
|
||||||
args: {
|
args: {
|
||||||
reason: tool.schema
|
reason: tool.schema
|
||||||
.string()
|
.string()
|
||||||
.describe(
|
.describe("Why this junction rendering action is needed for the user request."),
|
||||||
"Why this junction rendering action is needed for the user request.",
|
|
||||||
),
|
|
||||||
render_ref: tool.schema
|
render_ref: tool.schema
|
||||||
.string()
|
.string()
|
||||||
.describe(
|
.describe(
|
||||||
"上一步通过 store_render_ref 获得的渲染引用 ID(res-...)。前端会按该引用拉取完整 payload 并渲染。不可直接传入本地文件路径或完整 JSON 数据。",
|
"渲染引用 ID。直接传持久化 render_ref 即可,前端会按该引用读取完整 payload.data 并渲染,不需要先用 fetch_result_ref 提取完整数据。render_ref 对应的数据结构必须是 { node_area_map: { [junctionId]: areaId }, area_ids?: string[], area_colors?: { [areaId]: color } };node_area_map 必填,area_ids / area_colors 可选。",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
async execute() {
|
async execute() {
|
||||||
|
|||||||
@@ -22,21 +22,18 @@ export default tool({
|
|||||||
.describe("Optional maximum number of hits to return."),
|
.describe("Optional maximum number of hits to return."),
|
||||||
},
|
},
|
||||||
async execute(args, context) {
|
async execute(args, context) {
|
||||||
const response = await fetch(
|
const response = await fetch(`${internalBaseUrl}/internal/tools/session-search`, {
|
||||||
`${internalBaseUrl}/internal/tools/session-search`,
|
method: "POST",
|
||||||
{
|
headers: {
|
||||||
method: "POST",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
"x-agent-internal-token": internalToken,
|
||||||
"Content-Type": "application/json",
|
|
||||||
"x-agent-internal-token": internalToken,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
max_results: args.max_results,
|
|
||||||
query: args.query,
|
|
||||||
session_id: context.sessionID,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
);
|
body: JSON.stringify({
|
||||||
|
max_results: args.max_results,
|
||||||
|
query: args.query,
|
||||||
|
sessionId: context.sessionID,
|
||||||
|
}),
|
||||||
|
});
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(text);
|
throw new Error(text);
|
||||||
|
|||||||
@@ -21,14 +21,8 @@ export default tool({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.describe("Series data."),
|
.describe("Series data."),
|
||||||
x_axis_name: tool.schema
|
x_axis_name: tool.schema.string().optional().describe("X-axis display name."),
|
||||||
.string()
|
y_axis_name: tool.schema.string().optional().describe("Y-axis display name."),
|
||||||
.optional()
|
|
||||||
.describe("X-axis display name."),
|
|
||||||
y_axis_name: tool.schema
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Y-axis display name."),
|
|
||||||
},
|
},
|
||||||
async execute() {
|
async execute() {
|
||||||
// 图表数据已经在工具参数里,前端收到 tool_call 后直接渲染,不再二次请求后端。
|
// 图表数据已经在工具参数里,前端收到 tool_call 后直接渲染,不再二次请求后端。
|
||||||
|
|||||||
@@ -1,154 +1,115 @@
|
|||||||
import { tool } from "@opencode-ai/plugin";
|
import { tool } from "@opencode-ai/plugin";
|
||||||
|
|
||||||
import { SkillStore } from "../../src/skills/store.js";
|
import { SkillStore } from "../../src/skills/store.js";
|
||||||
import {
|
import { ToolSessionContextStore } from "../../src/session/toolContextStore.js";
|
||||||
getRuntimeSessionContext,
|
|
||||||
type RuntimeSessionContext,
|
|
||||||
} from "../../src/runtime/sessionContext.js";
|
|
||||||
|
|
||||||
type ToolContextReader = {
|
const toolContextStore = new ToolSessionContextStore();
|
||||||
read(sessionId: string): RuntimeSessionContext | null;
|
const initializePromise = toolContextStore.initialize();
|
||||||
};
|
const skillStore = new SkillStore();
|
||||||
|
|
||||||
const runtimeContextReader: ToolContextReader = {
|
export default tool({
|
||||||
read: getRuntimeSessionContext,
|
description:
|
||||||
};
|
"维护已验证、可复用、非敏感的 workflow 或方法模式。支持 list、append_pattern、remove_pattern、write_reference、remove_reference、write_script、remove_script。",
|
||||||
|
args: {
|
||||||
export const createSkillManagerTool = (
|
action: tool.schema
|
||||||
skillStore = new SkillStore(),
|
.enum([
|
||||||
toolContextStore: ToolContextReader = runtimeContextReader,
|
"list",
|
||||||
initializePromise: Promise<unknown> = Promise.resolve(),
|
"append_pattern",
|
||||||
) =>
|
"remove_pattern",
|
||||||
tool({
|
"write_reference",
|
||||||
description:
|
"remove_reference",
|
||||||
"维护已验证、可复用、非敏感的 workflow 或方法模式。支持 list、write_skill、remove_skill、append_pattern、remove_pattern、write_reference、remove_reference、write_script、remove_script。",
|
"write_script",
|
||||||
args: {
|
"remove_script",
|
||||||
action: tool.schema
|
])
|
||||||
.enum([
|
.describe("Skill maintenance operation."),
|
||||||
"list",
|
reason: tool.schema
|
||||||
"write_skill",
|
.string()
|
||||||
"remove_skill",
|
.describe(
|
||||||
"append_pattern",
|
"Why this skill maintenance action is justified for future reuse.",
|
||||||
"remove_pattern",
|
),
|
||||||
"write_reference",
|
skill_path: tool.schema
|
||||||
"remove_reference",
|
.string()
|
||||||
"write_script",
|
.describe(
|
||||||
"remove_script",
|
"Target skill directory path relative to .opencode/skills, for example analytics/simulation-analysis/leakage or platform/governance-observability/meta.",
|
||||||
])
|
),
|
||||||
.describe("Skill maintenance operation."),
|
pattern: tool.schema
|
||||||
reason: tool.schema
|
.string()
|
||||||
.string()
|
.optional()
|
||||||
.describe(
|
.describe("Pattern text used by append_pattern."),
|
||||||
"Why this skill maintenance action is justified for future reuse.",
|
target_id: tool.schema
|
||||||
),
|
.string()
|
||||||
skill_path: tool.schema
|
.optional()
|
||||||
.string()
|
.describe("Stable learned pattern id used by remove_pattern."),
|
||||||
.describe(
|
file_path: tool.schema
|
||||||
"Target skill directory path relative to .opencode/skills. Use 'workflow' for the workflow index, or '__root__' for the root skills index.",
|
.string()
|
||||||
),
|
.optional()
|
||||||
pattern: tool.schema
|
.describe("Asset file path. For references use references/*.md; for scripts use scripts/*.py."),
|
||||||
.string()
|
content: tool.schema
|
||||||
.optional()
|
.string()
|
||||||
.describe("Pattern text used by append_pattern."),
|
.optional()
|
||||||
target_id: tool.schema
|
.describe("Asset content used by write_reference or write_script."),
|
||||||
.string()
|
},
|
||||||
.optional()
|
async execute(args, context) {
|
||||||
.describe("Stable learned pattern id used by remove_pattern."),
|
await initializePromise;
|
||||||
file_path: tool.schema
|
const sessionContext = await toolContextStore.read(context.sessionID);
|
||||||
.string()
|
if (!sessionContext) {
|
||||||
.optional()
|
throw new Error(`session context not found for ${context.sessionID}`);
|
||||||
.describe(
|
}
|
||||||
"Asset file path. For references use references/*.md; for scripts use scripts/*.py.",
|
if (sessionContext.allowLearningWrite === false && args.action !== "list") {
|
||||||
),
|
return JSON.stringify({
|
||||||
content: tool.schema
|
ok: true,
|
||||||
.string()
|
kind: "skill",
|
||||||
.optional()
|
decision: "rejected",
|
||||||
.describe(
|
detail: "skill writes are disabled for this session",
|
||||||
"Content used by write_skill, write_reference, or write_script.",
|
});
|
||||||
),
|
}
|
||||||
},
|
if (args.action === "list") {
|
||||||
async execute(args, context) {
|
const result = await skillStore.list(args.skill_path);
|
||||||
await initializePromise;
|
if (!result) {
|
||||||
const sessionContext = toolContextStore.read(context.sessionID);
|
|
||||||
if (!sessionContext) {
|
|
||||||
throw new Error(`session context not found for ${context.sessionID}`);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
sessionContext.allowLearningWrite === false &&
|
|
||||||
args.action !== "list"
|
|
||||||
) {
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
ok: true,
|
ok: true,
|
||||||
kind: "skill",
|
kind: "skill",
|
||||||
decision: "rejected",
|
decision: "rejected",
|
||||||
detail: "skill writes are disabled for this session",
|
detail:
|
||||||
|
"invalid skill_path; expected a relative path under .opencode/skills",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (args.action === "list") {
|
|
||||||
const result = await skillStore.list(args.skill_path);
|
|
||||||
if (!result) {
|
|
||||||
return JSON.stringify({
|
|
||||||
ok: true,
|
|
||||||
kind: "skill",
|
|
||||||
decision: "rejected",
|
|
||||||
detail:
|
|
||||||
"invalid skill_path; expected a relative path under .opencode/skills",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return JSON.stringify({
|
|
||||||
ok: true,
|
|
||||||
kind: "skill",
|
|
||||||
decision: "accepted",
|
|
||||||
detail: "skill listed",
|
|
||||||
...result,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result =
|
|
||||||
args.action === "write_skill"
|
|
||||||
? await skillStore.writeSkill(args.skill_path, args.content ?? "")
|
|
||||||
: args.action === "remove_skill"
|
|
||||||
? await skillStore.removeSkill(args.skill_path)
|
|
||||||
: args.action === "append_pattern"
|
|
||||||
? await skillStore.appendPattern(
|
|
||||||
args.skill_path,
|
|
||||||
args.pattern ?? "",
|
|
||||||
)
|
|
||||||
: args.action === "remove_pattern"
|
|
||||||
? await skillStore.removePattern(
|
|
||||||
args.skill_path,
|
|
||||||
args.target_id ?? "",
|
|
||||||
)
|
|
||||||
: args.action === "write_reference"
|
|
||||||
? await skillStore.writeReference(
|
|
||||||
args.skill_path,
|
|
||||||
args.file_path ?? "",
|
|
||||||
args.content ?? "",
|
|
||||||
)
|
|
||||||
: args.action === "remove_reference"
|
|
||||||
? await skillStore.removeReference(
|
|
||||||
args.skill_path,
|
|
||||||
args.file_path ?? "",
|
|
||||||
)
|
|
||||||
: args.action === "write_script"
|
|
||||||
? await skillStore.writeScript(
|
|
||||||
args.skill_path,
|
|
||||||
args.file_path ?? "",
|
|
||||||
args.content ?? "",
|
|
||||||
)
|
|
||||||
: await skillStore.removeScript(
|
|
||||||
args.skill_path,
|
|
||||||
args.file_path ?? "",
|
|
||||||
);
|
|
||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
ok: true,
|
ok: true,
|
||||||
kind: "skill",
|
kind: "skill",
|
||||||
decision: result.changed ? "accepted" : "rejected",
|
decision: "accepted",
|
||||||
detail: result.detail,
|
detail: "skill listed",
|
||||||
target: result.target,
|
...result,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
export default createSkillManagerTool();
|
const result =
|
||||||
|
args.action === "append_pattern"
|
||||||
|
? await skillStore.appendPattern(args.skill_path, args.pattern ?? "")
|
||||||
|
: args.action === "remove_pattern"
|
||||||
|
? await skillStore.removePattern(args.skill_path, args.target_id ?? "")
|
||||||
|
: args.action === "write_reference"
|
||||||
|
? await skillStore.writeReference(
|
||||||
|
args.skill_path,
|
||||||
|
args.file_path ?? "",
|
||||||
|
args.content ?? "",
|
||||||
|
)
|
||||||
|
: args.action === "remove_reference"
|
||||||
|
? await skillStore.removeReference(args.skill_path, args.file_path ?? "")
|
||||||
|
: args.action === "write_script"
|
||||||
|
? await skillStore.writeScript(
|
||||||
|
args.skill_path,
|
||||||
|
args.file_path ?? "",
|
||||||
|
args.content ?? "",
|
||||||
|
)
|
||||||
|
: await skillStore.removeScript(args.skill_path, args.file_path ?? "");
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
kind: "skill",
|
||||||
|
decision: result.changed ? "accepted" : "rejected",
|
||||||
|
detail: result.detail,
|
||||||
|
target: result.target,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import { tool } from "@opencode-ai/plugin";
|
|
||||||
|
|
||||||
const internalBaseUrl =
|
|
||||||
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
|
||||||
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
|
||||||
|
|
||||||
export default tool({
|
|
||||||
description:
|
|
||||||
"将本地 JSON 渲染数据文件存储到受控路径,返回可供 render_junctions 使用的 render_ref(res-...)。前置步骤:先准备好符合 render_junctions 数据结构的 JSON 文件 { node_area_map, area_ids?, area_colors? },写入本地路径后再调用本工具传入该路径,获取 render_ref 后传给 render_junctions 完成前端渲染。",
|
|
||||||
args: {
|
|
||||||
reason: tool.schema
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"为何需要将此本地渲染数据持久化为 render_ref,以便后续通过 render_junctions 渲染到前端。",
|
|
||||||
),
|
|
||||||
file_path: tool.schema
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"本地 JSON 文件的绝对路径,内容为 render_junctions 所需的数据结构 { node_area_map, area_ids?, area_colors? }。",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
const response = await fetch(
|
|
||||||
`${internalBaseUrl}/internal/tools/store-render-ref`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"x-agent-internal-token": internalToken,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
session_id: context.sessionID,
|
|
||||||
file_path: args.file_path,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const text = await response.text();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(text);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { tool } from "@opencode-ai/plugin";
|
|
||||||
|
|
||||||
const internalBaseUrl =
|
|
||||||
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
|
||||||
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
|
||||||
|
|
||||||
export default tool({
|
|
||||||
description:
|
|
||||||
"通过本地 Agent 桥接调用 tjwater-cli 命令访问 TJWater 后端服务。提供 CLI 子命令和参数。",
|
|
||||||
args: {
|
|
||||||
reason: tool.schema
|
|
||||||
.string()
|
|
||||||
.describe("Why this tool call is required for the current user request."),
|
|
||||||
command: tool.schema
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"tjwater-cli 子命令,不含二进制路径。示例:'project list'、'data timeseries realtime links --start-time 2025-01-01T00:00:00+08:00 --end-time 2025-01-01T01:00:00+08:00'",
|
|
||||||
),
|
|
||||||
timeout: tool.schema
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("超时秒数,默认 120。大结果集建议设 300+。"),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
const response = await fetch(
|
|
||||||
`${internalBaseUrl}/internal/tools/tjwater-cli-call`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"x-agent-internal-token": internalToken,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
session_id: context.sessionID,
|
|
||||||
reason: args.reason,
|
|
||||||
command: args.command,
|
|
||||||
timeout: args.timeout,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const text = await response.text();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(text);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -5,23 +5,15 @@ export default tool({
|
|||||||
args: {
|
args: {
|
||||||
reason: tool.schema
|
reason: tool.schema
|
||||||
.string()
|
.string()
|
||||||
.describe(
|
.describe("Why this history panel should be opened for the current task."),
|
||||||
"Why this history panel should be opened for the current task.",
|
|
||||||
),
|
|
||||||
feature_infos: tool.schema
|
feature_infos: tool.schema
|
||||||
.array(tool.schema.tuple([tool.schema.string(), tool.schema.string()]))
|
.array(tool.schema.tuple([tool.schema.string(), tool.schema.string()]))
|
||||||
.describe("List of [id, type] pairs."),
|
.describe("List of [id, type] pairs."),
|
||||||
data_type: tool.schema
|
data_type: tool.schema
|
||||||
.enum(["realtime", "scheme", "none"])
|
.enum(["realtime", "scheme", "none"])
|
||||||
.describe("History data source type."),
|
.describe("History data source type."),
|
||||||
start_time: tool.schema
|
start_time: tool.schema.string().optional().describe("Optional ISO8601 start time."),
|
||||||
.string()
|
end_time: tool.schema.string().optional().describe("Optional ISO8601 end time."),
|
||||||
.optional()
|
|
||||||
.describe("Optional ISO8601 start time."),
|
|
||||||
end_time: tool.schema
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Optional ISO8601 end time."),
|
|
||||||
},
|
},
|
||||||
async execute() {
|
async execute() {
|
||||||
// 返回短确认即可;面板打开动作由前端根据 tool_call 参数完成。
|
// 返回短确认即可;面板打开动作由前端根据 tool_call 参数完成。
|
||||||
|
|||||||
@@ -10,18 +10,13 @@ export default tool({
|
|||||||
.array(tool.schema.string())
|
.array(tool.schema.string())
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Preferred SCADA device ids."),
|
.describe("Preferred SCADA device ids."),
|
||||||
device_id: tool.schema
|
device_id: tool.schema.string().optional().describe("Single SCADA device id."),
|
||||||
.string()
|
feature_infos: tool.schema
|
||||||
|
.array(tool.schema.tuple([tool.schema.string(), tool.schema.string()]))
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Single SCADA device id."),
|
.describe("Legacy [id, type] pairs."),
|
||||||
start_time: tool.schema
|
start_time: tool.schema.string().optional().describe("Optional ISO8601 start time."),
|
||||||
.string()
|
end_time: tool.schema.string().optional().describe("Optional ISO8601 end time."),
|
||||||
.optional()
|
|
||||||
.describe("Optional ISO8601 start time."),
|
|
||||||
end_time: tool.schema
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Optional ISO8601 end time."),
|
|
||||||
},
|
},
|
||||||
async execute() {
|
async execute() {
|
||||||
// SCADA 面板仍在浏览器侧执行,工具结果不承载实际监测数据。
|
// SCADA 面板仍在浏览器侧执行,工具结果不承载实际监测数据。
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import { tool } from "@opencode-ai/plugin";
|
|
||||||
|
|
||||||
const internalBaseUrl =
|
|
||||||
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
|
||||||
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
|
||||||
|
|
||||||
export default tool({
|
|
||||||
description:
|
|
||||||
"调用 TJWater 后端的实时网页搜索服务。适合查询新闻、政策、规范、产品资料、公开网页事实等可能变化的信息。",
|
|
||||||
args: {
|
|
||||||
reason: tool.schema
|
|
||||||
.string()
|
|
||||||
.describe("Why web search is required for the current user request."),
|
|
||||||
query: tool.schema.string().describe("Search query text."),
|
|
||||||
freshness: tool.schema
|
|
||||||
.enum(["noLimit", "oneDay", "oneWeek", "oneMonth", "oneYear"])
|
|
||||||
.optional()
|
|
||||||
.describe("Optional freshness filter. Defaults to noLimit."),
|
|
||||||
summary: tool.schema
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Whether the backend should include page summaries."),
|
|
||||||
count: tool.schema
|
|
||||||
.number()
|
|
||||||
.int()
|
|
||||||
.positive()
|
|
||||||
.optional()
|
|
||||||
.describe("Optional result count, backend accepts 1 to 50."),
|
|
||||||
include: tool.schema
|
|
||||||
.array(tool.schema.string())
|
|
||||||
.optional()
|
|
||||||
.describe("Optional domains to include."),
|
|
||||||
exclude: tool.schema
|
|
||||||
.array(tool.schema.string())
|
|
||||||
.optional()
|
|
||||||
.describe("Optional domains to exclude."),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
const response = await fetch(`${internalBaseUrl}/internal/tools/web-search`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"x-agent-internal-token": internalToken,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
session_id: context.sessionID,
|
|
||||||
query: args.query,
|
|
||||||
freshness: args.freshness,
|
|
||||||
summary: args.summary,
|
|
||||||
count: args.count,
|
|
||||||
include: args.include,
|
|
||||||
exclude: args.exclude,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const text = await response.text();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(text);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { tool } from "@opencode-ai/plugin";
|
|
||||||
|
|
||||||
export default tool({
|
|
||||||
description:
|
|
||||||
"在前端地图上缩放定位到坐标。默认坐标为 EPSG:3857;如果来自天地图 geocode 的 lon/lat,传 source_crs='EPSG:4326',前端会转换为 EPSG:3857 后缩放。",
|
|
||||||
args: {
|
|
||||||
reason: tool.schema
|
|
||||||
.string()
|
|
||||||
.describe("Why this map zoom action is needed for the current request."),
|
|
||||||
x: tool.schema
|
|
||||||
.number()
|
|
||||||
.describe("X coordinate. For EPSG:4326 this is longitude; for EPSG:3857 this is meters."),
|
|
||||||
y: tool.schema
|
|
||||||
.number()
|
|
||||||
.describe("Y coordinate. For EPSG:4326 this is latitude; for EPSG:3857 this is meters."),
|
|
||||||
source_crs: tool.schema
|
|
||||||
.enum(["EPSG:3857", "EPSG:4326"])
|
|
||||||
.optional()
|
|
||||||
.describe("Input coordinate CRS. Defaults to EPSG:3857."),
|
|
||||||
zoom: tool.schema
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Optional OpenLayers zoom level. Defaults to 18."),
|
|
||||||
duration_ms: tool.schema
|
|
||||||
.number()
|
|
||||||
.optional()
|
|
||||||
.describe("Optional animation duration in milliseconds. Defaults to 1000."),
|
|
||||||
},
|
|
||||||
async execute() {
|
|
||||||
return "已缩放到指定地图坐标。";
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Repository Guidelines
|
|
||||||
|
|
||||||
## Project Structure & Module Organization
|
|
||||||
|
|
||||||
This repository is the internal TJWater agent service. Runtime TypeScript code is organized under `src/`; CLI-specific code is under `cli/src`; Node test files live in `node-tests/`. Agent extensions and OpenCode integration files are under `.opencode/`, including `.opencode/agents`, `.opencode/skills`, and `.opencode/tools`. Runtime data, session metadata, logs, and result references are stored under `data/` and `logs/` and should be treated as local/generated state.
|
|
||||||
|
|
||||||
Deployment files are `Dockerfile`, `docker-compose.yml`, and `.gitea/workflows/package.yml`.
|
|
||||||
|
|
||||||
## Build, Test, and Development Commands
|
|
||||||
|
|
||||||
Use Bun for this project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun install
|
|
||||||
bun run dev
|
|
||||||
bun run check
|
|
||||||
bun run test:cli
|
|
||||||
bun run start
|
|
||||||
```
|
|
||||||
|
|
||||||
`bun run dev` starts `src/server.ts` in watch mode. `bun run check` runs TypeScript checks for the main project and `.opencode`. `bun run test:cli` runs Node CLI tests. `bun run start` starts the service without watch mode.
|
|
||||||
|
|
||||||
## Coding Style & Naming Conventions
|
|
||||||
|
|
||||||
Use TypeScript ESM and keep types explicit at module boundaries. Use two-space indentation, `camelCase` for functions and variables, `PascalCase` for classes/types, and kebab-case or descriptive lowercase names for scripts and data files. Prefer existing `src/` service, route, and runtime patterns before introducing new structure.
|
|
||||||
|
|
||||||
## Testing Guidelines
|
|
||||||
|
|
||||||
Tests use Node's built-in test runner for CLI coverage. Name test files with `.node.mjs` when using `node --test`, matching `node-tests/cli/*.node.mjs`. Add focused tests for CLI parsing, tool behavior, and session/runtime changes. Do not depend on mutable local files under `data/`.
|
|
||||||
|
|
||||||
## Commit & Pull Request Guidelines
|
|
||||||
|
|
||||||
History uses Conventional Commit messages such as `feat(tools): add search and map tools`, `fix(agent): warm up opencode on startup`, and `refactor(chat): ...`. Prefer `feat(scope):`, `fix(scope):`, or `refactor(scope):`.
|
|
||||||
|
|
||||||
PRs should describe runtime behavior changes, list `bun run check` and any test commands run, and mention changes to `.opencode`, secrets, ports, or deploy workflow behavior.
|
|
||||||
|
|
||||||
## Security & Configuration Tips
|
|
||||||
|
|
||||||
Do not commit `.env`, logs, session transcripts, generated result references, or `node_modules/`. Keep registry and deploy credentials in Gitea secrets.
|
|
||||||
+4
-9
@@ -15,7 +15,6 @@ RUN sed -i "s|http://archive.ubuntu.com|https://${UBUNTU_APT_MIRROR}|g; s|http:/
|
|||||||
sed -i "s|http://archive.ubuntu.com|https://${UBUNTU_APT_MIRROR}|g; s|http://security.ubuntu.com|https://${UBUNTU_APT_MIRROR}|g" /etc/apt/sources.list.d/*.sources 2>/dev/null || true && \
|
sed -i "s|http://archive.ubuntu.com|https://${UBUNTU_APT_MIRROR}|g; s|http://security.ubuntu.com|https://${UBUNTU_APT_MIRROR}|g" /etc/apt/sources.list.d/*.sources 2>/dev/null || true && \
|
||||||
apt-get update && apt-get install -y --no-install-recommends \
|
apt-get update && apt-get install -y --no-install-recommends \
|
||||||
curl \
|
curl \
|
||||||
jq \
|
|
||||||
unzip \
|
unzip \
|
||||||
python3 \
|
python3 \
|
||||||
python3-venv && \
|
python3-venv && \
|
||||||
@@ -52,11 +51,8 @@ RUN bun install --frozen-lockfile
|
|||||||
FROM base AS build
|
FROM base AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY tsconfig.json opencode.json README.md ./
|
||||||
COPY --from=deps /app/.opencode/node_modules ./.opencode/node_modules
|
|
||||||
COPY tsconfig.json opencode.json README.md .gitignore ./
|
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
COPY cli ./cli
|
|
||||||
COPY .opencode ./.opencode
|
COPY .opencode ./.opencode
|
||||||
RUN bun run check
|
RUN bun run check
|
||||||
|
|
||||||
@@ -66,20 +62,19 @@ WORKDIR /app
|
|||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV PORT=8787
|
ENV PORT=8787
|
||||||
ENV TJWATER_CLI_PATH=./cli/tjwater-cli
|
|
||||||
|
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY --from=deps /app/.opencode/node_modules ./.opencode/node_modules
|
COPY --from=deps /app/.opencode/node_modules ./.opencode/node_modules
|
||||||
COPY package.json bun.lock ./
|
COPY package.json bun.lock ./
|
||||||
COPY tsconfig.json opencode.json .gitignore ./
|
COPY tsconfig.json opencode.json ./
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
COPY .opencode ./.opencode
|
COPY .opencode ./.opencode
|
||||||
COPY cli ./cli
|
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh ./cli/tjwater-cli
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
COPY .opencode ./.opencode
|
||||||
|
|
||||||
EXPOSE 8787
|
EXPOSE 8787
|
||||||
CMD ["bun", "src/server.ts"]
|
CMD ["bun", "src/server.ts"]
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ TJWaterAgent/
|
|||||||
3. 管理前端 `session_id -> opencode sessionId` 的映射。
|
3. 管理前端 `session_id -> opencode sessionId` 的映射。
|
||||||
4. 保存并传递用户 `Authorization`、`x-user-id`、`x-project-id`、`x-trace-id`。
|
4. 保存并传递用户 `Authorization`、`x-user-id`、`x-project-id`、`x-trace-id`。
|
||||||
5. 把 opencode 输出适配成前端需要的 SSE 事件。
|
5. 把 opencode 输出适配成前端需要的 SSE 事件。
|
||||||
6. 为 `.opencode/tools/tjwater_cli.ts` 提供内部回调接口。
|
6. 为 `.opencode/tools/dynamic_http_call.ts` 提供内部回调接口。
|
||||||
7. 代理调用真实 TJWater 后端 API。
|
7. 代理调用真实 TJWater 后端 API。
|
||||||
|
|
||||||
当前 Agent API 的主入口:
|
当前 Agent API 的主入口:
|
||||||
@@ -84,45 +84,34 @@ src/
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
.opencode/tools/
|
.opencode/tools/
|
||||||
tjwater_cli.ts
|
dynamic_http_call.ts
|
||||||
store_render_ref.ts
|
|
||||||
locate_features.ts
|
locate_features.ts
|
||||||
view_history.ts
|
view_history.ts
|
||||||
view_scada.ts
|
view_scada.ts
|
||||||
show_chart.ts
|
show_chart.ts
|
||||||
render_junctions.ts
|
|
||||||
apply_layer_style.ts
|
|
||||||
memory_manager.ts
|
|
||||||
session_search.ts
|
|
||||||
skill_manager.ts
|
|
||||||
```
|
```
|
||||||
|
|
||||||
这些是 opencode 可以调用的自定义工具。
|
这些是 opencode 可以调用的自定义工具。
|
||||||
|
|
||||||
`tjwater_cli.ts` 不直接保存用户 token。它会回调 `TJWaterAgent` 的内部接口,由上级服务层根据当前 session 补上用户 token、项目 ID 和 trace ID,再调用 `tjwater-cli` 二进制执行后端命令。
|
`dynamic_http_call.ts` 不直接保存用户 token,也不直接访问后端。它会回调 `TJWaterAgent` 的内部接口,由上级服务层根据当前 session 补上用户 token、项目 ID 和 trace ID,再调用 TJWater 后端。
|
||||||
|
|
||||||
`store_render_ref.ts` 用于把大型 junction 渲染 payload 存成 `render_ref`,再由 `render_junctions.ts` 交给前端回读并渲染。
|
前端类工具如 `locate_features`、`view_history`、`view_scada`、`show_chart` 主要用于触发 UI 动作或可视化,不应被当作数据查询工具。
|
||||||
|
|
||||||
前端类工具如 `locate_features`、`view_history`、`view_scada`、`show_chart`、`render_junctions`、`apply_layer_style` 主要用于触发 UI 动作或可视化,不应被当作数据查询工具。
|
|
||||||
|
|
||||||
### skills
|
### skills
|
||||||
|
|
||||||
```text
|
```text
|
||||||
.opencode/skills/
|
.opencode/skills/tjwater-skills-root-index/
|
||||||
SKILL.md
|
SKILL.md
|
||||||
examples.md
|
ai/
|
||||||
runbook.md
|
analytics/
|
||||||
tjwater-cli/ ← tjwater-cli 可执行文件
|
business/
|
||||||
workflow/ ← 可复用分析工作流
|
data/
|
||||||
SKILL.md
|
platform/
|
||||||
simulation-diagnosis/
|
|
||||||
bottleneck-analysis/
|
|
||||||
source-service-area-analysis/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Skills 仅保留可复用的多步工作流。Agent 通过 `tjwater-cli help` 自行发现原子命令,无需逐接口技能树。
|
这里保存 TJWater 技能树,并保持树结构,符合渐进式披露设计。
|
||||||
|
|
||||||
agent 加载技能树时按需取用对应 workflow skill。
|
agent 需要某个领域知识时再按需加载对应 skill,不把整棵技能树作为 always-loaded prompt 一次性注入。
|
||||||
|
|
||||||
## 依赖边界
|
## 依赖边界
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "tjwater-agent",
|
"name": "tjwater-agent",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "^1.16.2",
|
"@opencode-ai/sdk": "^1.14.29",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
@@ -17,13 +17,12 @@
|
|||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.3",
|
||||||
"@types/node": "^24.7.2",
|
"@types/node": "^24.7.2",
|
||||||
"bun-types": "^1.3.3",
|
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.16.2", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-Z/xZ7q79dYeE0afqIk/yFEcRNGEQFcE+H8ssYivUiy+xGZ1mGwT72jpaQZKBwPn3JH4sRCu4KA2lcktBQfcOjg=="],
|
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.14.30", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-OgPEDvALekHZIjByo/okJ699aLPn+XtsVxgZxUqE8TlzAG7TtskMGFl0fro8O0T2p+nkOT/LstnKGbECvc0+YA=="],
|
||||||
|
|
||||||
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
||||||
|
|
||||||
@@ -57,8 +56,6 @@
|
|||||||
|
|
||||||
"body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="],
|
"body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
|
||||||
|
|
||||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||||
|
|
||||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
import { CliError } from "../core/errors.js";
|
|
||||||
import { emitApi, requestJson } from "../core/http.js";
|
|
||||||
import { assignDatasetKeys, parseBurstFile, parseValveSettingFile } from "../core/files.js";
|
|
||||||
import { optionalNumber, optionalString, optionalStringArray, parseOptions, requiredNumber, requiredString, validateChoice } from "../core/options.js";
|
|
||||||
import { requireNetwork, requireUsername, resolveScheme } from "../core/runtime.js";
|
|
||||||
import { parseTime } from "../core/time.js";
|
|
||||||
import { success } from "../core/output.js";
|
|
||||||
import type { HandlerMap, RuntimeContext } from "../core/types.js";
|
|
||||||
|
|
||||||
function analysisBurst(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { duration: "integer" });
|
|
||||||
const [ids, sizes] = parseBurstFile(requiredString(values, "burst-file"));
|
|
||||||
const schemeName = resolveScheme(ctx, optionalString(values, "scheme"), true)!;
|
|
||||||
return emitApi(ctx, "爆管分析执行成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: "/burst_analysis/",
|
|
||||||
params: {
|
|
||||||
network: requireNetwork(ctx),
|
|
||||||
modify_pattern_start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
|
|
||||||
burst_ID: ids,
|
|
||||||
burst_size: sizes,
|
|
||||||
modify_total_duration: requiredNumber(values, "duration"),
|
|
||||||
scheme_name: schemeName,
|
|
||||||
},
|
|
||||||
requireNetworkCtx: true,
|
|
||||||
}, [`tjwater-cli data scheme get --name ${schemeName}`, "tjwater-cli data scheme list"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function analysisValve(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { valve: "repeat", element: "repeat", "disabled-valve": "repeat", duration: "integer" });
|
|
||||||
const mode = validateChoice(requiredString(values, "mode"), ["close", "isolation"] as const, "--mode");
|
|
||||||
if (mode === "close") {
|
|
||||||
const valves = optionalStringArray(values, "valve");
|
|
||||||
const startTime = optionalString(values, "start-time");
|
|
||||||
if (!startTime || !valves) throw new CliError("CLI 参数错误", "INVALID_VALVE_CLOSE_ARGS", "close mode requires --start-time and at least one --valve", 2);
|
|
||||||
return emitApi(ctx, "阀门关闭分析执行成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: "/valve_close_analysis/",
|
|
||||||
params: {
|
|
||||||
network: requireNetwork(ctx),
|
|
||||||
start_time: parseTime(startTime, "--start-time"),
|
|
||||||
valves,
|
|
||||||
duration: optionalNumber(values, "duration") || 900,
|
|
||||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
|
||||||
},
|
|
||||||
requireNetworkCtx: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const elements = optionalStringArray(values, "element");
|
|
||||||
if (!elements) throw new CliError("CLI 参数错误", "INVALID_VALVE_ISOLATION_ARGS", "isolation mode requires at least one --element", 2);
|
|
||||||
return emitApi(ctx, "阀门隔离分析执行成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: "/valve_isolation_analysis/",
|
|
||||||
params: { network: requireNetwork(ctx), accident_element: elements, disabled_valves: optionalStringArray(values, "disabled-valve") },
|
|
||||||
requireNetworkCtx: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function analysisFlushing(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { flow: "number", duration: "integer" });
|
|
||||||
const [valves, openings] = parseValveSettingFile(requiredString(values, "valve-setting-file"));
|
|
||||||
return emitApi(ctx, "冲洗分析执行成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: "/flushing_analysis/",
|
|
||||||
params: {
|
|
||||||
network: requireNetwork(ctx),
|
|
||||||
start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
|
|
||||||
valves,
|
|
||||||
valves_k: openings,
|
|
||||||
drainage_node_ID: requiredString(values, "drainage-node"),
|
|
||||||
flush_flow: requiredNumber(values, "flow"),
|
|
||||||
duration: optionalNumber(values, "duration") || 900,
|
|
||||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
|
||||||
},
|
|
||||||
requireNetworkCtx: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function analysisAge(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { duration: "integer" });
|
|
||||||
return emitApi(ctx, "水龄分析执行成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: "/age_analysis/",
|
|
||||||
params: { network: requireNetwork(ctx), start_time: parseTime(requiredString(values, "start-time"), "--start-time"), duration: requiredNumber(values, "duration") },
|
|
||||||
requireNetworkCtx: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function analysisContaminant(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { duration: "integer", concentration: "number" });
|
|
||||||
const params: Record<string, unknown> = {
|
|
||||||
network: requireNetwork(ctx),
|
|
||||||
start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
|
|
||||||
source: requiredString(values, "source-node"),
|
|
||||||
concentration: requiredNumber(values, "concentration"),
|
|
||||||
duration: requiredNumber(values, "duration"),
|
|
||||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
|
||||||
};
|
|
||||||
const pattern = optionalString(values, "pattern");
|
|
||||||
if (pattern) params.pattern = pattern;
|
|
||||||
return emitApi(ctx, "污染物模拟执行成功", { method: "GET", path: "/contaminant_simulation/", params, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function sensorKmeans(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { count: "integer", "min-diameter": "integer" });
|
|
||||||
return emitApi(ctx, "传感器选址执行成功", {
|
|
||||||
method: "POST",
|
|
||||||
path: "/pressure_sensor_placement_kmeans/",
|
|
||||||
body: {
|
|
||||||
name: requireNetwork(ctx),
|
|
||||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
|
||||||
sensor_number: requiredNumber(values, "count"),
|
|
||||||
min_diameter: optionalNumber(values, "min-diameter") || 0,
|
|
||||||
username: requireUsername(ctx),
|
|
||||||
},
|
|
||||||
requireNetworkCtx: true,
|
|
||||||
requireUsernameCtx: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function schemeAnalysis(ctx: RuntimeContext, argv: string[], summary: string, path: string, networkKey: string, startKey: string, endKey: string): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
return emitApi(ctx, summary, {
|
|
||||||
method: "POST",
|
|
||||||
path,
|
|
||||||
body: {
|
|
||||||
[networkKey]: requireNetwork(ctx),
|
|
||||||
[startKey]: parseTime(requiredString(values, "start-time"), "--start-time"),
|
|
||||||
[endKey]: parseTime(requiredString(values, "end-time"), "--end-time"),
|
|
||||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
|
||||||
},
|
|
||||||
requireNetworkCtx: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function schemeList(ctx: RuntimeContext, summary: string, path: string): Promise<void> {
|
|
||||||
return emitApi(ctx, summary, { method: "GET", path, params: { network: requireNetwork(ctx) }, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function schemeGet(ctx: RuntimeContext, argv: string[], summary: string, path: string): Promise<void> {
|
|
||||||
const { positionals } = parseOptions(argv);
|
|
||||||
if (!positionals[0]) throw new CliError("CLI 参数错误", "MISSING_ARGUMENT", "Missing argument 'SCHEME_NAME'", 2);
|
|
||||||
return emitApi(ctx, summary, { method: "GET", path: `${path}${positionals[0]}`, params: { network: requireNetwork(ctx) }, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function burstLocation(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { "burst-leakage": "number", "pressure-scada-id": "repeat", "flow-scada-id": "repeat", "use-scada-flow": "boolean" });
|
|
||||||
const body: Record<string, unknown> = {
|
|
||||||
network: requireNetwork(ctx),
|
|
||||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
|
||||||
data_source: optionalString(values, "data-source") || "monitoring",
|
|
||||||
scada_burst_start: parseTime(requiredString(values, "start-time"), "--start-time"),
|
|
||||||
scada_burst_end: parseTime(requiredString(values, "end-time"), "--end-time"),
|
|
||||||
burst_leakage: requiredNumber(values, "burst-leakage"),
|
|
||||||
use_scada_flow: Boolean(values["use-scada-flow"]),
|
|
||||||
};
|
|
||||||
const pressureIds = optionalStringArray(values, "pressure-scada-id");
|
|
||||||
const flowIds = optionalStringArray(values, "flow-scada-id");
|
|
||||||
if (pressureIds) body.pressure_scada_ids = pressureIds;
|
|
||||||
if (flowIds) body.flow_scada_ids = flowIds;
|
|
||||||
const pressureFile = optionalString(values, "pressure-file");
|
|
||||||
const flowFile = optionalString(values, "flow-file");
|
|
||||||
if (pressureFile) assignDatasetKeys(body, pressureFile, ["burst_pressure", "normal_pressure"], "pressure");
|
|
||||||
if (flowFile) assignDatasetKeys(body, flowFile, ["burst_flow", "normal_flow"], "flow");
|
|
||||||
return emitApi(ctx, "爆管定位执行成功", { method: "POST", path: "/burst-location/locate/", body, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function riskPipe(ctx: RuntimeContext, argv: string[], summary: string, path: string): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
return emitApi(ctx, summary, { method: "GET", path, params: { network: requireNetwork(ctx), pipe_id: requiredString(values, "pipe") }, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function riskNetwork(ctx: RuntimeContext): Promise<void> {
|
|
||||||
const network = requireNetwork(ctx);
|
|
||||||
const [probabilities, a] = await requestJson(ctx, { method: "GET", path: "/getnetworkpiperiskprobabilitynow/", params: { network }, requireNetworkCtx: true });
|
|
||||||
const [geometries, b] = await requestJson(ctx, { method: "GET", path: "/getpiperiskprobabilitygeometries/", params: { network }, requireNetworkCtx: true });
|
|
||||||
success("读取全网风险成功", { probabilities, geometries }, ctx, a + b);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const analysisHandlers: HandlerMap = {
|
|
||||||
"analysis burst": analysisBurst,
|
|
||||||
"analysis valve": analysisValve,
|
|
||||||
"analysis flushing": analysisFlushing,
|
|
||||||
"analysis age": analysisAge,
|
|
||||||
"analysis contaminant": analysisContaminant,
|
|
||||||
"analysis sensor-placement kmeans": sensorKmeans,
|
|
||||||
"analysis leakage identify": (ctx, argv) => schemeAnalysis(ctx, argv, "漏损识别执行成功", "/leakage/identify/", "network", "scada_start", "scada_end"),
|
|
||||||
"analysis leakage schemes list": (ctx) => schemeList(ctx, "读取漏损方案列表成功", "/leakage/schemes/"),
|
|
||||||
"analysis leakage schemes get": (ctx, argv) => schemeGet(ctx, argv, "读取漏损方案详情成功", "/leakage/schemes/"),
|
|
||||||
"analysis burst-detection detect": (ctx, argv) => schemeAnalysis(ctx, argv, "爆管检测执行成功", "/burst-detection/detect/", "network", "scada_start", "scada_end"),
|
|
||||||
"analysis burst-detection schemes list": (ctx) => schemeList(ctx, "读取爆管检测方案列表成功", "/burst-detection/schemes/"),
|
|
||||||
"analysis burst-detection schemes get": (ctx, argv) => schemeGet(ctx, argv, "读取爆管检测方案详情成功", "/burst-detection/schemes/"),
|
|
||||||
"analysis burst-location locate": burstLocation,
|
|
||||||
"analysis burst-location schemes list": (ctx) => schemeList(ctx, "读取爆管定位方案列表成功", "/burst-location/schemes/"),
|
|
||||||
"analysis burst-location schemes get": (ctx, argv) => schemeGet(ctx, argv, "读取爆管定位方案详情成功", "/burst-location/schemes/"),
|
|
||||||
"analysis risk pipe-now": (ctx, argv) => riskPipe(ctx, argv, "读取当前管道风险成功", "/getpiperiskprobabilitynow/"),
|
|
||||||
"analysis risk pipe-history": (ctx, argv) => riskPipe(ctx, argv, "读取历史管道风险成功", "/getpiperiskprobability/"),
|
|
||||||
"analysis risk network": riskNetwork,
|
|
||||||
};
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { CliError } from "../core/errors.js";
|
|
||||||
import { emitApi } from "../core/http.js";
|
|
||||||
import { optionalString, parseOptions, requiredString, validateChoice } from "../core/options.js";
|
|
||||||
import { requireNetwork } from "../core/runtime.js";
|
|
||||||
import type { HandlerMap, RuntimeContext } from "../core/types.js";
|
|
||||||
|
|
||||||
type ComponentKind = "time" | "energy" | "pump-energy" | "network";
|
|
||||||
|
|
||||||
function componentOption(ctx: RuntimeContext, argv: string[], schema: boolean): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
const kind = validateChoice(requiredString(values, "kind"), ["time", "energy", "pump-energy", "network"] as const, "--kind");
|
|
||||||
const routes: Record<`${ComponentKind}:${boolean}`, string> = {
|
|
||||||
"time:true": "/gettimeschema",
|
|
||||||
"time:false": "/gettimeproperties/",
|
|
||||||
"energy:true": "/getenergyschema/",
|
|
||||||
"energy:false": "/getenergyproperties/",
|
|
||||||
"pump-energy:true": "/getpumpenergyschema/",
|
|
||||||
"pump-energy:false": "/getpumpenergyproperties//",
|
|
||||||
"network:true": "/getoptionschema/",
|
|
||||||
"network:false": "/getoptionproperties/",
|
|
||||||
};
|
|
||||||
const params: Record<string, unknown> = { network: requireNetwork(ctx) };
|
|
||||||
const pump = optionalString(values, "pump");
|
|
||||||
if (kind === "pump-energy") {
|
|
||||||
if (!schema && !pump) throw new CliError("CLI 参数错误", "PUMP_REQUIRED", "--pump is required when --kind pump-energy", 2);
|
|
||||||
if (pump) params.pump = pump;
|
|
||||||
}
|
|
||||||
return emitApi(ctx, schema ? "读取选项 schema 成功" : "读取选项属性成功", { method: "GET", path: routes[`${kind}:${schema}`], params, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
export const componentHandlers: HandlerMap = {
|
|
||||||
"component option schema": (ctx, argv) => componentOption(ctx, argv, true),
|
|
||||||
"component option get": (ctx, argv) => componentOption(ctx, argv, false),
|
|
||||||
};
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import { SCADA_FIELDS, type ElementType } from "../core/constants.js";
|
|
||||||
import { CliError } from "../core/errors.js";
|
|
||||||
import { emitApi } from "../core/http.js";
|
|
||||||
import { fieldsFor, optionalString, parseOptions, requiredString, requiredStringArray, validateChoice } from "../core/options.js";
|
|
||||||
import { requireNetwork, resolveScheme } from "../core/runtime.js";
|
|
||||||
import { parseTime } from "../core/time.js";
|
|
||||||
import type { HandlerMap, RuntimeContext } from "../core/types.js";
|
|
||||||
|
|
||||||
function rangeGet(ctx: RuntimeContext, argv: string[], summary: string, path: string): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
return emitApi(ctx, summary, {
|
|
||||||
method: "GET",
|
|
||||||
path,
|
|
||||||
params: { start_time: parseTime(requiredString(values, "start-time"), "--start-time"), end_time: parseTime(requiredString(values, "end-time"), "--end-time") },
|
|
||||||
requireProject: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function realtimeByIdTime(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
return emitApi(ctx, "读取实时模拟数据成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: "/realtime/query/by-id-time",
|
|
||||||
params: { id: requiredString(values, "id"), type: validateChoice(requiredString(values, "type"), ["pipe", "junction"] as const, "--type"), query_time: parseTime(requiredString(values, "time"), "--time") },
|
|
||||||
requireProject: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function realtimeByTimeProperty(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
const type = validateChoice(requiredString(values, "type"), ["pipe", "junction"] as const, "--type");
|
|
||||||
return emitApi(ctx, "读取实时属性聚合数据成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: "/realtime/query/by-time-property",
|
|
||||||
params: { type, query_time: parseTime(requiredString(values, "time"), "--time"), property: validateChoice(requiredString(values, "property"), fieldsFor(type), "--property") },
|
|
||||||
requireProject: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function schemeLinks(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
return emitApi(ctx, "读取方案管道数据成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: "/scheme/links",
|
|
||||||
params: {
|
|
||||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
|
||||||
scheme_type: optionalString(values, "scheme-type") || "simulation",
|
|
||||||
start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
|
|
||||||
end_time: parseTime(requiredString(values, "end-time"), "--end-time"),
|
|
||||||
},
|
|
||||||
requireProject: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function schemeNodeField(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
return emitApi(ctx, "读取方案节点字段成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: `/scheme/nodes/${requiredString(values, "node")}/field`,
|
|
||||||
params: {
|
|
||||||
field: validateChoice(requiredString(values, "field"), fieldsFor("junction"), "--field"),
|
|
||||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
|
||||||
scheme_type: optionalString(values, "scheme-type") || "simulation",
|
|
||||||
start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
|
|
||||||
end_time: parseTime(requiredString(values, "end-time"), "--end-time"),
|
|
||||||
},
|
|
||||||
requireProject: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function schemeSimulation(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
const query = validateChoice(requiredString(values, "query"), ["by-id-time", "by-scheme-time-property"] as const, "--query");
|
|
||||||
const type = validateChoice(optionalString(values, "type") || "pipe", ["pipe", "junction"] as const, "--type") as ElementType;
|
|
||||||
const params: Record<string, unknown> = {
|
|
||||||
scheme_name: resolveScheme(ctx, optionalString(values, "scheme"), true),
|
|
||||||
scheme_type: optionalString(values, "scheme-type") || "simulation",
|
|
||||||
query_time: parseTime(requiredString(values, "time"), "--time"),
|
|
||||||
type,
|
|
||||||
};
|
|
||||||
if (query === "by-id-time") {
|
|
||||||
params.id = requiredString(values, "id");
|
|
||||||
return emitApi(ctx, "读取方案单点模拟数据成功", { method: "GET", path: "/scheme/query/by-id-time", params, requireProject: true });
|
|
||||||
}
|
|
||||||
params.property = validateChoice(requiredString(values, "property"), fieldsFor(type), "--property");
|
|
||||||
return emitApi(ctx, "读取方案属性聚合数据成功", { method: "GET", path: "/scheme/query/by-scheme-time-property", params, requireProject: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function scadaQuery(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { "device-id": "repeat" });
|
|
||||||
const params: Record<string, unknown> = {
|
|
||||||
device_ids: requiredStringArray(values, "device-id").join(","),
|
|
||||||
start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
|
|
||||||
end_time: parseTime(requiredString(values, "end-time"), "--end-time"),
|
|
||||||
};
|
|
||||||
const field = optionalString(values, "field");
|
|
||||||
if (field) params.field = validateChoice(field, SCADA_FIELDS, "--field");
|
|
||||||
return emitApi(ctx, "读取 SCADA 时序成功", { method: "GET", path: field ? "/scada/by-ids-field-time-range" : "/scada/by-ids-time-range", params, requireProject: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function composite(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { feature: "repeat", "use-cleaned": "boolean" });
|
|
||||||
const kind = validateChoice(requiredString(values, "kind"), ["scada-simulation", "element-simulation", "element-scada"] as const, "--kind");
|
|
||||||
const params: Record<string, unknown> = {
|
|
||||||
start_time: parseTime(requiredString(values, "start-time"), "--start-time"),
|
|
||||||
end_time: parseTime(requiredString(values, "end-time"), "--end-time"),
|
|
||||||
};
|
|
||||||
const schemeName = resolveScheme(ctx, optionalString(values, "scheme"));
|
|
||||||
if (schemeName) Object.assign(params, { scheme_name: schemeName, scheme_type: optionalString(values, "scheme-type") || "simulation" });
|
|
||||||
if (kind === "scada-simulation") params.device_ids = requiredStringArray(values, "feature").join(",");
|
|
||||||
else if (kind === "element-simulation") params.feature_infos = requiredStringArray(values, "feature").join(",");
|
|
||||||
else {
|
|
||||||
const feature = requiredStringArray(values, "feature");
|
|
||||||
if (feature.length !== 1) throw new CliError("CLI 参数错误", "FEATURE_REQUIRED", "element-scada requires exactly one --feature as element_id", 2);
|
|
||||||
params.element_id = feature[0];
|
|
||||||
params.use_cleaned = Boolean(values["use-cleaned"]);
|
|
||||||
}
|
|
||||||
return emitApi(ctx, kind === "scada-simulation" ? "读取复合 SCADA-模拟数据成功" : kind === "element-simulation" ? "读取复合元素模拟数据成功" : "读取元素关联 SCADA 数据成功", { method: "GET", path: `/composite/${kind}`, params, requireProject: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function pipelineHealth(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
requiredString(values, "pipe");
|
|
||||||
requiredString(values, "start-time");
|
|
||||||
return emitApi(ctx, "读取管道健康预测成功", {
|
|
||||||
method: "GET",
|
|
||||||
path: "/composite/pipeline-health-prediction",
|
|
||||||
params: { network_name: requireNetwork(ctx), query_time: parseTime(requiredString(values, "end-time"), "--end-time") },
|
|
||||||
requireProject: true,
|
|
||||||
requireNetworkCtx: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function dataScadaGet(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
validateChoice(requiredString(values, "kind"), ["info"] as const, "--kind");
|
|
||||||
return emitApi(ctx, "读取 SCADA 数据成功", { method: "GET", path: "/getscadainfo/", params: { network: requireNetwork(ctx), id: requiredString(values, "id") }, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function dataScadaList(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
validateChoice(requiredString(values, "kind"), ["info"] as const, "--kind");
|
|
||||||
return emitApi(ctx, "读取 SCADA 列表成功", { method: "GET", path: "/getallscadainfo/", params: { network: requireNetwork(ctx) }, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function dataSchemeGet(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
return emitApi(ctx, "读取方案成功", { method: "GET", path: "/getscheme/", params: { network: requireNetwork(ctx), schema_name: requiredString(values, "name") }, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dataHandlers: HandlerMap = {
|
|
||||||
"data timeseries realtime links": (ctx, argv) => rangeGet(ctx, argv, "读取实时管道数据成功", "/realtime/links"),
|
|
||||||
"data timeseries realtime nodes": (ctx, argv) => rangeGet(ctx, argv, "读取实时节点数据成功", "/realtime/nodes"),
|
|
||||||
"data timeseries realtime simulation-by-id-time": realtimeByIdTime,
|
|
||||||
"data timeseries realtime simulation-by-time-property": realtimeByTimeProperty,
|
|
||||||
"data timeseries scheme links": schemeLinks,
|
|
||||||
"data timeseries scheme node-field": schemeNodeField,
|
|
||||||
"data timeseries scheme simulation": schemeSimulation,
|
|
||||||
"data timeseries scada query": scadaQuery,
|
|
||||||
"data timeseries composite": composite,
|
|
||||||
"data timeseries composite pipeline-health": pipelineHealth,
|
|
||||||
"data scada get": dataScadaGet,
|
|
||||||
"data scada list": dataScadaList,
|
|
||||||
"data scheme schema": (ctx) => emitApi(ctx, "读取方案 schema 成功", { method: "GET", path: "/getschemeschema/", params: { network: requireNetwork(ctx) }, requireNetworkCtx: true }),
|
|
||||||
"data scheme get": dataSchemeGet,
|
|
||||||
"data scheme list": (ctx) => emitApi(ctx, "读取方案列表成功", { method: "GET", path: "/getallschemes/", params: { network: requireNetwork(ctx) }, requireNetworkCtx: true }),
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { emitApi } from "../core/http.js";
|
|
||||||
import { parseOptions, requiredString } from "../core/options.js";
|
|
||||||
import { requireNetwork } from "../core/runtime.js";
|
|
||||||
import type { HandlerMap, RuntimeContext } from "../core/types.js";
|
|
||||||
|
|
||||||
function legacyGet(ctx: RuntimeContext, argv: string[], summary: string, path: string, key: string): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv);
|
|
||||||
return emitApi(ctx, summary, { method: "GET", path, params: { network: requireNetwork(ctx), [key]: requiredString(values, key) }, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function legacyGetAll(ctx: RuntimeContext, summary: string, path: string): Promise<void> {
|
|
||||||
return emitApi(ctx, summary, { method: "GET", path, params: { network: requireNetwork(ctx) }, requireNetworkCtx: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
export const networkHandlers: HandlerMap = {
|
|
||||||
"network get-junction-properties": (ctx, argv) => legacyGet(ctx, argv, "读取节点属性成功", "/getjunctionproperties/", "junction"),
|
|
||||||
"network get-pipe-properties": (ctx, argv) => legacyGet(ctx, argv, "读取管道属性成功", "/getpipeproperties/", "pipe"),
|
|
||||||
"network get-all-pipes-properties": (ctx) => legacyGetAll(ctx, "读取全部管道属性成功", "/getallpipeproperties/"),
|
|
||||||
"network get-reservoir-properties": (ctx, argv) => legacyGet(ctx, argv, "读取水库属性成功", "/getreservoirproperties/", "reservoir"),
|
|
||||||
"network get-all-reservoirs-properties": (ctx) => legacyGetAll(ctx, "读取全部水库属性成功", "/getallreservoirproperties/"),
|
|
||||||
"network get-tank-properties": (ctx, argv) => legacyGet(ctx, argv, "读取水箱属性成功", "/gettankproperties/", "tank"),
|
|
||||||
"network get-all-tanks-properties": (ctx) => legacyGetAll(ctx, "读取全部水箱属性成功", "/getalltankproperties/"),
|
|
||||||
"network get-pump-properties": (ctx, argv) => legacyGet(ctx, argv, "读取水泵属性成功", "/getpumpproperties/", "pump"),
|
|
||||||
"network get-all-pumps-properties": (ctx) => legacyGetAll(ctx, "读取全部水泵属性成功", "/getallpumpproperties/"),
|
|
||||||
"network get-valve-properties": (ctx, argv) => legacyGet(ctx, argv, "读取阀门属性成功", "/getvalveproperties/", "valve"),
|
|
||||||
"network get-all-valves-properties": (ctx) => legacyGetAll(ctx, "读取全部阀门属性成功", "/getallvalveproperties/"),
|
|
||||||
};
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { emitApi } from "../core/http.js";
|
|
||||||
import { parseOptions, requiredNumber, requiredString } from "../core/options.js";
|
|
||||||
import { requireNetwork } from "../core/runtime.js";
|
|
||||||
import { addMinutesPreservingOffset, parseTime } from "../core/time.js";
|
|
||||||
import type { HandlerMap, RuntimeContext } from "../core/types.js";
|
|
||||||
|
|
||||||
function simulationRun(ctx: RuntimeContext, argv: string[]): Promise<void> {
|
|
||||||
const { values } = parseOptions(argv, { duration: "integer" });
|
|
||||||
const start = parseTime(requiredString(values, "start-time"), "--start-time");
|
|
||||||
const duration = requiredNumber(values, "duration");
|
|
||||||
const end = addMinutesPreservingOffset(start, duration);
|
|
||||||
const network = requireNetwork(ctx);
|
|
||||||
return emitApi(
|
|
||||||
ctx,
|
|
||||||
"触发模拟成功",
|
|
||||||
{ method: "POST", path: "/runsimulationmanuallybydate/", body: { name: network, start_time: start.replace(/\.\d+/, ""), duration }, requireNetworkCtx: true },
|
|
||||||
[
|
|
||||||
`tjwater-cli data timeseries realtime links --start-time ${start} --end-time ${end}`,
|
|
||||||
`tjwater-cli data timeseries realtime nodes --start-time ${start} --end-time ${end}`,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const simulationHandlers: HandlerMap = {
|
|
||||||
"simulation run": simulationRun,
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
export const SCHEMA_VERSION = "tjwater-cli/v1";
|
|
||||||
export const DEFAULT_TIMEOUT = 180;
|
|
||||||
export const DEFAULT_SERVER = "http://192.168.1.114:8000";
|
|
||||||
|
|
||||||
export const PIPE_FIELDS = ["flow", "friction", "headloss", "quality", "reaction", "setting", "status", "velocity"] as const;
|
|
||||||
export const JUNCTION_FIELDS = ["actual_demand", "total_head", "pressure", "quality"] as const;
|
|
||||||
export const SCADA_FIELDS = ["monitored_value", "cleaned_value"] as const;
|
|
||||||
|
|
||||||
export type ElementType = "pipe" | "junction";
|
|
||||||
export type PipeField = (typeof PIPE_FIELDS)[number];
|
|
||||||
export type JunctionField = (typeof JUNCTION_FIELDS)[number];
|
|
||||||
export type ScadaField = (typeof SCADA_FIELDS)[number];
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
export class CliError extends Error {
|
|
||||||
summary: string;
|
|
||||||
code: string;
|
|
||||||
exitCode: number;
|
|
||||||
retryable: boolean;
|
|
||||||
data: unknown;
|
|
||||||
nextCommands: string[];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
summary: string,
|
|
||||||
code: string,
|
|
||||||
message: string,
|
|
||||||
exitCode = 2,
|
|
||||||
retryable = false,
|
|
||||||
data: unknown = null,
|
|
||||||
nextCommands: string[] = [],
|
|
||||||
) {
|
|
||||||
super(message);
|
|
||||||
this.summary = summary;
|
|
||||||
this.code = code;
|
|
||||||
this.exitCode = exitCode;
|
|
||||||
this.retryable = retryable;
|
|
||||||
this.data = data;
|
|
||||||
this.nextCommands = nextCommands;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function errorMessage(error: unknown): string {
|
|
||||||
return error instanceof Error ? error.message : String(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import { readFileSync } from "node:fs";
|
|
||||||
import { CliError } from "./errors.js";
|
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
||||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readJsonFile(path: string, label: string): unknown {
|
|
||||||
try {
|
|
||||||
return JSON.parse(readFileSync(path, "utf8"));
|
|
||||||
} catch (error) {
|
|
||||||
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
||||||
throw new CliError("CLI 参数错误", "INPUT_NOT_FOUND", `${label} file not found: ${path}`, 2);
|
|
||||||
}
|
|
||||||
if (error instanceof SyntaxError) throw new CliError("CLI 参数错误", "INPUT_INVALID_JSON", `${label} file must be valid JSON: ${path}`, 2);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseBurstFile(path: string): [string[], number[]] {
|
|
||||||
let raw = readJsonFile(path, "burst");
|
|
||||||
if (isRecord(raw) && "bursts" in raw) raw = raw.bursts;
|
|
||||||
if (isRecord(raw) && "burst_ID" in raw && "burst_size" in raw && Array.isArray(raw.burst_ID) && Array.isArray(raw.burst_size)) {
|
|
||||||
const ids = raw.burst_ID.map(String);
|
|
||||||
const sizes = raw.burst_size.map(Number);
|
|
||||||
if (ids.length !== sizes.length) throw new CliError("CLI 参数错误", "BURST_FILE_INVALID", "burst file burst_ID and burst_size must have the same length", 2);
|
|
||||||
return [ids, sizes];
|
|
||||||
}
|
|
||||||
if (Array.isArray(raw)) {
|
|
||||||
return [
|
|
||||||
raw.map((item) => {
|
|
||||||
if (!isRecord(item) || !("id" in item) || !("size" in item)) throw new CliError("CLI 参数错误", "BURST_FILE_INVALID", "burst file items must contain id and size", 2);
|
|
||||||
return String(item.id);
|
|
||||||
}),
|
|
||||||
raw.map((item) => Number((item as Record<string, unknown>).size)),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
throw new CliError("CLI 参数错误", "BURST_FILE_INVALID", "burst file must be a JSON array or object with burst_ID/burst_size", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseValveSettingFile(path: string): [string[], number[]] {
|
|
||||||
const raw = readJsonFile(path, "valve-setting");
|
|
||||||
if (isRecord(raw) && "valves" in raw && "valves_k" in raw && Array.isArray(raw.valves) && Array.isArray(raw.valves_k)) {
|
|
||||||
const valves = raw.valves.map(String);
|
|
||||||
const openings = raw.valves_k.map(Number);
|
|
||||||
if (valves.length !== openings.length) throw new CliError("CLI 参数错误", "VALVE_SETTING_INVALID", "valves and valves_k must have the same length", 2);
|
|
||||||
return [valves, openings];
|
|
||||||
}
|
|
||||||
if (Array.isArray(raw)) {
|
|
||||||
return [
|
|
||||||
raw.map((item) => {
|
|
||||||
if (!isRecord(item) || !("valve" in item) || !("opening" in item)) throw new CliError("CLI 参数错误", "VALVE_SETTING_INVALID", "valve-setting items must contain valve and opening", 2);
|
|
||||||
return String(item.valve);
|
|
||||||
}),
|
|
||||||
raw.map((item) => Number((item as Record<string, unknown>).opening)),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
throw new CliError("CLI 参数错误", "VALVE_SETTING_INVALID", "valve-setting file must be a JSON array or object with valves/valves_k", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function assignDatasetKeys(target: Record<string, unknown>, path: string, keys: string[], label: string): void {
|
|
||||||
const payload = readJsonFile(path, label);
|
|
||||||
if (isRecord(payload)) {
|
|
||||||
for (const key of keys) {
|
|
||||||
if (key in payload) target[key] = payload[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import { CliError, errorMessage } from "./errors.js";
|
|
||||||
import { requireNetwork, requireUsername } from "./runtime.js";
|
|
||||||
import { success } from "./output.js";
|
|
||||||
import type { RequestOptions, RuntimeContext } from "./types.js";
|
|
||||||
|
|
||||||
function headers(ctx: RuntimeContext, requireAuth: boolean, requireProject: boolean): Record<string, string> {
|
|
||||||
const out: Record<string, string> = {
|
|
||||||
Accept: "application/json, text/plain, */*",
|
|
||||||
"X-Request-Id": ctx.requestId,
|
|
||||||
...ctx.auth.headers,
|
|
||||||
};
|
|
||||||
if (requireAuth) {
|
|
||||||
if (!ctx.auth.accessToken) {
|
|
||||||
throw new CliError("认证失败", "UNAUTHENTICATED", "missing access token for agent context", 3, false, null, ["provide access_token via --auth-stdin or TJWATER_ACCESS_TOKEN env var"]);
|
|
||||||
}
|
|
||||||
out.Authorization = `Bearer ${ctx.auth.accessToken}`;
|
|
||||||
} else if (ctx.auth.accessToken) out.Authorization = `Bearer ${ctx.auth.accessToken}`;
|
|
||||||
if (requireProject) {
|
|
||||||
if (!ctx.auth.projectId) throw new CliError("认证失败", "PROJECT_CONTEXT_REQUIRED", "missing project_id for agent context", 3, false, null, ["add project_id to auth context"]);
|
|
||||||
out["X-Project-Id"] = ctx.auth.projectId;
|
|
||||||
} else if (ctx.auth.projectId) out["X-Project-Id"] = ctx.auth.projectId;
|
|
||||||
if (ctx.auth.userId) out["X-User-Id"] = ctx.auth.userId;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringifyParam(value: unknown): string {
|
|
||||||
if (typeof value === "boolean") return value ? "True" : "False";
|
|
||||||
return String(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendParams(url: URL, params: Record<string, unknown> = {}): void {
|
|
||||||
for (const [key, value] of Object.entries(params)) {
|
|
||||||
if (value === undefined || value === null) continue;
|
|
||||||
if (Array.isArray(value)) value.forEach((item) => url.searchParams.append(key, stringifyParam(item)));
|
|
||||||
else url.searchParams.set(key, stringifyParam(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function requestJson(ctx: RuntimeContext, request: RequestOptions): Promise<[unknown, number]> {
|
|
||||||
const { method, path, params, body, requireAuth = true, requireProject = false, requireNetworkCtx = false, requireUsernameCtx = false } = request;
|
|
||||||
if (requireNetworkCtx) requireNetwork(ctx);
|
|
||||||
if (requireUsernameCtx) requireUsername(ctx);
|
|
||||||
const url = new URL(`/api/v1${path}`, ctx.server.replace(/\/+$/, ""));
|
|
||||||
appendParams(url, params);
|
|
||||||
const started = performance.now();
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timer = setTimeout(() => controller.abort(), ctx.timeout * 1000);
|
|
||||||
let response: Response;
|
|
||||||
try {
|
|
||||||
response = await fetch(url, {
|
|
||||||
method: method.toUpperCase(),
|
|
||||||
headers: {
|
|
||||||
...headers(ctx, requireAuth, requireProject),
|
|
||||||
...(body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
||||||
},
|
|
||||||
body: body === undefined ? undefined : JSON.stringify(body),
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error && error.name === "AbortError") throw new CliError("请求超时", "REQUEST_TIMEOUT", `request timed out after ${ctx.timeout} seconds`, 7, true);
|
|
||||||
throw new CliError("连接失败", "REQUEST_FAILED", errorMessage(error), 7, true);
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timer);
|
|
||||||
}
|
|
||||||
const durationMs = Math.trunc(performance.now() - started);
|
|
||||||
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
||||||
const text = response.status === 204 ? "" : await response.text();
|
|
||||||
let payload: unknown = {};
|
|
||||||
if (contentType.includes("application/json") && text) payload = JSON.parse(text);
|
|
||||||
else if (text) payload = { report: text };
|
|
||||||
if (!response.ok) {
|
|
||||||
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? (payload as Record<string, unknown>) : {};
|
|
||||||
const message = typeof record.detail === "string" ? record.detail : typeof record.message === "string" ? record.message : text || `http ${response.status}`;
|
|
||||||
throw new CliError("请求失败", `HTTP_${response.status}`, message, mapStatus(response.status), response.status >= 500);
|
|
||||||
}
|
|
||||||
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
||||||
const record = payload as Record<string, unknown>;
|
|
||||||
if (record.status === "error") throw new CliError("服务端错误", "SERVER_ERROR", String(record.message || "server returned error status"), 7, false, payload);
|
|
||||||
}
|
|
||||||
return [payload, durationMs];
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStatus(status: number): number {
|
|
||||||
if (status === 400 || status === 422) return 2;
|
|
||||||
if (status === 401) return 3;
|
|
||||||
if (status === 403) return 4;
|
|
||||||
if (status === 404) return 5;
|
|
||||||
if (status === 409 || status === 412) return 6;
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function emitApi(ctx: RuntimeContext, summary: string, request: RequestOptions, nextCommands: string[] = []): Promise<void> {
|
|
||||||
const [data, durationMs] = await requestJson(ctx, request);
|
|
||||||
success(summary, data, ctx, durationMs, nextCommands);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import { JUNCTION_FIELDS, PIPE_FIELDS, type ElementType } from "./constants.js";
|
|
||||||
import { CliError } from "./errors.js";
|
|
||||||
import type { OptionSchema, ParsedOptionValue, ParsedOptions } from "./types.js";
|
|
||||||
|
|
||||||
export function requireValue(argv: string[], index: number, option: string): string {
|
|
||||||
const value = argv[index];
|
|
||||||
if (!value || value.startsWith("--")) throw new CliError("CLI 参数错误", "MISSING_OPTION_VALUE", `${option} requires a value`, 2);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseIntStrict(value: string, option: string): number {
|
|
||||||
const parsed = Number.parseInt(value, 10);
|
|
||||||
if (!Number.isFinite(parsed)) throw new CliError("CLI 参数错误", "INVALID_INTEGER", `${option} must be an integer`, 2);
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseFloatStrict(value: string, option: string): number {
|
|
||||||
const parsed = Number.parseFloat(value);
|
|
||||||
if (!Number.isFinite(parsed)) throw new CliError("CLI 参数错误", "INVALID_NUMBER", `${option} must be a number`, 2);
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseOptions(argv: string[], schema: OptionSchema = {}): ParsedOptions {
|
|
||||||
const values: Record<string, ParsedOptionValue> = {};
|
|
||||||
const positionals: string[] = [];
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
|
||||||
const arg = argv[i]!;
|
|
||||||
if (!arg.startsWith("--")) {
|
|
||||||
positionals.push(arg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const name = arg.slice(2);
|
|
||||||
const kind = schema[name] ?? "string";
|
|
||||||
if (kind === "boolean") {
|
|
||||||
values[name] = true;
|
|
||||||
} else {
|
|
||||||
const raw = requireValue(argv, ++i, arg);
|
|
||||||
if (kind === "repeat") values[name] = [...asStringArray(values[name]), raw];
|
|
||||||
else if (kind === "number") values[name] = parseFloatStrict(raw, arg);
|
|
||||||
else if (kind === "integer") values[name] = parseIntStrict(raw, arg);
|
|
||||||
else values[name] = raw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { values, positionals };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function required(values: Record<string, ParsedOptionValue>, name: string): string | number | boolean | string[] {
|
|
||||||
const value = values[name];
|
|
||||||
if (value === undefined || value === null || value === "") {
|
|
||||||
throw new CliError("CLI 参数错误", "MISSING_PARAMETER", `Missing option '--${name}'`, 2);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requiredString(values: Record<string, ParsedOptionValue>, name: string): string {
|
|
||||||
const value = required(values, name);
|
|
||||||
if (typeof value !== "string") throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be a string`, 2);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requiredNumber(values: Record<string, ParsedOptionValue>, name: string): number {
|
|
||||||
const value = required(values, name);
|
|
||||||
if (typeof value !== "number") throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be a number`, 2);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requiredStringArray(values: Record<string, ParsedOptionValue>, name: string): string[] {
|
|
||||||
const value = required(values, name);
|
|
||||||
if (!Array.isArray(value)) throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be repeatable`, 2);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function optionalString(values: Record<string, ParsedOptionValue>, name: string): string | undefined {
|
|
||||||
const value = values[name];
|
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (typeof value !== "string") throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be a string`, 2);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function optionalNumber(values: Record<string, ParsedOptionValue>, name: string): number | undefined {
|
|
||||||
const value = values[name];
|
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (typeof value !== "number") throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be a number`, 2);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function optionalStringArray(values: Record<string, ParsedOptionValue>, name: string): string[] | undefined {
|
|
||||||
const value = values[name];
|
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (!Array.isArray(value)) throw new CliError("CLI 参数错误", "INVALID_PARAMETER", `--${name} must be repeatable`, 2);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asStringArray(value: ParsedOptionValue | undefined): string[] {
|
|
||||||
if (value === undefined) return [];
|
|
||||||
if (Array.isArray(value)) return value;
|
|
||||||
throw new CliError("CLI 参数错误", "INVALID_PARAMETER", "repeat option received a non-array value", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateChoice<T extends readonly string[]>(value: string, valid: T, option: string): T[number] {
|
|
||||||
if (!valid.includes(value)) {
|
|
||||||
throw new CliError("CLI 参数错误", `INVALID_${option.replace(/^--/, "").replaceAll("-", "_").toUpperCase()}`, `${option} must be one of: ${valid.join(", ")}`, 2);
|
|
||||||
}
|
|
||||||
return value as T[number];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fieldsFor(type: ElementType): typeof PIPE_FIELDS | typeof JUNCTION_FIELDS {
|
|
||||||
if (type === "pipe") return PIPE_FIELDS;
|
|
||||||
if (type === "junction") return JUNCTION_FIELDS;
|
|
||||||
throw new CliError("CLI 参数错误", "INVALID_TYPE", "--type must be one of: pipe, junction", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { SCHEMA_VERSION } from "./constants.js";
|
|
||||||
import type { RuntimeContext } from "./types.js";
|
|
||||||
|
|
||||||
export function json(value: unknown): void {
|
|
||||||
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generatedAt(): string {
|
|
||||||
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function success(summary: string, data: unknown, ctx: RuntimeContext, durationMs: number, nextCommands: string[] = []): void {
|
|
||||||
json({
|
|
||||||
ok: true,
|
|
||||||
schema_version: SCHEMA_VERSION,
|
|
||||||
summary,
|
|
||||||
data,
|
|
||||||
metadata: {
|
|
||||||
request_id: ctx.requestId,
|
|
||||||
server: ctx.server,
|
|
||||||
duration_ms: durationMs,
|
|
||||||
generated_at: generatedAt(),
|
|
||||||
},
|
|
||||||
next_commands: nextCommands,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function failure({
|
|
||||||
summary,
|
|
||||||
code,
|
|
||||||
message,
|
|
||||||
retryable = false,
|
|
||||||
server = null,
|
|
||||||
requestId = null,
|
|
||||||
data = null,
|
|
||||||
nextCommands = [],
|
|
||||||
}: {
|
|
||||||
summary: string;
|
|
||||||
code: string;
|
|
||||||
message: string;
|
|
||||||
retryable?: boolean;
|
|
||||||
server?: string | null;
|
|
||||||
requestId?: string | null;
|
|
||||||
data?: unknown;
|
|
||||||
nextCommands?: string[];
|
|
||||||
}): void {
|
|
||||||
json({
|
|
||||||
ok: false,
|
|
||||||
schema_version: SCHEMA_VERSION,
|
|
||||||
summary,
|
|
||||||
error: { code, message, retryable },
|
|
||||||
data,
|
|
||||||
metadata: {
|
|
||||||
request_id: requestId,
|
|
||||||
server,
|
|
||||||
generated_at: generatedAt(),
|
|
||||||
},
|
|
||||||
next_commands: nextCommands,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
|
||||||
import { DEFAULT_SERVER, DEFAULT_TIMEOUT } from "./constants.js";
|
|
||||||
import { CliError } from "./errors.js";
|
|
||||||
import { requireValue, parseIntStrict } from "./options.js";
|
|
||||||
import type { AuthContext, GlobalArgs, ParsedGlobalArgs, RuntimeContext } from "./types.js";
|
|
||||||
|
|
||||||
function pick(source: Record<string, unknown>, ...keys: string[]): string | null {
|
|
||||||
for (const key of keys) {
|
|
||||||
const value = source[key];
|
|
||||||
if (value !== undefined && value !== null && value !== "") return String(value);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readStdin(): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let body = "";
|
|
||||||
process.stdin.setEncoding("utf8");
|
|
||||||
process.stdin.on("data", (chunk: string) => {
|
|
||||||
body += chunk;
|
|
||||||
});
|
|
||||||
process.stdin.on("end", () => resolve(body));
|
|
||||||
process.stdin.on("error", reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadAuthContext(authStdin: boolean): Promise<AuthContext> {
|
|
||||||
const raw = authStdin
|
|
||||||
? (JSON.parse(await readStdin()) as Record<string, unknown>)
|
|
||||||
: {
|
|
||||||
server: process.env.TJWATER_SERVER,
|
|
||||||
access_token: process.env.TJWATER_ACCESS_TOKEN,
|
|
||||||
project_id: process.env.TJWATER_PROJECT_ID,
|
|
||||||
user_id: process.env.TJWATER_USER_ID,
|
|
||||||
username: process.env.TJWATER_USERNAME,
|
|
||||||
network: process.env.TJWATER_NETWORK,
|
|
||||||
headers: process.env.TJWATER_EXTRA_HEADERS ? JSON.parse(process.env.TJWATER_EXTRA_HEADERS) : {},
|
|
||||||
};
|
|
||||||
const headers = (raw.headers ?? {}) as unknown;
|
|
||||||
if (!headers || Array.isArray(headers) || typeof headers !== "object") {
|
|
||||||
throw new CliError("认证失败", "AUTH_CONTEXT_INVALID", "auth context headers must be a JSON object", 3);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
server: pick(raw, "server", "base_url"),
|
|
||||||
accessToken: pick(raw, "access_token", "token", "accessToken"),
|
|
||||||
projectId: pick(raw, "project_id", "projectId", "x_project_id"),
|
|
||||||
userId: pick(raw, "user_id", "userId", "x_user_id"),
|
|
||||||
username: pick(raw, "username", "preferred_username"),
|
|
||||||
network: pick(raw, "network", "project_code", "projectCode", "project"),
|
|
||||||
headers: Object.fromEntries(Object.entries(headers as Record<string, unknown>).map(([key, value]) => [String(key), String(value)])),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseGlobalArgs(argv: string[]): ParsedGlobalArgs {
|
|
||||||
const globals: GlobalArgs = {
|
|
||||||
server: null,
|
|
||||||
authStdin: false,
|
|
||||||
scheme: null,
|
|
||||||
timeout: DEFAULT_TIMEOUT,
|
|
||||||
requestId: null,
|
|
||||||
};
|
|
||||||
const rest: string[] = [];
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
|
||||||
const arg = argv[i]!;
|
|
||||||
if (arg === "--auth-stdin") globals.authStdin = true;
|
|
||||||
else if (arg === "--server") globals.server = requireValue(argv, ++i, "--server");
|
|
||||||
else if (arg === "--scheme") globals.scheme = requireValue(argv, ++i, "--scheme");
|
|
||||||
else if (arg === "--timeout") globals.timeout = parseIntStrict(requireValue(argv, ++i, "--timeout"), "--timeout");
|
|
||||||
else if (arg === "--request-id") globals.requestId = requireValue(argv, ++i, "--request-id");
|
|
||||||
else rest.push(arg);
|
|
||||||
}
|
|
||||||
return { globals, rest };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function buildRuntime(globals: GlobalArgs): Promise<RuntimeContext> {
|
|
||||||
const auth = await loadAuthContext(globals.authStdin);
|
|
||||||
return {
|
|
||||||
server: globals.server || auth.server || DEFAULT_SERVER,
|
|
||||||
auth,
|
|
||||||
scheme: globals.scheme,
|
|
||||||
timeout: globals.timeout,
|
|
||||||
requestId: globals.requestId || randomUUID(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requireNetwork(ctx: RuntimeContext): string {
|
|
||||||
if (ctx.auth.network) return ctx.auth.network;
|
|
||||||
throw new CliError("认证失败", "NETWORK_CONTEXT_REQUIRED", "missing network in auth context for legacy network-based endpoints", 3, false, null, ["add network to auth context"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requireUsername(ctx: RuntimeContext): string {
|
|
||||||
if (ctx.auth.username) return ctx.auth.username;
|
|
||||||
throw new CliError("认证失败", "USERNAME_CONTEXT_REQUIRED", "missing username in auth context", 3, false, null, ["add username to auth context"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveScheme(ctx: RuntimeContext, explicit: string | undefined, must = false): string | null {
|
|
||||||
const scheme = explicit || ctx.scheme;
|
|
||||||
if (must && !scheme) throw new CliError("CLI 参数错误", "SCHEME_REQUIRED", "missing scheme; use --scheme", 2);
|
|
||||||
return scheme ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { CliError } from "./errors.js";
|
|
||||||
|
|
||||||
export function parseTime(value: string, option: string): string {
|
|
||||||
const date = new Date(value);
|
|
||||||
if (Number.isNaN(date.getTime())) {
|
|
||||||
throw new CliError("CLI 参数错误", "INVALID_TIME", `${option} must be a valid ISO 8601 / RFC 3339 timestamp`, 2);
|
|
||||||
}
|
|
||||||
if (!/[zZ]|[+-]\d\d:\d\d$/.test(value)) {
|
|
||||||
throw new CliError("CLI 参数错误", "TIMEZONE_REQUIRED", `${option} must include an explicit timezone offset`, 2);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addMinutesPreservingOffset(value: string, minutes: number): string {
|
|
||||||
const match = value.match(/([+-])(\d\d):(\d\d)$/);
|
|
||||||
const end = new Date(new Date(value).getTime() + minutes * 60_000);
|
|
||||||
if (!match) return end.toISOString().replace(".000Z", "Z");
|
|
||||||
const sign = match[1] === "+" ? 1 : -1;
|
|
||||||
const offsetMinutes = sign * (Number(match[2]) * 60 + Number(match[3]));
|
|
||||||
const local = new Date(end.getTime() + offsetMinutes * 60_000);
|
|
||||||
return `${local.toISOString().replace(".000Z", "")}${match[1]}${match[2]}:${match[3]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
export type JsonPrimitive = string | number | boolean | null;
|
|
||||||
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
|
||||||
export type Dict<T = unknown> = Record<string, T>;
|
|
||||||
|
|
||||||
export interface AuthContext {
|
|
||||||
server: string | null;
|
|
||||||
accessToken: string | null;
|
|
||||||
projectId: string | null;
|
|
||||||
userId: string | null;
|
|
||||||
username: string | null;
|
|
||||||
network: string | null;
|
|
||||||
headers: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RuntimeContext {
|
|
||||||
server: string;
|
|
||||||
auth: AuthContext;
|
|
||||||
scheme: string | null;
|
|
||||||
timeout: number;
|
|
||||||
requestId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GlobalArgs {
|
|
||||||
server: string | null;
|
|
||||||
authStdin: boolean;
|
|
||||||
scheme: string | null;
|
|
||||||
timeout: number;
|
|
||||||
requestId: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParsedGlobalArgs {
|
|
||||||
globals: GlobalArgs;
|
|
||||||
rest: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OptionKind = "string" | "boolean" | "repeat" | "number" | "integer";
|
|
||||||
export type OptionSchema = Record<string, OptionKind>;
|
|
||||||
export type ParsedOptionValue = string | number | boolean | string[];
|
|
||||||
|
|
||||||
export interface ParsedOptions {
|
|
||||||
values: Record<string, ParsedOptionValue>;
|
|
||||||
positionals: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestOptions {
|
|
||||||
method: string;
|
|
||||||
path: string;
|
|
||||||
params?: Record<string, unknown>;
|
|
||||||
body?: unknown;
|
|
||||||
requireAuth?: boolean;
|
|
||||||
requireProject?: boolean;
|
|
||||||
requireNetworkCtx?: boolean;
|
|
||||||
requireUsernameCtx?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Handler = (ctx: RuntimeContext, argv: string[]) => Promise<void> | void;
|
|
||||||
export type HandlerMap = Record<string, Handler>;
|
|
||||||
|
|
||||||
export interface CommandDoc {
|
|
||||||
ok: true;
|
|
||||||
schema_version: string;
|
|
||||||
command: string;
|
|
||||||
summary: string;
|
|
||||||
description: string;
|
|
||||||
usage: string;
|
|
||||||
options: CommandOptionDoc[];
|
|
||||||
examples: string[];
|
|
||||||
output: string;
|
|
||||||
next_commands: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CommandOptionDoc {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
required: boolean;
|
|
||||||
repeated: boolean;
|
|
||||||
default: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HelpPayload =
|
|
||||||
| CommandDoc
|
|
||||||
| {
|
|
||||||
ok: true;
|
|
||||||
schema_version: string;
|
|
||||||
summary: string;
|
|
||||||
menu_level?: number;
|
|
||||||
commands: Array<{ command: string; summary: string; usage?: string; example?: string }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { CliError } from "./core/errors.js";
|
|
||||||
import { handlers } from "./handlers.js";
|
|
||||||
import { helpPayload } from "./help/index.js";
|
|
||||||
import { failure, json } from "./core/output.js";
|
|
||||||
import type { RuntimeContext } from "./core/types.js";
|
|
||||||
|
|
||||||
export async function dispatch(ctx: RuntimeContext | null, argv: string[]): Promise<void> {
|
|
||||||
if (argv.length === 0 || argv.includes("--help")) {
|
|
||||||
process.stdout.write("Usage: tjwater-cli [OPTIONS] COMMAND [ARGS]...\n\nStructured JSON:\n tjwater-cli help\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (argv[0] === "help") {
|
|
||||||
const payload = helpPayload(argv.slice(1));
|
|
||||||
if (!payload) {
|
|
||||||
failure({
|
|
||||||
summary: "未找到命令",
|
|
||||||
code: "COMMAND_NOT_FOUND",
|
|
||||||
message: `unknown command path: ${argv.slice(1).join(" ")}`,
|
|
||||||
retryable: false,
|
|
||||||
data: { usage: "tjwater-cli help <command-path>", examples: ["tjwater-cli help simulation run", "tjwater-cli simulation help"] },
|
|
||||||
nextCommands: ["tjwater-cli help", "tjwater-cli help simulation"],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
json(payload);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const matched = matchCommand(argv);
|
|
||||||
if (!matched) throw new CliError("未找到命令", "COMMAND_NOT_FOUND", `No such command: ${argv.join(" ")}`, 2, false, { usage: "tjwater-cli help" }, ["tjwater-cli help"]);
|
|
||||||
const handler = handlers[matched.path];
|
|
||||||
if (!handler) throw new CliError("未找到命令", "COMMAND_NOT_FOUND", `No such command: ${argv.join(" ")}`, 2, false, { usage: "tjwater-cli help" }, ["tjwater-cli help"]);
|
|
||||||
if (!ctx && matched.path !== "__noop") throw new CliError("CLI 参数错误", "RUNTIME_REQUIRED", "runtime context is required for command execution", 2);
|
|
||||||
await handler(ctx!, matched.args);
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchCommand(argv: string[]): { path: string; args: string[] } | null {
|
|
||||||
const paths = Object.keys(handlers).sort((a, b) => b.split(" ").length - a.split(" ").length);
|
|
||||||
for (const path of paths) {
|
|
||||||
const parts = path.split(" ");
|
|
||||||
if (parts.every((part, index) => argv[index] === part)) return { path, args: argv.slice(parts.length) };
|
|
||||||
}
|
|
||||||
if (argv.at(-1) === "help") {
|
|
||||||
const payload = helpPayload(argv.slice(0, -1));
|
|
||||||
if (payload) {
|
|
||||||
json(payload);
|
|
||||||
return { path: "__noop", args: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { analysisHandlers } from "./commands/analysis.js";
|
|
||||||
import { componentHandlers } from "./commands/component.js";
|
|
||||||
import { dataHandlers } from "./commands/data.js";
|
|
||||||
import { networkHandlers } from "./commands/network.js";
|
|
||||||
import { simulationHandlers } from "./commands/simulation.js";
|
|
||||||
import type { HandlerMap } from "./core/types.js";
|
|
||||||
|
|
||||||
export const handlers: HandlerMap = {
|
|
||||||
"__noop": async () => {},
|
|
||||||
...networkHandlers,
|
|
||||||
...componentHandlers,
|
|
||||||
...simulationHandlers,
|
|
||||||
...analysisHandlers,
|
|
||||||
...dataHandlers,
|
|
||||||
};
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
import { SCHEMA_VERSION } from "../core/constants.js";
|
|
||||||
import type { CommandDoc, CommandOptionDoc } from "../core/types.js";
|
|
||||||
|
|
||||||
export const GROUP_SUMMARIES: Record<string, string> = {
|
|
||||||
network: "管网节点、管线等基础属性查询命令。",
|
|
||||||
component: "组件选项与配置读取命令。",
|
|
||||||
"component option": "组件选项查询命令。",
|
|
||||||
simulation: "模拟运行与调度相关命令。",
|
|
||||||
analysis: "分析计算与诊断相关命令。",
|
|
||||||
"analysis leakage": "漏损分析相关命令。",
|
|
||||||
"analysis leakage schemes": "漏损方案查询命令。",
|
|
||||||
"analysis burst-detection": "爆管检测相关命令。",
|
|
||||||
"analysis burst-detection schemes": "爆管检测方案查询命令。",
|
|
||||||
"analysis burst-location": "爆管定位相关命令。",
|
|
||||||
"analysis burst-location schemes": "爆管定位方案查询命令。",
|
|
||||||
"analysis risk": "风险分析相关命令。",
|
|
||||||
"analysis sensor-placement": "传感器选址相关命令。",
|
|
||||||
data: "时序、SCADA 和方案数据查询命令。",
|
|
||||||
"data timeseries": "时序数据查询命令。",
|
|
||||||
"data timeseries realtime": "实时模拟时序查询命令。",
|
|
||||||
"data timeseries scheme": "方案时序查询命令。",
|
|
||||||
"data timeseries scada": "SCADA 时序查询命令。",
|
|
||||||
"data timeseries composite": "复合时序查询命令。",
|
|
||||||
"data scada": "SCADA 元数据查询命令。",
|
|
||||||
"data scheme": "方案数据查询命令。",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HIDDEN_PATH_PREFIXES = ["analysis burst-location", "analysis risk"];
|
|
||||||
|
|
||||||
type CommandSpec = readonly [path: string, summary: string, options: readonly string[], examples: readonly string[], nextCommands?: readonly string[]];
|
|
||||||
|
|
||||||
const commandSpecs: readonly CommandSpec[] = [
|
|
||||||
["network get-junction-properties", "读取节点属性", ["--junction <JUNCTION>"], ["tjwater-cli network get-junction-properties --junction J1"]],
|
|
||||||
["network get-pipe-properties", "读取管道属性", ["--pipe <PIPE>"], ["tjwater-cli network get-pipe-properties --pipe P1"]],
|
|
||||||
["network get-all-pipes-properties", "读取全部管道属性", [], ["tjwater-cli network get-all-pipes-properties"]],
|
|
||||||
["network get-reservoir-properties", "读取水库属性", ["--reservoir <RESERVOIR>"], ["tjwater-cli network get-reservoir-properties --reservoir R1"]],
|
|
||||||
["network get-all-reservoirs-properties", "读取全部水库属性", [], ["tjwater-cli network get-all-reservoirs-properties"]],
|
|
||||||
["network get-tank-properties", "读取水箱属性", ["--tank <TANK>"], ["tjwater-cli network get-tank-properties --tank T1"]],
|
|
||||||
["network get-all-tanks-properties", "读取全部水箱属性", [], ["tjwater-cli network get-all-tanks-properties"]],
|
|
||||||
["network get-pump-properties", "读取水泵属性", ["--pump <PUMP>"], ["tjwater-cli network get-pump-properties --pump PU1"]],
|
|
||||||
["network get-all-pumps-properties", "读取全部水泵属性", [], ["tjwater-cli network get-all-pumps-properties"]],
|
|
||||||
["network get-valve-properties", "读取阀门属性", ["--valve <VALVE>"], ["tjwater-cli network get-valve-properties --valve V1"]],
|
|
||||||
["network get-all-valves-properties", "读取全部阀门属性", [], ["tjwater-cli network get-all-valves-properties"]],
|
|
||||||
["component option schema", "读取选项 schema", ["--kind <KIND>", "[--pump <PUMP>]"], ["tjwater-cli component option schema --kind time", "tjwater-cli component option schema --kind energy", "tjwater-cli component option schema --kind pump-energy --pump PUMP1", "tjwater-cli component option schema --kind network"]],
|
|
||||||
["component option get", "读取选项属性", ["--kind <KIND>", "[--pump <PUMP>]"], ["tjwater-cli component option get --kind time", "tjwater-cli component option get --kind energy", "tjwater-cli component option get --kind pump-energy --pump PUMP1", "tjwater-cli component option get --kind network"]],
|
|
||||||
["simulation run", "触发指定绝对时间的模拟运行", ["--start-time <START_TIME>", "--duration <DURATION>"], ["tjwater-cli simulation run --start-time 2025-01-02T03:04:05+08:00 --duration 30"], ["tjwater-cli data timeseries realtime links --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00", "tjwater-cli data timeseries realtime nodes --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00"]],
|
|
||||||
["analysis burst", "执行爆管分析", ["--start-time <START_TIME>", "--duration <DURATION>", "--burst-file <BURST_FILE>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis burst --start-time 2025-01-02T03:04:05+08:00 --duration 900 --burst-file ./burst.json --scheme burst_case_01", "tjwater-cli data scheme get --name burst_case_01", "tjwater-cli data scheme list"]],
|
|
||||||
["analysis valve", "阀门工况分析。", ["--mode <MODE>", "[--start-time <START_TIME>]", "[--valve <VALVE>]", "[--element <ELEMENT>]", "[--disabled-valve <DISABLED_VALVE>]", "[--duration <DURATION>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis valve --mode close --start-time 2025-01-02T03:04:05+08:00 --valve V1 --valve V2 --duration 900 --scheme valve_case_01", "tjwater-cli analysis valve --mode isolation --element E1 --element E2", "tjwater-cli analysis valve --mode isolation --element E1 --disabled-valve V3"]],
|
|
||||||
["analysis flushing", "执行冲洗分析", ["--start-time <START_TIME>", "--valve-setting-file <VALVE_SETTING_FILE>", "--drainage-node <DRAINAGE_NODE>", "--flow <FLOW>", "[--duration <DURATION>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis flushing --start-time 2025-01-02T03:04:05+08:00 --valve-setting-file ./valve.json --drainage-node N1 --flow 100.0 --duration 900 --scheme flush_case_01"]],
|
|
||||||
["analysis age", "执行水龄分析", ["--start-time <START_TIME>", "--duration <DURATION>"], ["tjwater-cli analysis age --start-time 2025-01-02T03:04:05+08:00 --duration 900"]],
|
|
||||||
["analysis contaminant", "执行污染物模拟", ["--start-time <START_TIME>", "--duration <DURATION>", "--source-node <SOURCE_NODE>", "--concentration <CONCENTRATION>", "[--pattern <PATTERN>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis contaminant --start-time 2025-01-02T03:04:05+08:00 --duration 900 --source-node N1 --concentration 10.0 --scheme contam_case_01"]],
|
|
||||||
["analysis sensor-placement kmeans", "执行 KMeans 传感器选址", ["--count <COUNT>", "[--min-diameter <MIN_DIAMETER>]", "[--scheme <SCHEME>]"], ["tjwater-cli analysis sensor-placement kmeans --count 5 --min-diameter 100 --scheme placement_case_01"]],
|
|
||||||
["analysis leakage identify", "执行漏损识别", ["--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis leakage identify --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme leak_case_01"]],
|
|
||||||
["analysis leakage schemes list", "列出漏损方案", [], ["tjwater-cli analysis leakage schemes list"]],
|
|
||||||
["analysis leakage schemes get", "读取漏损方案详情", ["<SCHEME_NAME>"], ["tjwater-cli analysis leakage schemes get my_scheme"]],
|
|
||||||
["analysis burst-detection detect", "执行爆管检测", ["--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis burst-detection detect --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme detect_case_01"]],
|
|
||||||
["analysis burst-detection schemes list", "列出爆管检测方案", [], ["tjwater-cli analysis burst-detection schemes list"]],
|
|
||||||
["analysis burst-detection schemes get", "读取爆管检测方案详情", ["<SCHEME_NAME>"], ["tjwater-cli analysis burst-detection schemes get my_scheme"]],
|
|
||||||
["analysis burst-location locate", "执行爆管定位", ["--start-time <START_TIME>", "--end-time <END_TIME>", "--burst-leakage <BURST_LEAKAGE>", "[--scheme <SCHEME>]"], ["tjwater-cli analysis burst-location locate --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --burst-leakage 100.0 --scheme locate_case_01"]],
|
|
||||||
["analysis burst-location schemes list", "列出爆管定位方案", [], ["tjwater-cli analysis burst-location schemes list"]],
|
|
||||||
["analysis burst-location schemes get", "读取爆管定位方案详情", ["<SCHEME_NAME>"], ["tjwater-cli analysis burst-location schemes get my_scheme"]],
|
|
||||||
["analysis risk pipe-now", "读取单条管道当前风险", ["--pipe <PIPE>"], ["tjwater-cli analysis risk pipe-now --pipe P1"]],
|
|
||||||
["analysis risk pipe-history", "读取单条管道历史风险", ["--pipe <PIPE>"], ["tjwater-cli analysis risk pipe-history --pipe P1"]],
|
|
||||||
["analysis risk network", "读取全网风险", [], ["tjwater-cli analysis risk network"]],
|
|
||||||
["data timeseries realtime links", "查询实时管道时序", ["--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries realtime links --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]],
|
|
||||||
["data timeseries realtime nodes", "查询实时节点时序", ["--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries realtime nodes --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]],
|
|
||||||
["data timeseries realtime simulation-by-id-time", "按元素和时间查询实时模拟结果", ["--id <ID>", "--type <TYPE>", "--time <TIME>"], ["tjwater-cli data timeseries realtime simulation-by-id-time --id J1 --type junction --time 2025-01-02T03:30:00+08:00", "tjwater-cli data timeseries realtime simulation-by-id-time --id P1 --type pipe --time 2025-01-02T03:30:00+08:00"]],
|
|
||||||
["data timeseries realtime simulation-by-time-property", "按时间和属性查询实时模拟结果", ["--type <TYPE>", "--time <TIME>", "--property <PROPERTY>"], ["tjwater-cli data timeseries realtime simulation-by-time-property --type pipe --time 2025-01-02T03:30:00+08:00 --property flow"]],
|
|
||||||
["data timeseries scheme links", "查询方案管道时序", ["--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]", "[--scheme-type <SCHEME_TYPE>]"], ["tjwater-cli data timeseries scheme links --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme my_scheme"]],
|
|
||||||
["data timeseries scheme node-field", "查询方案节点字段时序", ["--node <NODE>", "--field <FIELD>", "--start-time <START_TIME>", "--end-time <END_TIME>", "[--scheme <SCHEME>]", "[--scheme-type <SCHEME_TYPE>]"], ["tjwater-cli data timeseries scheme node-field --node J1 --field pressure --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme my_scheme"]],
|
|
||||||
["data timeseries scheme simulation", "查询方案模拟数据", ["--query <QUERY>", "[--scheme <SCHEME>]", "[--scheme-type <SCHEME_TYPE>]", "[--id <ID>]", "[--time <TIME>]", "[--type <TYPE>]", "[--property <PROPERTY>]"], ["tjwater-cli data timeseries scheme simulation --query by-id-time --id J1 --time 2025-01-02T03:30:00+08:00 --type junction --scheme my_scheme", "tjwater-cli data timeseries scheme simulation --query by-scheme-time-property --time 2025-01-02T03:30:00+08:00 --type pipe --property flow --scheme my_scheme"]],
|
|
||||||
["data timeseries scada query", "查询 SCADA 时序", ["--device-id <DEVICE_ID>", "--start-time <START_TIME>", "--end-time <END_TIME>", "[--field <FIELD>]"], ["tjwater-cli data timeseries scada query --device-id D1 --device-id D2 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00", "tjwater-cli data timeseries scada query --device-id D1 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --field monitored_value"]],
|
|
||||||
["data timeseries composite", "执行复合时序查询", ["[--kind <KIND>]", "[--feature <FEATURE>]", "[--start-time <START_TIME>]", "[--end-time <END_TIME>]", "[--pipe <PIPE>]", "[--scheme <SCHEME>]", "[--scheme-type <SCHEME_TYPE>]", "[--use-cleaned]"], ["tjwater-cli data timeseries composite --kind scada-simulation --feature D1 --feature D2 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme my_scheme", "tjwater-cli data timeseries composite --kind element-simulation --feature J1:pressure --feature P1:flow --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --scheme my_scheme", "tjwater-cli data timeseries composite --kind element-scada --feature J1 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00 --use-cleaned"]],
|
|
||||||
["data timeseries composite pipeline-health", "查询管道健康预测", ["--pipe <PIPE>", "--start-time <START_TIME>", "--end-time <END_TIME>"], ["tjwater-cli data timeseries composite pipeline-health --pipe P1 --start-time 2025-01-02T03:00:00+08:00 --end-time 2025-01-02T04:00:00+08:00"]],
|
|
||||||
["data scada get", "读取单条 SCADA 元数据", ["--kind <KIND>", "--id <ID>"], ["tjwater-cli data scada get --kind info --id SCADA-001"]],
|
|
||||||
["data scada list", "列出 SCADA 元数据", ["--kind <KIND>"], ["tjwater-cli data scada list --kind info"]],
|
|
||||||
["data scheme schema", "读取方案 schema", [], ["tjwater-cli data scheme schema"]],
|
|
||||||
["data scheme get", "读取单条方案", ["--name <NAME>"], ["tjwater-cli data scheme get --name my_scheme"]],
|
|
||||||
["data scheme list", "列出方案", [], ["tjwater-cli data scheme list"]],
|
|
||||||
];
|
|
||||||
|
|
||||||
export const commandDocs = new Map<string, CommandDoc>(
|
|
||||||
commandSpecs.map(([path, summary, options, examples, nextCommands = []]) => [
|
|
||||||
path,
|
|
||||||
{
|
|
||||||
ok: true,
|
|
||||||
schema_version: SCHEMA_VERSION,
|
|
||||||
command: path,
|
|
||||||
summary,
|
|
||||||
description: summary,
|
|
||||||
usage: buildUsage(path, options),
|
|
||||||
options: options.filter((item) => item.startsWith("--") || item.startsWith("[--")).map((item) => optionDoc(path, item)),
|
|
||||||
examples: [...(examples ?? [`tjwater-cli ${path}${exampleSuffix(options)}`])],
|
|
||||||
output: "标准 JSON 输出",
|
|
||||||
next_commands: [...nextCommands],
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
function buildUsage(path: string, options: readonly string[]): string {
|
|
||||||
const optionTokens = options.filter((item) => item.startsWith("--") || item.startsWith("[--"));
|
|
||||||
return `tjwater-cli ${path}${optionTokens.length ? ` ${optionTokens.join(" ")}` : ""}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function optionDoc(path: string, token: string): CommandOptionDoc {
|
|
||||||
const optional = token.startsWith("[");
|
|
||||||
const clean = token.replace(/^\[/, "").replace(/\]$/, "");
|
|
||||||
const name = clean.slice(2).split(/\s+/)[0]!;
|
|
||||||
const repeatedOptions: Record<string, string[]> = {
|
|
||||||
"analysis valve": ["valve", "element", "disabled-valve"],
|
|
||||||
"analysis burst-location locate": ["pressure-scada-id", "flow-scada-id"],
|
|
||||||
"data timeseries scada query": ["device-id"],
|
|
||||||
"data timeseries composite": ["feature"],
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
description: "",
|
|
||||||
required: !optional,
|
|
||||||
repeated: (repeatedOptions[path] ?? []).includes(name),
|
|
||||||
default: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function exampleSuffix(options: readonly string[]): string {
|
|
||||||
return options
|
|
||||||
.map((item) => {
|
|
||||||
if (item.includes("START_TIME")) return " --start-time 2025-01-02T03:00:00+08:00";
|
|
||||||
if (item.includes("END_TIME")) return " --end-time 2025-01-02T04:00:00+08:00";
|
|
||||||
if (item.includes("DURATION")) return " --duration 900";
|
|
||||||
if (item.includes("SCHEME")) return " --scheme my_scheme";
|
|
||||||
if (item.includes("KIND")) return " --kind info";
|
|
||||||
if (item.includes("TYPE")) return " --type pipe";
|
|
||||||
if (item.includes("TIME")) return " --time 2025-01-02T03:30:00+08:00";
|
|
||||||
return "";
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isHiddenPath(path: string): boolean {
|
|
||||||
return HIDDEN_PATH_PREFIXES.some((prefix) => path === prefix || path.startsWith(`${prefix} `));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasVisibleSubcommands(path: string): boolean {
|
|
||||||
return [...commandDocs.keys()].some((key) => !isHiddenPath(key) && key.startsWith(`${path} `));
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { SCHEMA_VERSION } from "../core/constants.js";
|
|
||||||
import { GROUP_SUMMARIES, commandDocs, hasVisibleSubcommands, isHiddenPath } from "./docs.js";
|
|
||||||
import type { HelpPayload } from "../core/types.js";
|
|
||||||
|
|
||||||
export function helpPayload(pathParts: string[]): HelpPayload | null {
|
|
||||||
const path = pathParts.join(" ");
|
|
||||||
if (path && isHiddenPath(path)) return null;
|
|
||||||
if (path && commandDocs.has(path)) return commandDocs.get(path)!;
|
|
||||||
if (!path) {
|
|
||||||
const seen = new Set<string>();
|
|
||||||
const commands: Array<{ command: string; summary: string }> = [];
|
|
||||||
for (const doc of [...commandDocs.values()].sort((a, b) => a.command.localeCompare(b.command))) {
|
|
||||||
if (isHiddenPath(doc.command)) continue;
|
|
||||||
const command = doc.command.split(" ")[0]!;
|
|
||||||
if (seen.has(command)) continue;
|
|
||||||
seen.add(command);
|
|
||||||
commands.push({ command, summary: GROUP_SUMMARIES[command] ?? `${command} 可用子命令` });
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
schema_version: SCHEMA_VERSION,
|
|
||||||
summary: "可用一级菜单",
|
|
||||||
menu_level: 1,
|
|
||||||
commands,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const prefix = path ? `${path} ` : "";
|
|
||||||
const children: Array<{ command: string; summary: string; usage: string; example: string }> = [];
|
|
||||||
const seen = new Set<string>();
|
|
||||||
for (const doc of [...commandDocs.values()].sort((a, b) => a.command.localeCompare(b.command))) {
|
|
||||||
if (isHiddenPath(doc.command)) continue;
|
|
||||||
if (!doc.command.startsWith(prefix)) continue;
|
|
||||||
const child = doc.command.slice(prefix.length).split(" ")[0]!;
|
|
||||||
if (seen.has(child)) continue;
|
|
||||||
seen.add(child);
|
|
||||||
const childPath = path ? `${path} ${child}` : child;
|
|
||||||
const isGroup = hasVisibleSubcommands(childPath);
|
|
||||||
const childDoc = commandDocs.get(childPath) ?? doc;
|
|
||||||
children.push({
|
|
||||||
command: childPath,
|
|
||||||
summary: isGroup ? (GROUP_SUMMARIES[childPath] ?? `${childPath} 可用子命令`) : childDoc.summary,
|
|
||||||
usage: isGroup ? `tjwater-cli ${childPath} help` : childDoc.usage,
|
|
||||||
example: isGroup ? `tjwater-cli ${childPath} help` : childDoc.examples[0]!,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!children.length && path) return null;
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
schema_version: SCHEMA_VERSION,
|
|
||||||
summary: GROUP_SUMMARIES[path] ?? `${path} 可用子命令`,
|
|
||||||
commands: children,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
||||||
if command -v bun >/dev/null 2>&1; then
|
|
||||||
exec bun "$SCRIPT_DIR/tjwater-cli.ts" "$@"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec node "$SCRIPT_DIR/tjwater-cli.ts" "$@"
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user