evolcore 0.0.21 → 0.0.23

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.
Files changed (112) hide show
  1. package/AGENTS.md +62 -0
  2. package/CHANGELOG.md +53 -0
  3. package/README.md +2 -0
  4. package/bin/codex-managed-acl.ps1 +155 -0
  5. package/bin/codex-managed-hook.mjs +3 -0
  6. package/bin/install-codex-managed-hooks.mjs +376 -96
  7. package/dist/agents/baseagent-availability.js +48 -0
  8. package/dist/agents/baseagent.js +1 -1
  9. package/dist/agents/claude-runner.js +44 -226
  10. package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
  11. package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
  12. package/dist/agents/codex/catalog.js +70 -0
  13. package/dist/agents/codex/diagnostics.js +154 -0
  14. package/dist/agents/codex-runner.js +1292 -219
  15. package/dist/agents/ecagent-runner.js +10 -31
  16. package/dist/agents/gemini-runner.js +2 -6
  17. package/dist/aun/aid/store.js +2 -0
  18. package/dist/aun/menu-client.js +272 -0
  19. package/dist/aun/msg/group.js +65 -4
  20. package/dist/aun/msg/index.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +51 -4
  22. package/dist/aun/outbox.js +7 -0
  23. package/dist/aun/rpc/client.js +8 -1
  24. package/dist/channels/aun.js +240 -36
  25. package/dist/channels/wechat.js +81 -10
  26. package/dist/cli/aun-commands.js +48 -4
  27. package/dist/cli/command-log.js +21 -2
  28. package/dist/cli/daemon-commands.js +169 -51
  29. package/dist/cli/index.js +20 -0
  30. package/dist/cli/init.js +2 -10
  31. package/dist/cli/menu-client.js +95 -0
  32. package/dist/cli/model.js +23 -8
  33. package/dist/cli/task-context.js +16 -2
  34. package/dist/cli/trigger-command.js +22 -5
  35. package/dist/config/config-field-policy.js +3 -0
  36. package/dist/config/config-manager.js +32 -5
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/schema-migration.js +14 -2
  39. package/dist/core/auth/agent-delegation.js +15 -0
  40. package/dist/core/auth/auth-gateway.js +8 -0
  41. package/dist/core/auth/authorization-audit.js +70 -6
  42. package/dist/core/auth/operation-catalog.js +4 -0
  43. package/dist/core/auth/remote-access-store.js +135 -0
  44. package/dist/core/bootstrap-messages.js +8 -0
  45. package/dist/core/bootstrap-service.js +93 -25
  46. package/dist/core/command/command-handler.js +62 -5
  47. package/dist/core/command/daemon-diagnostics.js +448 -0
  48. package/dist/core/command/menu-catalog.js +3 -0
  49. package/dist/core/command/menu-handler.js +150 -33
  50. package/dist/core/command/menu-protocol.js +120 -1
  51. package/dist/core/command/slash-gate.js +3 -3
  52. package/dist/core/command/slash-handler.js +83 -40
  53. package/dist/core/data-migration.js +11 -1
  54. package/dist/core/event-catalog.js +32 -0
  55. package/dist/core/handoff/runtime.js +23 -3
  56. package/dist/core/message/im-renderer.js +7 -3
  57. package/dist/core/message/message-bridge.js +60 -2
  58. package/dist/core/message/message-log.js +33 -0
  59. package/dist/core/message/message-queue.js +41 -15
  60. package/dist/core/message/response-engine.js +246 -114
  61. package/dist/core/model/catalog-service.js +308 -0
  62. package/dist/core/model/model-catalog.js +124 -334
  63. package/dist/core/model/model-diagnostics.js +4 -5
  64. package/dist/core/model/model-fallback.js +9 -16
  65. package/dist/core/model/model-permission.js +1 -1
  66. package/dist/core/permission/approval-gateway.js +16 -1
  67. package/dist/core/permission/ec-command-parser.js +272 -70
  68. package/dist/core/permission/protected-paths.js +74 -14
  69. package/dist/core/permission/sandbox-runtime.js +4 -2
  70. package/dist/core/permission/tool-error-code.js +12 -0
  71. package/dist/core/permission/tool-policy.js +290 -30
  72. package/dist/core/runtime-lock.js +13 -8
  73. package/dist/core/session/session-manager.js +30 -0
  74. package/dist/core/session/session-renew.js +18 -1
  75. package/dist/core/session/session-turn-coordinator.js +5 -1
  76. package/dist/index.js +104 -34
  77. package/dist/ipc.js +218 -96
  78. package/dist/paths.js +18 -0
  79. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  80. package/dist/stats/price-resolver.js +4 -0
  81. package/dist/trigger/feedback.js +14 -2
  82. package/dist/trigger/manager.js +3 -1
  83. package/dist/trigger/parser.js +86 -13
  84. package/dist/trigger/patch.js +104 -1
  85. package/dist/trigger/scheduler.js +20 -3
  86. package/dist/utils/ecweb-supervisor.js +11 -2
  87. package/dist/utils/ecweb-utils.js +8 -26
  88. package/dist/utils/error-utils.js +7 -0
  89. package/dist/utils/logger.js +9 -4
  90. package/dist/utils/npm-ops.js +47 -15
  91. package/dist/utils/tool-summary.js +59 -0
  92. package/dist/utils/windows-shell-trust.js +201 -0
  93. package/kits/docs/evolcore/INDEX.md +3 -2
  94. package/kits/docs/evolcore/agent-create.md +159 -0
  95. package/kits/docs/evolcore/agent.md +6 -0
  96. package/kits/docs/evolcore/group-collaboration.md +256 -0
  97. package/kits/docs/evolcore/group-rules.md +1 -19
  98. package/kits/docs/evolcore/group.md +3 -1
  99. package/kits/docs/evolcore/menu-client.md +34 -0
  100. package/kits/docs/evolcore/trigger.md +6 -3
  101. package/kits/docs/prompt-loading-architecture.md +6 -0
  102. package/kits/eck_message_manifest.json +6 -6
  103. package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
  104. package/kits/migrations/migrate-role-config-v4.mjs +11 -5
  105. package/kits/migrations/migrate-role-config-v5.mjs +8 -2
  106. package/kits/schemas/_meta.json +3 -2
  107. package/kits/schemas/agent-config.schema.12.json +427 -0
  108. package/kits/templates/message-fragments/item.md +1 -1
  109. package/kits/templates/roles/visitor.json +1 -0
  110. package/kits/templates/system-fragments/bootstrap.md +10 -2
  111. package/kits/templates/system-fragments/commands.md +2 -2
  112. package/package.json +11 -3
