evolcore 0.0.38 → 0.0.39

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.
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import { TextDecoder } from 'util';
4
4
  import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
5
5
  import { agentmdGet, getAidStore, isValidAid, loadClient, SLOT } from '../aun/aid/index.js';
6
- import { ipcQuery } from '../ipc.js';
6
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
7
7
  import { resolvePaths } from '../paths.js';
8
8
  import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
9
9
  import { DEFAULT_FS_CAT_BYTES, MAX_FS_CAT_BYTES, FsReadBudgetError, downloadGroupFsBytes, readPersonalFsBytesWithinBudget, } from '../aun/group/fs-download.js';
@@ -222,14 +222,15 @@ async function runManagedFs(args, formatJson) {
222
222
  fail('NO_SESSION', 'EVOLCORE_SESSION_ID is not set');
223
223
  if (!delegationToken)
224
224
  fail('DELEGATION_REQUIRED', `${AGENT_DELEGATION_TOKEN_ENV} is not set for the active task`);
225
- const response = await ipcQuery(resolvePaths().socket, {
225
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
226
226
  type: 'fs.op',
227
227
  argv: ['fs', ...args],
228
228
  sessionId,
229
229
  delegationToken,
230
230
  }, 30_000);
231
- if (!response)
232
- return fail('DAEMON_UNAVAILABLE', 'Unable to connect to the evolcore daemon');
231
+ if (!response) {
232
+ return fail('DAEMON_UNAVAILABLE', daemonUnavailableMessage(transportError, 'Unable to connect to the evolcore daemon'));
233
+ }
233
234
  if (!response.ok || !response.result)
234
235
  fail(String(response.code || 'FS_OPERATION_FAILED'), response.error || 'Filesystem operation failed');
235
236
  const result = response.result;
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import { resolvePaths } from '../paths.js';
3
- import { ipcQuery } from '../ipc.js';
3
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
4
4
  import { HANDOFF_QUERY_DEFAULT_LIMIT, HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from '../core/handoff/types.js';
5
5
  import { HANDOFF_ID_RE } from '../core/handoff/store.js';
6
6
  import { isHelpFlag, wantsHelp } from './help.js';
@@ -191,7 +191,7 @@ Commands:
191
191
  process.exitCode = 1;
192
192
  return;
193
193
  }
194
- const result = await ipcQuery(resolvePaths().socket, {
194
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
195
195
  type: 'handoff-return',
196
196
  argv: ['handoff', ...args],
197
197
  sessionId: process.env.EVOLCORE_SESSION_ID,
@@ -200,7 +200,7 @@ Commands:
200
200
  delegationToken: readDelegationCredential(),
201
201
  }, 5000);
202
202
  if (!result) {
203
- console.error('✗ daemon 暂时不可用');
203
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
204
204
  process.exitCode = 1;
205
205
  return;
206
206
  }
@@ -222,7 +222,7 @@ Commands:
222
222
  process.exitCode = 1;
223
223
  return;
224
224
  }
225
- const result = await ipcQuery(resolvePaths().socket, {
225
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
226
226
  type: 'handoff-recover-return',
227
227
  argv: ['handoff', ...args],
228
228
  sessionId: process.env.EVOLCORE_SESSION_ID,
@@ -231,7 +231,7 @@ Commands:
231
231
  delegationToken: readDelegationCredential(),
232
232
  }, 5000);
233
233
  if (!result) {
234
- console.error('✗ daemon 暂时不可用');
234
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
235
235
  process.exitCode = 1;
236
236
  return;
237
237
  }
@@ -245,13 +245,13 @@ Commands:
245
245
  process.exitCode = 1;
246
246
  return;
247
247
  }
248
- const result = await ipcQuery(resolvePaths().socket, {
248
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
249
249
  type: 'handoff-status', sessionId: process.env.EVOLCORE_SESSION_ID, handoffId,
250
250
  argv: ['handoff', ...args],
251
251
  delegationToken: readDelegationCredential(),
252
252
  }, 5000);
253
253
  if (!result) {
254
- console.error('✗ daemon 暂时不可用');
254
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
255
255
  process.exitCode = 1;
256
256
  return;
257
257
  }
@@ -273,7 +273,7 @@ Commands:
273
273
  process.exitCode = 1;
274
274
  return;
275
275
  }
276
- const result = await ipcQuery(resolvePaths().socket, {
276
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
277
277
  type: 'handoff-list',
278
278
  argv: ['handoff', ...args],
279
279
  sessionId: process.env.EVOLCORE_SESSION_ID,
@@ -284,7 +284,7 @@ Commands:
284
284
  delegationToken: readDelegationCredential(),
285
285
  }, 5000);
