evolcore 0.0.28 → 0.0.29

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 (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -113,10 +113,11 @@ export const MENU_NAME_COMMANDS = Object.freeze({
113
113
  model: '/model',
114
114
  effort: '/effort',
115
115
  chatmode: '/chatmode',
116
- mentionmode: '/mentionmode',
116
+ mentionmode: '/mention',
117
117
  group: '/group',
118
118
  permission: '/perm',
119
119
  activity: '/activity',
120
+ task: '/task',
120
121
  dispatch: '/dispatch',
121
122
  observable: '/observable',
122
123
  system: '/system',
@@ -215,7 +216,7 @@ export function isEvolMenuVersionGateEnabled() {
215
216
  export function isAunMenuTokenRequired() {
216
217
  return loadDaemonConfig().aun?.menuTokenRequired === true;
217
218
  }
218
- export function evolMenuResponseTransportMetadata(sdkVersion) {
219
+ export function evolMenuResponseTransportMetadata(sdkVersion, volatileCapability) {
219
220
  return {
220
221
  // Clear inherited request state so AUNChannel applies daemon aun.defaultEncrypt.
221
222
  encrypted: undefined,
@@ -226,6 +227,7 @@ export function evolMenuResponseTransportMetadata(sdkVersion) {
226
227
  ...(typeof sdkVersion === 'string' && sdkVersion.trim()
227
228
  ? { menuClientSdkVersion: sdkVersion.trim() }
228
229
  : {}),
230
+ ...(volatileCapability === '1' ? { menuClientVolatileV1: true } : {}),
229
231
  };
230
232
  }
231
233
  const EXPLICIT_SCOPE_UPDATE_NAMES = new Set([
@@ -243,7 +245,7 @@ const EXPLICIT_SCOPE_UPDATE_COMMANDS = new Set([
243
245
  '/effort',
244
246
  '/chatmode',
245
247
  '/dispatch',
246
- '/mentionmode',
248
+ '/mention',
247
249
  '/group',
248
250
  '/perm',
249
251
  '/activity',
@@ -315,6 +317,13 @@ export function validateMenuRequest(request) {
315
317
  if (request.args !== undefined && (!request.args || typeof request.args !== 'object' || Array.isArray(request.args))) {
316
318
  return { code: 'INVALID_ARGUMENT', message: 'Menu request args must be an object' };
317
319
  }
320
+ if (request.name === 'task' || request.cmd === '/task') {
321
+ if (type !== 'menu.query')
322
+ return { code: 'NOT_SUPPORTED', message: 'task only supports menu.query' };
323
+ const taskError = validateTaskActivityQueryArgs(request.args);
324
+ if (taskError)
325
+ return taskError;
326
+ }
318
327
  if (type === 'menu.list' && Object.hasOwn(request, 'context')) {
319
328
  return { code: 'INVALID_ARGUMENT', message: 'menu.list context must be inside args.context (for a daemon catalog, use args.context.kind=process)' };
320
329
  }
@@ -353,6 +362,32 @@ export function validateMenuRequest(request) {
353
362
  }
354
363
  return null;
355
364
  }
365
+ export function validateTaskActivityQueryArgs(value) {
366
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
367
+ return { code: 'INVALID_ARGUMENT', message: 'task query requires args' };
368
+ }
369
+ const args = value;
370
+ if (args.view !== 'activities' && args.view !== 'activity') {
371
+ return { code: 'INVALID_ARGUMENT', message: 'task query requires args.view=activities or activity' };
372
+ }
373
+ const allowed = args.view === 'activities' ? ['view', 'taskId', 'offset', 'limit'] : ['view', 'taskId', 'activityId'];
374
+ if (Object.keys(args).some(key => !allowed.includes(key))) {
375
+ return { code: 'INVALID_ARGUMENT', message: 'unsupported task query argument' };
376
+ }
377
+ if (typeof args.taskId !== 'string' || !/^task-[A-Za-z0-9._:-]+$/.test(args.taskId)) {
378
+ return { code: 'INVALID_ARGUMENT', message: 'invalid taskId' };
379
+ }
380
+ if (args.view === 'activity') {
381
+ if (typeof args.activityId !== 'string' || !/^activity-[A-Za-z0-9._:-]+$/.test(args.activityId)) {
382
+ return { code: 'INVALID_ARGUMENT', message: 'invalid activityId' };
383
+ }
384
+ }
385
+ else if ((args.offset !== undefined && (!Number.isSafeInteger(args.offset) || args.offset < 0))
386
+ || (args.limit !== undefined && (!Number.isSafeInteger(args.limit) || args.limit < 1 || args.limit > 100))) {
387
+ return { code: 'INVALID_ARGUMENT', message: 'offset must be a non-negative integer and limit must be between 1 and 100' };
388
+ }
389
+ return null;
390
+ }
356
391
  export function menuSuccess(header, data) {
357
392
  return {
358
393
  type: 'menu.response',
@@ -373,7 +408,9 @@ export function menuFailure(header, error) {
373
408
  },
374
409
  };
375
410
  }
376
- export function enforceMenuResponseSize(response, maxBytes = 128 * 1024) {
411
+ export const MENU_RESPONSE_MAX_BYTES = 384 * 1024;
412
+ export const MENU_RESPONSE_SAFETY_MARGIN_BYTES = 1024;
413
+ export function enforceMenuResponseSize(response, maxBytes = MENU_RESPONSE_MAX_BYTES) {
377
414
  let bytes;
378
415
  try {
379
416
  bytes = Buffer.byteLength(JSON.stringify(response), 'utf8');
@@ -408,6 +445,7 @@ const STABLE_CODES = new Set([
408
445
  'RATE_LIMITED', 'CATALOG_STALE', 'DATA_MIGRATION_REQUIRED',
409
446
  'INVALID_CLIENT', 'REMOTE_ACCESS_TOKEN_INVALID', 'ECWEB_TOKEN_INVALID', 'CLIENT_BUSY', 'INVALID_RESPONSE', 'TIMEOUT',
410
447
  'RESPONSE_TOO_LARGE',
448
+ 'ACTIVITY_NOT_FOUND', 'ACTIVITY_EXPIRED',
411
449
  'TOPIC_SESSION_NOT_FOUND', 'TOPIC_SESSION_LOOKUP_FAILED',
412
450
  'ROOT_NOT_FOUND', 'ROOT_FORBIDDEN', 'ROOT_EXPIRED', 'ROOT_SCOPE_MISMATCH', 'ROOT_PEER_MISMATCH',
413
451
  'INVALID_ROOT_ID', 'MISSING_ROOT_ID', 'INVALID_ROOT_PATH', 'NOT_A_DIRECTORY', 'FILE_TOO_LARGE',
@@ -43,7 +43,7 @@ export function probeSessionActivity(evidence, probes) {
43
43
  return evaluateSessionActivity(collected);
44
44
  }
45
45
  // 支持的命令列表
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', '/pair'];
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', '/mention', '/ask', '/resume', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair', '/memory'];
47
47
  const deprecatedCommands = ['/clear'];
48
48
  commands.push('/diag');
49
49
  const exactBoundaryCommands = new Set(['/pause', '/resume', '/stop', '/pair', '/diag']);
@@ -64,10 +64,11 @@ const aliases = {
64
64
  '/name': '/rename',
65
65
  '/rw': '/rewind',
66
66
  '/base': '/baseagent',
67
+ '/mentionmode': '/mention',
67
68
  };
68
- const GROUP_READ_ONLY_COMMANDS = new Set(['/mentionmode', '/model', '/effort', '/perm', '/baseagent']);
69
+ const GROUP_READ_ONLY_COMMANDS = new Set(['/model', '/effort', '/perm', '/baseagent']);
69
70
  const GROUP_TASK_COMMANDS = new Set([
70
- '/help', '/evolhelp', '/status', '/check', '/ask', '/pause', '/resume', '/stop', '/compact', '/renew',
71
+ '/help', '/evolhelp', '/status', '/check', '/ask', '/pause', '/resume', '/stop', '/compact', '/renew', '/mention', '/memory',
71
72
  ]);
72
73
  /** Group slash policy applies to every role, including old command-card clicks. */
73
74
  export function guardGroupSlashCommand(content, chatType) {
@@ -94,7 +95,7 @@ export function guardGroupSlashCommand(content, chatType) {
94
95
  };
95
96
  }
96
97
  // 命令快速路径前缀(所有命令都不进入消息队列)
97
- 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', '/pair'];
98
+ 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', '/mention', '/ask', '/resume', '/base ', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair', '/memory'];
98
99
  quickCommandPrefixes.push('/diag');
99
100
  /**
100
101
  * 计算两个字符串的 Levenshtein 距离(编辑距离)
@@ -149,8 +150,7 @@ export function guardThreadCommand(content, threadId) {
149
150
  const threadBlocked = ['/new', '/slist', '/s', '/session', '/fork', '/del', '/repair'];
150
151
  const hasCommandArgument = (command) => new RegExp(`^${command.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\s|$)`).test(content);
151
152
  const isBlocked = threadBlocked.some(hasCommandArgument);
152
- const isRewindMutation = /^\/rewind\s/.test(content);
153
- if (!isBlocked && !isRewindMutation)
153
+ if (!isBlocked)
154
154
  return undefined;
155
155
  return { kind: 'command.error', text: '⚠️ 话题中不支持此命令' };
156
156
  }
@@ -159,9 +159,9 @@ export function guardRoleCommand(content, activeChatType, isAdmin) {
159
159
  return undefined;
160
160
  // visitor/member 在群聊和私聊中均可访问的只读命令:纯查询形态(带参写操作由各 handler 内部守卫拦截)
161
161
  const userGroupCommands = [
162
- '/status', '/help', '/evolhelp', '/check', '/chatmode', '/mentionmode',
162
+ '/status', '/help', '/evolhelp', '/check', '/chatmode', '/mention',
163
163
  '/model', '/effort', '/baseagent', '/perm', '/activity', '/stop', '/pause',
164
- '/resume', '/trigger', '/file', '/ask',
164
+ '/resume', '/trigger', '/file', '/ask', '/memory',
165
165
  ];
166
166
  const userCommands = activeChatType === 'group' && !isAdmin
167
167
  ? userGroupCommands
@@ -186,14 +186,14 @@ export async function guardIdleCommand(opts) {
186
186
  // - 始终需要 idle(无参即写):/compact /repair /fork /new
187
187
  // - 仅带参时需要 idle(无参是列表/用法):/session /baseagent /rewind
188
188
  // - /chatmode:在 handler 内部自行做写操作的 idle 检查
189
- // - /mentionmode:在 handler 内部自行做写操作的 idle 检查
189
+ // - /mention:在 handler 内部自行做写操作的 idle 检查
190
190
  // /renew owns a stronger gate + authoritative probe and deliberately skips
191
191
  // this realtime precheck so delayed retries can return their committed
192
192
  // idempotent result even while later work is busy.
193
193
  const idleAlways = ['/compact', '/repair', '/fork', '/new'];
194
194
  const idleWhenArg = ['/session', '/baseagent', '/rewind'];
195
195
  const usesTopicBackendBoundary = !!opts.threadId
196
- && opts.content === '/compact'
196
+ && (opts.content === '/compact' || opts.content.startsWith('/rewind '))
197
197
  && typeof opts.messageQueue?.withSessionBarrier === 'function';
198
198
  const needsIdle = !usesTopicBackendBoundary && (idleAlways.some(cmd => opts.content === cmd || opts.content.startsWith(cmd + ' ')) ||
199
199
  idleWhenArg.some(cmd => opts.content.startsWith(cmd + ' ')));