@@ -0,0 +1,308 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { appendRequestPath, appendV1RequestPath } from '../../agents/request-identity.js';
5
+ import { atomicReadJson, atomicWriteJson } from '../../utils/atomic-write.js';
6
+ export const CATALOG_TTL_MS = 5 * 60_000;
7
+ export const CATALOG_STALE_MS = 24 * 60 * 60_000;
8
+ export const TRANSPORT_EFFORTS = {
9
+ claude: ['low', 'medium', 'high', 'xhigh', 'max'],
10
+ codex: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'],
11
+ ecagent: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'],
12
+ gemini: [],
13
+ };
14
+ function stable(value) {
15
+ if (Array.isArray(value))
16
+ return `[${value.map(stable).join(',')}]`;
17
+ if (value && typeof value === 'object')
18
+ return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${stable(v)}`).join(',')}}`;
19
+ return JSON.stringify(value) ?? 'null';
20
+ }
21
+ export const catalogHash = (value) => createHash('sha256').update(stable(value)).digest('hex');
22
+ export function catalogContextKey(context) {
23
+ const headers = Object.fromEntries(Object.entries(context.headers ?? {}).map(([k, v]) => [k.toLowerCase(), v]));
24
+ return catalogHash({ scope: context.scope, baseagent: context.baseagent, provider: context.provider,
25
+ baseUrl: context.baseUrl?.replace(/\/+$/, ''), apiKey: context.apiKey,
26
+ headers, queryParams: context.queryParams ?? {}, configUnavailable: context.configUnavailable });
27
+ }
28
+ const strings = (value) => Array.isArray(value)
29
+ ? [...new Set(value.filter((v) => typeof v === 'string' && !!v.trim()).map(v => v.trim()))] : undefined;
30
+ function rows(json) {
31
+ if (Array.isArray(json))
32
+ return json;
33
+ for (const candidate of [json?.data, json?.models, json?.data?.models])
34
+ if (Array.isArray(candidate))
35
+ return candidate;
36
+ throw new Error('Unrecognized model list response');
37
+ }
38
+ /** Normalize documented list containers; an empty array is a successful catalog. */
39
+ export function parseModelList(json, _url) {
40
+ const out = new Map();
41
+ const items = rows(json);
42
+ for (const item of items) {
43
+ const rawId = typeof item === 'string' ? item : item?.id ?? item?.slug ?? item?.name ?? item?.model;
44
+ if (typeof rawId !== 'string' || !rawId.trim())
45
+ continue;
46
+ const id = rawId.trim();
47
+ if (out.has(id))
48
+ continue;
49
+ const rawLevels = item?.reasoningLevels ?? item?.supportedReasoningEfforts ?? item?.supported_reasoning_levels;
50
+ const levels = Array.isArray(rawLevels) ? strings(rawLevels.map(v => typeof v === 'string' ? v : v?.reasoningEffort ?? v?.effort)) : undefined;
51
+ const owner = item?.owned_by ?? item?.owner ?? item?.provider;
52
+ out.set(id, {
53
+ id, verification: 'listed',
54
+ ...(typeof owner === 'string' ? { owned_by: owner } : {}),
55
+ ...(typeof item?.created === 'number' ? { created: item.created } : {}),
56
+ ...(typeof (item?.displayName ?? item?.display_name) === 'string' ? { displayName: item.displayName ?? item.display_name } : {}),
57
+ ...(levels ? { reasoningLevels: levels } : {}),
58
+ ...(typeof (item?.defaultReasoningEffort ?? item?.default_reasoning_level) === 'string' ? { defaultReasoningLevel: item.defaultReasoningEffort ?? item.default_reasoning_level } : {}),
59
+ ...(strings(item?.protocols ?? item?.supported_protocols) ? { protocols: strings(item.protocols ?? item.supported_protocols) } : {}),
60
+ ...(typeof item?.capabilities?.tool_calling === 'boolean' ? { supportsTools: item.capabilities.tool_calling } : {}),
61
+ ...(typeof (item?.context_window ?? item?.contextWindow) === 'number' ? { contextWindow: item.context_window ?? item.contextWindow } : {}),
62
+ ...(typeof item?.max_output_tokens === 'number' ? { maxOutputTokens: item.max_output_tokens } : {}),
63
+ });
64
+ }
65
+ if (items.length && !out.size)
66
+ throw new Error('Model list contains no valid IDs');
67
+ return [...out.values()];
68
+ }
69
+ const BUILTIN_IDS = {
70
+ codex: ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.2'],
71
+ claude: ['claude-opus-4-7', 'claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5-20251001', 'deepseek-v4-pro', 'deepseek-v4-flash', 'kimi-k2.6', 'kimi-k2.5', 'glm-5.1', 'glm-5', 'glm-4.7', 'MiniMax-M2.7'],
72
+ ecagent: ['claude-sonnet-4-6'],
73
+ gemini: ['gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite'],
74
+ };
75
+ export function builtinCatalog(baseagent) {
76
+ return (BUILTIN_IDS[baseagent] ?? []).map(id => ({ id, verification: 'unverified',
77
+ ...(baseagent === 'codex' ? { reasoningLevels: ['low', 'medium', 'high', 'xhigh'] } : {}),
78
+ ...(/^claude-(opus|sonnet|haiku)-/.test(id) && baseagent === 'claude' ? { reasoningLevels: [...TRANSPORT_EFFORTS.claude] } : {}),
79
+ }));
80
+ }
81
+ export function deriveAliases(models, baseagent) {
82
+ if (baseagent !== 'claude')
83
+ return {};
84
+ const best = {};
85
+ for (const { id } of models) {
86
+ const match = id.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:-(\d+))?/);
87
+ if (!match)
88
+ continue;
89
+ const [, family, major, minor = '0'] = match;
90
+ const current = best[family];
91
+ if (!current || +major > current.major || (+major === current.major && +minor > current.minor))
92
+ best[family] = { id, major: +major, minor: +minor };
93
+ }
94
+ return Object.fromEntries(Object.entries(best).map(([family, info]) => [family, info.id]));
95
+ }
96
+ /** Missing capability metadata is unknown. Never classify by vendor or model name. */
97
+ function compatible(model, baseagent) {
98
+ if (model.supportsTools === false)
99
+ return false;
100
+ const protocols = { claude: ['anthropic', 'anthropic-messages', 'messages'], codex: ['openai-responses', 'responses'], ecagent: ['openai', 'openai-completions', 'openai-chat-completions', 'chat-completions'] };
101
+ return !model.protocols || !protocols[baseagent] || model.protocols.some(p => protocols[baseagent].includes(p));
102
+ }
103
+ function adapt(models, baseagent) {
104
+ const available = models.filter(m => compatible(m, baseagent)).map(m => ({ ...m,
105
+ ...(m.reasoningLevels ? { reasoningLevels: m.reasoningLevels.filter(e => TRANSPORT_EFFORTS[baseagent]?.includes(e)) } : {}),
106
+ }));
107
+ const aliases = deriveAliases(available, baseagent);
108
+ return [...Object.entries(aliases).filter(([id]) => !available.some(m => m.id === id)).map(([id, aliasFor]) => ({ ...available.find(m => m.id === aliasFor), id, aliasFor, owned_by: 'alias' })), ...available];
109
+ }
110
+ export function resolveCatalogModel(snapshot, model) { return snapshot.models.find(m => m.id === model)?.aliasFor ?? model; }
111
+ export function catalogEfforts(snapshot, model, baseagent) {
112
+ const entry = snapshot.models.find(m => m.id === resolveCatalogModel(snapshot, model));
113
+ return entry?.reasoningLevels?.filter(e => TRANSPORT_EFFORTS[baseagent]?.includes(e)) ?? [];
114
+ }
115
+ export function assertCatalogSelectable(snapshot) {
116
+ if (snapshot.diagnostics.some(d => d.code === 'AUTH_FAILED'))
117
+ throw new Error('模型网关鉴权失败(401/403),请检查当前连接凭据');
118
+ }
119
+ class GatewayError extends Error {
120
+ status;
121
+ constructor(status) {
122
+ super(status ? `Model gateway HTTP ${status}` : 'Model gateway unavailable or invalid response');
123
+ this.status = status;
124
+ }
125
+ }
126
+ async function gatewayModels(context, fetcher) {
127
+ const urls = [...new Set([appendV1RequestPath(context.baseUrl, 'models', context.queryParams), appendRequestPath(context.baseUrl, 'models', context.queryParams)])];
128
+ let last;
129
+ for (const url of urls) {
130
+ try {
131
+ const models = [];
132
+ const seen = new Set();
133
+ let next = url;
134
+ // Bound the whole route, including response bodies and pagination.
135
+ const signal = AbortSignal.timeout(4000);
136
+ for (let page = 0; page < 100; page++) {
137
+ if (seen.has(next))
138
+ throw new GatewayError();
139
+ seen.add(next);
140
+ const headers = new Headers(context.headers);
141
+ if (context.apiKey && !headers.has('authorization')) {
142
+ headers.set('authorization', `Bearer ${context.apiKey}`);
143
+ }
144
+ if (context.apiKey && context.baseagent === 'claude' && !headers.has('x-api-key')) {
145
+ headers.set('x-api-key', context.apiKey);
146
+ }
147
+ if (context.baseagent === 'claude' && !headers.has('anthropic-version'))
148
+ headers.set('anthropic-version', '2023-06-01');
149
+ const response = await fetcher(next, { signal, headers, redirect: 'error' });
150
+ if (!response.ok)
151
+ throw new GatewayError(response.status);
152
+ const json = await response.json();
153
+ models.push(...parseModelList(json));
154
+ const cursor = json?.nextCursor ?? json?.next_cursor ?? (json?.has_more ? json?.last_id : undefined);
155
+ if (!cursor) {
156
+ if (json?.has_more)
157
+ throw new GatewayError();
158
+ return [...new Map(models.map(m => [m.id, m])).values()];
159
+ }
160
+ const parsed = new URL(url);
161
+ parsed.searchParams.set(json?.has_more && !json?.nextCursor && !json?.next_cursor ? 'after_id' : 'cursor', String(cursor));
162
+ next = parsed.toString();
163
+ }
164
+ throw new GatewayError();
165
+ }
166
+ catch (error) {
167
+ if (error instanceof GatewayError && [401, 403].includes(error.status))
168
+ throw error;
169
+ last = error;
170
+ }
171
+ }
172
+ throw last ?? new GatewayError();
173
+ }
174
+ /** One instance per daemon. Offline clients use the same persistence format. */
175
+ export class ModelCatalogService {
176
+ options;
177
+ cache = new Map();
178
+ successes = new Map();
179
+ inflight = new Map();
180
+ constructor(options = {}) {
181
+ this.options = options;
182
+ }
183
+ now() { return (this.options.now ?? Date.now)(); }
184
+ key(context) { return `${this.options.storageDir?.() ?? ''}:${catalogContextKey(context)}`; }
185
+ file(context) { const dir = this.options.storageDir?.(); return dir ? path.join(dir, `${catalogContextKey(context)}.json`) : undefined; }
186
+ previous(context) {
187
+ const key = this.key(context);
188
+ let snapshot = this.successes.get(key);
189
+ if (!snapshot) {
190
+ try {
191
+ const file = this.file(context);
192
+ const saved = file ? atomicReadJson(file) : undefined;
193
+ if (saved?.version === 1 && saved.snapshot && Array.isArray(saved.snapshot.models) && ['gateway', 'native'].includes(saved.snapshot.source))
194
+ snapshot = saved.snapshot;
195
+ }
196
+ catch { /* A missing/corrupt cache is a cold load. */ }
197
+ if (snapshot)
198
+ this.successes.set(key, snapshot);
199
+ }
200
+ return snapshot && this.now() - Date.parse(snapshot.fetchedAt) <= CATALOG_STALE_MS ? snapshot : undefined;
201
+ }
202
+ peek(context) {
203
+ return structuredClone(this.cache.get(this.key(context))?.snapshot ?? this.previous(context) ?? this.make(context, 'builtin', 'fallback', this.legacyBuiltins(context), [{ code: 'BUILTIN', message: '内置候选,未经当前连接验证' }]));
204
+ }
205
+ async get(context, refresh = false) {
206
+ const key = this.key(context);
207
+ const cached = this.cache.get(key);
208
+ if (!refresh && cached && cached.retryAt > this.now())
209
+ return structuredClone(cached.snapshot);
210
+ const previous = this.previous(context);
211
+ if (!refresh && previous && this.now() - Date.parse(previous.fetchedAt) < CATALOG_TTL_MS && !cached) {
212
+ this.cache.set(key, { snapshot: previous, retryAt: Date.parse(previous.fetchedAt) + CATALOG_TTL_MS });
213
+ return structuredClone(previous);
214
+ }
215
+ let pending = this.inflight.get(key);
216
+ if (!pending) {
217
+ pending = this.load(context, previous).then(snapshot => {
218
+ this.cache.set(key, { snapshot, retryAt: this.now() + (snapshot.freshness === 'fresh' ? CATALOG_TTL_MS : 30_000) });
219
+ return snapshot;
220
+ }).finally(() => this.inflight.delete(key));
221
+ this.inflight.set(key, pending);
222
+ }
223
+ if (!refresh && previous)
224
+ return structuredClone({ ...previous, freshness: 'stale', diagnostics: [...(cached?.snapshot.diagnostics ?? []), { code: 'STALE_CACHE', message: '显示同一连接的最近成功目录,正在刷新' }] });
225
+ return structuredClone(await pending);
226
+ }
227
+ clear() { this.cache.clear(); this.successes.clear(); }
228
+ make(context, source, freshness, models, diagnostics) {
229
+ const entries = adapt(models, context.baseagent);
230
+ const fetchedAt = new Date(this.now()).toISOString();
231
+ // A revision identifies the connection's model/capability set. Fetch time
232
+ // belongs to freshness metadata and must not make an unchanged catalog
233
+ // look like a different resource on every refresh.
234
+ return { revision: catalogHash({ connection: catalogContextKey(context), source, entries }), source, freshness, fetchedAt, models: entries, diagnostics };
235
+ }
236
+ save(context, snapshot) {
237
+ this.successes.set(this.key(context), snapshot);
238
+ try {
239
+ const file = this.file(context);
240
+ if (file) {
241
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
242
+ atomicWriteJson(file, { version: 1, snapshot });
243
+ }
244
+ }
245
+ catch { /* Persistence failure does not discard a live result. */ }
246
+ }
247
+ legacyBuiltins(context) {
248
+ const models = builtinCatalog(context.baseagent);
249
+ if (context.baseagent !== 'claude' || !context.baseUrl || !this.options.legacyAliasFile)
250
+ return models;
251
+ try {
252
+ const legacy = atomicReadJson(this.options.legacyAliasFile());
253
+ const key = createHash('sha256').update(context.baseUrl.trim().replace(/\/+$/, '')).digest('hex');
254
+ const aliases = legacy?.gateways?.[key]?.aliases ?? {};
255
+ // The legacy cache did not identify credentials. Import only unverified
256
+ // suggestions; never let it attest gateway membership for another key.
257
+ for (const [family, id] of Object.entries(aliases))
258
+ if (typeof id === 'string' && /^(opus|sonnet|haiku)$/.test(family) && new RegExp(`^claude-${family}-[A-Za-z0-9._-]+$`).test(id) && !models.some(m => m.id === id))
259
+ models.unshift({ id, verification: 'unverified', reasoningLevels: [...TRANSPORT_EFFORTS.claude] });
260
+ }
261
+ catch { /* No legacy data. */ }
262
+ return models;
263
+ }
264
+ async load(context, previous) {
265
+ const diagnostics = [];
266
+ if (context.configUnavailable)
267
+ diagnostics.push({ code: 'CONFIG_UNAVAILABLE', message: '当前连接配置或凭据不完整' });
268
+ if (context.baseUrl && !context.configUnavailable) {
269
+ try {
270
+ const models = await gatewayModels(context, this.options.fetch ?? fetch);
271
+ // Enrich only IDs the gateway listed. Native models never add/remove membership.
272
+ const native = context.baseagent === 'codex' && models.length ? await this.options.native?.(context).catch(() => null) : null;
273
+ const metadata = new Map((native ?? builtinCatalog(context.baseagent)).map(m => [m.id, m]));
274
+ const enriched = models.map(m => ({ ...m, ...(m.reasoningLevels === undefined && metadata.get(m.id)?.reasoningLevels ? { reasoningLevels: metadata.get(m.id).reasoningLevels } : {}) }));
275
+ const result = this.make(context, 'gateway', 'fresh', enriched, diagnostics);
276
+ this.save(context, result);
277
+ return result;
278
+ }
279
+ catch (error) {
280
+ const status = error instanceof GatewayError ? error.status : undefined;
281
+ const auth = status === 401 || status === 403;
282
+ diagnostics.push({ code: auth ? 'AUTH_FAILED' : 'GATEWAY_UNAVAILABLE', message: auth ? '当前模型网关鉴权失败' : '当前模型网关不可用或响应格式不支持', ...(status ? { status } : {}) });
283
+ if (previous?.source === 'gateway')
284
+ return { ...previous, freshness: 'stale', diagnostics: [...diagnostics, { code: 'STALE_CACHE', message: '使用同一连接的最近成功目录' }] };
285
+ if (auth)
286
+ return this.make(context, 'gateway', 'fallback', [], diagnostics);
287
+ }
288
+ }
289
+ if (previous)
290
+ return { ...previous, freshness: 'stale', diagnostics: [...diagnostics, { code: 'STALE_CACHE', message: '使用同一连接的最近成功目录' }] };
291
+ if (!context.configUnavailable && this.options.native) {
292
+ try {
293
+ const native = await this.options.native(context);
294
+ if (native !== null) {
295
+ const result = this.make(context, 'native', context.baseUrl ? 'fallback' : 'fresh', context.baseUrl ? native.map(m => ({ ...m, verification: 'unverified' })) : native, diagnostics);
296
+ // A native catalog cannot certify membership of a failed gateway.
297
+ if (!context.baseUrl)
298
+ this.save(context, result);
299
+ return result;
300
+ }
301
+ }
302
+ catch { /* Native failure continues to builtin. */ }
303
+ if (context.baseagent === 'codex')
304
+ diagnostics.push({ code: 'NATIVE_UNAVAILABLE', message: 'Codex 原生目录暂不可用' });
305
+ }
306
+ return this.make(context, 'builtin', 'fallback', this.legacyBuiltins(context), [...diagnostics, { code: 'BUILTIN', message: '内置候选,未经当前连接验证' }]);
307
+ }
308
+ }