feat: DSH编排基建首版(server/tests 88用例/P2插件/8020端口/PORT-NOTE/MOBILE_APP)

This commit is contained in:
2026-09-12 14:20:02 +08:00
commit f6acd20325
128 changed files with 24471 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
# 团队默认配置(team-defaults)
把团队的模型/供应商/界面偏好打包成一个可分发快照。新成员装上后一条命令
就能让自己的 harness 配置与服务端团队设置对齐,不用手工抄。
## 用法
```bash
# 下载并安装插件
python client/dsh_sync_client.py install team-defaults
# 应用到本地配置(已存在的键不会被覆盖)
python ~/.dsh/plugins/team-defaults/files/apply.py --config ~/.dsh/config.json
# 先看看会改什么
python .../apply.py --config ~/.dsh/config.json --dry-run
# 只应用模型相关
python .../apply.py --config ~/.dsh/config.json --only 'model.*' 'provider.*'
# 强制以团队值为准(覆盖本地改动)
python .../apply.py --config ~/.dsh/config.json --overwrite
```
写入前会自动在同目录留一份 `config.json.bak`。
## 更新快照
`files/defaults.json` 可以手工维护,也可以从服务端导出:
```bash
curl -H "Authorization: Bearer <token>" http://<server>:8020/v1/settings \
| python -c "import json,sys; d=json.load(sys.stdin)['settings']; \
print(json.dumps({k: v['value'] for k,v in d.items()}, indent=2, ensure_ascii=False))" \
> files/defaults.json
```
改完 `manifest.json` 里的 `version`,再发布:
```bash
bash tools/publish-plugin.sh team-defaults
```
## 安全
插件里不含任何 API Key。`provider.base_url` 只是端点地址;真实密钥按
ARCHITECTURE.md §7 留在各设备本地或走团队网关。
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""Apply this plugin's settings snapshot to a harness config file.
python apply.py --config ~/.dsh/config.json # merge defaults in
python apply.py --config ~/.dsh/config.json --dry-run
python apply.py --config ~/.dsh/config.json --only model.* provider.*
Existing keys are kept unless --overwrite is given, so applying this to a
machine someone has already tuned will not stomp their choices.
A backup of the original file is written next to it before any change.
"""
from __future__ import annotations
import argparse
import fnmatch
import json
import shutil
import sys
from pathlib import Path
DEFAULTS = Path(__file__).resolve().parent / "defaults.json"
def flatten(prefix: str, value, out: dict) -> None:
"""`{"model": {"temperature": 0.7}}` -> `{"model.temperature": 0.7}`."""
if isinstance(value, dict):
for k, v in value.items():
flatten(f"{prefix}.{k}" if prefix else k, v, out)
else:
out[prefix] = value
def load_defaults() -> dict:
raw = json.loads(DEFAULTS.read_text(encoding="utf-8"))
out: dict = {}
flatten("", raw, out)
return {k: v for k, v in out.items() if not k.startswith("_")}
def set_path(cfg: dict, dotted: str, value) -> None:
parts = dotted.split(".")
node = cfg
for p in parts[:-1]:
nxt = node.get(p)
if not isinstance(nxt, dict):
nxt = {}
node[p] = nxt
node = nxt
node[parts[-1]] = value
def get_path(cfg: dict, dotted: str):
node = cfg
for p in dotted.split("."):
if not isinstance(node, dict) or p not in node:
return None, False
node = node[p]
return node, True
def apply(config_path: Path, *, overwrite: bool, dry_run: bool,
only: list[str] | None) -> dict:
defaults = load_defaults()
if only:
defaults = {k: v for k, v in defaults.items()
if any(fnmatch.fnmatch(k, pat) for pat in only)}
if config_path.is_file():
cfg = json.loads(config_path.read_text(encoding="utf-8") or "{}")
else:
cfg = {}
added, kept = [], []
for key, value in sorted(defaults.items()):
_cur, present = get_path(cfg, key)
if present and not overwrite:
kept.append(key)
continue
set_path(cfg, key, value)
added.append(key)
if added and not dry_run:
config_path.parent.mkdir(parents=True, exist_ok=True)
if config_path.is_file():
shutil.copy2(config_path, config_path.with_suffix(config_path.suffix + ".bak"))
config_path.write_text(
json.dumps(cfg, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
return {"added": added, "kept": kept, "config": str(config_path)}
def main(argv=None) -> int:
ap = argparse.ArgumentParser(description="apply team default settings")
ap.add_argument("--config", type=Path, required=True)
ap.add_argument("--overwrite", action="store_true",
help="replace values that already exist")
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--only", nargs="*", metavar="GLOB",
help="limit to keys matching these patterns, e.g. 'model.*'")
args = ap.parse_args(argv)
result = apply(args.config, overwrite=args.overwrite, dry_run=args.dry_run,
only=args.only)
if args.dry_run:
print("dry run — nothing written")
print(f"config : {result['config']}")
print(f"added : {len(result['added'])}" + (f" {result['added']}" if result["added"] else ""))
print(f"kept : {len(result['kept'])}" + (f" {result['kept']}" if result["kept"] else ""))
return 0
if __name__ == "__main__":
sys.exit(main())
+12
View File
@@ -0,0 +1,12 @@
{
"_comment": "由 publish-plugin.sh 从服务端 settings 导出,或手工维护",
"model.default": "deepseek-chat",
"model.temperature": 0.7,
"model.max_tokens": 4096,
"provider.base_url": "https://api.deepseek.com",
"provider.timeout_seconds": 120,
"provider.max_retries": 3,
"harness.plugin_channel": "stable",
"ui.theme": "dark",
"ui.language": "zh-CN"
}
+10
View File
@@ -0,0 +1,10 @@
{
"id": "team-defaults",
"version": "1.0.0",
"name": "团队默认配置",
"description": "把当前团队设置固化成一个可分发快照,新成员的 harness 装上即用",
"channel": "stable",
"min_harness_version": "0.0.0",
"entry": "files/apply.py",
"files": ["files/apply.py", "files/defaults.json", "README.md"]
}