evolcore 0.0.35 → 0.0.37

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 (224) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +6 -0
  3. package/bin/install-codex-managed-hooks.mjs +20 -3
  4. package/dist/agents/baseagent-availability.js +1 -1
  5. package/dist/agents/baseagent.js +88 -0
  6. package/dist/agents/claude-runner.js +59 -12
  7. package/dist/agents/codex/app-server-client.js +18 -1
  8. package/dist/agents/codex-runner.js +262 -21
  9. package/dist/agents/ecagent-runner.js +47 -11
  10. package/dist/agents/gemini-runner.js +36 -12
  11. package/dist/aun/aid/client.js +4 -3
  12. package/dist/aun/aid/control-aid.js +270 -8
  13. package/dist/aun/aid/identity.js +6 -3
  14. package/dist/aun/aid/ownership-operation.js +58 -0
  15. package/dist/aun/aid/store.js +1 -0
  16. package/dist/aun/group/fs-download.js +67 -2
  17. package/dist/aun/group/history.js +20 -5
  18. package/dist/aun/group/operations.js +8 -2
  19. package/dist/aun/menu/client.js +39 -2
  20. package/dist/aun/menu/transport.js +1 -1
  21. package/dist/aun/msg/history.js +43 -4
  22. package/dist/aun/msg/managed-operation.js +30 -53
  23. package/dist/aun/msg/p2p.js +51 -15
  24. package/dist/aun/outbox.js +4 -0
  25. package/dist/channels/aun.js +260 -73
  26. package/dist/channels/daemon.js +6 -4
  27. package/dist/channels/feishu.js +3 -0
  28. package/dist/cli/agent-command.js +161 -223
  29. package/dist/cli/agent-create.js +190 -223
  30. package/dist/cli/agent.js +3 -1
  31. package/dist/cli/aid-bind-command.js +53 -0
  32. package/dist/cli/aun-commands.js +384 -91
  33. package/dist/cli/bench.js +1 -1
  34. package/dist/cli/cli-argv.js +74 -0
  35. package/dist/cli/daemon-commands.js +105 -74
  36. package/dist/cli/diag.js +1 -1
  37. package/dist/cli/fs-command.js +18 -19
  38. package/dist/cli/getmeta-command.js +159 -6
  39. package/dist/cli/index.js +6 -12
  40. package/dist/cli/init-channel.js +8 -55
  41. package/dist/cli/init.js +125 -218
  42. package/dist/cli/queue-command.js +11 -8
  43. package/dist/cli/restart-monitor.js +177 -85
  44. package/dist/cli/session-command.js +1 -1
  45. package/dist/cli/stats.js +7 -5
  46. package/dist/cli/thread-command.js +103 -30
  47. package/dist/cli/trigger-command.js +1 -1
  48. package/dist/cli/watch-msg-diag.js +1 -0
  49. package/dist/config/access-policy.js +2 -2
  50. package/dist/config/boot-log.js +14 -11
  51. package/dist/config/config-field-policy.js +9 -1
  52. package/dist/config/config-manager.js +176 -90
  53. package/dist/config/config-operation-service.js +7 -14
  54. package/dist/config/contact-book-store.js +142 -26
  55. package/dist/config/contact-book.js +4 -2
  56. package/dist/config/lifecycle.js +1 -1
  57. package/dist/config/owner-config.js +96 -0
  58. package/dist/config/peer-role-resolver.js +12 -18
  59. package/dist/config/providers-config.js +382 -0
  60. package/dist/config/providers-import.js +74 -0
  61. package/dist/config/providers-management.js +328 -0
  62. package/dist/config/providers.js +22 -0
  63. package/dist/config/resolved-config-op.js +17 -14
  64. package/dist/config/snapshot.js +28 -16
  65. package/dist/config-store.js +18 -14
  66. package/dist/core/agent-ipc-registration.js +21 -63
  67. package/dist/core/agent-runtime.js +16 -5
  68. package/dist/core/auth/agent-delegation.js +199 -25
  69. package/dist/core/auth/auth-gateway.js +44 -2
  70. package/dist/core/auth/authorization-denial.js +1 -1
  71. package/dist/core/auth/debug-context.js +2 -1
  72. package/dist/core/auth/delegation-authorization.js +2 -2
  73. package/dist/core/auth/delegation-diagnostics.js +4 -1
  74. package/dist/core/auth/operation-authorizer.js +10 -1
  75. package/dist/core/auth/operation-catalog.js +19 -1
  76. package/dist/core/bootstrap-service.js +43 -20
  77. package/dist/core/channel-loader.js +0 -1
  78. package/dist/core/channel-runtime.js +30 -4
  79. package/dist/core/command/agent-control.js +196 -37
  80. package/dist/core/command/cli-intent-parser.js +60 -4
  81. package/dist/core/command/command-handler.js +212 -60
  82. package/dist/core/command/connect-menu.js +1 -0
  83. package/dist/core/command/diag-service.js +5 -4
  84. package/dist/core/command/file-search.js +368 -0
  85. package/dist/core/command/menu-catalog.js +2 -1
  86. package/dist/core/command/menu-handler.js +292 -107
  87. package/dist/core/command/menu-protocol.js +23 -0
  88. package/dist/core/command/role-menu.js +7 -19
  89. package/dist/core/command/skill-slash.js +130 -25
  90. package/dist/core/command/slash-handler.js +101 -53
  91. package/dist/core/command/topic-slash.js +16 -5
  92. package/dist/core/daemon-bootstrap.js +7 -1
  93. package/dist/core/daemon-control.js +19 -1
  94. package/dist/core/daemon-lifecycle.js +2 -1
  95. package/dist/core/event-catalog.js +2 -0
  96. package/dist/core/evolagent-registry.js +13 -2
  97. package/dist/core/evolagent.js +3 -6
  98. package/dist/core/handoff/ipc-registration.js +2 -2
  99. package/dist/core/handoff/runtime.js +40 -6
  100. package/dist/core/handoff/store.js +1 -0
  101. package/dist/core/inference/text-inference.js +95 -6
  102. package/dist/core/ipc-registration.js +43 -24
  103. package/dist/core/memory/consolidation-context.js +4 -1
  104. package/dist/core/memory/consolidation.js +25 -14
  105. package/dist/core/message/aun-ipc-sender.js +296 -88
  106. package/dist/core/message/aun-request-registry.js +226 -0
  107. package/dist/core/message/create-status.js +33 -4
  108. package/dist/core/message/message-bridge.js +62 -13
  109. package/dist/core/message/message-log.js +0 -1
  110. package/dist/core/message/message-queue.js +4 -1
  111. package/dist/core/message/message-utils.js +8 -1
  112. package/dist/core/message/peer-mode.js +17 -9
  113. package/dist/core/message/response-engine.js +266 -124
  114. package/dist/core/message/retry-scheduler.js +11 -8
  115. package/dist/core/model/catalog-service.js +60 -18
  116. package/dist/core/model/model-catalog.js +112 -9
  117. package/dist/core/model/model-fallback.js +94 -26
  118. package/dist/core/ownership/owner-resolver.js +687 -0
  119. package/dist/core/permission/approval-gateway.js +196 -62
  120. package/dist/core/permission/ec-command-parser.js +138 -25
  121. package/dist/core/permission/filesystem-approval-scope.js +141 -0
  122. package/dist/core/permission/protected-paths.js +14 -1
  123. package/dist/core/permission/sandbox-runtime.js +171 -18
  124. package/dist/core/permission/tool-policy.js +13 -2
  125. package/dist/core/role/runtime-policy.js +3 -4
  126. package/dist/core/session/session-idle-monitor.js +10 -8
  127. package/dist/core/session/session-manager.js +72 -45
  128. package/dist/core/session/session-mapper.js +18 -7
  129. package/dist/core/session/session-renew.js +5 -4
  130. package/dist/core/session/session-title.js +2 -1
  131. package/dist/core/session/session-turn-coordinator.js +4 -2
  132. package/dist/core/skill/index-generator.js +2 -16
  133. package/dist/core/task/task-recovery-service.js +15 -2
  134. package/dist/core/thread/command-parser.js +28 -13
  135. package/dist/core/thread/index.js +1 -0
  136. package/dist/core/thread/ipc-operation.js +11 -8
  137. package/dist/core/thread/thread-operation.js +79 -44
  138. package/dist/core/thread/thread-route.js +54 -6
  139. package/dist/core/thread/thread-service.js +236 -45
  140. package/dist/core/thread/topic-id.js +248 -0
  141. package/dist/eck/kit-renderer.js +1 -1
  142. package/dist/eck/message-renderer.js +12 -2
  143. package/dist/index.js +50 -27
  144. package/dist/ipc.js +59 -6
  145. package/dist/paths.js +1 -0
  146. package/dist/product.js +1 -0
  147. package/dist/response-system/engines/v1/proactive-flow.js +10 -3
  148. package/dist/stats/db.js +8 -0
  149. package/dist/stats/writer.js +26 -4
  150. package/dist/trigger/authorization.js +48 -3
  151. package/dist/trigger/feedback.js +28 -10
  152. package/dist/trigger/ipc-service.js +86 -10
  153. package/dist/trigger/local-files.js +55 -23
  154. package/dist/trigger/scheduler.js +57 -11
  155. package/dist/trigger/system-definitions.js +1 -0
  156. package/dist/trigger/validation.js +44 -15
  157. package/dist/utils/aid-bind.js +17 -297
  158. package/dist/utils/avatar-upload.js +16 -4
  159. package/dist/utils/cross-platform.js +115 -36
  160. package/dist/utils/ecweb-supervisor.js +1 -1
  161. package/dist/utils/ecweb-utils.js +8 -1
  162. package/dist/utils/error-utils.js +44 -6
  163. package/dist/utils/instance-registry.js +12 -2
  164. package/dist/utils/npm-ops.js +102 -27
  165. package/dist/utils/package-version-info.js +72 -27
  166. package/dist/utils/restart-safety.js +50 -0
  167. package/dist/utils/upgrade-jobs.js +304 -0
  168. package/dist/utils/windows-autostart.js +17 -10
  169. package/kits/docs/INDEX.md +5 -0
  170. package/kits/docs/aun/ABILITY_CALLING.md +239 -0
  171. package/kits/docs/evolcore/INDEX.md +14 -3
  172. package/kits/docs/evolcore/agent-create.md +55 -141
  173. package/kits/docs/evolcore/agent.md +38 -16
  174. package/kits/docs/evolcore/aid.md +34 -0
  175. package/kits/docs/evolcore/config.md +20 -1
  176. package/kits/docs/evolcore/event.md +2 -2
  177. package/kits/docs/evolcore/fs.md +18 -2
  178. package/kits/docs/evolcore/group-collaboration.md +13 -246
  179. package/kits/docs/evolcore/group.md +2 -1
  180. package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
  181. package/kits/docs/evolcore/menu-protocol.md +65 -1
  182. package/kits/docs/evolcore/msg.md +64 -2
  183. package/kits/docs/evolcore/skill.md +86 -0
  184. package/kits/docs/evolcore/stats.md +6 -0
  185. package/kits/docs/evolcore/task.md +2 -0
  186. package/kits/docs/evolcore/thread-chain.md +145 -51
  187. package/kits/docs/evolcore/trigger.md +48 -3
  188. package/kits/eck_manifest.json +21 -0
  189. package/kits/eck_message_manifest.json +4 -4
  190. package/kits/migrations/migrate-role-config-v5.mjs +20 -7
  191. package/kits/rules/01-overview.md +2 -1
  192. package/kits/rules/03-identity.md +3 -1
  193. package/kits/rules/04-relation.md +1 -1
  194. package/kits/rules/07-memory-and-thread.md +1 -1
  195. package/kits/schemas/_meta.json +16 -1
  196. package/kits/schemas/agent-config.schema.13.json +44 -2
  197. package/kits/schemas/contact-book.schema.3.json +5 -0
  198. package/kits/schemas/daemon.schema.6.json +3 -1
  199. package/kits/schemas/daemon.schema.8.json +329 -0
  200. package/kits/schemas/defaults.schema.5.json +39 -3
  201. package/kits/schemas/providers.schema.1.json +76 -0
  202. package/kits/schemas/relation-config.schema.9.json +5 -1
  203. package/kits/skills/.switches.json +28 -0
  204. package/kits/skills/aid-ability/SKILL.md +214 -0
  205. package/kits/skills/ec-agent-create/SKILL.md +162 -0
  206. package/kits/skills/ec-group/SKILL.md +2 -1
  207. package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
  208. package/kits/skills/ec-identity/SKILL.md +143 -0
  209. package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
  210. package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
  211. package/kits/skills/ec-trigger/SKILL.md +167 -0
  212. package/kits/skills/ec-trigger/references/examples.md +153 -0
  213. package/kits/skills/thread-ops/SKILL.md +108 -420
  214. package/kits/templates/message-fragments/thread-context.md +1 -2
  215. package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
  216. package/kits/templates/roles/member.json +1 -1
  217. package/kits/templates/roles/visitor.json +1 -0
  218. package/kits/templates/system-fragments/bootstrap.md +1 -0
  219. package/kits/templates/system-fragments/channel.md +2 -1
  220. package/kits/templates/system-fragments/commands.md +5 -4
  221. package/kits/templates/system-fragments/identity.md +2 -0
  222. package/kits/templates/system-fragments/session.md +1 -1
  223. package/kits/templates/system-fragments/venue.md +1 -1
  224. package/package.json +4 -3
