evolcore 0.0.32 → 0.0.34

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 (214) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +135 -37
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +111 -15
  6. package/dist/agents/codex/app-server-client.js +3 -0
  7. package/dist/agents/codex/diagnostics.js +112 -54
  8. package/dist/agents/codex-runner.js +241 -63
  9. package/dist/agents/ecagent-runner.js +78 -33
  10. package/dist/agents/runner-types.js +5 -6
  11. package/dist/aun/aid/identity.js +1 -1
  12. package/dist/aun/group/operations.js +0 -1
  13. package/dist/aun/msg/history.js +1 -0
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/channels/aun.js +44 -8
  17. package/dist/channels/daemon.js +261 -3
  18. package/dist/cli/agent-command.js +58 -24
  19. package/dist/cli/agent.js +1 -1
  20. package/dist/cli/aun-commands.js +368 -31
  21. package/dist/cli/contact.js +8 -2
  22. package/dist/cli/ctl-command.js +3 -1
  23. package/dist/cli/daemon-commands.js +197 -27
  24. package/dist/cli/debug-mode.js +154 -0
  25. package/dist/cli/fs-command.js +2 -1
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +98 -67
  28. package/dist/cli/init.js +2 -2
  29. package/dist/cli/integrity-command.js +196 -0
  30. package/dist/cli/managed-command-guard.js +5 -0
  31. package/dist/cli/memory-command.js +166 -146
  32. package/dist/cli/queue-command.js +9 -4
  33. package/dist/cli/session-command.js +194 -0
  34. package/dist/cli/skill-command.js +78 -2
  35. package/dist/cli/task-context.js +1 -0
  36. package/dist/cli/thread-command.js +622 -0
  37. package/dist/cli/trigger-command.js +10 -3
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-field-policy.js +6 -3
  40. package/dist/config/config-manager.js +26 -2
  41. package/dist/config/contact-book-store.js +5 -0
  42. package/dist/config/resolved-config-op.js +3 -0
  43. package/dist/config/role-service.js +13 -0
  44. package/dist/config/runtime-env.js +16 -0
  45. package/dist/config-store.js +45 -0
  46. package/dist/core/audit/log-integrity.js +3 -1
  47. package/dist/core/auth/agent-delegation.js +133 -36
  48. package/dist/core/auth/debug-context.js +139 -0
  49. package/dist/core/auth/delegation-diagnostics.js +414 -0
  50. package/dist/core/auth/operation-authorizer.js +25 -2
  51. package/dist/core/auth/operation-catalog.js +49 -0
  52. package/dist/core/background-session/session-cleanup.js +212 -0
  53. package/dist/core/background-session/types.js +10 -0
  54. package/dist/core/bootstrap-messages.js +1 -1
  55. package/dist/core/bootstrap-service.js +6 -3
  56. package/dist/core/command/agent-control.js +18 -26
  57. package/dist/core/command/command-handler.js +197 -27
  58. package/dist/core/command/daemon-diagnostics.js +1 -0
  59. package/dist/core/command/memory-slash.js +6 -2
  60. package/dist/core/command/menu-catalog.js +51 -1
  61. package/dist/core/command/menu-handler.js +114 -23
  62. package/dist/core/command/menu-protocol.js +25 -1
  63. package/dist/core/command/skill-slash.js +322 -0
  64. package/dist/core/command/slash-gate.js +6 -4
  65. package/dist/core/command/slash-handler.js +195 -13
  66. package/dist/core/command/topic-slash.js +528 -0
  67. package/dist/core/daemon-bootstrap.js +4 -7
  68. package/dist/core/evolagent-registry.js +20 -5
  69. package/dist/core/file-integrity/read.js +61 -0
  70. package/dist/core/file-integrity/store.js +262 -0
  71. package/dist/core/handoff/runtime.js +5 -11
  72. package/dist/core/handoff/store.js +5 -0
  73. package/dist/core/ipc-registration.js +175 -3
  74. package/dist/core/memory/batch-meta.js +132 -0
  75. package/dist/core/memory/consolidation-context.js +83 -29
  76. package/dist/core/memory/consolidation-ops-records.js +49 -16
  77. package/dist/core/memory/consolidation-queue-format.js +21 -0
  78. package/dist/core/memory/consolidation.js +264 -205
  79. package/dist/core/memory/index-manager.js +19 -28
  80. package/dist/core/memory/memory-admin.js +19 -14
  81. package/dist/core/memory/memory-document.js +53 -9
  82. package/dist/core/memory/memory-writer.js +30 -15
  83. package/dist/core/message/aun-ipc-sender.js +161 -6
  84. package/dist/core/message/im-renderer.js +169 -10
  85. package/dist/core/message/message-bridge.js +15 -4
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/peer-mode.js +6 -1
  88. package/dist/core/message/response-engine.js +929 -110
  89. package/dist/core/message/subtask-tool-projection.js +140 -0
  90. package/dist/core/model/catalog-service.js +17 -12
  91. package/dist/core/permission/ec-command-parser.js +217 -9
  92. package/dist/core/permission/protected-paths.js +5 -1
  93. package/dist/core/permission/readonly-shell-query.js +43 -5
  94. package/dist/core/permission/session-path-grants.js +334 -0
  95. package/dist/core/permission/tool-canonical.js +226 -0
  96. package/dist/core/permission/tool-policy.js +30 -62
  97. package/dist/core/role/runtime-policy.js +21 -10
  98. package/dist/core/session/session-fs-store.js +123 -8
  99. package/dist/core/session/session-idle-monitor.js +249 -22
  100. package/dist/core/session/session-manager.js +129 -14
  101. package/dist/core/skill/command-parser.js +13 -0
  102. package/dist/core/skill/index-generator.js +21 -4
  103. package/dist/core/skill/startup.js +41 -0
  104. package/dist/core/skill/switch-resolver.js +55 -29
  105. package/dist/core/system-session/grants.js +40 -0
  106. package/dist/core/system-session/memory.js +222 -0
  107. package/dist/core/system-session/registry.js +74 -0
  108. package/dist/core/system-session/types.js +14 -0
  109. package/dist/core/thread/command-parser.js +125 -0
  110. package/dist/core/thread/index.js +1 -0
  111. package/dist/core/thread/ipc-operation.js +95 -0
  112. package/dist/core/thread/thread-context-storage.js +131 -0
  113. package/dist/core/thread/thread-operation.js +258 -0
  114. package/dist/core/thread/thread-route.js +33 -0
  115. package/dist/core/thread/thread-service.js +770 -0
  116. package/dist/core/thread/types.js +1 -0
  117. package/dist/eck/kit-renderer.js +7 -2
  118. package/dist/eck/manifest-engine.js +24 -4
  119. package/dist/eck/message-renderer.js +35 -0
  120. package/dist/index.js +19 -7
  121. package/dist/ipc.js +140 -50
  122. package/dist/paths.js +93 -0
  123. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  124. package/dist/stats/billing.js +20 -8
  125. package/dist/trigger/authorization.js +57 -28
  126. package/dist/trigger/feedback.js +13 -2
  127. package/dist/trigger/parser.js +70 -1
  128. package/dist/trigger/system-definitions.js +3 -3
  129. package/dist/trigger/validation.js +14 -0
  130. package/dist/utils/cross-platform.js +11 -2
  131. package/dist/utils/error-dict.json +18 -4
  132. package/dist/utils/error-utils.js +143 -1
  133. package/dist/utils/instance-registry.js +13 -8
  134. package/dist/utils/log-writer.js +12 -0
  135. package/dist/utils/process-introspect.js +11 -0
  136. package/kits/docs/context-assembly.md +1 -1
  137. package/kits/docs/evolcore/INDEX.md +1 -0
  138. package/kits/docs/evolcore/agent-create.md +40 -28
  139. package/kits/docs/evolcore/agent.md +21 -6
  140. package/kits/docs/evolcore/config.md +2 -2
  141. package/kits/docs/evolcore/ctl.md +2 -2
  142. package/kits/docs/evolcore/ec-capabilities.md +5 -1
  143. package/kits/docs/evolcore/fs-architecture.md +4 -5
  144. package/kits/docs/evolcore/fs.md +1 -1
  145. package/kits/docs/evolcore/group-collaboration.md +4 -6
  146. package/kits/docs/evolcore/group.md +11 -12
  147. package/kits/docs/evolcore/memory-architecture.md +4 -5
  148. package/kits/docs/evolcore/memory-consolidation.md +19 -15
  149. package/kits/docs/evolcore/memory-tags.md +3 -1
  150. package/kits/docs/evolcore/memory.md +24 -12
  151. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  152. package/kits/docs/evolcore/menu-protocol.md +7 -0
  153. package/kits/docs/evolcore/model.md +16 -0
  154. package/kits/docs/evolcore/msg.md +8 -11
  155. package/kits/docs/evolcore/thread-chain.md +267 -0
  156. package/kits/docs/prompt-loading-architecture.md +1 -1
  157. package/kits/eck_manifest.json +55 -40
  158. package/kits/eck_manifest.memory.json +0 -12
  159. package/kits/eck_message_manifest.json +169 -10
  160. package/kits/rules/01-overview.md +11 -0
  161. package/kits/rules/03-identity.md +6 -3
  162. package/kits/rules/05-venue.md +1 -1
  163. package/kits/rules/06-channel.md +1 -1
  164. package/kits/rules/07-memory-and-thread.md +199 -0
  165. package/kits/schemas/_meta.json +7 -1
  166. package/kits/schemas/agent-config.schema.13.json +1 -1
  167. package/kits/schemas/daemon.schema.7.json +140 -0
  168. package/kits/schemas/relation-config.schema.9.json +18 -3
  169. package/kits/schemas/role-config.schema.1.json +6 -6
  170. package/kits/schemas/single-session.schema.1.json +6 -6
  171. package/kits/schemas/single-session.schema.2.json +6 -6
  172. package/kits/schemas/single-session.schema.3.json +6 -6
  173. package/kits/skills/.switches.json +12 -1
  174. package/kits/skills/ec-group/SKILL.md +234 -0
  175. package/kits/skills/ec-skill/SKILL.md +10 -1
  176. package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
  177. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  178. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  179. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  180. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  181. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  182. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  183. package/kits/skills/thread-ops/SKILL.md +492 -0
  184. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  185. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  186. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  187. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  188. package/kits/templates/bootstrap-welcome.md +7 -7
  189. package/kits/templates/memory-consolidation/forget.md +7 -2
  190. package/kits/templates/memory-consolidation/generalize.md +9 -4
  191. package/kits/templates/memory-consolidation/notes.md +11 -11
  192. package/kits/templates/memory-consolidation/profile.md +8 -3
  193. package/kits/templates/memory-consolidation/promote.md +8 -3
  194. package/kits/templates/memory-consolidation/workflow.md +6 -6
  195. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  196. package/kits/templates/message-fragments/item.md +2 -1
  197. package/kits/templates/message-fragments/thread-context.md +6 -0
  198. package/kits/templates/message-fragments/trigger-session.md +2 -0
  199. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  200. package/kits/templates/roles/member.json +2 -0
  201. package/kits/templates/roles/visitor.json +2 -0
  202. package/kits/templates/system-fragments/bootstrap.md +67 -72
  203. package/kits/templates/system-fragments/channel.md +3 -6
  204. package/kits/templates/system-fragments/commands.md +1 -0
  205. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  206. package/kits/templates/system-fragments/memory.md +16 -9
  207. package/kits/templates/system-fragments/reply.md +7 -6
  208. package/kits/templates/system-fragments/session.md +5 -14
  209. package/package.json +6 -3
  210. package/dist/core/memory/memory-session-policy.js +0 -41
  211. package/dist/core/memory/memory-signature.js +0 -257
  212. package/kits/docs/evolcore/group-fs.md +0 -17
  213. package/kits/docs/venues/aun-group.md +0 -45
  214. package/kits/docs/venues/group.md +0 -43