286
286
  if (!result) {
287
- console.error('✗ daemon 暂时不可用');
287
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
288
288
  process.exitCode = 1;
289
289
  return;
290
290
  }
@@ -318,7 +318,7 @@ Commands:
318
318
  process.exitCode = 1;
319
319
  return;
320
320
  }
321
- const result = await ipcQuery(resolvePaths().socket, {
321
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
322
322
  type: 'handoff-trace',
323
323
  argv: ['handoff', ...args],
324
324
  sessionId: process.env.EVOLCORE_SESSION_ID,
@@ -328,7 +328,7 @@ Commands:
328
328
  delegationToken: readDelegationCredential(),
329
329
  }, 5000);
330
330
  if (!result) {
331
- console.error('✗ daemon 暂时不可用');
331
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
332
332
  process.exitCode = 1;
333
333
  return;
334
334
  }
package/dist/cli/help.js CHANGED
@@ -11,7 +11,25 @@
11
11
  * - 单层命令(如 cmdLinkRules、cmdAid 的具体 sub 处理块内)用 wantsHelp(args),
12
12
  * 支持 option 风格帮助,同时不把 `--` 后的正文误判为帮助。
13
13
  */
14
+ import { IpcTransportError, ipcQuery } from '../ipc.js';
14
15
  const HELP_TOKENS = new Set(['help', '--help', '-h']);
16
+ /** Query a daemon-backed command while preserving concrete transport failures. */
17
+ export async function queryDaemon(socketPath, command, timeoutMs = 3000) {
18
+ try {
19
+ return {
20
+ response: await ipcQuery(socketPath, command, timeoutMs, { throwOnError: true }),
21
+ };
22
+ }
23
+ catch (error) {
24
+ if (error instanceof IpcTransportError) {
25
+ return { response: null, transportError: error };
26
+ }
27
+ throw error;
28
+ }
29
+ }
30
+ export function daemonUnavailableMessage(transportError, fallback) {
31
+ return transportError?.message ?? fallback;
32
+ }
15
33
  export function isHelpFlag(token) {
16
34
  return token !== undefined && HELP_TOKENS.has(token);
17
35
  }
@@ -8,6 +8,7 @@
8
8
  import readline from 'readline';
9
9
  import { cmdAidBind } from './aid-bind-command.js';
10
10
  import { getOwners } from '../core/ownership/owner-resolver.js';
11
+ import { MAIN_CLI_BIN } from '../product.js';
11
12
  import crypto from 'crypto';
12
13
  import { selectInstance } from './init.js';
13
14
  import { loadAllAgents, loadAgent, loadDaemonConfig } from '../config-store.js';
