ppxans-harness 2.4.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +218 -265
- package/bin/ppx-channels.js +2 -2
- package/bin/ppx-serve.js +5 -5
- package/bin/ppx-setup.js +124 -0
- package/bin/ppx-web.js +140 -0
- package/bin/ppx.js +2 -2
- package/config/identity.md +6 -6
- package/config/ishiki.md +16 -16
- package/config/ppx.json +15 -151
- package/config/ppx.json.example +143 -0
- package/package.json +17 -10
- package/skills/.usage.json +6 -0
- package/skills/agent-professional-training/SKILL.md +94 -0
- package/skills/brainstorm/SKILL.md +24 -0
- package/skills/cupid-lover-comms/SKILL.md +37 -0
- package/skills/debug/SKILL.md +26 -0
- package/skills/plan/SKILL.md +25 -0
- package/skills/ponytail/SKILL.md +25 -0
- package/skills/ppx-memory/SKILL.md +91 -0
- package/skills/ppx-memory/scripts/cli.js +192 -0
- package/skills/ppx-memory/scripts/experience.js +133 -0
- package/skills/ppx-memory/scripts/fact-store.js +842 -0
- package/skills/ppx-memory/scripts/l0.js +52 -0
- package/skills/ppx-memory/scripts/l2.js +146 -0
- package/skills/ppx-memory/scripts/l3.js +112 -0
- package/skills/ppx-memory/scripts/memory-ticker.js +238 -0
- package/skills/ppx-memory/scripts/pii.js +42 -0
- package/skills/ppx-memory/scripts/schema.js +80 -0
- package/skills/ppx-memory/scripts/session.js +398 -0
- package/skills/ppx-memory/scripts/similarity.js +43 -0
- package/skills/ppx-memory/scripts/store.js +116 -0
- package/skills/ppx-memory/scripts/wal.js +38 -0
- package/skills/ppx-selfheal/SKILL.md +24 -0
- package/skills/ppx-selfheal/scripts/cli.js +80 -0
- package/skills/ppx-selfheal/scripts/healer.js +184 -0
- package/skills/ppx-selfheal/scripts/logger.js +17 -0
- package/skills/ppx-selfheal/scripts/store.js +116 -0
- package/skills/prompt-depth-kit/SKILL.md +28 -0
- package/skills/session-naming/SKILL.md +36 -0
- package/skills/verify/SKILL.md +25 -0
- package/src/agent/index.js +1340 -717
- package/src/agent/prompts.js +47 -3
- package/src/aml-server.js +197 -151
- package/src/ans/eviction.js +123 -143
- package/src/ans/guard.js +159 -120
- package/src/ans/lifecycle.js +96 -93
- package/src/ans/proactive.js +112 -129
- package/src/ans/reward.js +95 -111
- package/src/ans/values.js +15 -15
- package/src/audit/audit-chain.js +43 -7
- package/src/audit/verifier.js +157 -120
- package/src/bus/circuit-breaker.js +9 -1
- package/src/bus/runtime-bus.js +107 -93
- package/src/channels/base.js +57 -34
- package/src/channels/feishu.js +118 -126
- package/src/channels/http.js +1090 -592
- package/src/channels/index.js +111 -110
- package/src/channels/log.js +29 -29
- package/src/channels/wechat-crypto.js +73 -74
- package/src/channels/wechat.js +191 -197
- package/src/channels/workspace.js +94 -0
- package/src/channels-cli.js +126 -124
- package/src/cli.js +129 -120
- package/src/commands/index.js +142 -0
- package/src/config/channels.js +137 -170
- package/src/config/index.js +277 -224
- package/src/config/placeholder.js +31 -0
- package/src/config/providers.js +148 -188
- package/src/config/settings.js +156 -182
- package/src/core/policy.js +69 -18
- package/src/core/trace.js +8 -6
- package/src/edit/editblock.js +266 -0
- package/src/edit/snapshot.js +67 -0
- package/src/evidence/index.js +153 -0
- package/src/evolve/playbook.js +9 -10
- package/src/hooks/index.js +113 -0
- package/src/llm/client.js +188 -446
- package/src/llm/dsml.js +74 -74
- package/src/llm/embedder.js +41 -35
- package/src/llm/fence.js +52 -105
- package/src/llm/index.js +4 -4
- package/src/llm/local-embedder.js +94 -0
- package/src/llm/presets.js +113 -0
- package/src/llm/pricing.js +93 -0
- package/src/llm/retry.js +73 -73
- package/src/llm/router.js +92 -97
- package/src/mcp/admin.js +326 -0
- package/src/mcp/client.js +487 -375
- package/src/mcp/http.js +203 -0
- package/src/mcp/index.js +116 -116
- package/src/mcp/server.js +392 -0
- package/src/mcp/tasks.js +133 -0
- package/src/memory/asset-hub.js +6 -11
- package/src/memory/canvas.js +2 -5
- package/src/memory/compaction.js +28 -28
- package/src/memory/experience.js +133 -122
- package/src/memory/fact-store.js +914 -698
- package/src/memory/failure-episode.js +20 -11
- package/src/memory/fork.js +17 -8
- package/src/memory/index.js +8 -6
- package/src/memory/l0.js +53 -52
- package/src/memory/l2.js +145 -130
- package/src/memory/l3.js +111 -111
- package/src/memory/legion-board.js +71 -0
- package/src/memory/memory-ticker.js +239 -240
- package/src/memory/session.js +398 -397
- package/src/memory/sqlite-store.js +581 -0
- package/src/mode/blackboard.js +49 -49
- package/src/mode/graph.js +42 -41
- package/src/mode/index.js +64 -64
- package/src/mode/legion.js +54 -51
- package/src/mode/plan-exec.js +50 -50
- package/src/mode/router.js +28 -40
- package/src/orchestrator/agent-worker.js +69 -69
- package/src/orchestrator/dag.js +90 -83
- package/src/orchestrator/experts.js +76 -0
- package/src/orchestrator/index.js +1 -1
- package/src/orchestrator/legion.js +179 -187
- package/src/orchestrator/supervisor.js +6 -8
- package/src/permissions/index.js +378 -0
- package/src/persona/index.js +28 -29
- package/src/plugin/builtin.js +313 -212
- package/src/plugin/context.js +80 -79
- package/src/plugin/index.js +62 -62
- package/src/plugin/v3.js +73 -0
- package/src/protocol/index.js +148 -0
- package/src/repomap/index.js +309 -0
- package/src/review/index.js +393 -0
- package/src/seam/registry.js +3 -0
- package/src/seam/shell.js +55 -55
- package/src/security/injection.js +79 -0
- package/src/selfheal/evolve.js +67 -67
- package/src/selfheal/healer.js +184 -167
- package/src/selfheal/run.js +9 -9
- package/src/server.js +63 -60
- package/src/services/diagnose.js +180 -0
- package/src/services/learning-service.js +9 -0
- package/src/services/memory-health.js +34 -6
- package/src/services/memory-service.js +42 -9
- package/src/services/triage.js +138 -0
- package/src/session/parts.js +76 -0
- package/src/session/projection.js +73 -0
- package/src/session/rollout.js +54 -0
- package/src/session/turn.js +137 -0
- package/src/skills/lint.js +72 -0
- package/src/skills/loader.js +231 -150
- package/src/skills/search.js +58 -0
- package/src/skills/verify.js +95 -100
- package/src/tools/advanced.js +388 -352
- package/src/tools/builtin.js +384 -297
- package/src/tools/catalog.js +283 -159
- package/src/tools/command-guard.js +112 -112
- package/src/tools/custom.js +47 -47
- package/src/tools/delegate.js +383 -297
- package/src/tools/document.js +254 -253
- package/src/tools/git.js +151 -0
- package/src/tools/governance.js +47 -20
- package/src/tools/index.js +16 -11
- package/src/tools/methods.js +178 -178
- package/src/tools/ocr.js +59 -59
- package/src/tools/sandbox-worker.js +40 -0
- package/src/tools/sandbox.js +92 -0
- package/src/tools/seam.js +162 -125
- package/src/tools/selfmod.js +196 -176
- package/src/tools/v3.js +225 -0
- package/src/tools/vad.js +176 -0
- package/src/tools/voice.js +238 -0
- package/src/utils/async.js +14 -0
- package/src/utils/config-file.js +53 -0
- package/src/utils/crashguard.js +88 -0
- package/src/utils/http.js +53 -0
- package/src/utils/id.js +8 -0
- package/src/utils/json-state.js +33 -0
- package/src/utils/logger.js +17 -17
- package/src/utils/ndjson.js +25 -0
- package/src/utils/pii.js +42 -42
- package/src/utils/rate-limit.js +50 -0
- package/src/utils/schema.js +80 -0
- package/src/utils/similarity.js +43 -0
- package/src/utils/store.js +170 -108
- package/src/utils/text.js +15 -15
- package/src/utils/trace.js +153 -153
- package/src/utils/wal.js +39 -0
- package/src/utils/winutf8.js +16 -15
- package/src/wiki/index.js +170 -0
package/src/config/providers.js
CHANGED
|
@@ -1,189 +1,149 @@
|
|
|
1
|
-
// src/config/providers.js - LLM 提供方 CRUD (HTTP API 后端)
|
|
2
|
-
// 设计:
|
|
3
|
-
// - 唯一事实源 = config/ppx.json 的 providers 数组
|
|
4
|
-
// - 写盘: 备份原文件 → 原子写 (.tmp + rename) → 防配置丢失
|
|
5
|
-
// - 返回前端时: 抹掉 api_key 明文, 只暴露 api_key_env 与 api_key_set 标志
|
|
6
|
-
// - 校验: id 命名规则 / 字段必填 / id 冲突检测
|
|
7
|
-
// - 热重载: 调用方写盘后, 调 agent.reloadProviders() 重建内存中的 LLM 客户端列表
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
export function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
providers[idx] = clean;
|
|
150
|
-
cfg.providers = providers;
|
|
151
|
-
writeConfigAtomic(root, cfg);
|
|
152
|
-
return sanitizeProvider(clean);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// 删除
|
|
157
|
-
export function removeProvider(root, id) {
|
|
158
|
-
return withFileLock(getProvidersPath(root), () => {
|
|
159
|
-
const { providers, raw: cfg } = readConfig(root);
|
|
160
|
-
const idx = providers.findIndex((p) => p.id === id);
|
|
161
|
-
if (idx < 0) throw new Error(`提供方不存在: ${id}`);
|
|
162
|
-
const [removed] = providers.splice(idx, 1);
|
|
163
|
-
cfg.providers = providers;
|
|
164
|
-
writeConfigAtomic(root, cfg);
|
|
165
|
-
return sanitizeProvider(removed);
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// 重排 (默认 = 数组第 0 个)
|
|
170
|
-
export function reorderProviders(root, order) {
|
|
171
|
-
return withFileLock(getProvidersPath(root), () => {
|
|
172
|
-
const { providers, raw: cfg } = readConfig(root);
|
|
173
|
-
if (!Array.isArray(order) || order.length !== providers.length) {
|
|
174
|
-
throw new Error("order 必须是包含全部 id 的数组");
|
|
175
|
-
}
|
|
176
|
-
const byId = new Map(providers.map((p) => [p.id, p]));
|
|
177
|
-
const next = [];
|
|
178
|
-
for (const id of order) {
|
|
179
|
-
const p = byId.get(id);
|
|
180
|
-
if (!p) throw new Error(`未知 id: ${id}`);
|
|
181
|
-
next.push(p);
|
|
182
|
-
byId.delete(id);
|
|
183
|
-
}
|
|
184
|
-
if (byId.size) throw new Error("order 缺少部分 id");
|
|
185
|
-
cfg.providers = next;
|
|
186
|
-
writeConfigAtomic(root, cfg);
|
|
187
|
-
return next.map((p) => sanitizeProvider(p));
|
|
188
|
-
});
|
|
1
|
+
// src/config/providers.js - LLM 提供方 CRUD (HTTP API 后端)
|
|
2
|
+
// 设计:
|
|
3
|
+
// - 唯一事实源 = config/ppx.json 的 providers 数组
|
|
4
|
+
// - 写盘: 备份原文件 → 原子写 (.tmp + rename) → 防配置丢失
|
|
5
|
+
// - 返回前端时: 抹掉 api_key 明文, 只暴露 api_key_env 与 api_key_set 标志
|
|
6
|
+
// - 校验: id 命名规则 / 字段必填 / id 冲突检测
|
|
7
|
+
// - 热重载: 调用方写盘后, 调 agent.reloadProviders() 重建内存中的 LLM 客户端列表
|
|
8
|
+
import { withFileLock } from "../utils/store.js";
|
|
9
|
+
import { configFilePath, readPpxConfig, writeConfigAtomic } from "../utils/config-file.js";
|
|
10
|
+
import { isPlaceholder } from "./placeholder.js";
|
|
11
|
+
|
|
12
|
+
// 提供方字段白名单 (写入磁盘时的过滤)
|
|
13
|
+
const PROVIDER_KEYS = [
|
|
14
|
+
"id", "backend", "base_url", "api_key", "api_key_env",
|
|
15
|
+
"model", "models", "vision", "timeout_ms",
|
|
16
|
+
"dsml",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const getProvidersPath = configFilePath;
|
|
20
|
+
|
|
21
|
+
// 读取整个 config, 返回 { providers, raw }
|
|
22
|
+
export function readConfig(root) {
|
|
23
|
+
const raw = readPpxConfig(root, null);
|
|
24
|
+
if (raw === null) return { providers: [], raw: { providers: [] } };
|
|
25
|
+
const providers = Array.isArray(raw.providers) ? raw.providers : [];
|
|
26
|
+
return { providers, raw };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 校验一个提供方对象是否合法; 返回 null 或错误信息
|
|
30
|
+
export function validateProvider(p) {
|
|
31
|
+
if (!p || typeof p !== "object") return "提供方必须是对象";
|
|
32
|
+
if (!p.id || typeof p.id !== "string") return "缺少 id";
|
|
33
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,29}$/.test(p.id)) return "id 必须以字母开头, 仅含字母/数字/横线/下划线, 2-30字符";
|
|
34
|
+
// 自研底座: 仅支持 http 后端 (OpenAI 兼容 API 直连); 显式非 http 后端一律拒绝
|
|
35
|
+
if (p.backend && p.backend !== "http") {
|
|
36
|
+
return `后端 ${p.backend} 已不受支持 (v2.5.0 起仅保留自研 http 底座, 外部引擎底座已移除)`;
|
|
37
|
+
}
|
|
38
|
+
if (!p.base_url) return "http 后端需 base_url";
|
|
39
|
+
if (p.vision != null && typeof p.vision !== "boolean") return "vision 必须是布尔";
|
|
40
|
+
if (p.dsml != null && typeof p.dsml !== "boolean") return "dsml 必须是布尔";
|
|
41
|
+
if (p.timeout_ms != null && (!Number.isFinite(p.timeout_ms) || p.timeout_ms < 1000)) return "timeout_ms 至少 1000ms";
|
|
42
|
+
// 占位符校验: 模板残留 (如 REPLACE_WITH_YOUR_ENDPOINT / YOUR_LOCAL_MODEL_NAME) 视为未配置,
|
|
43
|
+
// 避免配了 key 却静默失败。v1.0.8 (P2-3): 判定收敛到 config/placeholder.js 唯一真相源。
|
|
44
|
+
for (const field of ["model", "base_url", "api_key"]) {
|
|
45
|
+
const v = p[field];
|
|
46
|
+
if (isPlaceholder(v)) {
|
|
47
|
+
return `${field} 仍是占位符 (${v.slice(0, 40)}), 请替换为真实值`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 抹掉 key 明文, 暴露给前端的安全视图
|
|
54
|
+
export function sanitizeProvider(p) {
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const k of PROVIDER_KEYS) {
|
|
57
|
+
if (k === "api_key") continue; // 永远不回传
|
|
58
|
+
if (p[k] !== undefined) out[k] = p[k];
|
|
59
|
+
}
|
|
60
|
+
out.api_key_set = !!p.api_key;
|
|
61
|
+
out.api_key_env = p.api_key_env || "";
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function listProviders(root) {
|
|
66
|
+
const { providers } = readConfig(root);
|
|
67
|
+
return providers.map((p) => sanitizeProvider(p));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getProvider(root, id) {
|
|
71
|
+
const { providers } = readConfig(root);
|
|
72
|
+
const p = providers.find((x) => x.id === id);
|
|
73
|
+
return p ? sanitizeProvider(p) : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 新增
|
|
77
|
+
// v1.0.8: 写盘在文件锁内读-改-写 (防并发 API 请求丢更新)
|
|
78
|
+
export function addProvider(root, raw) {
|
|
79
|
+
return withFileLock(getProvidersPath(root), () => {
|
|
80
|
+
const { providers, raw: cfg } = readConfig(root);
|
|
81
|
+
const err = validateProvider(raw);
|
|
82
|
+
if (err) throw new Error(err);
|
|
83
|
+
if (providers.find((p) => p.id === raw.id)) throw new Error(`id 冲突: ${raw.id}`);
|
|
84
|
+
// 仅保留白名单字段
|
|
85
|
+
const norm = {};
|
|
86
|
+
for (const k of PROVIDER_KEYS) if (raw[k] !== undefined) norm[k] = raw[k];
|
|
87
|
+
if (!norm.id) norm.id = raw.id;
|
|
88
|
+
providers.push(norm);
|
|
89
|
+
cfg.providers = providers;
|
|
90
|
+
writeConfigAtomic(root, cfg);
|
|
91
|
+
return sanitizeProvider(norm);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 更新 (id 不可改, 其他字段覆盖)
|
|
96
|
+
export function updateProvider(root, id, patch) {
|
|
97
|
+
return withFileLock(getProvidersPath(root), () => {
|
|
98
|
+
const { providers, raw: cfg } = readConfig(root);
|
|
99
|
+
const idx = providers.findIndex((p) => p.id === id);
|
|
100
|
+
if (idx < 0) throw new Error(`提供方不存在: ${id}`);
|
|
101
|
+
const merged = { ...providers[idx], ...patch, id };
|
|
102
|
+
// 抹掉 patch 里的 api_key 设了空字符串, 视为清空
|
|
103
|
+
if (patch.api_key === "") delete merged.api_key;
|
|
104
|
+
const err = validateProvider(merged);
|
|
105
|
+
if (err) throw new Error(err);
|
|
106
|
+
// 仅保留白名单
|
|
107
|
+
const clean = {};
|
|
108
|
+
for (const k of PROVIDER_KEYS) if (merged[k] !== undefined) clean[k] = merged[k];
|
|
109
|
+
providers[idx] = clean;
|
|
110
|
+
cfg.providers = providers;
|
|
111
|
+
writeConfigAtomic(root, cfg);
|
|
112
|
+
return sanitizeProvider(clean);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 删除
|
|
117
|
+
export function removeProvider(root, id) {
|
|
118
|
+
return withFileLock(getProvidersPath(root), () => {
|
|
119
|
+
const { providers, raw: cfg } = readConfig(root);
|
|
120
|
+
const idx = providers.findIndex((p) => p.id === id);
|
|
121
|
+
if (idx < 0) throw new Error(`提供方不存在: ${id}`);
|
|
122
|
+
const [removed] = providers.splice(idx, 1);
|
|
123
|
+
cfg.providers = providers;
|
|
124
|
+
writeConfigAtomic(root, cfg);
|
|
125
|
+
return sanitizeProvider(removed);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 重排 (默认 = 数组第 0 个)
|
|
130
|
+
export function reorderProviders(root, order) {
|
|
131
|
+
return withFileLock(getProvidersPath(root), () => {
|
|
132
|
+
const { providers, raw: cfg } = readConfig(root);
|
|
133
|
+
if (!Array.isArray(order) || order.length !== providers.length) {
|
|
134
|
+
throw new Error("order 必须是包含全部 id 的数组");
|
|
135
|
+
}
|
|
136
|
+
const byId = new Map(providers.map((p) => [p.id, p]));
|
|
137
|
+
const next = [];
|
|
138
|
+
for (const id of order) {
|
|
139
|
+
const p = byId.get(id);
|
|
140
|
+
if (!p) throw new Error(`未知 id: ${id}`);
|
|
141
|
+
next.push(p);
|
|
142
|
+
byId.delete(id);
|
|
143
|
+
}
|
|
144
|
+
if (byId.size) throw new Error("order 缺少部分 id");
|
|
145
|
+
cfg.providers = next;
|
|
146
|
+
writeConfigAtomic(root, cfg);
|
|
147
|
+
return next.map((p) => sanitizeProvider(p));
|
|
148
|
+
});
|
|
189
149
|
}
|