@@ -1,5 +1,7 @@
1
1
  import { isValidAid } from '../../aun/aid/validation.js';
2
+ import { parseTimeArg, TIME_ARG_FORMATS } from '../../cli/cli-argv.js';
2
3
  import { normalizeThreadPeer } from './thread-service.js';
4
+ import { resolveThreadRef } from './topic-id.js';
3
5
  const STATUSES = new Set(['pending', 'in_progress', 'completed', 'archived']);
4
6
  const COMMON_OPTIONS = ['--self', '--format', '--json'];
5
7
  const VALUE_OPTIONS = new Set(['--self', '--agent', '--format', '--name', '--label', '--status', '--sort', '--page', '--limit', '--before', '--after', '--if-rev']);
@@ -45,6 +47,14 @@ export function resolveThreadCommandArgv(argv) {
45
47
  throw new Error(`Invalid number: ${flag}`);
46
48
  return value;
47
49
  };
50
+ const time = (flag) => {
51
+ if (!options.has(flag))
52
+ return undefined;
53
+ const value = parseTimeArg(text(flag));
54
+ if (value === undefined)
55
+ throw new Error(`Invalid time: ${flag}(支持 ${TIME_ARG_FORMATS})`);
56
+ return value;
57
+ };
48
58
  const selfAidValue = text('--self');
