evolcore 0.0.27 → 0.0.28

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.
package/CHANGELOG.md CHANGED
@@ -5,24 +5,35 @@
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.0.28 (2026-09-24)
9
+
10
+ - 增加 npm 版本缓存,改进升级状态展示及启动、重启流程。
11
+ - 改进 AUN 回复投递与消息展示。
12
+ - 加固 Windows CLI 启动和 PowerShell 命令解析,完善 Codex CLI 诊断。
13
+ - 强化 Menu 请求校验与错误处理。
14
+
8
15
  ## 0.0.27 (2026-09-24)
9
16
 
17
+ ### AUN、群聊与消息
18
+
10
19
  - 持久化 AUN 群邀请的 Owner 审核流程,并增加 Base Agent 清单查询入口。
11
- - 支持 EC Agent 的角色级模型与推理强度策略;为 Claude、Codex、EC Agent runner 会话增加任务历史聚合,并在未确认中断后设置会话重用栅栏。
12
- - 模型类 Trigger 支持单次执行时限;目标会话失联时明确记录为孤儿执行失败。
13
- - 修复 AUN 撤回事件导致的私聊游标跳跃;手动停止的 Agent reload 后恢复渠道连接。
14
- - 规范 Shell 工具活动中的命令展示;本地缺少证书时跳过控制 AID 名片发布。
15
- - 收紧 Codex 分支会话的权限上下文,避免从旧会话缓存继承过期授权。
16
- - 移除 CodeBuddy、QwenWork 非标准 Base Agent 的运行时自动管理路径;EC Agent 默认配置恢复显式凭证要求。
17
- - 移除运行时命令索引中尚未实现的组织管理、任务、资源和工作流命令说明。
18
- - 修复 AUN 出站 payload 截断按原始字符串大小判断的问题;现在按最终 JSON/UTF-8 大小压缩嵌套日志和转义输出,避免可截断负载被误判为仍超限。
19
- - 升级 `@agentunion/fastaun` 至 `0.5.17`,同步运行时最低版本要求;包含 Storage / Group FS 上传 Content-Type 一致性修复,避免 OSS 预签名校验不一致导致的 403。
20
- - 附件 MIME 采用 SDK 字节识别及存储结果;头像与 Storage 上传、删除复用 SDK 文件系统,公开文件返回稳定 AID URL。
21
- - 订阅 `agentmd.changed` 失效 Core 身份缓存,按需刷新并保留失败重试,避免旧查询覆盖更新。
22
- - 对齐无持久序号消息的 Pull 类型,按实际返回透传 volatile 元数据,并修正 SDK 未返回 count 时的消息数量。
23
- - 群命令与托管群文件操作支持 `{base}.{issuer}` 群 AID;不再接受或转换 `group.{issuer}/{base}`,保留当前群权限检查。
24
- - 默认建群名称改为 `g-` 加五位随机数字(10000–99999),明确身份占用时最多尝试 5 次;自定义名称完整保留 `g-`,并提示实际创建身份与请求不一致的情况。
25
- - 群参数格式校验与主体类型提示分离;关系角色查询优先采用已存储的关系类型和已验签身份信息。
20
+ - 支持 EC Agent 的角色级模型与推理强度策略;完善 Claude、Codex、EC Agent 会话的任务历史和中断恢复。
21
+ - 模型类 Trigger 支持单次执行时限;目标会话失联时明确记录为失败。
22
+ - 修复 AUN 撤回消息造成的私聊游标跳跃;手动停止的 Agent 重新加载后可恢复渠道连接。
23
+ - 规范工具活动中的 Shell 命令展示,并在缺少本地证书时跳过控制 AID 名片发布。
24
+ - 优化消息大小处理,提升长消息和复杂内容的发送稳定性。
25
+ - 升级 AUN SDK 至 `0.5.17`,改进消息、群文件和存储上传的兼容性。
26
+ - 改进附件、头像和文件存储处理,提升文件类型识别、上传和公开访问的稳定性。
27
+ - 改进身份信息缓存刷新,确保对端资料更新后及时生效。
28
+ - 支持无连续编号消息的完整拉取信息,并修正消息数量统计。
29
+ - 群命令和托管群文件操作支持标准群 AID 格式。
30
+ - 优化群名称生成和冲突处理,自定义名称保持原样。
31
+ - 改进群组参数校验和成员身份识别,提升群聊操作准确性。
32
+
33
+ ### Agent、权限与运行稳定性
34
+
35
+ - 收紧 Codex 分支会话的权限上下文,避免旧会话继承过期授权。
36
+ - 完善 Agent 初始化、会话恢复、权限边界和运行状态处理。
26
37
 
