dsh-mobile-plugin 0.5.17 → 0.5.19
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/config/agent-presets/standard/agent.cordis.yml +9 -0
- package/host.js +26 -4
- package/lib/host-compat.cjs +51 -4
- package/lib/impl.js +6 -0
- package/lib/mobile/cli/acp.js +216 -0
- package/lib/mobile/cli/agents.js +217 -0
- package/lib/mobile/cli/blocks.js +228 -0
- package/lib/mobile/cli/config-prefs.js +155 -0
- package/lib/mobile/cli/icons.js +138 -0
- package/lib/mobile/cli/images.js +132 -0
- package/lib/mobile/cli/mobile.js +1552 -0
- package/lib/mobile/cli/model-prefs.js +77 -0
- package/lib/mobile/cli/model-visibility.js +92 -0
- package/lib/mobile/cli/registry.js +207 -0
- package/lib/mobile/cli/service.js +901 -0
- package/lib/mobile/cli/usage.js +133 -0
- package/lib/mobile/monitor.js +17 -6
- package/lib/mobile/narration.js +205 -0
- package/lib/mobile/phone/binding.js +247 -0
- package/lib/mobile/phone/index.js +63 -0
- package/lib/mobile/phone/routes.js +168 -0
- package/lib/mobile/phone/service.js +327 -0
- package/lib/mobile/phone/tools.js +565 -0
- package/lib/mobile/questions.js +9 -3
- package/lib/mobile/services.js +16 -0
- package/lib/mobile/session-send.js +1 -0
- package/lib/mobile/stats-ledger.cjs +8 -23
- package/lib/mobile/terminal.js +574 -0
- package/lib/routes/mobile-api.js +139 -3
- package/package.json +2 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 「这条 CLI 上次用的是哪个模型」—— 一台电脑每个 CLI 记一份。
|
|
5
|
+
*
|
|
6
|
+
* 为什么要记:ACP 的模型是**会话级**配置(`session/new` 之后才有 `configOptions`,`set_config_option`
|
|
7
|
+
* 只对那一条会话生效)。不记的话,每次从侧栏新建一条 CLI 对话都回到 CLI 自己的默认模型,用户
|
|
8
|
+
* 每一轮都要在手机上重新选一次 —— 这在普通会话里是不存在的体验(宿主那边默认模型是设备级的)。
|
|
9
|
+
*
|
|
10
|
+
* 记账口径是 `agentId`(codex / claude / gemini / opencode …),不是会话 id:换一条对话、换一台
|
|
11
|
+
* 手机、宿主重启之后,用户选过的仍然是那一个。
|
|
12
|
+
*
|
|
13
|
+
* 只记「配置项 id + 选中值」这一对,不记目录:模型清单每次由 CLI 自己给,我们记的只是「上次
|
|
14
|
+
* 选的是哪一个」。那个值在新会话里不存在(CLI 升级换了模型名)时就当没记过 —— 悄悄退回默认,
|
|
15
|
+
* 不去设一个它不认识的值。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
|
|
21
|
+
const paths = require('../../shared/paths');
|
|
22
|
+
|
|
23
|
+
/** 文件:`$DSH_HOME/storages/mobile-plugin/cli-models.json`。 */
|
|
24
|
+
function storeFile() {
|
|
25
|
+
return path.join(paths.dshHome(), 'storages', 'mobile-plugin', 'cli-models.json');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function text(value) {
|
|
29
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function load() {
|
|
33
|
+
try {
|
|
34
|
+
const raw = JSON.parse(fs.readFileSync(storeFile(), 'utf8'));
|
|
35
|
+
return raw && typeof raw === 'object' && raw.agents && typeof raw.agents === 'object' ? raw.agents : {};
|
|
36
|
+
} catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function save(agents) {
|
|
42
|
+
try {
|
|
43
|
+
fs.mkdirSync(path.dirname(storeFile()), { recursive: true });
|
|
44
|
+
fs.writeFileSync(storeFile(), JSON.stringify({ agents }, null, 0), 'utf8');
|
|
45
|
+
} catch {
|
|
46
|
+
// 写不进去只是下次要重新选一次,不影响这一次。
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 这个 CLI 上次选的模型:`{ configId, value }`,没记过就 null。 */
|
|
51
|
+
function preferredModel(agentId) {
|
|
52
|
+
const entry = load()[text(agentId)];
|
|
53
|
+
const configId = text(entry && entry.configId);
|
|
54
|
+
const value = text(entry && entry.value);
|
|
55
|
+
return configId && value ? { configId, value } : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 记下这个 CLI 这次选的模型(`value` 空 = 清掉)。 */
|
|
59
|
+
function rememberModel(agentId, configId, value) {
|
|
60
|
+
const id = text(agentId);
|
|
61
|
+
if (!id) return;
|
|
62
|
+
const agents = load();
|
|
63
|
+
const cleanConfig = text(configId);
|
|
64
|
+
const cleanValue = text(value);
|
|
65
|
+
if (!cleanConfig || !cleanValue) {
|
|
66
|
+
delete agents[id];
|
|
67
|
+
} else {
|
|
68
|
+
agents[id] = { configId: cleanConfig, value: cleanValue };
|
|
69
|
+
}
|
|
70
|
+
save(agents);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
storeFile,
|
|
75
|
+
preferredModel,
|
|
76
|
+
rememberModel,
|
|
77
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI 的模型开关 —— 手机上「这个 CLI 里哪些模型要显示、哪些藏起来」的那本账。
|
|
5
|
+
*
|
|
6
|
+
* 为什么存在插件里而不是手机本地:ACP 的模型目录是**那台电脑上的 CLI 自己给的**,各家会不会
|
|
7
|
+
* 「只列一部分」全看它自己,我们既不能替它删、也写不回它的配置。所以这一档只能当**展示层**
|
|
8
|
+
* 的账来做:哪几个要显示由用户定,账存在电脑上,换一台手机看到的是同一份(`model-visibility`
|
|
9
|
+
* 在 App 里存的是那份「按设备分桶的副本」,开机对一次源)。
|
|
10
|
+
*
|
|
11
|
+
* 与 App 那本 `model-visibility` 的关系:宿主这套模型目录(`/session/models`)本来就有「只影响
|
|
12
|
+
* 本机显示」的开关,CLI 这边多了个「换手机也要一样」的要求,所以真值放这儿,App 那边只读。
|
|
13
|
+
* 只记「被关掉的」:模型目录天天在长,记白名单会让新模型默认不出现。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
|
|
19
|
+
const paths = require('../../shared/paths');
|
|
20
|
+
|
|
21
|
+
/** 文件:`$DSH_HOME/storages/mobile-plugin/cli-model-visibility.json`。 */
|
|
22
|
+
function storeFile() {
|
|
23
|
+
return path.join(paths.dshHome(), 'storages', 'mobile-plugin', 'cli-model-visibility.json');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function text(value) {
|
|
27
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* `{ agents: { <agentId>: { models: [modelId…], configs: [configId…] } } }`
|
|
32
|
+
*
|
|
33
|
+
* 两个桶:
|
|
34
|
+
* - `models`:被关掉的模型(目录里不出现);
|
|
35
|
+
* - `configs`:被关掉的整组(一个 CLI 里可能有多组模型,opencode 的「Go 订阅」就是一组)。
|
|
36
|
+
*/
|
|
37
|
+
function load() {
|
|
38
|
+
try {
|
|
39
|
+
const raw = JSON.parse(fs.readFileSync(storeFile(), 'utf8'));
|
|
40
|
+
const agents = raw && typeof raw === 'object' ? raw.agents : null;
|
|
41
|
+
return agents && typeof agents === 'object' ? agents : {};
|
|
42
|
+
} catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function save(agents) {
|
|
48
|
+
try {
|
|
49
|
+
fs.mkdirSync(path.dirname(storeFile()), { recursive: true });
|
|
50
|
+
fs.writeFileSync(storeFile(), JSON.stringify({ agents }, null, 0), 'utf8');
|
|
51
|
+
} catch {
|
|
52
|
+
// 写不进去只是这台电脑重启后开关回到全开,不影响这一次点击。
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 这个 CLI 被关掉的模型与整组:`{ models: string[], configs: string[] }`(没有就是空数组)。 */
|
|
57
|
+
function hiddenOf(agentId) {
|
|
58
|
+
const entry = load()[text(agentId)];
|
|
59
|
+
const list = (value) => (Array.isArray(value) ? value.filter((item) => typeof item === 'string' && item) : []);
|
|
60
|
+
return {
|
|
61
|
+
models: list(entry && entry.models),
|
|
62
|
+
configs: list(entry && entry.configs),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 改一个模型(或一整组)的开关。
|
|
68
|
+
*
|
|
69
|
+
* `kind`:
|
|
70
|
+
* - `'model'`:`id` 是模型 id(就是 ACP 那个 `value`);
|
|
71
|
+
* - `'config'`:`id` 是一组的 id(模型 id 里斜杠前那一段)。
|
|
72
|
+
*
|
|
73
|
+
* `visible === true` 是把它从账里删掉 —— **账只记关掉的**,所以「打开」等于「不记」。
|
|
74
|
+
*/
|
|
75
|
+
function setVisible(agentId, kind, id, visible) {
|
|
76
|
+
const agent = text(agentId);
|
|
77
|
+
const target = text(id);
|
|
78
|
+
if (!agent || !target) return;
|
|
79
|
+
const agents = load();
|
|
80
|
+
const entry = agents[agent] && typeof agents[agent] === 'object' ? { ...agents[agent] } : {};
|
|
81
|
+
const bucket = kind === 'config' ? 'configs' : 'models';
|
|
82
|
+
const current = new Set(Array.isArray(entry[bucket]) ? entry[bucket].filter((item) => typeof item === 'string' && item) : []);
|
|
83
|
+
if (visible) current.delete(target);
|
|
84
|
+
else current.add(target);
|
|
85
|
+
if (current.size) entry[bucket] = [...current].sort();
|
|
86
|
+
else delete entry[bucket];
|
|
87
|
+
if (entry.models || entry.configs) agents[agent] = entry;
|
|
88
|
+
else delete agents[agent];
|
|
89
|
+
save(agents);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { storeFile, hiddenOf, setVisible };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 官方 ACP 注册表:手机上那份「能起哪些 CLI」不再写死。
|
|
5
|
+
*
|
|
6
|
+
* 为什么改成读注册表:逐家适配是件永远追不完的活,而 ACP 生态已经把这件事做成了一份清单 ——
|
|
7
|
+
* `cdn.agentclientprotocol.com/registry/v1/latest/registry.json` 里每个 agent 都写清了起法
|
|
8
|
+
* (包名、参数、分发方式、维护方、图标),由各家和 Zed 一起维护。我们只需要读它,把它们当成
|
|
9
|
+
* 同一种东西交给 ACP 连接层;新 CLI 上线不需要我们发版。
|
|
10
|
+
*
|
|
11
|
+
* **只收 `distribution.npx` 那一档**:能 `npx` 拉起来的是纯 JS 包,起法与现有桥包完全一样;
|
|
12
|
+
* `binary` 那一档要下载可执行文件、按平台选架构、还要校验,是另一件事,这轮不做 —— 于是
|
|
13
|
+
* 注册表里有、但只有二进制分发的(Cursor / Kiro / Amp …)在清单里不出现,而不是出现一个点了
|
|
14
|
+
* 会失败的行。
|
|
15
|
+
*
|
|
16
|
+
* 三级兜底,任何一级拿到就收手:
|
|
17
|
+
* 1. 内存(这个进程问过一次就不再问);
|
|
18
|
+
* 2. 盘上缓存(`$DSH_HOME/storages/mobile-plugin/acp-registry.json`,一天内算新鲜);
|
|
19
|
+
* 3. 联网拉一次;拉不到就用过期缓存;连缓存都没有就退回 `agents.js` 的内置四家。
|
|
20
|
+
*
|
|
21
|
+
* 联网失败**不抛也不报错**:手机要的是「能起哪些」,离线时给出上次那份(或内置那份)就够了,
|
|
22
|
+
* 为一份清单弹一张红卡不值得。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
|
|
28
|
+
const paths = require('../../shared/paths');
|
|
29
|
+
|
|
30
|
+
const REGISTRY_URL = 'https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json';
|
|
31
|
+
|
|
32
|
+
/** 缓存多久算新鲜:一天。注册表按小时自动更新版本号,一天一拉足够跟上。 */
|
|
33
|
+
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
34
|
+
|
|
35
|
+
/** 拉取超时:清单是几十 KB 的一发,慢到这个份上就当作拉不到。 */
|
|
36
|
+
const FETCH_TIMEOUT_MS = 8000;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 拉失败之后多久再试。
|
|
40
|
+
*
|
|
41
|
+
* 失败也记一份(用的是过期缓存或内置表),但只记五分钟:断网时不该每问一次清单就干等八秒;
|
|
42
|
+
* 网络刚恢复时也不该让人等一天才看见新 CLI。
|
|
43
|
+
*/
|
|
44
|
+
const FAILURE_TTL_MS = 5 * 60 * 1000;
|
|
45
|
+
|
|
46
|
+
/** 内存里那一份(含拉取成败的结论,失败也记,免得每次问都再等 8 秒)。 */
|
|
47
|
+
let memory = null;
|
|
48
|
+
|
|
49
|
+
function cacheFile() {
|
|
50
|
+
return path.join(paths.dshHome(), 'storages', 'mobile-plugin', 'acp-registry.json');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function text(value) {
|
|
54
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 一条注册表条目 → 我们的适配器形状。
|
|
59
|
+
*
|
|
60
|
+
* 只认 npx 那一档:`package` 可能带版本(注册表就是这么发的,`@agentclientprotocol/codex-acp@2.5.0`),
|
|
61
|
+
* 原样交给 `npx -y`,版本由注册表钉住 —— 我们不去猜「最新版」。
|
|
62
|
+
*/
|
|
63
|
+
function adapterOf(entry) {
|
|
64
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
65
|
+
const id = text(entry.id);
|
|
66
|
+
const pkg = text(entry.distribution && entry.distribution.npx && entry.distribution.npx.package);
|
|
67
|
+
if (!id || !pkg) return null;
|
|
68
|
+
const args = Array.isArray(entry.distribution.npx.args)
|
|
69
|
+
? entry.distribution.npx.args.filter((arg) => typeof arg === 'string')
|
|
70
|
+
: [];
|
|
71
|
+
return {
|
|
72
|
+
id,
|
|
73
|
+
label: text(entry.name) || id,
|
|
74
|
+
transport: 'npx',
|
|
75
|
+
package: pkg,
|
|
76
|
+
...(args.length ? { args } : {}),
|
|
77
|
+
// 图标地址原样留着:`cli/icons.js` 用它在本地缓存一份 SVG,App 按 id 取(见那边文件头)。
|
|
78
|
+
...(text(entry.icon) ? { icon: text(entry.icon) } : {}),
|
|
79
|
+
...(text(entry.description) ? { description: text(entry.description) } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 注册表 JSON → 适配器数组(去重、丢掉没有 npx 分发的)。
|
|
85
|
+
*/
|
|
86
|
+
function adaptersOf(body) {
|
|
87
|
+
const agents = body && Array.isArray(body.agents) ? body.agents : [];
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
const out = [];
|
|
90
|
+
for (const entry of agents) {
|
|
91
|
+
const adapter = adapterOf(entry);
|
|
92
|
+
if (!adapter || seen.has(adapter.id)) continue;
|
|
93
|
+
seen.add(adapter.id);
|
|
94
|
+
out.push(adapter);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 内置表与注册表合一份清单:**内置的那几条优先**,注册表只补它没有的。
|
|
101
|
+
*
|
|
102
|
+
* 为什么不是「注册表整份替换内置」:注册表只收 npx 分发,而 opencode 在那里只有二进制分发
|
|
103
|
+
* (会整条被过滤掉),gemini / codex / claude 又各有一个 `-acp` 后缀的桥包条目 —— 直接替换
|
|
104
|
+
* 的结果是「opencode 从清单里消失」,接着建会话那一步回「不认识这个 CLI」,而用起来只是
|
|
105
|
+
* 「列表里少了几个、点了报错」。所以两边并起来,同一家只留一条:
|
|
106
|
+
*
|
|
107
|
+
* 1. id 完全相同 → 留内置(`gemini`);
|
|
108
|
+
* 2. id 去掉 `-acp` 后缀后相同 → 留内置(`codex-acp` / `claude-acp` 对 `codex` / `claude`);
|
|
109
|
+
* 3. 包名相同 → 留内置(同一条适配器换个名字而已)。
|
|
110
|
+
*
|
|
111
|
+
* 内置那几条用的是本机已经装好的 CLI(`gemini --acp` / `opencode acp`),比去 `npx` 拉一份
|
|
112
|
+
* 新包快得多,所以「优先内置」不只是去重策略,也是更短的那条路。
|
|
113
|
+
*/
|
|
114
|
+
function mergeWithBuiltin(builtin, fromRegistry) {
|
|
115
|
+
const kept = Array.isArray(builtin) ? builtin.filter(Boolean).slice() : [];
|
|
116
|
+
const ids = new Set(kept.map((agent) => agent.id));
|
|
117
|
+
const bareIds = new Set(kept.map((agent) => String(agent.id).replace(/-acp$/, '')));
|
|
118
|
+
const packages = new Set(kept.map((agent) => agent.package).filter(Boolean));
|
|
119
|
+
for (const agent of Array.isArray(fromRegistry) ? fromRegistry : []) {
|
|
120
|
+
if (!agent || !agent.id) continue;
|
|
121
|
+
if (ids.has(agent.id)) continue;
|
|
122
|
+
if (bareIds.has(String(agent.id).replace(/-acp$/, ''))) continue;
|
|
123
|
+
if (agent.package && packages.has(agent.package)) continue;
|
|
124
|
+
ids.add(agent.id);
|
|
125
|
+
kept.push(agent);
|
|
126
|
+
}
|
|
127
|
+
return kept;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
function readCache() {
|
|
132
|
+
try {
|
|
133
|
+
const raw = JSON.parse(fs.readFileSync(cacheFile(), 'utf8'));
|
|
134
|
+
const agents = Array.isArray(raw && raw.agents) ? raw.agents.filter((item) => item && typeof item.id === 'string') : [];
|
|
135
|
+
if (!agents.length) return null;
|
|
136
|
+
return { fetchedAt: Number(raw.fetchedAt) || 0, agents };
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function writeCache(agents) {
|
|
143
|
+
try {
|
|
144
|
+
fs.mkdirSync(path.dirname(cacheFile()), { recursive: true });
|
|
145
|
+
fs.writeFileSync(cacheFile(), JSON.stringify({ fetchedAt: Date.now(), agents }), 'utf8');
|
|
146
|
+
} catch {
|
|
147
|
+
// 写不进去只是下次还要联网拉一遍,不影响这次。
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function fetchRegistry() {
|
|
152
|
+
try {
|
|
153
|
+
const res = await fetch(REGISTRY_URL, {
|
|
154
|
+
headers: { accept: 'application/json' },
|
|
155
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
156
|
+
});
|
|
157
|
+
if (!res.ok) return null;
|
|
158
|
+
const agents = adaptersOf(await res.json());
|
|
159
|
+
return agents.length ? agents : null;
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 这一次该用哪份清单。`builtin` 是调用方给的内置表(`agents.js` 那四家)——
|
|
167
|
+
* 它**永远在清单里**,注册表只补它没有的(理由见 `mergeWithBuiltin`)。
|
|
168
|
+
*/
|
|
169
|
+
async function listRegistryAgents(builtin) {
|
|
170
|
+
const now = Date.now();
|
|
171
|
+
const base = Array.isArray(builtin) ? builtin : [];
|
|
172
|
+
if (memory && now - memory.at < (memory.degraded ? FAILURE_TTL_MS : TTL_MS)) {
|
|
173
|
+
return mergeWithBuiltin(base, memory.agents);
|
|
174
|
+
}
|
|
175
|
+
const cached = readCache();
|
|
176
|
+
if (cached && now - cached.fetchedAt < TTL_MS) {
|
|
177
|
+
memory = { at: cached.fetchedAt, agents: cached.agents };
|
|
178
|
+
return mergeWithBuiltin(base, cached.agents);
|
|
179
|
+
}
|
|
180
|
+
const fetched = await fetchRegistry();
|
|
181
|
+
if (fetched) {
|
|
182
|
+
writeCache(fetched);
|
|
183
|
+
memory = { at: now, agents: fetched };
|
|
184
|
+
return mergeWithBuiltin(base, fetched);
|
|
185
|
+
}
|
|
186
|
+
// 拉不到:过期缓存也比只有内置那四家强,先用它;都没有就只剩内置。
|
|
187
|
+
const fallback = cached && cached.agents.length ? cached.agents : [];
|
|
188
|
+
memory = { at: now, agents: fallback, degraded: true };
|
|
189
|
+
return mergeWithBuiltin(base, fallback);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** 测试用:清掉内存那一份(盘上缓存照旧)。 */
|
|
193
|
+
function resetRegistryMemory() {
|
|
194
|
+
memory = null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
REGISTRY_URL,
|
|
199
|
+
TTL_MS,
|
|
200
|
+
FAILURE_TTL_MS,
|
|
201
|
+
listRegistryAgents,
|
|
202
|
+
adaptersOf,
|
|
203
|
+
adapterOf,
|
|
204
|
+
mergeWithBuiltin,
|
|
205
|
+
resetRegistryMemory,
|
|
206
|
+
cacheFile,
|
|
207
|
+
};
|