evolcore 0.0.9 → 0.0.11

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 (64) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +3 -3
  3. package/dist/agents/baseagent.js +4 -0
  4. package/dist/agents/claude-runner.js +123 -42
  5. package/dist/agents/codex-app-server-client.js +33 -9
  6. package/dist/agents/codex-runner.js +58 -8
  7. package/dist/agents/ecagent-runner.js +17 -2
  8. package/dist/agents/request-identity.js +55 -0
  9. package/dist/aun/outbox.js +28 -31
  10. package/dist/channels/aun.js +131 -128
  11. package/dist/cli/agent-command.js +16 -9
  12. package/dist/cli/agent.js +82 -19
  13. package/dist/cli/daemon-commands.js +21 -2
  14. package/dist/cli/index.js +76 -61
  15. package/dist/cli/init-cancel.js +208 -0
  16. package/dist/cli/init-channel.js +343 -195
  17. package/dist/cli/init.js +21 -9
  18. package/dist/config/builtin-roles.js +1 -0
  19. package/dist/config/contact-book-store.js +1 -1
  20. package/dist/config/gateway-config.js +26 -10
  21. package/dist/core/agent-reload-coordinator.js +53 -0
  22. package/dist/core/auth/operation-authorizer.js +32 -147
  23. package/dist/core/auth/operation-catalog.js +80 -0
  24. package/dist/core/bootstrap-messages.js +50 -0
  25. package/dist/core/bootstrap-service.js +85 -10
  26. package/dist/core/channel-loader.js +23 -6
  27. package/dist/core/command/agent-control.js +14 -11
  28. package/dist/core/command/menu-handler.js +67 -76
  29. package/dist/core/command/slash-handler.js +4 -4
  30. package/dist/core/data-migration.js +79 -27
  31. package/dist/core/evolagent-registry.js +125 -35
  32. package/dist/core/evolagent.js +8 -3
  33. package/dist/core/inference/text-inference.js +38 -4
  34. package/dist/core/message/message-bridge.js +1 -1
  35. package/dist/core/message/message-log.js +22 -0
  36. package/dist/core/message/message-queue.js +19 -4
  37. package/dist/core/model/model-catalog.js +143 -24
  38. package/dist/core/model/model-diagnostics.js +28 -10
  39. package/dist/core/permission/index.js +1 -0
  40. package/dist/core/permission/readonly-shell-query.js +532 -0
  41. package/dist/core/permission/shell-environment.js +46 -0
  42. package/dist/core/permission/tool-policy.js +231 -93
  43. package/dist/core/protected-paths.js +10 -7
  44. package/dist/core/runner-reload-transaction.js +57 -0
  45. package/dist/index.js +262 -84
  46. package/dist/ipc.js +29 -11
  47. package/dist/utils/aid-bind.js +3 -8
  48. package/dist/utils/log-writer.js +6 -10
  49. package/dist/utils/logger.js +5 -5
  50. package/kits/docs/evolcore/msg.md +13 -0
  51. package/kits/rules/01-overview.md +9 -0
  52. package/kits/schemas/agent-config.schema.3.json +1 -1
  53. package/kits/schemas/agent-config.schema.4.json +1 -1
  54. package/kits/schemas/relation-config.schema.2.json +1 -1
  55. package/kits/schemas/role-config.schema.1.json +1 -1
  56. package/kits/templates/roles/admin.json +5 -0
  57. package/kits/templates/roles/member.json +17 -0
  58. package/kits/templates/roles/visitor.json +8 -0
  59. package/kits/templates/system-fragments/bootstrap.md +12 -6
  60. package/kits/templates/system-fragments/channel.md +6 -0
  61. package/kits/templates/system-fragments/session.md +2 -0
  62. package/package.json +2 -1
  63. package/skills/eclink/SKILL.md +15 -3
  64. package/skills/eclink/agents/openai.yaml +3 -3
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * model-catalog: 模型目录与单模型元数据。
3
3
  *
4
- * 目录来源(按序回退):
5
- * {baseUrl}/v1/models{baseUrl}/models → 内置 mock catalog
4
+ * 目录来源按 baseagent 隔离:Codex 预创建优先使用 CLI 动态目录;
5
+ * 其他场景探测本 baseagent 的 {baseUrl}/v1/models{baseUrl}/models
6
+ * 最后只回退到同 baseagent 的内置目录。
6
7
  *
7
8
  * baseUrl/apiKey 复用 baseagent 凭据解析链(与发消息同源),命令不接收 url/key 参数。