27
38
  ## 0.0.26 (2026-09-18)
28
39
 
@@ -12,22 +12,26 @@
12
12
  import fs from 'fs';
13
13
  import path from 'path';
14
14
  import os from 'os';
15
- import { commandExists, isWindows, resolveProbePath } from '../utils/cross-platform.js';
15
+ import { buildCliLaunchCommand, commandExists, isWindows, resolvePowerShellShimPath, resolveProbePath, } from '../utils/cross-platform.js';
16
16
  import { execFileSync } from 'child_process';
17
17
  import { parse as parseToml } from 'smol-toml';
18
18
  /** Resolve the installed Codex CLI entrypoint, including npm shims on Windows. */
19
19
  export function resolveCodexCliPath() {
20
20
  return resolveProbePath('codex');
21
21
  }
22
+ /** Return a PS1-only Codex shim for installation diagnostics, never execution. */
23
+ export function resolveCodexPowerShellShimPath() {
24
+ return resolvePowerShellShimPath('codex');
25
+ }
22
26
  export function resolveCodexLaunchCommand(args, options = {}) {
23
27
  const windows = options.isWindows ?? isWindows;
24
28
  const commandPath = options.commandPath !== undefined ? options.commandPath : resolveCodexCliPath();
25
29
  if (!commandPath)
26
30
  return null;
27
- if (windows && /\.(cmd|bat)$/i.test(commandPath)) {
28
- return { command: options.comSpec ?? process.env.ComSpec ?? 'cmd.exe', args: ['/d', '/s', '/c', commandPath, ...args] };
29
- }
30
- return { command: commandPath, args };
31
+ return buildCliLaunchCommand(commandPath, args, {
32
+ isWindows: windows,
33
+ comSpec: options.comSpec,
34
+ });
31
35
  }
32
36
  /** Execute a Codex CLI command using the resolved npm shim/path. */