@@ -8,6 +8,7 @@ import { validateModelSelectionForRole } from '../core/model/model-permission.js
8
8
  import { isValidAunDeliveryForChannel } from '../core/message/outbound.js';
9
9
  import { tryParseChannelKey } from '../core/channel-loader.js';
10
10
  import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from '../core/role/runtime-policy.js';
11
+ import { resolveThreadRouteTarget } from '../core/thread/thread-route.js';
11
12
  export function originFromActorSession(session, authenticatedPeerId, authenticatedChannelKey, authenticatedChatType) {
12
13
  const peerId = authenticatedPeerId || session.metadata?.peerId;
13
14
  if (!peerId)
@@ -15,16 +16,21 @@ export function originFromActorSession(session, authenticatedPeerId, authenticat
15
16
  const sessionChannelKey = session.metadata?.channelKey || session.channel;
16
17
  const channelKey = authenticatedChannelKey || sessionChannelKey;
17
18
  const sameConversation = channelKey === sessionChannelKey;
19
+ if (authenticatedChatType && (authenticatedChatType === 'group') !== (session.chatType === 'group')) {
20
+ return undefined;
21
+ }
18
22
  const isGroup = authenticatedChatType === 'group' || session.chatType === 'group';
23
+ const channelId = isGroup ? session.channelId : peerId;
24
+ const sessionKind = sameConversation && session.threadId ? 'thread' : 'main';
19
25
  return {
20
26
  channelKey,
21
27
  channelType: session.channelType || tryParseChannelKey(channelKey)?.type || channelKey,
22
- channelId: peerId,
23
- session: sameConversation && !isGroup && session.threadId ? 'thread' : 'main',
24
- threadId: sameConversation && !isGroup ? (session.threadId || undefined) : undefined,
28
+ channelId,
29
+ session: sessionKind,
30
+ threadId: sessionKind === 'thread' ? session.threadId : undefined,
25
31
  peerId,
26
- sessionKey: sameConversation && !isGroup ? session.sessionKey : undefined,
27
- delivery: { chatType: 'private' },
32
+ sessionKey: sameConversation ? session.sessionKey : undefined,
33
+ delivery: isGroup ? { chatType: 'group', groupId: channelId } : { chatType: 'private' },
28
34
  };
29
35
  }
30
36
  export function daemonPrivilegedTriggerReason(definition) {
@@ -76,6 +82,36 @@ export function validateTriggerFeedbackChannels(definition, getTriggerChannel) {
76
82
  export function hasDaemonTriggerAuthority(subject) {
77
83
  return subject.isDaemonOwner || subject.processRole === 'daemon-service' || subject.dataScope === 'daemon';
78
84
  }
85
+ function triggerOriginChatType(origin) {
86
+ return origin.delivery?.chatType ?? 'private';
87
+ }
88
+ function feedbackTargetMatchesOrigin(target, origin) {
89
+ return target.channelKey === origin.channelKey
90
+ && target.channelId === origin.channelId
91
+ && target.session === origin.session
92
+ && (target.threadId ?? '') === (origin.threadId ?? '')
93
+ && (target.chatType === undefined || target.chatType === triggerOriginChatType(origin));
94
+ }
95
+ function feedbackTargetIsSamePeerThread(definition, target, origin) {
96
+ if (!target || !origin || target.session !== 'thread' || !target.threadId)
97
+ return false;
98
+ if (target.channelKey !== origin.channelKey || target.channelId !== origin.channelId)
99
+ return false;
100
+ const originChatType = triggerOriginChatType(origin);
101
+ if (target.chatType !== originChatType)
102
+ return false;
103
+ return !!resolveThreadRouteTarget(definition.agentAid, target.threadId, target.chatType === 'group' ? 'group' : 'msg', target.channelId);
104
+ }
105
+ function feedbackTargetAllowedForNonManagement(definition, origin) {
106
+ const target = definition.feedback.target;
107
+ if (!target || !origin)
108
+ return false;
109
+ if (feedbackTargetMatchesOrigin(target, origin))
110
+ return true;
111
+ if (target.chatType === 'group')
112
+ return false;
113
+ return feedbackTargetIsSamePeerThread(definition, target, origin);
114
+ }
79
115
  export function validateTriggerDefinitionForActor(definition, actor, dependencies) {
80
116
  if (definition.execution.permissionMode === 'fullaccess') {
81
117
  if (!actor.daemonOwner && !actor.fullAccess) {
@@ -127,14 +163,8 @@ export function validateTriggerDefinitionForActor(definition, actor, dependencie
127
163
  throw new Error('event-session feedback requires owner/admin');
128
164
  }
129
165
  if (!actor.management && definition.feedback.strategy === 'target') {
130
- const target = definition.feedback.target;
131
- const origin = actor.origin;
132
- if (!target || !origin
133
- || target.channelKey !== origin.channelKey
134
- || target.channelId !== origin.channelId
135
- || target.session !== origin.session
136
- || (target.threadId ?? '') !== (origin.threadId ?? '')) {
137
- throw new Error('non-management trigger target must be the creator private conversation');
166
+ if (!feedbackTargetAllowedForNonManagement(definition, actor.origin)) {
167
+ throw new Error('non-management trigger target must be the creator conversation or an existing same-peer private thread');
138
168
  }
139
169
  }
140
170
  }
@@ -187,13 +217,15 @@ export function authorizeTriggerExecution(definition, dependencies) {
187
217
  const channelType = origin.channelType || tryParseChannelKey(origin.channelKey)?.type;
188
218
  if (!channelType)
189
219
  return { allowed: false, reason: 'trigger origin channel type is missing' };
220
+ const chatType = triggerOriginChatType(origin);
221
+ const conversationId = chatType === 'group' ? origin.channelId : origin.peerId;
190
222
  const roleDetail = resolvePeerRoleDetail({
191
223
  selfAid: definition.agentAid,
192
224
  channelKey: origin.channelKey,
193
225
  channelType,
194
- chatType: 'private',
226
+ chatType,
195
227
  actorId: origin.peerId,
196
- conversationId: origin.peerId,
228
+ conversationId,
197
229
  });
198
230
  const role = roleDetail.effectiveRole;
199
231
  if (!checkRoleAccess(role, definition.agentAid)) {
@@ -205,8 +237,8 @@ export function authorizeTriggerExecution(definition, dependencies) {
205
237
  channel: origin.channelKey,
206
238
  channelType,
207
239
  channelId: origin.channelId,
208
- chatType: 'private',
209
- conversationId: origin.peerId,
240
+ chatType,
241
+ conversationId,
210
242
  roleDetail,
211
243
  identity: roleToSessionIdentity(role),
212
244
  processOwners: dependencies.getDaemonOwners(),
@@ -236,13 +268,8 @@ export function authorizeTriggerExecution(definition, dependencies) {
236
268
  return { allowed: false, reason: 'event-session feedback requires current owner/admin role' };
237
269
  }
238
270
  if (definition.feedback.strategy === 'target' && !isManagementRole(role)) {
239
- const target = definition.feedback.target;
240
- if (!target
241
- || target.channelKey !== origin.channelKey
242
- || target.channelId !== origin.channelId
243
- || target.session !== origin.session
244
- || (target.threadId ?? '') !== (origin.threadId ?? '')) {
245
- return { allowed: false, reason: `cross-conversation target requires current owner/admin role (role=${role ?? 'none'})` };
271
+ if (!feedbackTargetAllowedForNonManagement(definition, origin)) {
272
+ return { allowed: false, reason: `cross-conversation target requires current owner/admin role or an existing same-peer private thread (role=${role ?? 'none'})` };
246
273
  }
247
274
  }
248
275
  const permissionDecision = constrainRuntimePermissionMode({
@@ -314,13 +341,15 @@ export async function authenticatedTriggerActor(dependencies, agentAid, actorSes
314
341
  if (validation.grant.actorId !== actorOrigin.peerId) {
315
342
  throw new Error('trigger delegation actor does not match session peer');
316
343
  }
344
+ const actorChatType = triggerOriginChatType(actorOrigin);
345
+ const actorConversationId = actorChatType === 'group' ? actorOrigin.channelId : actorOrigin.peerId;
317
346
  const roleDetail = resolvePeerRoleDetail({
318
347
  selfAid: agentAid,
319
348
  channelKey: validation.grant.channel,
320
349
  channelType: validation.grant.channelType,
321
- chatType: 'private',
350
+ chatType: actorChatType,
322
351
  actorId: validation.grant.actorId,
323
- conversationId: validation.grant.actorId,
352
+ conversationId: actorConversationId,
324
353
  });
325
354
  const currentRole = roleDetail.effectiveRole;
326
355
  const subject = buildAuthSubject({
@@ -329,8 +358,8 @@ export async function authenticatedTriggerActor(dependencies, agentAid, actorSes
329
358
  channel: validation.grant.channel,
330
359
  channelType: validation.grant.channelType,
331
360
  channelId: actorOrigin.channelId,
332
- chatType: 'private',
333
- conversationId: validation.grant.actorId,
361
+ chatType: actorChatType,
362
+ conversationId: actorConversationId,
334
363
  roleDetail,
335
364
  identity: roleToSessionIdentity(currentRole),
336
365
  processOwners: dependencies.getDaemonOwners(),
@@ -272,10 +272,16 @@ export class TriggerFeedbackDispatcher {
272
272
  originChannelKey: eventSession ? target.channelKey : input.trigger.origin?.channelKey,
273
273
  originChannelType: eventSession ? channelType : input.trigger.origin?.channelType
274
274
  ?? (input.trigger.origin?.channelKey ? channelTypeFromKey(input.trigger.origin.channelKey) : undefined),
275
- ...(eventSession ? { originChatType: messageChatType, originConversationId: target.channelId } : {}),
275
+ originChatType: eventSession ? messageChatType : input.trigger.origin?.delivery?.chatType,
276
+ originConversationId: eventSession
277
+ ? target.channelId
278
+ : input.trigger.origin?.delivery?.chatType === 'group'
279
+ ? input.trigger.origin.channelId
280
+ : input.trigger.origin?.peerId,
276
281
  originControl: !eventSession && input.trigger.origin?.channelKey === 'daemon',
277
282
  noopSentinel: input.trigger.execution.noopSentinel,
278
283
  authorizedBy: input.trigger.authorizedBy,
284
+ labels: input.trigger.labels,
279
285
  },
280
286
  };
281
287
  outcome = this.waitForTriggerOutcome(input.runId, input.attemptId, delivery.signal);
@@ -297,7 +303,12 @@ export class TriggerFeedbackDispatcher {
297
303
  });
298
304
  await binding.adapter.send(envelope, { kind: 'status.error',
299
305
  metadata: { errorType: 'orphaned_target_session', message: text } });
300
- await binding.adapter.send(envelope, { kind: 'result.error', reason: 'orphaned_target_session', text });
306
+ await binding.adapter.send(envelope, {
307
+ kind: 'result.error',
308
+ subtype: 'session_unavailable',
309
+ reason: 'orphaned_target_session',
310
+ text,
311
+ });
301
312
  };
302
313
  const inspect = async () => {
303
314
  if (!this.deps.inspectTargetExecution || disposed || delivery.signal.aborted)
@@ -11,7 +11,7 @@ const TRIGGER_UPDATE_FLAGS = new Set([
11
11
  ]);
12
12
  const TRIGGER_CREATE_FLAGS = new Set([
13
13
  'once', 'delay', 'at', 'cron', 'every', 'event', 'event-filter', 'tz',
14
- 'exec', 'prompt', 'name', 'agent',
14
+ 'exec', 'prompt', 'name', 'description', 'agent', 'label',
15
15
  'script-path', 'script-runtime', 'script-args', 'script-timeout', 'execution-timeout',
16
16
  'feedback', 'target-channel', 'target-channel-id', 'target-chat-type', 'target-session', 'target-thread-id',
17
17
  'trigger-thread', 'baseagent',
@@ -68,6 +68,60 @@ export function parseDuration(s) {
68
68
  const multiplier = unit === 'd' ? 86_400_000 : unit === 'h' ? 3_600_000 : unit === 'm' ? 60_000 : 1_000;
69
69
  return amount * multiplier;
70
70
  }
71
+ /**
72
+ * T10: 从原始 args 里提取 `--label key=value`(支持多次出现,值内可用逗号分隔多对)。
73
+ * 约定:labels 若存在则必须含 `kind`(渲染模板路由 key)。
74
+ */
75
+ export function extractLabels(args) {
76
+ const tokens = [];
77
+ if (Array.isArray(args)) {
78
+ for (let i = 0; i < args.length; i++) {
79
+ const a = args[i];
80
+ if (a === '--label') {
81
+ const next = args[i + 1];
82
+ if (next === undefined || next.startsWith('--'))
83
+ return { error: '--label 不能为空(格式:--label key=value)' };
84
+ tokens.push(next);
85
+ i += 1;
86
+ }
87
+ else if (a.startsWith('--label=')) {
88
+ tokens.push(a.slice('--label='.length));
89
+ }
90
+ }
91
+ }
92
+ else {
93
+ const re = /--label(?:=(\S+)|(?:\s+(?!--)(\S+)))?/g;
94
+ let m;
95
+ while ((m = re.exec(args)) !== null) {
96
+ const v = m[1] ?? m[2];
97
+ if (v === undefined)
98
+ return { error: '--label 不能为空(格式:--label key=value)' };
99
+ tokens.push(v.replace(/^["']|["']$/g, ''));
100
+ }
101
+ }
102
+ if (tokens.length === 0)
103
+ return { labels: {} };
104
+ const labels = {};
105
+ for (const token of tokens) {
106
+ for (const pair of token.split(',')) {
107
+ const trimmed = pair.trim();
108
+ if (!trimmed)
109
+ continue;
110
+ const eq = trimmed.indexOf('=');
111
+ if (eq <= 0)
112
+ return { error: `--label 格式无效: "${trimmed}"(应为 key=value)` };
113
+ const key = trimmed.slice(0, eq).trim();
114
+ const value = trimmed.slice(eq + 1);
115
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
116
+ return { error: `--label key 名无效: "${key}"(只允许字母、数字、下划线,且不以数字开头)` };
117
+ }
118
+ labels[key] = value;
119
+ }
120
+ }
121
+ if (!('kind' in labels))
122
+ return { error: '--label 必须包含 kind(如 --label kind=default)' };
123
+ return { labels };
124
+ }
71
125
  export function parseIsoDate(s) {
72
126
  const d = new Date(s);
73
127
  return isNaN(d.getTime()) ? null : d.getTime();
@@ -390,6 +444,9 @@ function commonParsed(flags, opts = {}) {
390
444
  return { ok: false, error: '--script-args 必须是合法的 JSON' };
391
445
  }
392
446
  }
447
+ const description = optionalFlagString(flags, 'description');
448
+ if ('error' in description)
449
+ return { ok: false, error: description.error };
393
450
  const prompt = flags.get('prompt');
394
451
  if (prompt === true)
395
452
  return { ok: false, error: '--prompt 不能为空;复杂内容建议改用 --prompt-file <path>' };
@@ -471,6 +528,8 @@ function commonParsed(flags, opts = {}) {
471
528
  targetThreadId: targetThreadId,
472
529
  agentId: flags.has('agent') ? flags.get('agent') : undefined,
473
530
  name: flags.has('name') ? flags.get('name') : undefined,
531
+ // create 语义:空字符串等同于「未提供」,不写该字段(与 update 的清除语义不同)。
532
+ description: description.value || undefined,
474
533
  prompt: prompt,
475
534
  scriptPath: scriptPath,
476
535
  scriptRuntime: scriptRuntime,
@@ -496,6 +555,9 @@ export function parseTriggerSet(args) {
496
555
  if (!parsed.ok)
497
556
  return { ok: false, error: withPromptFileHint(parsed.error, args) };
498
557
  const value = parsed.value;
558
+ const labelsResult = extractLabels(args);
559
+ if ('error' in labelsResult)
560
+ return { ok: false, error: labelsResult.error };
499
561
  return {
500
562
  ok: true,
501
563
  value: {
@@ -512,6 +574,7 @@ export function parseTriggerSet(args) {
512
574
  targetThreadId: value.targetThreadId,
513
575
  agentId: value.agentId,
514
576
  name: value.name,
577
+ description: value.description,
515
578
  prompt: value.prompt,
516
579
  scriptPath: value.scriptPath,
517
580
  scriptRuntime: value.scriptRuntime,
@@ -525,6 +588,7 @@ export function parseTriggerSet(args) {
525
588
  model: value.model,
526
589
  effort: value.effort,
527
590
  permissionMode: value.permissionMode,
591
+ ...(Object.keys(labelsResult.labels).length > 0 ? { labels: labelsResult.labels } : {}),
528
592
  },
529
593
  };
530
594
  }
@@ -538,6 +602,9 @@ export function parseTriggerSetArgv(args, opts = {}) {
538
602
  if (!parsed.ok)
539
603
  return { ok: false, error: withPromptFileHint(parsed.error, args, opts) };
540
604
  const value = parsed.value;
605
+ const labelsResult = extractLabels(args);
606
+ if ('error' in labelsResult)
607
+ return { ok: false, error: labelsResult.error };
541
608
  return {
542
609
  ok: true,
543
610
  value: {
@@ -554,6 +621,7 @@ export function parseTriggerSetArgv(args, opts = {}) {
554
621
  targetThreadId: value.targetThreadId,
555
622
  agentId: value.agentId,
556
623
  name: value.name,
624
+ description: value.description,
557
625
  prompt: value.prompt,
558
626
  scriptPath: value.scriptPath,
559
627
  scriptRuntime: value.scriptRuntime,
@@ -567,6 +635,7 @@ export function parseTriggerSetArgv(args, opts = {}) {
567
635
  model: value.model,
568
636
  effort: value.effort,
569
637
  permissionMode: value.permissionMode,
638
+ ...(Object.keys(labelsResult.labels).length > 0 ? { labels: labelsResult.labels } : {}),
570
639
  },
571
640
  };
572
641
  }
@@ -121,7 +121,7 @@ export function seedTaskRecoveryTrigger(manager, owner) {
121
121
  fs.rmSync(manager.activePath(definition.id), { force: true });
122
122
  }
123
123
  const SESSION_IDLE_CHECK_PROMPT = `本会话已闲置一段时间。请依据你的职责执行一次例行巡检:
124
- 1. 判断群内是否存在成员有未完成的工作却停滞(如长时间无进展、被点名未回应、承诺后失联)。有,则向该成员催促一次并说明停滞事项,随后即止;无,则不做任何动作。大家都在正常工作或无待办时保持沉默。
124
+ 1. 检查当前会话中未完成却停滞的事项。私聊检查自己承诺或正在推进的任务;群聊还需检查成员长时间无进展、被点名未回应、承诺后失联等情况。有,则在既有授权和职责内推进一次,或向相关成员催促一次并说明停滞事项,随后即止;无,则不做任何动作。不要自行新增任务,正常推进或无待办时保持沉默。
125
125
  2. 巡检优先基于已有上下文判断,确需补充背景再查询历史,并遵守会话自身的发送规范。
126
126
  3. 判定无需任何对外动作时,本轮只输出 [[NOOP]],不发送任何消息、不发布巡检结论。注意:最终对外输出必须是且仅是 [[NOOP]] 这一行——不要在输出它之前先发送任何说明文字或巡检判定,判定过程仅在内部完成。
127
127
  (本指令为通用模板,可按具体职责增删检查项。)`;
@@ -133,7 +133,7 @@ export function seedSessionIdleCheckTrigger(manager, owner) {
133
133
  agentAid: owner.aid,
134
134
  enabled: true,
135
135
  name: '__session-idle-check',
136
- description: 'System trigger: inspect configured idle group sessions.',
136
+ description: 'System trigger: inspect configured idle private and group sessions.',
137
137
  createdAt: now,
138
138
  updatedAt: now,
139
139
  origin: {
@@ -147,7 +147,7 @@ export function seedSessionIdleCheckTrigger(manager, owner) {
147
147
  source: {
148
148
  type: 'event',
149
149
  eventPattern: 'session:idle',
150
- filter: { match: { chatType: 'group' } },
150
+ filter: { match: { chatType: { $in: ['private', 'group'] } } },
151
151
  },
152
152
  execution: {
153
153
  type: 'target_session',
@@ -252,6 +252,20 @@ function normalizeV3Phase2(raw, opts) {
252
252
  reliability: normalizeReliability(raw.reliability),
253
253
  limits: normalizeLimits(raw.limits),
254
254
  };
255
+ // T10: labels 字典原样保留,只做类型校验(如果存在必须是 Record<string, string>)
256
+ const rawLabels = raw.labels;
257
+ if (rawLabels !== undefined && rawLabels !== null) {
258
+ if (typeof rawLabels !== 'object' || Array.isArray(rawLabels)) {
259
+ throw new Error('labels must be a plain object');
260
+ }
261
+ const labels = {};
262
+ for (const [k, v] of Object.entries(rawLabels)) {
263
+ if (typeof v !== 'string')
264
+ throw new Error(`labels.${k} must be a string`);
265
+ labels[k] = v;
266
+ }
267
+ definition.labels = labels;
268
+ }
255
269
  validateTriggerSemantics(definition);
256
270
  return definition;
257
271
  }
@@ -666,6 +666,13 @@ export async function waitForProcessExit(pid, timeoutMs = 10_000, intervalMs = 2
666
666
  * Returns list of matching PIDs.
667
667
  */
668
668
  export function findProcesses(pattern) {
669
+ return findProcessesWithCommandLine(pattern).map(match => match.pid);
670
+ }
671
+ /**
672
+ * Like findProcesses, but keeps the command line returned by the Windows CIM
673
+ * snapshot so callers do not need a second PowerShell launch per candidate.
674
+ */
675
+ export function findProcessesWithCommandLine(pattern) {
669
676
  try {
670
677
  if (isWindows) {
671
678
  // WMI LIKE is wildcard matching, not JavaScript regex matching. Query a
@@ -696,13 +703,15 @@ export function findProcesses(pattern) {
696
703
  const normalized = commandLine.replace(/\\/g, '/');
697
704
  return matcherCandidates.some(matcher => matcher.test(commandLine) || matcher.test(normalized));
698
705
  })
699
- .map(row => row.ProcessId);
706
+ .map(row => ({ pid: row.ProcessId, commandLine: row.CommandLine || '' }));
700
707
  }
701
708
  return [];
702
709
  }
703
710
  else {
704
711
  const output = execFileSync('pgrep', ['-f', pattern], { encoding: 'utf-8' }).trim();
705
- return output ? output.split('\n').map(Number).filter(pid => pid !== process.pid) : [];
712
+ return output
713
+ ? output.split('\n').map(Number).filter(pid => pid !== process.pid).map(pid => ({ pid }))
714
+ : [];
706
715
  }
707
716
  }
708
717
  catch {
@@ -85,11 +85,25 @@
85
85
  "message": "❌ 当前模型不受支持,请正确配置模型后重试"
86
86
  },
87
87
  {
88
- "id": "api-error-403",
89
- "match": "api error: 403",
90
- "action": "retry",
88
+ "id": "claude-client-upgrade-403-cn",
89
+ "match": "api error: 403 请升级claude code 客户端到最新版",
90
+ "action": "stop",
91
+ "type": "api_error",
92
+ "message": "⚠️ Claude Code 客户端版本过旧,请升级到最新版后重试"
93
+ },
94
+ {
95
+ "id": "insufficient-credit-403-cn",
96
+ "match": "api error: 403 预扣后为-",
97
+ "action": "stop",
91
98
  "type": "api_error",
92
- "message": "⚠️ API 访问受限,请稍后重试"
99
+ "message": "⚠️ 积分余额不足,请充值积分或升级套餐后重试"
100
+ },
101
+ {
102
+ "id": "team-context-limit-403-cn",
103
+ "match": "api error: 403 请求上下文约",
104
+ "action": "stop",
105
+ "type": "context_too_long",
106
+ "message": "⚠️ 请求已超过团队上下文上限,请使用 /compact 压缩上下文后重试,或联系管理员调整上下文配额"
93
107
  },
94
108
  {
95
109
  "id": "api-error-429",
@@ -91,6 +91,117 @@ export var ErrorType;
91
91
  ErrorType["CLAUDE_CODE_STARTUP_FAILED"] = "claude_code_startup_failed";
92
92
  ErrorType["UNKNOWN"] = "unknown";
93
93
  })(ErrorType || (ErrorType = {}));
94
+ export function getErrorRawText(error, seen = new Set()) {
95
+ if (typeof error === 'string') {
96
+ const text = error.trim();
97
+ return text || undefined;
98
+ }
99
+ if (error === null || error === undefined)
100
+ return undefined;
101
+ if (typeof error === 'object') {
102
+ const record = error;
103
+ if (seen.has(error))
104
+ return undefined;
105
+ seen.add(error);
106
+ if ('raw' in record) {
107
+ const raw = record.raw;
108
+ if (typeof raw === 'string' && raw.trim())
109
+ return raw.trim();
110
+ }
111
+ const causeText = getErrorRawText(record.cause, seen);
112
+ if (causeText)
113
+ return causeText;
114
+ for (const key of ['message', 'error', 'reason']) {
115
+ const value = record[key];
116
+ if (typeof value === 'string' && value.trim())
117
+ return value.trim();
118
+ if (value && typeof value === 'object') {
119
+ const nestedText = getErrorRawText(value, seen);
120
+ if (nestedText)
121
+ return nestedText;
122
+ }
123
+ }
124
+ const code = record.code;
125
+ if (typeof code === 'string' && code.trim())
126
+ return code.trim();
127
+ return undefined;
128
+ }
129
+ const text = String(error).trim();
130
+ return text || undefined;
131
+ }
132
+ export function normalizeCoreErrorSubtype(error, reportedSubtype, terminalReason) {
133
+ const reported = reportedSubtype?.trim().toLowerCase();
134
+ if (error instanceof OrphanedTargetSessionError)
135
+ return ErrorType.SESSION_UNAVAILABLE;
136
+ if (error instanceof ExecutionTimeoutError)
137
+ return ErrorType.SDK_TIMEOUT;
138
+ if (terminalReason === 'prompt_too_long' || terminalReason === 'context_compact_failed') {
139
+ return ErrorType.CONTEXT_TOO_LONG;
140
+ }
141
+ if (terminalReason === 'max_output_tokens')
142
+ return ErrorType.MAX_OUTPUT_TOKENS;
143
+ if (terminalReason === 'model_error')
144
+ return ErrorType.MODEL_UNAVAILABLE;
145
+ if (terminalReason === 'permission_denied')
146
+ return 'permission_error';
147
+ if (terminalReason === 'timeout' || terminalReason === 'total_execution_timeout')
148
+ return ErrorType.SDK_TIMEOUT;
149
+ if (reported === 'protocol_incomplete')
150
+ return 'protocol_incomplete';
151
+ if (reported === 'permission_denied' || reported?.startsWith('permission_'))
152
+ return 'permission_error';
153
+ const classified = classifyError(typeof error === 'string' ? new Error(error) : error);
154
+ if (classified !== ErrorType.UNKNOWN)
155
+ return classified;
156
+ if (reported && Object.values(ErrorType).includes(reported))
157
+ return reported;
158
+ switch (reported) {
159
+ case 'error_api':
160
+ case 'api':
161
+ case 'infrastructure':
162
+ return ErrorType.API_ERROR;
163
+ case 'error_model':
164
+ case 'model_error':
165
+ return ErrorType.MODEL_UNAVAILABLE;
166
+ case 'error_tool_use':
167
+ case 'tool_error':
168
+ return ErrorType.STREAM_ERROR;
169
+ }
170
+ if (terminalReason === 'rapid_refill_breaker')
171
+ return ErrorType.API_ERROR;
172
+ if (terminalReason === 'tool_error')
173
+ return ErrorType.STREAM_ERROR;
174
+ if (reported)
175
+ return reported;
176
+ return ErrorType.UNKNOWN;
177
+ }
178
+ export function normalizeCoreErrorReason(reason) {
179
+ const normalized = reason?.trim().toLowerCase();
180
+ switch (normalized) {
181
+ case 'retry.exhausted':
182
+ return 'retry_exhausted';
183
+ case 'sdk_timeout':
184
+ case 'total_execution_timeout':
185
+ return 'timeout';
186
+ case 'aborted_streaming':
187
+ case 'aborted_tools':
188
+ case 'stop':
189
+ return 'cancelled';
190
+ case 'protocol_incomplete':
191
+ case 'max_output_tokens':
192
+ case 'target_session_timeout':
193
+ case 'retry_exhausted':
194
+ case 'timeout':
195
+ case 'cancelled':
196
+ case 'permission_denied':
197
+ case 'input_already_submitted':
198
+ case 'fallback_exhausted':
199
+ case 'orphaned_target_session':
200
+ return normalized;
201
+ default:
202
+ return undefined;
203
+ }
204
+ }
94
205
  /**
95
206
  * 错误来源前缀 — 区分基础设施异常 vs Agent 任务失败
96
207
  *
@@ -466,7 +577,7 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
466
577
  }
467
578
  }
468
579
  // 回退到原有的错误消息匹配逻辑
469
- const msg = error?.message || String(error);
580
+ const msg = resolveErrorMessage(error);
470
581
  const warnPrefix = includeEmoji ? '⚠️ ' : '';
471
582
  const errPrefix = includeEmoji ? '❌ ' : '';
472
583
  if (/invalid (?:jsonl )?session file/i.test(msg)) {
@@ -512,5 +623,36 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
512
623
  if (msg.includes('timeout')) {
513
624
  return `${warnPrefix}请求超时,请重试`;
514
625
  }
626
+ if (msg)
627
+ return `${errPrefix}暂无法判定错误原因:${msg}`;
515
628
  return `${errPrefix}处理消息时出错,请稍后重试`;
516
629
  }
630
+ export function getToolResultErrorText(event) {
631
+ const text = event.error || event.reason
632
+ || (event.name === 'Shell'
633
+ ? typeof event.exitCode === 'number' && Number.isInteger(event.exitCode)
634
+ ? 'exitCode: ' + event.exitCode
635
+ : '执行失败'
636
+ : typeof event.result === 'string' ? event.result : JSON.stringify(event.result))
637
+ || '执行失败';
638
+ return text.replace(/<tool_use_error>(.*?)<\/tool_use_error>/gs, '$1');
639
+ }
640
+ function resolveErrorMessage(error) {
641
+ if (typeof error === 'string')
642
+ return error.trim();
643
+ if (error === null || error === undefined)
644
+ return '';
645
+ if (typeof error === 'object') {
646
+ const message = 'message' in error ? String(error.message ?? '').trim() : '';
647
+ if (message)
648
+ return message;
649
+ const code = 'code' in error ? String(error.code ?? '').trim() : '';
650
+ if (code)
651
+ return code;
652
+ if ('message' in error)
653
+ return '';
654
+ const stringified = String(error).trim();
655
+ return stringified === '[object Object]' || stringified === 'Error' ? '' : stringified;
656
+ }
657
+ return String(error).trim();
658
+ }
@@ -11,8 +11,8 @@
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
13
  import { spawnSync } from 'child_process';
14
- import { getPackageRoot, resolvePaths } from '../paths.js';
15
- import { isProcessRunning, killProcess, isWindows, findProcesses } from './cross-platform.js';
14
+ import { getPackageRoot, resolvePaths, isDevMode } from '../paths.js';
15
+ import { isProcessRunning, killProcess, isWindows, findProcessesWithCommandLine } from './cross-platform.js';
16
16
  import { getProcessStartTime, startTimeMatches } from './process-introspect.js';
17
17
  import { isConfirmedLeakedTestDaemon, runtimeHomeFromEnv } from './restart-safety.js';
18
18
  import { decodeWindowsOutput } from './windows-output.js';
@@ -55,6 +55,7 @@ export function writeMain(launchedBy) {
55
55
  startedAt,
56
56
  startedAtIso: new Date(startedAt).toISOString(),
57
57
  launchedBy,
58
+ runMode: isDevMode() ? 'dev' : 'install',
58
59
  };
59
60
  const filePath = path.join(dir, mainFileName(process.pid));
60
61
  writeAtomic(filePath, JSON.stringify(record, null, 2));
@@ -376,10 +377,10 @@ export function isEvolCoreMainCommand(cmdline, packageRoot = getPackageRoot()) {
376
377
  *
377
378
  * 不会主动 kill——清理由调用方决定。
378
379
  */
379
- export function findOrphanProcesses() {
380
- // 1. 已登记 PID(自己 HOME 下的 main + 自己进程)
380
+ export function findOrphanProcesses(status = scanInstances()) {
381
+ // 1. 已登记 PID(自己 HOME 下的 main + 自己进程)。调用方刚扫过登记簿时可直接传入,
382
+ // Windows 上每次 scanInstances 都要为活实例跑一遍 CIM 查询。
381
383
  const known = new Set([process.pid]);
382
- const status = scanInstances();
383
384
  for (const m of status.mains) {
384
385
  if (m.alive)
385
386
  known.add(m.record.pid);
@@ -393,14 +394,18 @@ export function findOrphanProcesses() {
393
394
  // regex contains escaped separators and is applied after command lines have
394
395
  // been fetched; embedding it in the CIM pre-filter can silently miss a
395
396
  // backslash-delimited `dist\\index.js` command on some PowerShell builds.
396
- const candidates = findProcesses('index\\.js');
397
+ // `index.js` 会命中所有 Node 服务(各会话拉起的 MCP server 常有十几个),
398
+ // 所以先用快照里的命令行过滤,只对真正的 EvolCore 入口做后续探测。
399
+ const candidates = findProcessesWithCommandLine('index\\.js');
397
400
  const orphans = [];
398
- for (const pid of candidates) {
401
+ for (const { pid, commandLine } of candidates) {
399
402
  if (known.has(pid))
400
403
  continue;
404
+ if (commandLine !== undefined && !isEvolCoreMainCommand(commandLine))
405
+ continue;
401
406
  if (!isProcessRunning(pid))
402
407
  continue;
403
- const cmdline = readCmdline(pid);
408
+ const cmdline = commandLine || readCmdline(pid);
404
409
  // 二次验证:入口必须是当前 EvolCore 包的 dist/index.js。
405
410
  if (!isEvolCoreMainCommand(cmdline))
406
411
  continue;