49
59
  const agentAidValue = text('--agent');
50
60
  const selfAid = (agentAidValue ?? selfAidValue)?.replace(/^@/, '');
@@ -77,18 +87,23 @@ export function resolveThreadCommandArgv(argv) {
77
87
  else if (maximum !== 0) {
78
88
  request.ref = positional[0];
79
89
  if (request.ref !== 'current') {
80
- const pieces = positional[0].split('/');
81
- if (pieces.length > 2 || !pieces[0] || pieces[1] === '')
82
- throw new Error('Invalid thread reference');
83
- request.peer = normalizeThreadPeer(pieces[0]);
84
- // 主会话的 threadId 是空串;`<peer>/main` 是同义写法(list 也这样显示)。
85
- // `rename` 额外把裸 peer 也当成主会话:不写斜杠时改的是主会话名,
86
- // 与 `list` / `history` 不传 threadId 读主会话的口径一致。
87
- request.threadId = pieces[1] === 'main' || (command === 'rename' && pieces.length === 1)
88
- ? ''
89
- : pieces[1];
90
- if (!request.peer || request.threadId && !/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(request.threadId))
91
- throw new Error('Invalid thread reference');
90
+ // 与 CLI 共用同一套解析:主话题是 `main:<aid>:<aid>`,话题是裸 id,
91
+ // 历史写法 `<peer>/<id>` 仍接受。裸 `main` 与 `<peer>/main` 都被拒绝。
92
+ const selfForRef = selfAid ?? (process.env.EVOLCORE_SELF_AID?.replace(/^@/, '') || '');
93
+ const resolved = resolveThreadRef(request.ref, selfForRef);
94
+ if (resolved.kind === 'invalid')
95
+ throw new Error(resolved.error);
96
+ if (resolved.kind === 'thread') {
97
+ request.peer = resolved.peer ? normalizeThreadPeer(resolved.peer) : undefined;
98
+ request.threadId = resolved.threadId;
99
+ }
100
+ else if (resolved.kind === 'main') {
101
+ request.peer = normalizeThreadPeer(resolved.peer);
102
+ request.threadId = resolved.topicId;
103
+ }
104
+ // 裸对端 AID 已由 resolveThreadRef 解析成主话题(peer + 规范 id),
105
+ // 这里不能再按"裸值就是对端"覆盖,否则会把 `rename <peer>` 变成
106
+ // "改当前会话所在话题",静默改错对象。
92
107
  }
93
108
  }
94
109
  if (command === 'list')
@@ -97,7 +112,7 @@ export function resolveThreadCommandArgv(argv) {
97
112
  page: number('--page', 1), limit: number('--limit', 1),
98
113
  });
