evolcore 0.0.20 → 0.0.22

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 (147) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +58 -9
  3. package/bin/codex-managed-hook.mjs +3 -0
  4. package/bin/install-codex-managed-hooks.mjs +3 -1
  5. package/dist/agents/baseagent.js +10 -6
  6. package/dist/agents/claude-runner.js +393 -108
  7. package/dist/agents/codex-app-server-client.js +41 -7
  8. package/dist/agents/codex-runner.js +1292 -220
  9. package/dist/agents/ecagent-runner.js +171 -61
  10. package/dist/agents/gemini-runner.js +130 -30
  11. package/dist/agents/request-identity.js +25 -0
  12. package/dist/agents/runner-types.js +19 -0
  13. package/dist/aun/aid/agentmd.js +59 -2
  14. package/dist/aun/aid/identity.js +4 -1
  15. package/dist/aun/aid/index.js +1 -1
  16. package/dist/aun/msg/group.js +72 -6
  17. package/dist/aun/msg/history.js +213 -36
  18. package/dist/aun/msg/managed-operation.js +58 -9
  19. package/dist/aun/msg/p2p.js +5 -0
  20. package/dist/aun/outbox.js +189 -80
  21. package/dist/aun/service-proxy.js +43 -25
  22. package/dist/channels/aun.js +618 -123
  23. package/dist/channels/daemon.js +6 -1
  24. package/dist/cli/agent-command.js +4 -3
  25. package/dist/cli/agent.js +66 -56
  26. package/dist/cli/aun-commands.js +177 -42
  27. package/dist/cli/command-log.js +10 -11
  28. package/dist/cli/contact.js +1 -0
  29. package/dist/cli/daemon-commands.js +98 -123
  30. package/dist/cli/init.js +27 -15
  31. package/dist/cli/task-context.js +50 -0
  32. package/dist/cli/trigger-command.js +14 -5
  33. package/dist/cli/watch-logs.js +10 -3
  34. package/dist/config/builtin-roles.js +1 -0
  35. package/dist/config/config-field-policy.js +19 -5
  36. package/dist/config/config-manager.js +167 -22
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/contact-operation-service.js +32 -1
  39. package/dist/config/contact-request-service.js +44 -0
  40. package/dist/config/daemon-services.js +186 -0
  41. package/dist/config/gateway-config.js +20 -9
  42. package/dist/config/role-service.js +54 -3
  43. package/dist/config/schema-migration.js +550 -0
  44. package/dist/config-store.js +151 -9
  45. package/dist/core/agent-application-service.js +279 -0
  46. package/dist/core/audit/log-integrity.js +102 -0
  47. package/dist/core/auth/agent-delegation.js +43 -1
  48. package/dist/core/auth/auth-gateway.js +41 -4
  49. package/dist/core/auth/authorization-audit.js +216 -8
  50. package/dist/core/auth/operation-authorizer.js +41 -1
  51. package/dist/core/auth/operation-catalog.js +9 -1
  52. package/dist/core/bootstrap-messages.js +8 -0
  53. package/dist/core/bootstrap-service.js +99 -27
  54. package/dist/core/causation/aun-association.js +7 -4
  55. package/dist/core/command/agent-control.js +56 -16
  56. package/dist/core/command/command-handler.js +311 -44
  57. package/dist/core/command/connect-menu.js +3 -4
  58. package/dist/core/command/group-menu.js +5 -7
  59. package/dist/core/command/menu-handler.js +288 -80
  60. package/dist/core/command/menu-protocol.js +1 -1
  61. package/dist/core/command/role-menu.js +21 -11
  62. package/dist/core/command/slash-gate.js +85 -18
  63. package/dist/core/command/slash-handler.js +377 -36
  64. package/dist/core/data-migration.js +11 -1
  65. package/dist/core/event-catalog.js +37 -0
  66. package/dist/core/evolagent.js +4 -0
  67. package/dist/core/handoff/dispatcher.js +4 -0
  68. package/dist/core/handoff/runtime.js +33 -3
  69. package/dist/core/handoff/store.js +32 -9
  70. package/dist/core/inference/text-inference.js +7 -15
  71. package/dist/core/message/im-renderer.js +90 -87
  72. package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
  73. package/dist/core/message/message-bridge.js +184 -12
  74. package/dist/core/message/message-log.js +47 -7
  75. package/dist/core/message/message-queue.js +227 -16
  76. package/dist/core/message/message-utils.js +12 -5
  77. package/dist/core/message/response-engine.js +658 -109
  78. package/dist/core/message/send-receipt.js +1 -0
  79. package/dist/core/message/stream-debouncer.js +9 -2
  80. package/dist/core/model/model-catalog.js +23 -15
  81. package/dist/core/model/model-diagnostics.js +28 -10
  82. package/dist/core/permission/approval-gateway.js +180 -6
  83. package/dist/core/permission/ec-command-parser.js +627 -69
  84. package/dist/core/permission/mode.js +18 -3
  85. package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
  86. package/dist/core/permission/readonly-shell-query.js +263 -9
  87. package/dist/core/permission/sandbox-runtime.js +159 -1
  88. package/dist/core/permission/tool-error-code.js +12 -0
  89. package/dist/core/permission/tool-policy.js +618 -23
  90. package/dist/core/session/session-fs-store.js +154 -5
  91. package/dist/core/session/session-manager.js +329 -30
  92. package/dist/core/session/session-renew.js +37 -13
  93. package/dist/core/session/session-turn-coordinator.js +16 -5
  94. package/dist/eck/kit-renderer.js +1 -1
  95. package/dist/index.js +316 -50
  96. package/dist/ipc.js +459 -29
  97. package/dist/paths.js +82 -7
  98. package/dist/response-system/context-builder.js +1 -7
  99. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  100. package/dist/stats/price-resolver.js +4 -0
  101. package/dist/trigger/anomaly-store.js +1 -0
  102. package/dist/trigger/feedback.js +70 -7
  103. package/dist/trigger/history.js +79 -4
  104. package/dist/trigger/legacy-session-history.js +2 -2
  105. package/dist/trigger/parser.js +13 -3
  106. package/dist/trigger/scheduler.js +20 -3
  107. package/dist/trigger/validation.js +6 -1
  108. package/dist/utils/atomic-write.js +27 -0
  109. package/dist/utils/ecweb-utils.js +16 -2
  110. package/dist/utils/error-utils.js +4 -1
  111. package/dist/utils/logger.js +30 -6
  112. package/dist/utils/process-tree-stats.js +24 -4
  113. package/dist/utils/process-tree-worker.js +31 -0
  114. package/dist/utils/project-path.js +1 -2
  115. package/dist/utils/tool-summary.js +59 -0
  116. package/dist/utils/windows-shell-trust.js +201 -0
  117. package/kits/docs/INDEX.md +1 -1
  118. package/kits/docs/evolcore/INDEX.md +3 -3
  119. package/kits/docs/evolcore/agent-create.md +146 -0
  120. package/kits/docs/evolcore/agent.md +6 -0
  121. package/kits/docs/evolcore/contact.md +7 -1
  122. package/kits/docs/evolcore/group-collaboration.md +251 -0
  123. package/kits/docs/evolcore/group-rules.md +1 -19
  124. package/kits/docs/evolcore/group.md +3 -1
  125. package/kits/docs/evolcore/msg.md +16 -0
  126. package/kits/docs/evolcore/trigger.md +6 -3
  127. package/kits/docs/prompt-loading-architecture.md +6 -0
  128. package/kits/eck_message_manifest.json +6 -6
  129. package/kits/schemas/_meta.json +7 -4
  130. package/kits/schemas/agent-config.schema.11.json +13 -0
  131. package/kits/schemas/agent-config.schema.12.json +427 -0
  132. package/kits/schemas/daemon.schema.5.json +0 -1
  133. package/kits/schemas/daemon.schema.6.json +131 -0
  134. package/kits/schemas/defaults.schema.5.json +15 -3
  135. package/kits/schemas/migrations/README.md +3 -1
  136. package/kits/schemas/relation-config.schema.8.json +13 -0
  137. package/kits/schemas/role-config.schema.1.json +1 -2
  138. package/kits/schemas/single-session.schema.3.json +32 -0
  139. package/kits/templates/message-fragments/item.md +1 -1
  140. package/kits/templates/roles/admin.json +1 -0
  141. package/kits/templates/roles/member.json +1 -0
  142. package/kits/templates/roles/visitor.json +1 -0
  143. package/kits/templates/system-fragments/bootstrap.md +2 -1
  144. package/kits/templates/system-fragments/commands.md +2 -2
  145. package/package.json +6 -3
  146. package/skills/eclink/SKILL.md +2 -0
  147. package/dist/config/aun-gateway-config.js +0 -2