33
37
  export function execCodexCliSync(args, options) {
@@ -40,6 +44,7 @@ export function execCodexCliSync(args, options) {
40
44
  return execFileSync(launchCommand.command, launchCommand.args, {
41
45
  ...(options ?? {}),
42
46
  windowsHide: isWindows,
47
+ windowsVerbatimArguments: launchCommand.windowsVerbatimArguments,
43
48
  });
44
49
  }
45
50
  const BASEAGENT_ALIASES = {
@@ -481,6 +481,12 @@ export class CodexAppServerClient {
481
481
  const launchCommand = resolveCodexLaunchCommand(args);
482
482
  if (!launchCommand)
483
483
  throw new Error('Codex CLI not found');
484
+ logger.info(`[CodexAppServer] launch ${JSON.stringify({
485
+ resolvedPath: launchCommand.resolvedPath,
486
+ launcherKind: launchCommand.launcherKind,
487
+ launcherPath: launchCommand.launcherPath,
488
+ codexVersion: this.options.codexVersion ?? null,
489
+ })}`);
484
490
  try {
485
491
  let command = launchCommand.command;
486
492
  let commandArgs = launchCommand.args;
@@ -509,6 +515,7 @@ export class CodexAppServerClient {
509
515
  env,
510
516
  stdio: ['pipe', 'pipe', 'pipe'],
511
517
  windowsHide: process.platform === 'win32',
518
+ windowsVerbatimArguments: launchCommand.windowsVerbatimArguments,
512
519
  });
513
520
  try {
514
521
  this.processRegistrationPath = registerCodexAppServerProcess(this.proc.pid);
@@ -7,7 +7,13 @@ async function cliModels() {
7
7
  if (!launch)
8
8
  return null;
9
9
  return new Promise(resolve => {
10
- execFile(launch.command, launch.args, { timeout: 3000, windowsHide: true, maxBuffer: 16 * 1024 * 1024, encoding: 'utf8' }, (error, stdout) => {
10
+ execFile(launch.command, launch.args, {
11
+ timeout: 3000,
12
+ windowsHide: true,
13
+ windowsVerbatimArguments: launch.windowsVerbatimArguments,
14
+ maxBuffer: 16 * 1024 * 1024,
15
+ encoding: 'utf8',
16
+ }, (error, stdout) => {
11
17
  try {
12
18
  if (error)
13
19
  return resolve(null);
@@ -7,7 +7,7 @@ import { checkReadonly, checkDangerousCommand, evaluateToolPreflight } from '../
7
7
  import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
8
8
  import { assertWindowsCodexFilesystemRuleBudget, buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
9
9
  import { CODEX_THREAD_ACTIVE_WRITER, CodexAppServerClient, } from './codex/app-server-client.js';
10
- import { resolveOpenaiConfig, resolveCodexCliPath } from './baseagent.js';
10
+ import { resolveOpenaiConfig, resolveCodexCliPath, resolveCodexPowerShellShimPath, } from './baseagent.js';
11
11
  import { logger } from '../utils/logger.js';
12
12
  import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
13
13
  import { auditCodexApprovalDecision, auditPermissionLifecycle, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
@@ -212,10 +212,13 @@ function compactCodexProbeText(value, maxLength = 600) {
212
212
  const text = value.replace(/\s+/gu, ' ').trim();
213
213
  return text.length <= maxLength ? text : `${text.slice(0, maxLength - 3).trimEnd()}...`;
214
214
  }
215
- function logCodexProbeFailure(args, startedAt, error, timeoutMs) {
215
+ function logCodexProbeFailure(args, startedAt, error, timeoutMs, launch) {
216
216
  const detail = error;
217
217
  logger.warn(`[CodexProbe] CLI probe failed: ${JSON.stringify({
218
218
  command: ['codex', ...args],
219
+ resolvedPath: launch?.resolvedPath ?? null,
220
+ launcherKind: launch?.launcherKind ?? null,
221
+ launcherPath: launch?.launcherPath ?? null,
219
222
  path: detail?.path ?? null,
220
223
  spawnargs: detail?.spawnargs ?? null,
221
224
  elapsedMs: Date.now() - startedAt,
@@ -229,13 +232,13 @@ function logCodexProbeFailure(args, startedAt, error, timeoutMs) {
229
232
  stderr: compactCodexProbeText(detail?.stderr),
230
233
  })}`);
231
234
  }
232
- function probeCodexCli(args) {
233
- return probeCliSync('codex', args, { onFailure: logCodexProbeFailure });
235
+ function probeCodexCli(args, executable) {
236
+ return probeCliSync('codex', args, { executable, onFailure: logCodexProbeFailure });
234
237
  }
235
- export function getCodexCliVersion() {
238
+ export function getCodexCliVersion(executable) {
236
239
  const startedAt = Date.now();
237
240
  try {
238
- const output = probeCodexCli(['--version']);
241
+ const output = probeCodexCli(['--version'], executable);
239
242
  const version = parseCodexCliVersion(output);
240
243
  if (!version) {
241
244
  logCodexProbeFailure(['--version'], startedAt, {
@@ -254,9 +257,15 @@ export function getCodexCliVersion() {
254
257
  export function getCodexAppServerAvailability() {
255
258
  const cliPath = resolveCodexCliPath();
256
259
  if (!cliPath) {
257
- return { available: false, reason: '未检测到 Codex CLI。' };
260
+ const powershellShim = resolveCodexPowerShellShimPath();
261
+ return {
262
+ available: false,
263
+ reason: powershellShim
264
+ ? '检测到 codex.ps1,但当前 Windows 启动路径要求 codex.exe/codex.cmd/codex.bat;请重新安装 @openai/codex 以恢复 npm shim。'
265
+ : '未检测到 Codex CLI。',
266
+ };
258
267
  }
259
- const version = getCodexCliVersion();
268
+ const version = getCodexCliVersion(cliPath);
260
269
  if (!version) {
261
270
  return { available: false, reason: '无法读取 Codex CLI 版本。' };
262
271
  }
@@ -269,7 +278,7 @@ export function getCodexAppServerAvailability() {
269
278
  };
270
279
  }
271
280
  try {
272
- probeCodexCli(['app-server', '--help']);
281
+ probeCodexCli(['app-server', '--help'], cliPath);
273
282
  return { available: true, version };
274
283
  }
275
284
  catch (error) {
@@ -284,10 +293,21 @@ export function isCodexAppServerAvailable() {
284
293
  }
285
294
  /** Daemon startup/hot reload use asynchronous probes; sync CLI callers share the cache. */
286
295
  export async function getCodexAppServerAvailabilityAsync() {
296
+ const cliPath = resolveCodexCliPath();
287
297
  const options = {
288
298
  timeouts: [CODEX_CLI_PROBE_TIMEOUT_MS, CODEX_CLI_PROBE_RETRY_TIMEOUT_MS],
299
+ executable: cliPath ?? undefined,
289
300
  onFailure: logCodexProbeFailure,
290
301
  };
302
+ if (!cliPath) {
303
+ const powershellShim = resolveCodexPowerShellShimPath();
304
+ return {
305
+ available: false,
306
+ reason: powershellShim
307
+ ? '检测到 codex.ps1,但当前 Windows 启动路径要求 codex.exe/codex.cmd/codex.bat;请重新安装 @openai/codex 以恢复 npm shim。'
308
+ : '未检测到 Codex CLI。',
309
+ };
310
+ }
291
311
  let version;
292
312
  try {
293
313
  version = parseCodexCliVersion(await probeCli('codex', ['--version'], options)) ?? undefined;
@@ -348,6 +368,7 @@ export class CodexRunner {
348
368
  /** Fallback authorization lifecycle ids for app-server requests without an RPC id. */
349
369
  approvalLifecycleIds = new WeakMap();
350
370
  resolvedConfig;
371
+ codexVersion;
351
372
  pendingInterruptTtlMs = 30_000;
352
373
  /** Fallback for direct runner callers that do not inject SessionManager. */
353
374
  codexSessionFileAdapter = new CodexSessionFileAdapter();
@@ -389,6 +410,7 @@ export class CodexRunner {
389
410
  if (this.resolvedConfig.effort)
390
411
  this.effort = this.resolvedConfig.effort;
391
412
  this.onSessionIdUpdate = callbacks.onSessionIdUpdate;
413
+ this.codexVersion = runtime?.codexVersion;
392
414
  }
393
415
  getAppServerClient(fullAccess = false) {
394
416
  const existing = fullAccess ? this.fullAccessAppServerClient : this.appServerClient;
@@ -396,6 +418,7 @@ export class CodexRunner {
396
418
  return existing;
397
419
  const client = new CodexAppServerClient({
398
420
  apiKey: this.resolvedConfig.apiKey,
421
+ codexVersion: this.codexVersion,
399
422
  authType: this.resolvedConfig.authType,
400
423
  baseUrl: this.resolvedConfig.baseUrl,
401
424
  provider: this.resolvedConfig.provider,
@@ -3056,6 +3079,9 @@ export class CodexRunner {
3056
3079
  }
3057
3080
  return await denyInfrastructure('ec_command_not_canonical', canonicalEcApproval?.parseFailure
3058
3081
  ? `EC command approval denied because canonical argv parsing failed (${canonicalEcApproval.parseFailure.issue})`
3082
+ + (canonicalEcApproval.parseFailure.dialect === 'powershell' && canonicalEcApproval.parseFailure.issue === 'invalid-quote'
3083
+ ? '. PowerShell treats smart quotes as delimiters too; escape quotes for PowerShell or use --text-from-file for message text.'
3084
+ : '')
3059
3085
  : 'EC command approval denied because no canonical argv could be derived');
3060
3086
  }
3061
3087
  try {
@@ -4680,7 +4706,10 @@ export class CodexRunner {
4680
4706
  const text = typeof item.text === 'string' && item.text ? item.text : buffered;
4681
4707
  if (text) {
4682
4708
  state.lastAssistantUuid = item.id || state.turnId;
4683
- yield { type: 'text', text, assistantUuid: state.lastAssistantUuid };
4709
+ yield {
4710
+ type: 'text', text, assistantUuid: state.lastAssistantUuid,
4711
+ ...(item.phase === 'commentary' || item.phase === 'final_answer' ? { phase: item.phase } : {}),
4712
+ };
4684
4713
  }
4685
4714
  if (item.id)
4686
4715
  state.agentMessageDeltaText.delete(item.id);
@@ -5077,6 +5106,14 @@ export class CodexAgentPlugin {
5077
5106
  },
5078
5107
  },
5079
5108
  };
5080
- return { evolagentName: agent.name, baseagent: 'codex', agent: new CodexRunner(merged, callbacks, { agentAid: agent.config.aid, agentConfig: agent.config }) };
5109
+ return {
5110
+ evolagentName: agent.name,
5111
+ baseagent: 'codex',
5112
+ agent: new CodexRunner(merged, callbacks, {
5113
+ agentAid: agent.config.aid,
5114
+ agentConfig: agent.config,
5115
+ codexVersion: availability.version,
5116
+ }),
5117
+ };
5081
5118
  }
5082
5119
  }
@@ -67,6 +67,7 @@ export class ManagedMenuClient {
67
67
  let timer;
68
68
  try {
69
69
  for (let attempt = 0; attempt < attempts; attempt++) {
70
+ options.beforeSend?.();
70
71
  const response = new Promise((resolve, reject) => {
71
72
  this.pending.set(key, { resolve, reject });
72
73
  timer = setTimeout(() => reject(new MenuClientError('TIMEOUT', 'Remote Menu response timed out')), timeout);
@@ -715,8 +715,9 @@ export class AUNChannel {
715
715
  * 统一的 RPC 调用包装:自动记录 OUT 发送、.ok 结果、.error 错误(含 trace + daemon.log 失败日志)。
716
716
  * 所有 client.call() 都应通过此方法调用,保证 aun-trace 里每个 OUT 调用都有"发+收/错"成对记录。
717
717
  */
718
- callClient(method, params, lane = 'normal', onStart) {
718
+ callClient(method, params, lane = 'normal', onStart, onPrepared) {
719
719
  params = menuTransportParams(method, params);
720
+ onPrepared?.(params);
720
721
  return this.withOutboundSendGate(method, () => (this.client.call(method, params).then(value => value)), lane, onStart);
721
722
  }
722
723
  withOutboundSendGate(method, run, lane = 'normal', onStart) {
@@ -784,7 +785,6 @@ export class AUNChannel {
784
785
  });
785
786
  }
786
787
  async callAndTrace(method, params, opts) {
787
- this.trace('OUT', method, params);
788
788
  // 计时同时覆盖排队和 SDK 往返;callStartedAt 让日志能区分「本地队列堵塞」
789
789
  // 与「网关慢」。两者对 /evolhelp 等命令的回复延迟都有直接影响。
790
790
  const rpcStart = Date.now();
@@ -794,6 +794,11 @@ export class AUNChannel {
794
794
  try {
795
795
  const result = await this.callClient(method, params, opts?.lane ?? 'normal', () => {
796
796
  callStartedAt = Date.now();
797
+ }, preparedParams => {
798
+ // Apply Menu compatibility exactly once, then log the same parameters
799
+ // passed to the SDK (TTL and device targeting may both be removed).
800
+ params = preparedParams;
801
+ this.trace('OUT', method, params);
797
802
  });
798
803
  const durationMs = Date.now() - rpcStart;
799
804
  const rpcDurationMs = Date.now() - callStartedAt;
@@ -844,14 +849,17 @@ export class AUNChannel {
844
849
  state.maxDurationMs = 0;
845
850
  this.rpcWarningStates.set(key, state);
846
851
  const summary = suppressed > 0 ? ` suppressed=${suppressed} max_duration=${maxDurationMs}ms` : '';
852
+ const menuTransport = method === 'message.send' && payloadType.startsWith('menu.')
853
+ ? ` ttl=${params.ttl ?? 'default'} to_device_id=${JSON.stringify(params.to_device_id ?? null)}`
854
+ : '';
847
855
  if (kind === 'failed') {
848
856
  const name = error instanceof Error ? error.name : '';
849
857
  const message = error instanceof Error ? error.message : String(error ?? '');
850
858
  const errorLabel = `${name}${code ? `(${code})` : ''} ${message}`.trim();
851
- logger.warn(`${this.logPrefix()} rpc ${method} failed after ${durationMs}ms: ${errorLabel} queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${summary}`);
859
+ logger.warn(`${this.logPrefix()} rpc ${method} failed after ${durationMs}ms: ${errorLabel} queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${menuTransport}${summary}`);
852
860
  return;
853
861
  }
854
- logger.warn(`${this.logPrefix()} rpc ${method} SLOW: duration=${durationMs}ms queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${summary} (发送管线可能饱和或链路抖动)`);
862
+ logger.warn(`${this.logPrefix()} rpc ${method} SLOW: duration=${durationMs}ms queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${menuTransport}${summary} (发送管线可能饱和或链路抖动)`);
855
863
  }
856
864
  observerQueueDropCount = 0;
857
865
  observerQueueDropLastWarnAt = 0;
@@ -4838,7 +4846,12 @@ export class AUNChannel {
4838
4846
  }
4839
4847
  // 从 context.metadata.source 读取 source,默认为 'daemon'
4840
4848
  const source = context?.metadata?.source ?? 'daemon';
4841
- const payload = withAunAttention({ type: 'text', text: finalText }, context?.attention);
4849
+ // Put the label in the visible body without requiring client title support.
4850
+ // Derive it from the durable context without mutating the stored text.
4851
+ const title = delivery.chatType === 'private' ? context?.title?.trim() : undefined;
4852
+ const wireText = title && finalText.split(/\r?\n/, 1)[0].trim() !== title
4853
+ ? `${title}\n\n${finalText}` : finalText;
4854
+ const payload = withAunAttention({ type: 'text', text: wireText }, context?.attention);
4842
4855
  if (delivery.chatType === 'group') {
4843
4856
  const extracted = this.extractMentionEntriesFromText(finalText);
4844
4857
  if (extracted.length > 0)
@@ -4863,7 +4876,7 @@ export class AUNChannel {
4863
4876
  registerAunCausation(causationRef, this.config.aid, channelId, causation);
4864
4877
  }
4865
4878
  // 诊断日志:记录 payload 构造结果(含 task_id / thread_id / chatmode)
4866
- logger.info(`${this.logPrefix()} deliverTextEntry: channelId=${channelId} thread_id=${payload.thread_id ?? 'none'} task_id=${payload.task_id ?? 'none'} chatmode=${payload.chatmode ?? 'none'} source=${source} textLen=${finalText.length}`);
4879
+ logger.info(`${this.logPrefix()} deliverTextEntry: channelId=${channelId} thread_id=${payload.thread_id ?? 'none'} task_id=${payload.task_id ?? 'none'} chatmode=${payload.chatmode ?? 'none'} source=${source} textLen=${wireText.length}`);
4867
4880
  const isGroup = delivery.chatType === 'group';
4868
4881
  const targetAid = channelId;
4869
4882
  if (entry.deliveryReceipt) {
@@ -6239,6 +6252,8 @@ export class AUNChannelPlugin {
6239
6252
  if (replyCtx) {
6240
6253
  replyCtx = { ...replyCtx };
6241
6254
  delete replyCtx.attention;
6255
+ // Assign titles per payload below, never inherit a final-reply title.
6256
+ delete replyCtx.title;
6242
6257
  }
6243
6258
  if (attention !== undefined)
6244
6259
  replyCtx = { ...(replyCtx ?? {}), attention };
@@ -6283,8 +6298,9 @@ export class AUNChannelPlugin {
6283
6298
  ? { forkAnchor: payload.metadata.forkAnchor } : {}),
6284
6299
  },
6285
6300
  };
6286
- if (payload.kind === 'result.text' && payload.isFinal)
6301
+ if (payload.kind === 'result.text' && payload.isFinal && delivery.chatType === 'private') {
6287
6302
  sendCtx.title = '✅ 最终回复:';
6303
+ }
6288
6304
  let result;
6289
6305
  try {
6290
6306
  result = await channel.sendMessage(channelId, payload.text, sendCtx);