8
9
  * 单模型详情(info)现阶段全部为 mock,{baseUrl}/models 接口补全字段后自动接真实数据。
@@ -10,28 +11,52 @@
10
11
  * 详见 docs/model-command-design.md。
11
12
  */
12
13
  import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig } from '../../agents/baseagent.js';
14
+ import { buildModelRequestHeaders } from '../../agents/request-identity.js';
13
15
  import { activeBaseagent } from './config-scope.js';
14
16
  import { resolveEffective } from '../../config/config-manager.js';
17
+ import { loadDefaults } from '../../config-store.js';
18
+ import { execCodexCliSync } from '../../utils/codex-cli.js';
15
19
  /** 取指定 baseagent 的 baseUrl + apiKey(复用现有解析链)。 */
16
20
  function resolveCreds(self, ba) {
17
21
  const baseagent = ba || activeBaseagent(self);
18
- // baseagents 现在在 config.json 中
19
- const config = self ? resolveEffective({ self }, { cache: true }) : { baseagents: {} };
22
+ // A new Agent has no self config yet, so resolve its gateway from defaults.
23
+ const config = self
24
+ ? resolveEffective({ self }, { cache: true, expand: true })
25
+ : { baseagents: loadDefaults()?.baseagents ?? {} };
20
26
  const block = (config.baseagents || {});
21
27
  try {
22
28
  if (baseagent === 'codex') {
23
29
  const c = block.codex;
24
30
  const r = resolveOpenaiConfig({ agents: { codex: c } }, c);
25
- return { baseUrl: r.baseUrl, apiKey: r.apiKey };
31
+ return {
32
+ baseUrl: r.baseUrl,
33
+ apiKey: r.apiKey,
34
+ agentAid: self,
35
+ headers: buildModelRequestHeaders({ baseagent: 'codex', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
36
+ };
26
37
  }
27
38
  if (baseagent === 'ecagent') {
28
39
  const c = block.ecagent;
29
40
  const r = resolveEcagentConfig({ agents: { ecagent: c } }, c);
30
- return { baseUrl: r.baseUrl, apiKey: r.apiKey };
41
+ return {
42
+ baseUrl: r.baseUrl,
43
+ apiKey: r.apiKey,
44
+ agentAid: self,
45
+ headers: buildModelRequestHeaders({ baseagent: 'ecagent', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
46
+ };
31
47
  }
48
+ if (baseagent === 'gemini')
49
+ return {};
50
+ if (baseagent !== 'claude')
51
+ return {};
32
52
  const c = block.claude;
33
53
  const r = resolveAnthropicConfig({ agents: { claude: c } }, c);
34
- return { baseUrl: r.baseUrl, apiKey: r.apiKey };
54
+ return {
55
+ baseUrl: r.baseUrl,
56
+ apiKey: r.apiKey,
57
+ agentAid: self,
58
+ headers: buildModelRequestHeaders({ baseagent: 'claude', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
59
+ };
35
60
  }
36
61
  catch {
37
62
  return {};
@@ -97,15 +122,21 @@ export function parseModelList(json, url) {
97
122
  }
98
123
  return genericParse(json);
99
124
  }
100
- async function fetchModelList(url, apiKey) {
125
+ const MODEL_LIST_TIMEOUT_MS = 2000;
126
+ const MODEL_CATALOG_TTL_MS = 30_000;
127
+ const CODEX_CLI_CATALOG_TIMEOUT_MS = 2000;
128
+ async function fetchModelList(url, apiKey, requestHeaders) {
129
+ let timer;
101
130
  try {
102
131
  const controller = new AbortController();
103
- const timer = setTimeout(() => controller.abort(), 5000);
132
+ timer = setTimeout(() => controller.abort(), MODEL_LIST_TIMEOUT_MS);
104
133
  const resp = await fetch(url, {
105
134
  signal: controller.signal,
106
- headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
135
+ headers: {
136
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
137
+ ...requestHeaders,
138
+ },
107
139
  });
108
- clearTimeout(timer);
109
140
  if (!resp.ok)
110
141
  return null;
111
142
  const json = await resp.json();
@@ -115,6 +146,10 @@ async function fetchModelList(url, apiKey) {
115
146
  catch {
116
147
  return null;
117
148
  }
149
+ finally {
150
+ if (timer)
151
+ clearTimeout(timer);
152
+ }
118
153
  }
119
154
  /** 内置 mock catalog(接口未就绪时的兜底;实测可用模型,见 docs 附录 A)。 */
120
155
  const MOCK_CATALOG = [
@@ -131,6 +166,53 @@ const MOCK_CATALOG = [
131
166
  { id: 'glm-4.7', owned_by: 'zhipu' },
132
167
  { id: 'MiniMax-M2.7', owned_by: 'minimax' },
133
168
  ];
169
+ const CODEX_BUILTIN_CATALOG = [
170
+ { id: 'gpt-5.5', owned_by: 'openai' },
171
+ { id: 'gpt-5.4', owned_by: 'openai' },
172
+ { id: 'gpt-5.4-mini', owned_by: 'openai' },
173
+ { id: 'gpt-5.3-codex', owned_by: 'openai' },
174
+ { id: 'gpt-5.2', owned_by: 'openai' },
175
+ ];
176
+ const GEMINI_BUILTIN_CATALOG = [
177
+ { id: 'gemini-2.5-pro', owned_by: 'google' },
178
+ { id: 'gemini-2.5-flash', owned_by: 'google' },
179
+ { id: 'gemini-2.5-flash-lite', owned_by: 'google' },
180
+ ];
181
+ const catalogCache = new Map();
182
+ const catalogInFlight = new Map();
183
+ export function clearModelCatalogCache() {
184
+ catalogCache.clear();
185
+ catalogInFlight.clear();
186
+ }
187
+ /** A failed gateway may only fall back within the selected baseagent. */
188
+ export function builtinCatalog(baseagent) {
189
+ if (baseagent === 'codex')
190
+ return CODEX_BUILTIN_CATALOG.map(model => ({ ...model }));
191
+ if (baseagent === 'gemini')
192
+ return GEMINI_BUILTIN_CATALOG.map(model => ({ ...model }));
193
+ if (baseagent === 'ecagent')
194
+ return [{ id: 'claude-sonnet-4-6', owned_by: 'anthropic' }];
195
+ return MOCK_CATALOG.map(model => ({ ...model }));
196
+ }
197
+ function codexCliCatalog() {
198
+ try {
199
+ const output = execCodexCliSync(['debug', 'models'], {
200
+ encoding: 'utf-8',
201
+ timeout: CODEX_CLI_CATALOG_TIMEOUT_MS,
202
+ stdio: ['pipe', 'pipe', 'pipe'],
203
+ });
204
+ const payload = JSON.parse(String(output));
205
+ const models = Array.isArray(payload?.models)
206
+ ? payload.models
207
+ .filter((model) => model?.visibility === 'list' && typeof model?.slug === 'string')
208
+ .map((model) => ({ id: model.slug, owned_by: 'openai' }))
209
+ : [];
210
+ return models.length > 0 ? models : null;
211
+ }
212
+ catch {
213
+ return null;
214
+ }
215
+ }
134
216
  export function modelMatches(agent, candidate, current) {
135
217
  if (!current)
136
218
  return false;
@@ -166,24 +248,61 @@ function withAliases(models, ba) {
166
248
  .map(a => ({ id: a, owned_by: 'alias' }));
167
249
  return [...aliasEntries, ...models];
168
250
  }
169
- /** 拉取模型目录:v1/models models 远端接口 → mock,并并入别名。 */
251
+ /** 拉取模型目录,并按 self + baseagent 缓存短时间结果。 */
170
252
  export async function getCatalog(self, ba) {
171
253
  const baseagent = ba || activeBaseagent(self);
172
- const { baseUrl, apiKey } = resolveCreds(self, baseagent);
254
+ const cacheKey = `${self ?? '<defaults>'}:${baseagent}`;
255
+ const cached = catalogCache.get(cacheKey);
256
+ if (cached && cached.expiresAt > Date.now())
257
+ return cached.value;
258
+ const pending = catalogInFlight.get(cacheKey);
259
+ if (pending)
260
+ return pending;
261
+ const promise = loadCatalog(self, baseagent)
262
+ .then(value => {
263
+ catalogCache.set(cacheKey, { value, expiresAt: Date.now() + MODEL_CATALOG_TTL_MS });
264
+ return value;
265
+ })
266
+ .finally(() => catalogInFlight.delete(cacheKey));
267
+ catalogInFlight.set(cacheKey, promise);
268
+ return promise;
269
+ }
270
+ async function loadCatalog(self, baseagent) {
271
+ // Pre-create has no runner yet. Mirror CodexRunner's dynamic CLI fallback
272
+ // without changing existing self-scoped catalog resolution.
273
+ if (!self && baseagent === 'codex') {
274
+ const cliModels = codexCliCatalog();
275
+ if (cliModels)
276
+ return { models: cliModels, source: 'codex-cli' };
277
+ }
278
+ const { baseUrl, apiKey, headers } = resolveCreds(self, baseagent);
173
279
  if (baseUrl) {
174
280
  const base = baseUrl.replace(/\/+$/, '');
175
- const v1 = await fetchModelList(base.endsWith('/v1') ? `${base}/models` : `${base}/v1/models`, apiKey);
176
- if (v1)
177
- return { models: withAliases(v1, baseagent), source: 'v1/models' };
178
- const plain = await fetchModelList(`${base}/models`, apiKey);
179
- if (plain)
180
- return { models: withAliases(plain, baseagent), source: 'models' };
281
+ const urls = [...new Set([
282
+ base.endsWith('/v1') ? `${base}/models` : `${base}/v1/models`,
283
+ `${base}/models`,
284
+ ])];
285
+ // Probe compatible routes concurrently so a dead route does not add
286
+ // another full timeout before the other route is attempted.
287
+ const results = await Promise.all(urls.map(url => fetchModelList(url, apiKey, headers)));
288
+ const hitIndex = results.findIndex((models) => !!models);
289
+ if (hitIndex >= 0) {
290
+ return {
291
+ models: withAliases(results[hitIndex], baseagent),
292
+ source: hitIndex === 0 ? 'v1/models' : 'models',
293
+ };
294
+ }
295
+ }
296
+ // This remote directory is Claude-only. Never use it for Codex or Gemini.
297
+ if (baseagent === 'claude') {
298
+ const remote = await fetchModelList(REMOTE_CATALOG_URL, undefined, buildModelRequestHeaders({
299
+ baseagent: 'claude',
300
+ baseUrl: REMOTE_CATALOG_URL,
301
+ }));
302
+ if (remote)
303
+ return { models: withAliases(remote, baseagent), source: 'remote' };
181
304
  }
182
- // 本地接口未实现 降级到远端目录接口(无需鉴权)
183
- const remote = await fetchModelList(REMOTE_CATALOG_URL, apiKey);
184
- if (remote)
185
- return { models: withAliases(remote, baseagent), source: 'remote' };
186
- return { models: withAliases(MOCK_CATALOG, baseagent), source: 'mock' };
305
+ return { models: withAliases(builtinCatalog(baseagent), baseagent), source: 'builtin' };
187
306
  }
188
307
  /** claude 系判定:完整 ID 或别名(opus/sonnet/haiku)。 */
189
308
  function isClaudeFamily(id) {
@@ -1,4 +1,5 @@
1
- import { resolveAnthropicConfig, resolveOpenaiConfig } from '../../agents/baseagent.js';
1
+ import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig } from '../../agents/baseagent.js';
2
+ import { buildModelRequestHeaders } from '../../agents/request-identity.js';
2
3
  import { resolveEffective, resolveEffectiveFieldWithSource } from '../../config/config-manager.js';
3
4
  import { constrainResolvedModelForRole } from './model-permission.js';
4
5
  import { getCatalog } from './model-catalog.js';
@@ -17,17 +18,34 @@ async function fetchWithTimeout(url, opts, timeoutMs) {
17
18
  }
18
19
  function resolveGatewayCreds(self, baseagent) {
19
20
  try {
20
- const config = self ? resolveEffective({ self }, { cache: true }) : { baseagents: {} };
21
+ const config = self ? resolveEffective({ self }, { cache: true, expand: true }) : { baseagents: {} };
21
22
  const block = config.baseagents || {};
22
23
  if (baseagent === 'codex') {
23
24
  const codexCfg = block.codex || {};
24
25
  const resolved = resolveOpenaiConfig({ agents: { codex: codexCfg } }, codexCfg);
25
- return { baseUrl: resolved.baseUrl, apiKey: resolved.apiKey };
26
+ return {
27
+ baseUrl: resolved.baseUrl,
28
+ apiKey: resolved.apiKey,
29
+ headers: buildModelRequestHeaders({ baseagent: 'codex', baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }),
30
+ };
26
31
  }
27
32
  if (baseagent === 'claude') {
28
33
  const claudeCfg = block.claude || {};
29
34
  const resolved = resolveAnthropicConfig({ agents: { claude: claudeCfg } }, claudeCfg);
30
- return { baseUrl: resolved.baseUrl, apiKey: resolved.apiKey };
35
+ return {
36
+ baseUrl: resolved.baseUrl,
37
+ apiKey: resolved.apiKey,
38
+ headers: buildModelRequestHeaders({ baseagent: 'claude', baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }),
39
+ };
40
+ }
41
+ if (baseagent === 'ecagent') {
42
+ const ecagentCfg = block.ecagent || {};
43
+ const resolved = resolveEcagentConfig({ agents: { ecagent: ecagentCfg } }, ecagentCfg);
44
+ return {
45
+ baseUrl: resolved.baseUrl,
46
+ apiKey: resolved.apiKey,
47
+ headers: buildModelRequestHeaders({ baseagent: 'ecagent', baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }),
48
+ };
31
49
  }
32
50
  }
33
51
  catch {
@@ -46,7 +64,7 @@ async function responseError(resp) {
46
64
  }
47
65
  return String(message).slice(0, 80);
48
66
  }
49
- export async function probeModel(baseUrl, apiKey, modelId, timeoutMs) {
67
+ export async function probeModel(baseUrl, apiKey, modelId, timeoutMs, requestHeaders) {
50
68
  const base = baseUrl.replace(/\/+$/, '');
51
69
  const authHeaders = apiKey
52
70
  ? { 'x-api-key': apiKey, Authorization: `Bearer ${apiKey}` }
@@ -56,7 +74,7 @@ export async function probeModel(baseUrl, apiKey, modelId, timeoutMs) {
56
74
  const anthropicStartedAt = Date.now();
57
75
  const anthropic = await fetchWithTimeout(`${base}/v1/messages`, {
58
76
  method: 'POST',
59
- headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', ...authHeaders },
77
+ headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', ...authHeaders, ...requestHeaders },
60
78
  body: JSON.stringify({ model: modelId, max_tokens: 5, messages: [{ role: 'user', content: 'ok' }] }),
61
79
  }, Math.min(5000, remainingMs()));
62
80
  const anthropicMs = Date.now() - anthropicStartedAt;
@@ -74,7 +92,7 @@ export async function probeModel(baseUrl, apiKey, modelId, timeoutMs) {
74
92
  const openaiStartedAt = Date.now();
75
93
  const openai = await fetchWithTimeout(`${base}/v1/chat/completions`, {
76
94
  method: 'POST',
77
- headers: { 'Content-Type': 'application/json', ...authHeaders },
95
+ headers: { 'Content-Type': 'application/json', ...authHeaders, ...requestHeaders },
78
96
  body: JSON.stringify({ model: modelId, max_tokens: 5, messages: [{ role: 'user', content: 'ok' }] }),
79
97
  }, Math.min(5000, remainingMs()));
80
98
  const openaiMs = Date.now() - openaiStartedAt;
@@ -99,7 +117,7 @@ export async function runModelCheck(input) {
99
117
  steps.push(step);
100
118
  input.onStep?.(step);
101
119
  };
102
- const { baseUrl, apiKey } = resolveGatewayCreds(input.self, input.baseagent);
120
+ const { baseUrl, apiKey, headers } = resolveGatewayCreds(input.self, input.baseagent);
103
121
  if (!baseUrl) {
104
122
  log({ label: '网关地址', ok: false, detail: '未配置受支持的自定义网关,跳过 API 探测' });
105
123
  }
@@ -107,7 +125,7 @@ export async function runModelCheck(input) {
107
125
  const startedAt = Date.now();
108
126
  const ping = await fetchWithTimeout(`${baseUrl.replace(/\/+$/, '')}/v1/models`, {
109
127
  method: 'GET',
110
- headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
128
+ headers: { ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), ...headers },
111
129
  }, 4000);
112
130
  const elapsed = Date.now() - startedAt;
113
131
  log(ping
@@ -151,7 +169,7 @@ export async function runModelCheck(input) {
151
169
  ];
152
170
  for (let index = 0; index < targets.length; index += 2) {
153
171
  const batch = await Promise.all(targets.slice(index, index + 2).map(async (target) => {
154
- const result = await probeModel(baseUrl, apiKey, target.probe, 10_000);
172
+ const result = await probeModel(baseUrl, apiKey, target.probe, 10_000, headers);
155
173
  return { id: target.display, ...result };
156
174
  }));
157
175
  for (const result of batch) {
@@ -1,5 +1,6 @@
1
1
  export * from './tool-policy.js';
2
2
  export * from './ec-command-parser.js';
3
+ export * from './readonly-shell-query.js';
3
4
  export * from './approval-gateway.js';
4
5
  export * from './execution-sandbox.js';
5
6
  // Tool summaries live in utils; keep the historical permission import path compatible.