@@ -16,7 +16,7 @@ import { fileCache } from '../daemon-file-cache.js';
16
16
  import { currentVersion } from '../../config/schema-registry.js';
17
17
  import { loadSchema } from '../../config/schema-registry.js';
18
18
  import { rolePermissionFields, rolePermissionFieldOptions, rolePermissionFieldPatterns, validateRoleConfig, } from '../../config/role-schema.js';
19
- import { groupInfo, groupList } from '../../aun/msg/group.js';
19
+ import { groupInfo, groupList, groupDirectoryErrorCode } from '../../aun/msg/group.js';
20
20
  import { aidLookup, isValidAid } from '../../aun/aid/identity.js';
21
21
  import { isExplicitGroupId } from '../../aun/group-identity.js';
22
22
  import { readJsonFile, readLastJsonlLine, scanChatDirs, scanMetaFiles } from '../session/session-fs-store.js';
@@ -341,7 +341,7 @@ function assertRequest(context, args) {
341
341
  receivingSelf: context.self,
342
342
  });
343
343
  }
344
- if (context.actorRole !== 'owner' && context.actorRole !== 'admin') {
344
+ if (!isDaemonOwnerControl(context) && context.actorRole !== 'owner' && context.actorRole !== 'admin') {
345
345
  throw roleError('PERMISSION_DENIED', 'Role management requires Agent owner or admin', context.self, {
346
346
  kind: 'role_permission_denied',
347
347
  actorRole: context.actorRole,
@@ -350,7 +350,7 @@ function assertRequest(context, args) {
350
350
  }
351
351
  function assertRolePolicyMutationTarget(context, args) {
352
352
  const roleId = requiredRoleId(args, context.self);
353
- if (context.actorRole === 'admin' && roleId === 'owner') {
353
+ if (!isDaemonOwnerControl(context) && context.actorRole === 'admin' && roleId === 'owner') {
354
354
  throw roleError('PERMISSION_DENIED', 'Agent admin cannot modify the owner role policy', context.self, {
355
355
  kind: 'role_hierarchy_violation',
356
356
  actorRole: context.actorRole,
@@ -485,7 +485,7 @@ function updateDefaultRoles(context, args, value) {
485
485
  const registry = readRoleRegistry(self);
486
486
  const changesAdminDefault = ['private', 'group'].some(scene => registry.defaultRoles[scene] !== nextDefaults[scene]
487
487
  && (registry.defaultRoles[scene] === 'admin' || nextDefaults[scene] === 'admin'));
488
- if (context.actorRole !== 'owner' && changesAdminDefault) {
488
+ if (!isDaemonOwnerControl(context) && context.actorRole !== 'owner' && changesAdminDefault) {
489
489
  throw roleError('NOT_ALLOWED', 'Only an owner can set or replace an admin default role', self, {
490
490
  kind: 'admin_owner_only',
491
491
  });
@@ -521,7 +521,7 @@ async function updateAssignment(context, args, value) {
521
521
  });
522
522
  }
523
523
  const current = readRelation(target.peerKey, self);
524
- if (context.actorRole !== 'owner' && (roleId === 'admin' || current?.config.role === 'admin')) {
524
+ if (!isDaemonOwnerControl(context) && context.actorRole !== 'owner' && (roleId === 'admin' || current?.config.role === 'admin')) {
525
525
  throw roleError('NOT_ALLOWED', 'Only an owner can grant or revoke admin', self, {
526
526
  kind: 'admin_owner_only',
527
527
  target: target.peerKey,
@@ -545,7 +545,7 @@ async function updateAssignment(context, args, value) {
545
545
  }
546
546
  async function updateAdminAssignment(context, args, value) {
547
547
  const self = context.self;
548
- if (context.actorRole !== 'owner') {
548
+ if (!isDaemonOwnerControl(context) && context.actorRole !== 'owner') {
549
549
  throw roleError('NOT_ALLOWED', 'Only an owner can grant or revoke admin', self, { kind: 'admin_owner_only' });
550
550
  }
551
551
  let target = normalizeTarget(args?.target, self);
@@ -679,7 +679,7 @@ async function deleteRole(context, args) {
679
679
  const replacement = hasReplacement
680
680
  ? validateAssignableRole(args?.replacementRoleId, self, 'replacementRoleId')
681
681
  : undefined;
682
- if (replacement === 'admin' && context.actorRole !== 'owner') {
682
+ if (replacement === 'admin' && !isDaemonOwnerControl(context) && context.actorRole !== 'owner') {
683
683
  throw roleError('NOT_ALLOWED', 'Only an owner can replace role assignments with admin', self, {
684
684
  kind: 'admin_owner_only',
685
685
  });
@@ -778,13 +778,19 @@ async function targetSnapshot(context, args, policyRevision) {
778
778
  const self = context.self;
779
779
  const target = await validateTargetExists(context, normalizeTarget(args?.target, self), { allowAuthenticatedPrivateActor: true });
780
780
  const relation = readRelation(target.peerKey, self);
781
+ const roleView = targetRoleView(self, target, relation?.config ?? null);
782
+ const override = target.kind === 'private'
783
+ ? roleView.assignment.explicitRole
784
+ : roleView.assignment.explicitDefaultRole;
781
785
  return {
782
786
  $schema_version: ROLE_MENU_SCHEMA_VERSION,
783
787
  self,
784
788
  policyRevision,
785
789
  target,
786
790
  relationRevision: relation?.revision ?? null,
787
- ...targetRoleView(self, target, relation?.config ?? null),
791
+ scope: 'relation',
792
+ override,
793
+ ...roleView,
788
794
  ...(target.kind === 'group' ? { requesterRole: requesterRoleResult(context) } : {}),
789
795
  relationConfig: relationConfigView(context, target, relation?.config ?? null),
790
796
  };
@@ -926,10 +932,11 @@ async function validateTargetExists(context, target, options = {}) {
926
932
  }
927
933
  return target;
928
934
  }
929
- const info = await directory.getGroup({ from: context.self, groupId: target.groupId, required: ['member'] });
935
+ const info = await directory.getGroup({ from: context.self, groupId: target.groupId, required: ['member', 'state'] });
930
936
  if (!info.ok) {
931
- throw roleError('NOT_FOUND', info.error || 'Group Role target was not found', context.self, {
932
- kind: 'target_not_found', target,
937
+ const code = groupDirectoryErrorCode(info.code, info.error);
938
+ throw roleError(code, info.error || 'Unable to verify Group Role target', context.self, {
939
+ kind: code === 'NOT_FOUND' ? 'target_not_found' : 'target_source_unavailable', target,
933
940
  });
934
941
  }
935
942
  const group = info.group;
@@ -1168,6 +1175,9 @@ function requesterRoleResult(context) {
1168
1175
  : false,
1169
1176
  };
1170
1177
  }
1178
+ function isDaemonOwnerControl(context) {
1179
+ return context.isDaemonOwner === true && context.fromControlChannel === true;
1180
+ }
1171
1181
  function readAgent(self) {
1172
1182
  const agent = read(ConfigTarget.Agent, { self }, { cache: false });
1173
1183
  if (!agent)
@@ -1,7 +1,62 @@
1
+ /**
2
+ * Maps already-collected activity evidence to a command decision. Collection
3
+ * stays at the caller because topic management needs a barrier snapshot while
4
+ * legacy guards intentionally retain their narrower, non-atomic evidence.
5
+ */
6
+ export function evaluateSessionActivity(evidence) {
7
+ const signals = [
8
+ evidence.active,
9
+ evidence.dispatching,
10
+ evidence.processing,
11
+ evidence.activeTurn,
12
+ evidence.paused,
13
+ evidence.activeStream,
14
+ evidence.pendingPermission,
15
+ evidence.pendingInteraction,
16
+ evidence.activeHandoff,
17
+ evidence.openHandoffTarget,
18
+ ];
19
+ if ((evidence.prequeue ?? 0) > 0 || (evidence.queued ?? 0) > 0 || signals.some(signal => signal === true)) {
20
+ return 'busy';
21
+ }
22
+ return signals.some(signal => signal === 'unknown') ? 'unknown' : 'idle';
23
+ }
24
+ /** Collect synchronous probes consistently; a configured probe failure is unknown, not idle. */
25
+ export function probeSessionActivity(evidence, probes) {
26
+ const collected = { ...evidence };
27
+ // Queue/turn evidence is authoritative enough to reject immediately. Avoid
28
+ // touching secondary probes after BUSY is already proven (and preserve the
29
+ // existing paused-thread short-circuit behavior).
30
+ if (evaluateSessionActivity(collected) === 'busy')
31
+ return 'busy';
32
+ for (const key of Object.keys(probes)) {
33
+ const probe = probes[key];
34
+ if (!probe)
35
+ continue;
36
+ try {
37
+ collected[key] = probe();
38
+ }
39
+ catch {
40
+ collected[key] = 'unknown';
41
+ }
42
+ }
43
+ return evaluateSessionActivity(collected);
44
+ }
1
45
  // 支持的命令列表
2
- const commands = ['/new', '/pwd', '/help', '/evolhelp', '/status', '/restart', '/reload', '/model', '/effort', '/baseagent', '/slist', '/session', '/rename', '/stop', '/pause', '/compact', '/repair', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/observable', '/chatmode', '/mentionmode', '/ask', '/resume', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade'];
46
+ const commands = ['/new', '/pwd', '/help', '/evolhelp', '/status', '/restart', '/reload', '/model', '/effort', '/baseagent', '/slist', '/session', '/rename', '/stop', '/pause', '/compact', '/renew', '/repair', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/observable', '/chatmode', '/mentionmode', '/ask', '/resume', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade'];
3
47
  const deprecatedCommands = ['/clear'];
4
48
  const exactBoundaryCommands = new Set(['/pause', '/resume', '/stop']);
49
+ /** Parse the execution command without treating it as a quick/result command. */
50
+ export function parseFullAccessCommand(content) {
51
+ const match = content.match(/^\s*(\/fa|\/fullaccess)(?=\s|$)([\s\S]*)$/);
52
+ if (!match)
53
+ return { matched: false };
54
+ return {
55
+ matched: true,
56
+ command: match[1],
57
+ prompt: match[2].trim(),
58
+ };
59
+ }
5
60
  // 命令别名映射
6
61
  const aliases = {
7
62
  '/s': '/session',
@@ -10,7 +65,7 @@ const aliases = {
10
65
  '/base': '/baseagent',
11
66
  };
12
67
  // 命令快速路径前缀(所有命令都不进入消息队列)
13
- const quickCommandPrefixes = ['/new', '/pwd', '/help', '/evolhelp', '/status', '/restart', '/reload', '/model', '/effort', '/baseagent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/pause', '/clear', '/compact', '/del', '/perm', '/file', '/check', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/observable', '/chatmode', '/mentionmode', '/ask', '/resume', '/base ', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade'];
68
+ const quickCommandPrefixes = ['/new', '/pwd', '/help', '/evolhelp', '/status', '/restart', '/reload', '/model', '/effort', '/baseagent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/pause', '/clear', '/compact', '/renew', '/del', '/perm', '/file', '/check', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/observable', '/chatmode', '/mentionmode', '/ask', '/resume', '/base ', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade'];
14
69
  /**
15
70
  * 计算两个字符串的 Levenshtein 距离(编辑距离)
16
71
  */
@@ -61,11 +116,13 @@ export function isRecognizedSlashCommand(content) {
61
116
  export function guardThreadCommand(content, threadId) {
62
117
  if (!threadId)
63
118
  return undefined;
64
- const threadBlocked = ['/new', '/slist', '/s', '/session', '/fork', '/del'];
65
- const isBlocked = threadBlocked.some(c => content === c || content.startsWith(c + ' '));
66
- const isBaseagentSwitch = content.startsWith('/baseagent ');
67
- const isBaseAliasSwitch = content.startsWith('/base ');
68
- if (!isBlocked && !isBaseagentSwitch && !isBaseAliasSwitch)
119
+ const threadBlocked = ['/new', '/slist', '/s', '/session', '/fork', '/del', '/repair'];
120
+ const hasCommandArgument = (command) => new RegExp(`^${command.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\s|$)`).test(content);
121
+ const isBlocked = threadBlocked.some(hasCommandArgument);
122
+ const isBaseagentSwitch = /^\/baseagent\s/.test(content);
123
+ const isBaseAliasSwitch = /^\/base\s/.test(content);
124
+ const isRewindMutation = /^\/rewind\s/.test(content);
125
+ if (!isBlocked && !isBaseagentSwitch && !isBaseAliasSwitch && !isRewindMutation)
69
126
  return undefined;
70
127
  return { kind: 'command.error', text: '⚠️ 话题中不支持此命令' };
71
128
  }
@@ -83,7 +140,7 @@ export function guardRoleCommand(content, activeChatType, isAdmin) {
83
140
  : [
84
141
  ...userGroupCommands,
85
142
  // 私聊 visitor/member 额外可用:会话自管理 + 私聊专属的 /rewind 历史查看
86
- '/slist', '/new', '/session', '/rename', '/name', '/del', '/s ', '/rewind',
143
+ '/slist', '/new', '/session', '/rename', '/name', '/del', '/s ', '/rewind', '/renew',
87
144
  ];
88
145
  const isUserCommand = userCommands.some(cmd => content === cmd.trimEnd() || content.startsWith(cmd));
89
146
  if (isUserCommand || isAdmin)
@@ -102,10 +159,16 @@ export async function guardIdleCommand(opts) {
102
159
  // - 仅带参时需要 idle(无参是列表/用法):/session /baseagent /rewind
103
160
  // - /chatmode:在 handler 内部自行做写操作的 idle 检查
104
161
  // - /mentionmode:在 handler 内部自行做写操作的 idle 检查
162
+ // /renew owns a stronger gate + authoritative probe and deliberately skips
163
+ // this realtime precheck so delayed retries can return their committed
164
+ // idempotent result even while later work is busy.
105
165
  const idleAlways = ['/compact', '/repair', '/fork', '/new'];
106
166
  const idleWhenArg = ['/session', '/baseagent', '/rewind'];
107
- const needsIdle = idleAlways.some(cmd => opts.content === cmd || opts.content.startsWith(cmd + ' ')) ||
108
- idleWhenArg.some(cmd => opts.content.startsWith(cmd + ' '));
167
+ const usesTopicBackendBoundary = !!opts.threadId
168
+ && opts.content === '/compact'
169
+ && typeof opts.messageQueue?.withSessionBarrier === 'function';
170
+ const needsIdle = !usesTopicBackendBoundary && (idleAlways.some(cmd => opts.content === cmd || opts.content.startsWith(cmd + ' ')) ||
171
+ idleWhenArg.some(cmd => opts.content.startsWith(cmd + ' ')));
109
172
  if (!needsIdle)
110
173
  return undefined;
111
174
  if (opts.threadId) {
@@ -121,10 +184,12 @@ export async function guardIdleCommand(opts) {
121
184
  catch {
122
185
  // Runner mismatch should not block recovery commands such as /baseagent.
123
186
  }
124
- const isBusy = hasActiveStream
125
- || opts.messageQueue?.isProcessing(threadSession.id)
126
- || (opts.messageQueue?.getQueueLength(threadSession.id) ?? 0) > 0;
127
- if (isBusy) {
187
+ const activity = evaluateSessionActivity({
188
+ queued: opts.messageQueue?.getQueueLength(threadSession.id) ?? 0,
189
+ processing: opts.messageQueue?.isProcessing(threadSession.id) ?? false,
190
+ activeStream: hasActiveStream,
191
+ });
192
+ if (activity === 'busy') {
128
193
  return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
129
194
  }
130
195
  }
@@ -132,10 +197,12 @@ export async function guardIdleCommand(opts) {
132
197
  else if (opts.activeSession) {
133
198
  if (opts.isSessionPaused?.(opts.activeSession.id))
134
199
  return undefined;
135
- const isBusy = (opts.activeAgent?.hasActiveStream(opts.activeSession.id) ?? false) ||
136
- opts.messageQueue?.isProcessing(opts.activeSession.id) ||
137
- (opts.messageQueue?.getQueueLength(opts.activeSession.id) ?? 0) > 0;
138
- if (isBusy) {
200
+ const activity = evaluateSessionActivity({
201
+ queued: opts.messageQueue?.getQueueLength(opts.activeSession.id) ?? 0,
202
+ processing: opts.messageQueue?.isProcessing(opts.activeSession.id) ?? false,
203
+ activeStream: opts.activeAgent?.hasActiveStream(opts.activeSession.id) ?? false,
204
+ });
205
+ if (activity === 'busy') {
139
206
  return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
140
207
  }
141
208
  }