99
114
  if (command === 'history')
100
- Object.assign(request, { limit: number('--limit', 1), before: number('--before'), after: number('--after') });
115
+ Object.assign(request, { limit: number('--limit', 1), before: time('--before'), after: time('--after') });
101
116
  if (command === 'create') {
102
117
  if (!text('--name')?.trim())
103
118
  throw new Error('Topic name is required');
@@ -1 +1,2 @@
1
1
  export { ThreadService, ThreadServiceError, normalizeThreadPeer } from './thread-service.js';
2
+ export { MAIN_TOPIC_PREFIX, normalizeTopicAid, formatMainTopicId, safeFormatMainTopicId, isMainTopicId, parseMainTopicId, mainTopicPeer, canonicalizeMainTopicId, isMainSessionThreadId, isTopicThreadId, resolveThreadRef, } from './topic-id.js';
@@ -1,7 +1,8 @@
1
1
  import { trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
2
- import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
2
+ import { authorizeFreshOperation as authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
3
3
  import { parsePeerKey } from '../relation/peer-identity.js';
4
4
  import { resolveThreadCommandArgv } from './command-parser.js';
5
+ import { isTopicThreadId, sessionTopicThreadId } from './topic-id.js';
5
6
  import { executeThreadOperation } from './thread-operation.js';
6
7
  import { ThreadService } from './thread-service.js';
7
8
  export function createThreadOperationExecutor(dependencies) {
@@ -12,9 +13,10 @@ export function createThreadOperationExecutor(dependencies) {
12
13
  return { ok: false, code: 'INVALID_REQUEST', error: 'Thread operations require argv' };
13
14
  const request = resolveThreadCommandArgv(incoming.argv);
14
15
  const token = typeof incoming.delegationToken === 'string' ? incoming.delegationToken.trim() : '';
15
- // `request` 是从 argv 重建的,不含调用方凭据。后续系统通知(topic-renamed
16
- // 等)要经 `sendAunMsg` 再授权一次,必须带上来源会话与 token;否则会被
17
- // 判为 missing_token。宿主终端调用没有 token,这里如实留空。
16
+ // `request` 是从 argv 重建的,不含调用方凭据。创建话题的旧式
17
+ // `topic-created` 通知仍经 `sendAunMsg` 走托管授权;宿主终端没有 token,
18
+ // 这类通知会按尽力而为处理。`topic.renamed` 是已有的系统事件,发送器
19
+ // 对它沿用系统事件豁免,因此菜单改名不需要伪造托管任务凭据。
18
20
  if (token) {
19
21
  request.delegationToken = token;
20
22
  if (incoming.delegationCommandHash)
@@ -58,15 +60,16 @@ export function createThreadOperationExecutor(dependencies) {
58
60
  }
59
61
  // `current` 必须先解析成具体 threadId,后续判断(日志、通知)都依赖它。
60
62
  // 群主会话改名的限制不在这里做:普通成员允许改自己的本地称呼,
61
- // 只是不发通知(见 thread-operation.ts 的 topic-renamed 分支)。
63
+ // 只是不发通知(见 thread-operation.ts 的 topic.renamed 分支)。
62
64
  if (request.ref === 'current') {
63
65
  const indexed = threadService.resolveCurrentThread(request.selfAid, session.id);
64
66
  request.peer = indexed?.peer ?? currentPeer;
65
- request.threadId = indexed?.threadId ?? session.threadId ?? undefined;
67
+ // 主会话保持 ''(rename 等分支用 `threadId === undefined` 判缺参,'' 才表示主话题)。
68
+ request.threadId = indexed?.threadId ?? sessionTopicThreadId(session) ?? '';
66
69
  request.sessionId = session.id;
67
70
  }
68
71
  const globalOperation = ['list-peers', 'check-index', 'rebuild-index'].includes(request.op);
69
- const decision = authorizeOperation({
72
+ const decision = await authorizeOperation({
70
73
  source: 'agent-tool', subject,
71
74
  intent: { operation: `ec.thread.${request.op}`, scope: globalOperation && !(relationOnly && request.op === 'list-peers') ? 'agent' : 'relation', source: 'agent-tool', args: { self: request.selfAid, peer: request.peer ?? currentPeer, peerKey: delegation.grant.peerKey } },
72
75
  auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
@@ -89,7 +92,7 @@ export function createThreadOperationExecutor(dependencies) {
89
92
  // 被要求联系 C 时,查/建/写的是我与 C 的话题,不属于 G,也不该受 G 约束。
90
93
  // "只能操作自己数据"由 authorizeOperation 的 targetCurrentAgentOnly 负责。
91
94
  if (request.op.startsWith('summary-') && !request.sessionId) {
92
- const target = request.threadId
95
+ const target = isTopicThreadId(request.threadId)
93
96
  ? await threadService.showThread({ selfAid: request.selfAid, peer: request.peer, threadId: request.threadId })
94
97
  : await threadService.showMainSession(request.selfAid, request.peer);
95
98
  if (!target)
@@ -1,5 +1,42 @@
1
1
  import { logger } from '../../utils/logger.js';
2
2
  import { normalizeThreadPeer } from './thread-service.js';
3
+ import { isMainSessionThreadId, isTopicThreadId } from './topic-id.js';
4
+ /**
5
+ * Emit the existing cross-device rename event after a local rename succeeds.
6
+ * Storage stays in ThreadService; transport stays in the operation layer.
7
+ */
8
+ export async function notifyThreadRename(input) {
9
+ logger.info(`[Thread] notifyRename called: peer=${input.peer} threadId=${input.threadId} changed=${input.result.changed} nameUpdatedAt=${input.result.nameUpdatedAt} sendSystemMsg=${!!input.sendSystemMsg}`);
10
+ if (!input.sendSystemMsg || !input.result.changed || !input.result.nameUpdatedAt)
11
+ return;
12
+ const peerKind = input.result.peerKind
13
+ ?? (isMainSessionThreadId(input.threadId)
14
+ ? undefined
15
+ : input.threadService.findInIndex(input.selfAid, input.peer, input.threadId)?.peerKind)
16
+ ?? 'private';
17
+ // 群主话题是“本地对群的称呼”,发给群只会覆盖所有人的本地叫法。
18
+ if (peerKind === 'group' && isMainSessionThreadId(input.threadId))
19
+ return;
20
+ try {
21
+ await input.sendSystemMsg({
22
+ from: input.selfAid,
23
+ to: input.peer,
24
+ threadId: input.threadId,
25
+ topicName: input.result.name,
26
+ peerKind,
27
+ kind: 'topic.renamed',
28
+ nameUpdatedAt: input.result.nameUpdatedAt,
29
+ originSessionId: input.originSessionId,
30
+ delegationToken: input.delegationToken,
31
+ delegationCommandHash: input.delegationCommandHash,
32
+ });
33
+ }
34
+ catch (error) {
35
+ // 通知失败不影响本地改名,但必须留痕:静默失败会让“改名同步”
36
+ // 看起来像没实现(实测已踩过)。
37
+ logger.warn(`[Thread] topic.renamed notification failed: peer=${input.peer} thread=${input.threadId} name=${JSON.stringify(input.result.name)} error=${error instanceof Error ? error.message : String(error)}`);
38
+ }
39
+ }
3
40
  export function executeThreadOperation(request, threadService, sendSystemMsg) {
4
41
  return _executeThreadOperation(request, threadService, sendSystemMsg);
5
42
  }
@@ -38,7 +75,7 @@ async function _executeThreadOperation(request, threadService, sendSystemMsg) {
38
75
  case 'show': {
39
76
  let peer = request.peer;
40
77
  let threadId = request.threadId;
41
- if ((!peer || !threadId) && request.sessionId) {
78
+ if ((!peer || threadId === undefined) && request.sessionId) {
42
79
  const resolved = threadService.resolveCurrentThread(selfAid, request.sessionId);
43
80
  if (resolved) {
44
81
  peer = resolved.peer;
@@ -47,8 +84,8 @@ async function _executeThreadOperation(request, threadService, sendSystemMsg) {
47
84
  }
48
85
  if (!peer)
49
86
  return { ok: false, code: 'MISSING_ARGS', error: 'peer is required' };
50
- // peer-only ref (no threadId) → show main session
51
- if (!threadId) {
87
+ // 主话题(空 / `main` / 规范 id)→ 读该关系的主会话
88
+ if (!isTopicThreadId(threadId)) {
52
89
  const result = await threadService.showMainSession(selfAid, peer);
53
90
  if (!result)
54
91
  return { ok: false, code: 'NOT_FOUND', error: `与 ${peer} 无活跃主会话` };
@@ -134,7 +171,7 @@ async function _executeThreadOperation(request, threadService, sendSystemMsg) {
134
171
  case 'rename': {
135
172
  let peer = request.peer;
136
173
  let threadId = request.threadId;
137
- if ((!peer || !threadId) && request.sessionId) {
174
+ if ((!peer || threadId === undefined) && request.sessionId) {
138
175
  const resolved = threadService.resolveCurrentThread(selfAid, request.sessionId);
139
176
  if (resolved) {
140
177
  peer = resolved.peer;
@@ -146,65 +183,49 @@ async function _executeThreadOperation(request, threadService, sendSystemMsg) {
146
183
  return { ok: false, code: 'MISSING_ARGS', error: 'peer, threadId and name required' };
147
184
  }
148
185
  const result = await threadService.renameThread({ selfAid, peer, threadId, name: request.name });
149
- // 改名通知(尽力而为,失败不影响本地改名):
150
- // - 私聊主会话/子话题:通知对端 agent 或人类,便于其按名字选择话题。
151
- // - 群子话题:发到群,所有成员同步。
152
- // - 群主会话:不发(只有 admin/owner 能改,且它是本地对群本身的称呼)。
153
- if (sendSystemMsg && result.changed && result.nameUpdatedAt) {
154
- const entry = threadId ? threadService.findInIndex(selfAid, peer, threadId) : undefined;
155
- const peerKind = entry?.peerKind ?? (request.peerKind ?? 'private');
156
- const isGroupMain = peerKind === 'group' && !threadId;
157
- if (!isGroupMain) {
158
- try {
159
- await sendSystemMsg({
160
- from: selfAid,
161
- to: peer,
162
- threadId: threadId ?? '',
163
- topicName: result.name,
164
- peerKind,
165
- kind: 'topic-renamed',
166
- nameUpdatedAt: result.nameUpdatedAt,
167
- originSessionId: request.sessionId,
168
- delegationToken: request.delegationToken,
169
- delegationCommandHash: request.delegationCommandHash,
170
- });
171
- }
172
- catch (error) {
173
- // 通知失败不影响本地改名,但必须留痕:静默失败会让"改名同步"
174
- // 看起来像没实现(实测已踩过)。
175
- logger.warn(`[Thread] topic-renamed notification failed: peer=${peer} thread=${threadId ?? 'main'} name=${JSON.stringify(result.name)} error=${error instanceof Error ? error.message : String(error)}`);
176
- }
177
- }
178
- }
186
+ // 改名通知走统一出口(同名、失败和群主话题不发)。
187
+ await notifyThreadRename({
188
+ selfAid,
189
+ peer,
190
+ threadId: threadId ?? '',
191
+ result,
192
+ threadService,
193
+ sendSystemMsg,
194
+ originSessionId: request.sessionId,
195
+ delegationToken: request.delegationToken,
196
+ delegationCommandHash: request.delegationCommandHash,
197
+ });
179
198
  return { ok: true, result };
180
199
  }
181
200
  case 'label': {
182
201
  let peer = request.peer;
183
202
  let threadId = request.threadId;
184
- if ((!peer || !threadId) && request.sessionId) {
203
+ if ((!peer || threadId === undefined) && request.sessionId) {
185
204
  const resolved = threadService.resolveCurrentThread(selfAid, request.sessionId);
186
205
  if (resolved) {
187
206
  peer = resolved.peer;
188
207
  threadId = resolved.threadId;
189
208
  }
190
209
  }
191
- if (!peer || !threadId)
192
- return { ok: false, code: 'MISSING_ARGS', error: 'peer and threadId required' };
210
+ if (!peer || !isTopicThreadId(threadId)) {
211
+ return { ok: false, code: 'MISSING_ARGS', error: '标签只适用于话题(主话题没有标签)' };
212
+ }
193
213
  await threadService.labelThread({ selfAid, peer, threadId, label: request.label ?? null });
194
214
  return { ok: true };
195
215
  }
196
216
  case 'status': {
197
217
  let peer = request.peer;
198
218
  let threadId = request.threadId;
199
- if ((!peer || !threadId) && request.sessionId) {
219
+ if ((!peer || threadId === undefined) && request.sessionId) {
200
220
  const resolved = threadService.resolveCurrentThread(selfAid, request.sessionId);
201
221
  if (resolved) {
202
222
  peer = resolved.peer;
203
223
  threadId = resolved.threadId;
204
224
  }
205
225
  }
206
- if (!peer || !threadId || !request.topicStatus)
207
- return { ok: false, code: 'MISSING_ARGS', error: 'peer, threadId and topicStatus required' };
226
+ if (!peer || !isTopicThreadId(threadId) || !request.topicStatus) {
227
+ return { ok: false, code: 'MISSING_ARGS', error: '状态只适用于话题(主话题没有业务状态)' };
228
+ }
208
229
  await threadService.statusThread({ selfAid, peer, threadId, topicStatus: request.topicStatus });
209
230
  return { ok: true };
210
231
  }
@@ -221,15 +242,16 @@ async function _executeThreadOperation(request, threadService, sendSystemMsg) {
221
242
  case 'delete': {
222
243
  let peer = request.peer;
223
244
  let threadId = request.threadId;
224
- if ((!peer || !threadId) && request.sessionId) {
245
+ if ((!peer || threadId === undefined) && request.sessionId) {
225
246
  const resolved = threadService.resolveCurrentThread(selfAid, request.sessionId);
226
247
  if (resolved) {
227
248
  peer = resolved.peer;
228
249
  threadId = resolved.threadId;
229
250
  }
230
251
  }
231
- if (!peer || !threadId)
232
- return { ok: false, code: 'MISSING_ARGS', error: 'peer and threadId required' };
252
+ if (!peer || !isTopicThreadId(threadId)) {
253
+ return { ok: false, code: 'NOT_A_TOPIC', error: '只能删除话题(主话题不可删除)' };
254
+ }
233
255
  await threadService.deleteThread(selfAid, peer, threadId);
234
256
  return { ok: true };
235
257
  }
@@ -251,7 +273,7 @@ async function _executeThreadOperation(request, threadService, sendSystemMsg) {
251
273
  if (!name)
252
274
  return { ok: true, result };
253
275
  // 解析改名目标:主会话(threadId 为空串)不进索引,靠 peer + sessionId 定位;
254
- // 子话题优先用索引里的 peer/threadId,索引查不到时退回请求里带的 ref。
276
+ // 话题优先用索引里的 peer/threadId,索引查不到时退回请求里带的 ref。
255
277
  const indexed = threadService.readThreadIndex(selfAid).threads.find(t => t.sessionId === request.sessionId);
256
278
  const peer = request.peer ?? indexed?.peer;
257
279
  const threadId = request.threadId ?? indexed?.threadId;
@@ -264,6 +286,19 @@ async function _executeThreadOperation(request, threadService, sendSystemMsg) {
264
286
  }
265
287
  try {
266
288
  const renamed = await threadService.renameThread({ selfAid, peer, threadId: threadId ?? '', name });
289
+ // 总结伴随的真实改名同样要通知对端:与 `ec thread rename` 共用同一出口,
290
+ // 否则 App 与对端索引只在 CLI rename 路径同步,summary set 改名静默。
291
+ await notifyThreadRename({
292
+ selfAid,
293
+ peer,
294
+ threadId: threadId ?? '',
295
+ result: renamed,
296
+ threadService,
297
+ sendSystemMsg,
298
+ originSessionId: request.sessionId,
299
+ delegationToken: request.delegationToken,
300
+ delegationCommandHash: request.delegationCommandHash,
301
+ });
267
302
  return { ok: true, result: { ...result, renamedTo: renamed.name, renamedChanged: renamed.changed } };
268
303
  }
269
304
  catch (e) {
@@ -1,18 +1,57 @@
1
1
  import { sameGroupAid } from '../../aun/group/identity.js';
2
2
  import { ThreadService } from './thread-service.js';
3
+ import { isMainSessionThreadId, resolveThreadRef } from './topic-id.js';
3
4
  /**
4
5
  * 解析 `--thread` 跨话题发送的目标话题。
5
6
  *
6
- * 话题必须存在于发送方本端索引、属于 AUN 渠道、peerKind 与发送 scope 一致;
7
- * 给出 target 时还必须与话题 peer 相同(群按群 AID 规范化比较)。
8
- * 返回 undefined 表示该 thread 不能作为跨话题路由依据。
7
+ * 输入是**话题引用**:话题 id、主话题 id(`main:<a>:<b>`),或历史写法
8
+ * `<peer>/<id>`。目标对端由引用自身推出(话题取索引条目,主话题取 id 里的
9
+ * 另一端),`target` 仅用于矛盾校验——写了就必须与引用一致。
10
+ *
11
+ * 话题必须存在于本端:话题查索引,主话题查该关系的 active session。
12
+ * 返回 undefined 表示该引用不能作为跨话题路由依据。
9
13
  */
10
- export function resolveThreadRouteTarget(selfAid, threadId, scope, target) {
11
- if (!selfAid || !threadId)
14
+ export function resolveThreadRouteTarget(selfAid, threadRef, scope, target) {
15
+ if (!selfAid)
16
+ return undefined;
17
+ const normalizedSelf = selfAid.replace(/^@/, '');
18
+ const resolved = resolveThreadRef(threadRef, normalizedSelf);
19
+ if (resolved.kind === 'invalid' || resolved.kind === 'current')
12
20
  return undefined;
21
+ // 主话题:对端只能从引用里来。旧的空串/`main` 没有对端信息,只能靠显式的
22
+ // target 补齐(保留给尚未归一的历史调用方)。
23
+ if (resolved.kind === 'main' || isMainSessionThreadId(threadRef)) {
24
+ const peerFromRef = resolved.kind === 'main' ? resolved.peer : undefined;
25
+ const effectiveTarget = peerFromRef ?? target;
26
+ if (!effectiveTarget)
27
+ return undefined;
28
+ if (target !== undefined && peerFromRef !== undefined) {
29
+ const normalizedTarget = target.replace(/^@/, '');
30
+ const matches = scope === 'group'
31
+ ? sameGroupAid(peerFromRef, normalizedTarget)
32
+ : peerFromRef === normalizedTarget;
33
+ if (!matches)
34
+ return undefined;
35
+ }
36
+ let entry;
37
+ try {
38
+ entry = ThreadService.findMainSessionEntry(normalizedSelf, effectiveTarget);
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ if (!entry)
44
+ return undefined;
45
+ if ((entry.channelType ?? 'aun') !== 'aun')
46
+ return undefined;
47
+ if (entry.peerKind !== (scope === 'group' ? 'group' : 'private'))
48
+ return undefined;
49
+ return entry;
50
+ }
51
+ const threadId = resolved.threadId;
13
52
  let entry;
14
53
  try {
15
- entry = ThreadService.findThreadById(selfAid.replace(/^@/, ''), threadId);
54
+ entry = ThreadService.findThreadById(normalizedSelf, threadId);
16
55
  }
17
56
  catch {
18
57
  return undefined;
@@ -23,6 +62,15 @@ export function resolveThreadRouteTarget(selfAid, threadId, scope, target) {
23
62
  return undefined;
24
63
  if (entry.peerKind !== (scope === 'group' ? 'group' : 'private'))
25
64
  return undefined;
65
+ // 引用里写了 `<peer>/<id>` 时,peer 必须与索引一致——不一致说明引用来自别处。
66
+ if (resolved.peer !== undefined) {
67
+ const normalizedPeer = resolved.peer.replace(/^@/, '');
68
+ const samePeer = scope === 'group'
69
+ ? sameGroupAid(entry.peer, normalizedPeer)
70
+ : entry.peer === normalizedPeer;
71
+ if (!samePeer)
72
+ return undefined;
73
+ }
26
74
  if (target === undefined)
27
75
  return entry;
28
76
  const normalizedTarget = target.replace(/^@/, '');