@@ -695,7 +696,7 @@ export async function cmdInitWechat() {
695
696
  // ==================== AUN ====================
696
697
  export async function cmdInitAun() {
697
698
  console.log('AUN 协议 Owner 统一使用 ec aid bind <ownerAid> [--subject <subjectAid>]');
698
- console.log('状态查询: ec aid bind status [--subject <subjectAid>];Evol App 扫码绑定留待二期。');
699
+ console.log(`状态查询: ${MAIN_CLI_BIN} aid bind status [--subject <subjectAid>];邀约审批: ${MAIN_CLI_BIN} aid bind accept|reject <operationId>。`);
699
700
  }
700
701
  async function pickAunOwnerBindTarget(rl, daemonAid) {
701
702
  const { agents } = loadAllAgents();
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import fs from 'node:fs';
15
15
  import { resolvePaths } from '../paths.js';
16
- import { ipcQuery } from '../ipc.js';
16
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
17
17
  import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
18
18
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
19
19
  import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
@@ -337,7 +337,7 @@ async function cmdMemoryNoteCreate(args, json) {
337
337
  if (!content)
338
338
  failWithUsage(json, '必须提供记忆内容(content)', USAGE.note);
339
339
  const { socketPath, ...session } = requireManagedSession('ec memory note', flags, json);
340
- const result = await ipcQuery(socketPath, {
340
+ const result = await queryDaemon(socketPath, {
341
341
  type: 'memory.note', argv: ['memory', 'note', ...args], ...session, slug, content, tags,
342
342
  speakers: speakers && speakers.length > 0 ? speakers : undefined,
343
343
  }, 15_000);
@@ -359,7 +359,7 @@ async function cmdMemoryNoteUpdate(args, json) {
359
359
  failWithUsage(json, '至少提供一项要改的内容(--slug / --tags / 正文)', USAGE.noteUpdate);
360
360
  }
361
361
  const { socketPath, ...session } = requireManagedSession('ec memory note update', flags, json);
362
- const result = await ipcQuery(socketPath, {
362
+ const result = await queryDaemon(socketPath, {
363
363
  type: 'memory.note.update', argv: ['memory', 'note', 'update', ...args], ...session, slug,
364
364
  newSlug, tags, tagsProvided: tagsRaw !== undefined,
365
365
  content: content || undefined,
@@ -375,7 +375,7 @@ async function cmdMemoryNoteDrop(args, json) {
375
375
  if (!slug)
376
376
  failWithUsage(json, '必须提供要撤销的 slug', USAGE.noteDrop);
377
377
  const { socketPath, ...session } = requireManagedSession('ec memory note drop', flags, json);
378
- const result = await ipcQuery(socketPath, {
378
+ const result = await queryDaemon(socketPath, {
379
379
  type: 'memory.note.drop', argv: ['memory', 'note', 'drop', ...args], ...session, slug,
380
380
  }, 15_000);
381
381
  reportResult(result, json, (r) => {
@@ -385,7 +385,7 @@ async function cmdMemoryNoteDrop(args, json) {
385
385
  async function cmdMemoryNoteList(args, json) {
386
386
  const { flags } = parseCliArgs(args, [], json);
387
387
  const { socketPath, ...session } = requireManagedSession('ec memory note list', flags, json);
388
- const result = await ipcQuery(socketPath, {
388
+ const result = await queryDaemon(socketPath, {
389
389
  type: 'memory.note.list', argv: ['memory', 'note', 'list', ...args], ...session,
390
390
  }, 15_000);
391
391
  reportResult(result, json, (r) => {
@@ -430,7 +430,7 @@ async function cmdMemoryCreate(args, json) {
430
430
  failWithUsage(json, '--peer 只在 --scope relation 时有效', USAGE.create);
431
431
  }
432
432
  const { socketPath, ...session } = requireManagedSession('ec memory create', flags, json);
433
- const result = await ipcQuery(socketPath, {
433
+ const result = await queryDaemon(socketPath, {
434
434
  type: 'memory.create', argv: ['memory', 'create', ...args], ...session, scope, slug, description: desc,
435
435
  content,
436
436
  tags: parseTags(tagsRaw),
@@ -456,7 +456,7 @@ async function cmdMemoryConsolidatedUpdate(args, json) {
456
456
  if (tagsRaw === undefined && !touch)
457
457
  failWithUsage(json, '至少提供 --tags 或 --touch', USAGE.update);
458
458
  const { socketPath, ...session } = requireManagedSession('ec memory update', flags, json);
459
- const result = await ipcQuery(socketPath, {
459
+ const result = await queryDaemon(socketPath, {
460
460
  type: 'memory.update', argv: ['memory', 'update', ...args], ...session, filePath,
461
461
  tags: parseTags(tagsRaw), touch,
462
462
  }, 15_000);
@@ -469,7 +469,7 @@ async function cmdMemoryArchive(args, json) {
469
469
  if (positional.length === 0)
470
470
  failWithUsage(json, '必须提供至少一个要归档的文件路径', USAGE.archive);
471
471
  const { socketPath, ...session } = requireManagedSession('ec memory archive', flags, json);
472
- const result = await ipcQuery(socketPath, {
472
+ const result = await queryDaemon(socketPath, {
473
473
  type: 'memory.archive', argv: ['memory', 'archive', ...args], ...session, filePaths: positional,
474
474
  }, 15_000);
475
475
  reportResult(result, json, (r) => {
@@ -496,7 +496,7 @@ async function cmdMemoryDemote(args, json) {
496
496
  fail(json, 'INVALID_ARGUMENT', '--scope 必须是 agent 或 owner', { text: '--scope 必须是 agent 或 owner' });
497
497
  }
498
498
  const { socketPath, ...session } = requireManagedSession('ec memory demote', flags, json);
499
- const result = await ipcQuery(socketPath, {
499
+ const result = await queryDaemon(socketPath, {
500
500
  type: 'memory.demote', argv: ['memory', 'demote', ...args], ...session, slug, scope,
501
501
  }, 15_000);
502
502
  reportResult(result, json, (r) => {
@@ -528,7 +528,7 @@ async function cmdMemoryConsolidate(args, json) {
528
528
  // JSON 模式下 stdout 只留结果对象
529
529
  if (!json)
530
530
  console.log(`正在触发记忆整理(mode=${mode})...`);
531
- const result = await ipcQuery(socketPath, {
531
+ const result = await queryDaemon(socketPath, {
532
532
  type: 'memory.consolidate', argv: ['memory', 'consolidate', ...args], ...session, mode,
533
533
  }, 30_000);
534
534
  reportResult(result, json, (r) => {
@@ -551,10 +551,11 @@ async function cmdMemoryConsolidate(args, json) {
551
551
  }
552
552
  // ── 结果输出 ───────────────────────────────────────────────────────────────
553
553
  function reportResult(result, json, ok) {
554
- if (!result) {
555
- fail(json, 'DAEMON_UNAVAILABLE', '无法连接 evolcore 服务', { text: '错误: 无法连接 evolcore 服务' });
554
+ if (!result.response) {
555
+ const message = daemonUnavailableMessage(result.transportError, '无法连接 evolcore 服务');
556
+ fail(json, 'DAEMON_UNAVAILABLE', message, { text: `错误: ${message}` });
556
557
  }
557
- const r = result;
558
+ const r = result.response;
558
559
  if (!r.ok) {
559
560
  const message = r.error || '操作失败';
560
561
  fail(json, r.code || 'MEMORY_OPERATION_FAILED', message, { text: message });
package/dist/cli/model.js CHANGED
@@ -16,7 +16,7 @@ import { resolveEffectiveFieldWithSource } from '../config/config-manager.js';
16
16
  import { getCatalog, getModelInfo, catalogEfforts, resolveCatalogModel, assertCatalogSelectable } from '../core/model/model-catalog.js';
17
17
  import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole, } from '../core/model/model-permission.js';
18
18
  import { resolvePaths } from '../paths.js';
19
- import { ipcQuery } from '../ipc.js';
19
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
20
20
  import { parsePeerKey } from '../core/relation/peer-identity.js';
21
21
  import { resolvePeerRoleDetail } from '../config/peer-role-resolver.js';
22
22
  import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
@@ -50,11 +50,12 @@ async function runManagedCliOperation(args, formatJson, timeoutMs = 10_000) {
50
50
  const delegationToken = readDelegationCredential();
51
51
  if (!delegationToken)
52
52
  fail(formatJson, 'DELEGATION_REQUIRED', `${AGENT_DELEGATION_TOKEN_ENV} 未设置`);
53
- const response = await ipcQuery(resolvePaths().socket, {
53
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
54
54
  type: 'ctl', argv: args, sessionId, delegationToken,
55
55
  }, timeoutMs);
56
- if (!response)
57
- fail(formatJson, 'DAEMON_UNAVAILABLE', '无法连接 evolcore 服务');
56
+ if (!response) {
57
+ fail(formatJson, 'DAEMON_UNAVAILABLE', daemonUnavailableMessage(transportError, '无法连接 evolcore 服务'));
58
+ }
58
59
  if (!response.ok)
59
60
  fail(formatJson, response.code || 'CTL_FAILED', response.error || '命令执行失败');
60
61
  printCtlResult({ text: response.result || '', data: response.data }, formatJson);
@@ -1,5 +1,5 @@
1
1
  import { resolvePaths } from '../paths.js';
2
- import { ipcQuery } from '../ipc.js';
2
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
3
3
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
4
4
  import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
5
5
  import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
@@ -94,7 +94,7 @@ JSON 时间字段:
94
94
  : undefined;
95
95
  const socketPath = resolvePaths().socket;
96
96
  const managed = process.env.EVOLCORE_SESSION_ID;
97
- const result = await ipcQuery(socketPath, managed ? {
97
+ const { response: result, transportError } = await queryDaemon(socketPath, managed ? {
98
98
  type: 'queue.op',
99
99
  argv: ['queue', ...args],
100
100
  agent,
@@ -115,7 +115,7 @@ JSON 时间字段:
115
115
  before,
116
116
  });
117
117
  if (!result) {
118
- console.error('错误: 无法连接 evolcore 服务');
118
+ console.error(`错误: ${daemonUnavailableMessage(transportError, '无法连接 evolcore 服务')}`);
119
119
  process.exit(1);
120
120
  }
121
121
  const r = result;
@@ -15,7 +15,7 @@ import { readField, writeField, determineFieldScope, normalizePeer, ModelScopeEr
15
15
  import { readFieldWithSource, resolveAgentConfig, ConfigTarget } from '../config/config-manager.js';
16
16
  import { ResponseModeRegistry } from '../response-system/registry.js';
17
17
  import { registerBuiltinModes } from '../response-system/modes/index.js';
18
- import { ipcQuery } from '../ipc.js';
18
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
19
19
  import { resolvePaths } from '../paths.js';
20
20
  import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
21
21
  const RESPONSE_MODE_FIELD = 'responseMode';
@@ -64,11 +64,12 @@ async function runManagedResponse(args, formatJson) {
64
64
  fail(formatJson, 'NO_SESSION', 'EVOLCORE_SESSION_ID is not set');
65
65
  if (!delegationToken)
66
66
  fail(formatJson, 'DELEGATION_REQUIRED', `${AGENT_DELEGATION_TOKEN_ENV} is not set`);
67
- const response = await ipcQuery(resolvePaths().socket, {
67
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
68
68
  type: 'ctl', argv: ['response', ...args], sessionId, delegationToken,
69
69
  }, 10_000);
70
- if (!response)
71
- fail(formatJson, 'DAEMON_UNAVAILABLE', 'Unable to connect to the evolcore daemon');
70
+ if (!response) {
71
+ fail(formatJson, 'DAEMON_UNAVAILABLE', daemonUnavailableMessage(transportError, 'Unable to connect to the evolcore daemon'));
72
+ }
72
73
  if (!response.ok)
73
74
  fail(formatJson, response.code || 'CTL_FAILED', response.error || 'response operation failed');
74
75
  emit(formatJson, response.data ?? { ok: true, result: response.result }, () => response.result || 'ok');
@@ -22,7 +22,7 @@ import path from 'path';
22
22
  import { spawn } from 'child_process';
23
23
  import { getPackageRoot, agentDir, resolvePaths } from '../paths.js';
24
24
  import { readTaskRuntimeContextFromEnv } from './task-context.js';
25
- import { ipcQuery } from '../ipc.js';
25
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
26
26
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
27
27
  import { authorizeLocalCommand, TASK_RUNTIME_CONTEXT_ENV } from './task-context.js';
28
28
  import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
@@ -87,17 +87,17 @@ async function cmdSkillRefresh(args) {
87
87
  process.exitCode = 1;
88
88
  return;
89
89
  }
90
- let response;
91
- try {
92
- response = await ipcQuery(resolvePaths().socket, {
93
- type: 'skill.refresh',
94
- aid,
95
- argv,
96
- ...(sessionId ? { sessionId, delegationToken } : {}),
97
- }, 30_000, { throwOnError: true });
98
- }
99
- catch (e) {
100
- console.error(`❌ 刷新失败 (DAEMON_UNAVAILABLE): ${e instanceof Error ? e.message : String(e)}`);
90
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
91
+ type: 'skill.refresh',
92
+ aid,
93
+ argv,
94
+ ...(sessionId ? { sessionId, delegationToken } : {}),
95
+ }, 30_000);
96
+ if (transportError) {
97
+ if (asJson)
98
+ console.log(JSON.stringify({ ok: false, code: 'DAEMON_UNAVAILABLE', error: transportError.message }, null, 2));
99
+ else
100
+ console.error(`❌ 刷新失败 (DAEMON_UNAVAILABLE): ${transportError.message}`);
101
101
  process.exitCode = 1;
102
102
  return;
103
103
  }
@@ -109,7 +109,7 @@ async function cmdSkillRefresh(args) {
109
109
  }
110
110
  if (!response?.ok) {
111
111
  const code = response?.code ?? (response ? 'FAILED' : 'DAEMON_UNAVAILABLE');
112
- console.error(`❌ 刷新失败 (${code}): ${response?.error ?? '无法连接 evolcore daemon'}`);
112
+ console.error(`❌ 刷新失败 (${code}): ${response?.error ?? daemonUnavailableMessage(transportError, '无法连接 evolcore daemon')}`);
113
113
  if (response?.data?.nextStep)
114
114
  console.error(` ${response.data.nextStep}`);
115
115
  process.exitCode = 1;
@@ -1,5 +1,5 @@
1
1
  import { resolvePaths } from '../paths.js';
2
- import { ipcQuery } from '../ipc.js';
2
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
3
3
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
4
4
  import { wantsHelp } from './help.js';
5
5
  const TASK_HELP = `用法: ec task resume <sessionId> [--format json]
@@ -39,14 +39,14 @@ export async function cmdTask(args) {
39
39
  printError('DELEGATION_REQUIRED', '托管任务缺少 delegation token');
40
40
  return;
41
41
  }
42
- const result = await ipcQuery(resolvePaths().socket, {
42
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
43
43
  type: 'task.resume',
44
44
  argv: ['task', ...args],
45
45
  targetSessionId,
46
46
  ...(sessionId ? { sessionId, delegationToken } : {}),
47
47
  }, 10_000);
48
48
  if (!result) {
49
- printError('IPC_UNAVAILABLE', '无法连接 EvolCore 服务;请查询任务状态后再决定是否重试');
49
+ printError('IPC_UNAVAILABLE', daemonUnavailableMessage(transportError, '无法连接 EvolCore 服务;请查询任务状态后再决定是否重试'));
50
50
  return;
51
51
  }
52
52
  if (json)
@@ -1,5 +1,5 @@
1
1
  import { resolvePaths } from '../paths.js';
2
- import { ipcQuery } from '../ipc.js';
2
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
3
3
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
4
4
  import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
5
5
  import { isMainSessionThreadId, resolveThreadRef } from '../core/thread/topic-id.js';
@@ -91,7 +91,7 @@ async function sendThreadOp(req, commandArgv) {
91
91
  const cred = readDelegationCredential();
92
92
  const { socket } = resolvePaths();
93
93
  const invokingSessionId = process.env.EVOLCORE_SESSION_ID;
94
- const result = await ipcQuery(socket, {
94
+ const { response: result, transportError } = await queryDaemon(socket, {
95
95
  ...req,
96
96
  type: 'thread.op',
97
97
  argv: commandArgv,
@@ -99,7 +99,9 @@ async function sendThreadOp(req, commandArgv) {
99
99
  sessionId: invokingSessionId,
100
100
  }, 10000);
101
101
  if (!result)
102
- return { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'daemon 暂时不可用' };
102
+ return {
103
+ ok: false, code: 'DAEMON_UNAVAILABLE', error: daemonUnavailableMessage(transportError, 'daemon 暂时不可用'),
104
+ };
103
105
  return result;
104
106
  }
105
107
  function printResult(data, jsonFormat) {
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { resolvePaths } from '../paths.js';
4
- import { ipcQuery } from '../ipc.js';
4
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
5
5
  import { isHelpFlag, wantsHelp } from './help.js';
6
6
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
7
7
  import { readTaskRuntimeContextFromEnv } from './task-context.js';
@@ -642,9 +642,9 @@ async function request(cmd, timeoutMs = 10_000) {
642
642
  }
643
643
  catch { }
644
644
  }
645
- const res = await ipcQuery(resolvePaths().socket, actorSessionId ? { ...cmd, actorSessionId, delegationToken } : delegationToken ? { ...cmd, delegationToken } : { ...cmd, controlToken }, timeoutMs);
645
+ const { response: res, transportError } = await queryDaemon(resolvePaths().socket, actorSessionId ? { ...cmd, actorSessionId, delegationToken } : delegationToken ? { ...cmd, delegationToken } : { ...cmd, controlToken }, timeoutMs);
646
646
  if (!res)
647
- throw new Error('错误: 无法连接 evolcore 服务');
647
+ throw new Error(`错误: ${daemonUnavailableMessage(transportError, '无法连接 evolcore 服务')}`);
648
648
  if (res.ok === false || res.error)
649
649
  throw new Error(res.error || 'trigger command failed');
650
650
  return res;
@@ -6,6 +6,7 @@ import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { MAIN_PACKAGE_NAME, PRODUCT_DISPLAY_NAME, WEB_PACKAGE_NAME } from '../product.js';
8
8
  import { getArgValue, wantsHelp } from './help.js';
9
+ import { resolveGlobalPkg } from '../utils/npm-ops.js';
9
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
11
  const PACKAGE_ROOT = path.resolve(__dirname, '../..');
11
12
  const RESET = '\x1b[0m';
@@ -54,8 +55,11 @@ export function handleVersion(args) {
54
55
  const mainPkg = readPkgJson(PACKAGE_ROOT);
55
56
  const mainEntry = path.join(PACKAGE_ROOT, 'dist', 'cli', 'index.js');
56
57
  const mainTs = getFileModTime(mainEntry);
57
- // ecweb 包
58
- const ecwebDir = path.join(PACKAGE_ROOT, 'ecweb');
58
+ // EC Web may be in the repository (dev mode) or nested under the globally
59
+ // installed EvolCore package. Use the shared resolver so both layouts report
60
+ // the actual installed package instead of `?`.
61
+ const ecwebInstalled = resolveGlobalPkg(WEB_PACKAGE_NAME);
62
+ const ecwebDir = ecwebInstalled ? path.dirname(ecwebInstalled.path) : path.join(PACKAGE_ROOT, 'ecweb');
59
63
  const ecwebPkg = readPkgJson(ecwebDir);
60
64
  const ecwebEntry = path.join(ecwebDir, 'dist', 'index.js');
61
65
  const ecwebTs = getFileModTime(ecwebEntry);
@@ -1123,7 +1123,7 @@ const OPERATIONS = [
1123
1123
  },
1124
1124
  {
1125
1125
  id: 'ec.aid.bind', category: 'write-agent', dangerous: true, defaultScopes: ['agent'],
1126
- description: 'Request a protocol Owner binding using the subject SDK client', sources: ['agent-tool', 'menu.cli'],
1126
+ description: 'Request or decide a protocol Owner binding using the subject SDK client', sources: ['agent-tool', 'menu.cli'],
1127
1127
  },
1128
1128
  {
1129
1129
  id: 'ec.aid.bind.status', category: 'read', dangerous: false, defaultScopes: ['agent'],
@@ -3,8 +3,8 @@ import { ownerResolver, OWNERSHIP_AUTHORIZATION_MAX_AGE_MS } from './ownership/o
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { randomBytes } from 'crypto';
6
- import { kitsTemplatesDir, agentMdPath, resolvePaths } from '../paths.js';
7
- import { buildInitialAgentMd, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
6
+ import { kitsTemplatesDir, resolvePaths } from '../paths.js';
7
+ import { buildInitialAgentMd, resolveAgentDisplayName, verifyLocalAgentMd } from '../aun/aid/agentmd.js';
8
8
  import { logger } from '../utils/logger.js';
9
9
  import { readAgentWelcomeTemplate } from '../utils/welcome.js';
10
10
  import { loadAgent, saveAgent } from '../config-store.js';
@@ -308,7 +308,13 @@ export class BootstrapService {
308
308
  return false;
309
309
  }
310
310
  if (starting) {
311
- await this.publishAgentMdIfSupported(ctx.adapter, aid, agentName);
311
+ // AUN creation/binding has already published agent.md, and the fresh
312
+ // Owner consensus above requires it. A normal upload here re-signs the
313
+ // approved original while the Owner may still be confirming its hash
314
+ // and ETag. Sending a welcome must not mutate that binding evidence.
315
+ if (channelType !== 'aun') {
316
+ await this.publishAgentMdIfSupported(ctx.adapter, aid, agentName);
317
+ }
312
318
  this.setLifecycle(agent, aid, 'bootstrapping');
313
319
  lifecycleStarted = true;
314
320
  }
@@ -546,18 +552,20 @@ export class BootstrapService {
546
552
  async publishAgentMdIfSupported(adapter, aid, fallbackName) {
547
553
  if (typeof adapter.uploadAgentMd !== 'function')
548
554
  return;
549
- const existing = fs.existsSync(agentMdPath(aid)) ? fs.readFileSync(agentMdPath(aid), 'utf-8') : '';
550
- const content = existing.trim() || buildInitialAgentMd({
555
+ const verification = await verifyLocalAgentMd(aid);
556
+ if (verification.status === 'verified') {
557
+ logger.info(`[Bootstrap] Preserving verified local agent.md for ${aid}; upload skipped`);
558
+ return;
559
+ }
560
+ if (verification.status !== 'missing') {
561
+ throw new Error(`Local agent.md cannot be safely republished for ${aid}: ${verification.reason ?? verification.status}`);
562
+ }
563
+ const content = buildInitialAgentMd({
551
564
  aid,
552
565
  name: fallbackName,
553
566
  type: 'agent',
554
567
  });
555
- try {
556
- await adapter.uploadAgentMd(content);
557
- logger.info(`[Bootstrap] Published agent.md for ${aid}`);
558
- }
559
- catch (e) {
560
- logger.warn(`[Bootstrap] Failed to publish agent.md for ${aid}: ${e instanceof Error ? e.message : String(e)}`);
561
- }
568
+ await adapter.uploadAgentMd(content);
569
+ logger.info(`[Bootstrap] Published initial agent.md for ${aid}`);
562
570
  }
563
571
  }
@@ -243,11 +243,14 @@ function parseAidCommand(argv, source, defaultSelfAid) {
243
243
  try {
244
244
  const command = parseOwnershipCommand(argv.slice(1));
245
245
  const subjectAid = command.subjectAid ?? defaultSelfAid;
246
- return recognized(command.action === 'bind' ? 'ec.aid.bind' : 'ec.aid.bind.status', 'agent', source, {
246
+ const isMutation = command.action !== 'status';
247
+ return recognized(isMutation ? 'ec.aid.bind' : 'ec.aid.bind.status', 'agent', source, {
247
248
  ...(subjectAid ? { self: subjectAid } : {}),
248
249
  ...(command.subjectAid ? { subjectAid: command.subjectAid } : {}),
249
250
  ...(command.action === 'bind' ? { ownerAid: command.ownerAid } : {}),
250
- }, argv, command.action === 'bind');
251
+ ...(command.action === 'accept' || command.action === 'reject'
252
+ ? { decision: command.action, operationId: command.operationId } : {}),
253
+ }, argv, isMutation);
251
254
  }
252
255
  catch (error) {
253
256
  return raw(source, argv, error instanceof Error ? error.message : 'Invalid aid bind command');
@@ -2196,8 +2196,9 @@ export class CommandHandler {
2196
2196
  const command = parseOwnershipCommand(argv);
2197
2197
  const targetAid = command.subjectAid ?? grant.selfAid;
2198
2198
  ownerResolver.assertLocalSubject(targetAid);
2199
- if (command.action === 'bind' && ['readonly', 'auto'].includes(grant.permissionMode ?? '')) {
2200
- return { ok: false, code: 'NOT_ALLOWED', error: 'Binding requires a request or bypass task' };
2199
+ const isMutation = command.action !== 'status';
2200
+ if (isMutation && !['request', 'bypass'].includes(grant.permissionMode ?? '')) {
2201
+ return { ok: false, code: 'NOT_ALLOWED', error: 'Binding changes and decisions require a request or bypass task' };
2201
2202
  }
2202
2203
  await refreshOwnershipForAuthorization(targetAid);
2203
2204
  let conversationId;
@@ -2215,12 +2216,20 @@ export class CommandHandler {
2215
2216
  if (targetAid !== grant.selfAid && !subject.isDaemonOwner) {
2216
2217
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'A different local subject requires the current daemon Owner' };
2217
2218
  }
2218
- if (!subject.isDaemonOwner && (command.action === 'bind' ? subject.role !== 'owner' : !isManagementRole(subject.role))) {
2219
+ if (!subject.isDaemonOwner && (isMutation ? subject.role !== 'owner' : !isManagementRole(subject.role))) {
2219
2220
  return { ok: false, code: 'OWNER_REQUIRED', error: 'Ownership binding requires the subject Owner or daemon Owner; first daemon binding uses the local CLI' };
2220
2221
  }
2221
2222
  const decision = await authorizeFreshOperation({
2222
2223
  source: 'agent-tool', subject,
2223
- intent: { operation: command.action === 'bind' ? 'ec.aid.bind' : 'ec.aid.bind.status', scope: 'agent', source: 'agent-tool', args: { self: targetAid, ...(command.action === 'bind' ? { ownerAid: command.ownerAid } : {}) } },
2224
+ intent: {
2225
+ operation: isMutation ? 'ec.aid.bind' : 'ec.aid.bind.status', scope: 'agent', source: 'agent-tool',
2226
+ args: {
2227
+ self: targetAid,
2228
+ ...(command.action === 'bind' ? { ownerAid: command.ownerAid } : {}),
2229
+ ...(command.action === 'accept' || command.action === 'reject'
2230
+ ? { decision: command.action, operationId: command.operationId } : {}),
2231
+ },
2232
+ },
2224
2233
  auditMetadata: { taskId: grant.taskId, messageId: grant.messageId },
2225
2234
  });
2226
2235
  if (!decision.allow)
@@ -4691,8 +4691,8 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
4691
4691
  }
4692
4692
  catch (error) {
4693
4693
  const failure = ownershipFailure(error);
4694
- const code = ['INVALID_ARGUMENT', 'INVALID_AID', 'INVALID_OWNER'].includes(failure.code) ? 'INVALID_ARGUMENT'
4695
- : ['SUBJECT_REQUIRED', 'SUBJECT_UNAVAILABLE'].includes(failure.code) ? 'NOT_FOUND'
4694
+ const code = ['INVALID_ARGUMENT', 'INVALID_AID', 'INVALID_OWNER', 'INVALID_OPERATION'].includes(failure.code) ? 'INVALID_ARGUMENT'
4695
+ : ['SUBJECT_REQUIRED', 'SUBJECT_UNAVAILABLE', 'REQUEST_NOT_FOUND', 'not_found'].includes(failure.code) ? 'NOT_FOUND'
4696
4696
  : ['OWNER_ALREADY_BOUND', 'BIND_CONFLICT', 'BIND_UNCERTAIN', 'CONSENSUS_REQUIRED', 'CLIENT_REPLACED', 'conflict', 'owner_locked'].includes(failure.code)
4697
4697
  ? 'CONFLICT' : 'INTERNAL_ERROR';
4698
4698
  rawExecution = { error: failure.error, code, data: failure };