@ai-setting/roy-agent-cli 1.7.56 → 1.7.58

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/dist/index.js CHANGED
@@ -364,25 +364,46 @@ var exports_stream_output_service = {};
364
364
  __export(exports_stream_output_service, {
365
365
  streamAbortSignal: () => streamAbortSignal,
366
366
  resetStreamAbort: () => resetStreamAbort,
367
+ isStreamAborted: () => isStreamAborted,
367
368
  hasNumericUsage: () => hasNumericUsage,
368
369
  abortStream: () => abortStream,
370
+ __resetAllStreamAbortForTest: () => __resetAllStreamAbortForTest,
369
371
  StreamOutputService: () => StreamOutputService,
370
372
  COLORS: () => COLORS
371
373
  });
372
374
  import chalk2 from "chalk";
373
375
  import { createLogger as createLogger2, TracedAs as TracedAs2 } from "@ai-setting/roy-agent-core";
374
- function resetStreamAbort() {
375
- streamAbortSignal.aborted = false;
376
+ function resetStreamAbort(scope) {
377
+ if (scope === undefined) {
378
+ streamAbortSignal.aborted = false;
379
+ return;
380
+ }
381
+ abortedRuns.delete(scope);
382
+ }
383
+ function abortStream(scope) {
384
+ if (scope === undefined) {
385
+ streamAbortSignal.aborted = true;
386
+ return;
387
+ }
388
+ abortedRuns.add(scope);
389
+ }
390
+ function isStreamAborted(scope) {
391
+ if (streamAbortSignal.aborted)
392
+ return true;
393
+ if (scope === undefined)
394
+ return false;
395
+ return abortedRuns.has(scope);
376
396
  }
377
- function abortStream() {
378
- streamAbortSignal.aborted = true;
397
+ function __resetAllStreamAbortForTest() {
398
+ streamAbortSignal.aborted = false;
399
+ abortedRuns.clear();
379
400
  }
380
401
  function hasNumericUsage(usage) {
381
402
  if (!usage)
382
403
  return false;
383
404
  return typeof usage.promptTokens === "number" || typeof usage.completionTokens === "number" || typeof usage.totalTokens === "number" || typeof usage.cacheRead === "number" || typeof usage.cacheWrite === "number" || typeof usage.promptTokensTotal === "number";
384
405
  }
385
- var logger2, COLORS, streamAbortSignal, StreamOutputService;
406
+ var logger2, COLORS, streamAbortSignal, abortedRuns, StreamOutputService;
386
407
  var init_stream_output_service = __esm(() => {
387
408
  init_markdown_renderer();
388
409
  logger2 = createLogger2("stream-output");
@@ -405,6 +426,7 @@ var init_stream_output_service = __esm(() => {
405
426
  streamAbortSignal = {
406
427
  aborted: false
407
428
  };
429
+ abortedRuns = new Set;
408
430
  StreamOutputService = class StreamOutputService {
409
431
  fullText = "";
410
432
  fullReasoning = "";
@@ -420,6 +442,9 @@ var init_stream_output_service = __esm(() => {
420
442
  reasoningClosedByText = false;
421
443
  chunkCallback = undefined;
422
444
  silent = false;
445
+ sessionFilter = undefined;
446
+ acceptsSidelessEvents = true;
447
+ runId = undefined;
423
448
  constructor(options = {}) {
424
449
  this.options = options;
425
450
  this.silent = options.silent ?? false;
@@ -434,6 +459,15 @@ var init_stream_output_service = __esm(() => {
434
459
  setChunkCallback(cb) {
435
460
  this.chunkCallback = cb;
436
461
  }
462
+ setSessionFilter(sessionId) {
463
+ this.sessionFilter = sessionId;
464
+ }
465
+ setAcceptsSidelessEvents(accepts) {
466
+ this.acceptsSidelessEvents = accepts;
467
+ }
468
+ setRunId(runId) {
469
+ this.runId = runId;
470
+ }
437
471
  emitChunk(event) {
438
472
  if (!this.chunkCallback)
439
473
  return;
@@ -454,7 +488,8 @@ var init_stream_output_service = __esm(() => {
454
488
  console.log(message);
455
489
  }
456
490
  checkAbort() {
457
- if (streamAbortSignal.aborted) {
491
+ const aborted = this.runId !== undefined ? isStreamAborted(this.runId) : streamAbortSignal.aborted;
492
+ if (aborted) {
458
493
  this.logStdout(`
459
494
  ${COLORS.system("[已中断]")}
460
495
  `);
@@ -462,7 +497,25 @@ ${COLORS.system("[已中断]")}
462
497
  }
463
498
  return false;
464
499
  }
500
+ extractEventSessionId(event) {
501
+ const fromMeta = event?.metadata?.sessionId;
502
+ if (typeof fromMeta === "string" && fromMeta.length > 0)
503
+ return fromMeta;
504
+ const fromPayload = event?.payload?.metadata?.sessionId;
505
+ if (typeof fromPayload === "string" && fromPayload.length > 0)
506
+ return fromPayload;
507
+ return;
508
+ }
465
509
  handleEvent(event) {
510
+ if (this.sessionFilter !== undefined) {
511
+ const eventSessionId = this.extractEventSessionId(event);
512
+ if (eventSessionId === undefined) {
513
+ if (!this.acceptsSidelessEvents)
514
+ return;
515
+ } else if (eventSessionId !== this.sessionFilter) {
516
+ return;
517
+ }
518
+ }
466
519
  if (this.checkAbort()) {
467
520
  return;
468
521
  }
@@ -9585,7 +9638,7 @@ class CliQuietModeService {
9585
9638
  // package.json
9586
9639
  var package_default = {
9587
9640
  name: "@ai-setting/roy-agent-cli",
9588
- version: "1.7.56",
9641
+ version: "1.7.58",
9589
9642
  type: "module",
9590
9643
  description: "CLI for roy-agent - Non-interactive command execution",
9591
9644
  main: "./dist/index.js",
@@ -11238,6 +11291,9 @@ class QueryExecutor {
11238
11291
  showToolResults: false
11239
11292
  };
11240
11293
  _pendingContextConfig = null;
11294
+ _pendingSessionScope = undefined;
11295
+ _pendingAcceptsSideless = undefined;
11296
+ _sidelessPolicyExplicit = false;
11241
11297
  _pendingChunkCallback = undefined;
11242
11298
  failureNotifier;
11243
11299
  constructor(options) {
@@ -11276,6 +11332,14 @@ class QueryExecutor {
11276
11332
  this.streamService.setChunkCallback(this._pendingChunkCallback);
11277
11333
  this._pendingChunkCallback = undefined;
11278
11334
  }
11335
+ if (this._pendingAcceptsSideless !== undefined) {
11336
+ this.streamService.setAcceptsSidelessEvents(this._pendingAcceptsSideless);
11337
+ this._pendingAcceptsSideless = undefined;
11338
+ }
11339
+ if (this._pendingSessionScope !== undefined) {
11340
+ this.streamService.setSessionFilter(this._pendingSessionScope);
11341
+ this.streamService.setRunId(this._pendingSessionScope);
11342
+ }
11279
11343
  if (this._pendingContextConfig) {
11280
11344
  this.streamService.setContextInfo(this._pendingContextConfig.contextWindow, this._pendingContextConfig.contextWindow * this._pendingContextConfig.thresholdRatio);
11281
11345
  this._pendingContextConfig = null;
@@ -11301,6 +11365,18 @@ class QueryExecutor {
11301
11365
  this._pendingChunkCallback = cb;
11302
11366
  }
11303
11367
  }
11368
+ setSessionScope(sessionId) {
11369
+ this._pendingSessionScope = sessionId;
11370
+ if (this.streamService) {
11371
+ this.streamService.setSessionFilter(sessionId);
11372
+ this.streamService.setRunId(sessionId);
11373
+ }
11374
+ }
11375
+ setAcceptsSidelessEvents(accepts) {
11376
+ this._pendingAcceptsSideless = accepts;
11377
+ this._sidelessPolicyExplicit = true;
11378
+ this.streamService?.setAcceptsSidelessEvents(accepts);
11379
+ }
11304
11380
  handleEvent(event) {
11305
11381
  if (event.type === "context.threshold_exceeded" && !this.quiet) {
11306
11382
  const payload = event.payload;
@@ -11321,6 +11397,13 @@ class QueryExecutor {
11321
11397
  showToolResults: streamOptions?.showToolResults ?? this.streamOptions.showToolResults
11322
11398
  };
11323
11399
  this.subscribeToEvents(options);
11400
+ if (sessionId) {
11401
+ this.streamService?.setSessionFilter(sessionId);
11402
+ this.streamService?.setRunId(sessionId);
11403
+ if (!this._sidelessPolicyExplicit) {
11404
+ this.streamService?.setAcceptsSidelessEvents(true);
11405
+ }
11406
+ }
11324
11407
  this.streamService?.reset();
11325
11408
  if (!this.quiet) {
11326
11409
  this.output.info(`执行: ${message}`);
@@ -11695,8 +11778,268 @@ import {
11695
11778
  } from "@ai-setting/roy-agent-core";
11696
11779
 
11697
11780
  // src/commands/shared/event-handler.ts
11781
+ import {
11782
+ getEnvContext,
11783
+ runWithEnvContextAsync
11784
+ } from "@ai-setting/roy-agent-core/env";
11698
11785
  import { TracedAs as TracedAs5 } from "@ai-setting/roy-agent-core";
11699
11786
  import { notifyReactFailure as notifyReactFailure2 } from "@ai-setting/roy-agent-core/env/error-reply";
11787
+
11788
+ // src/commands/shared/telemetry-notice.ts
11789
+ import { spawn } from "node:child_process";
11790
+ import { normalizeLarkAtMentions } from "@ai-setting/roy-agent-core/env/lark-mention-utils";
11791
+ import { deriveIdempotencyKey } from "@ai-setting/roy-agent-core/env/event-source/idempotency-key";
11792
+ var TELEMETRY_EVENT_TYPES = new Set([
11793
+ "task.background.started",
11794
+ "task.background.progress"
11795
+ ]);
11796
+ function isTelemetryEvent(type) {
11797
+ if (!type)
11798
+ return false;
11799
+ return TELEMETRY_EVENT_TYPES.has(type);
11800
+ }
11801
+ var MAX_TITLE_CHARS = 40;
11802
+ function pad2(n) {
11803
+ return n < 10 ? `0${n}` : String(n);
11804
+ }
11805
+ function formatElapsed(ms) {
11806
+ if (!Number.isFinite(ms) || ms <= 0)
11807
+ return "0m";
11808
+ const totalMin = Math.floor(ms / 60000);
11809
+ if (totalMin < 60)
11810
+ return `${totalMin}m`;
11811
+ const totalHour = Math.floor(totalMin / 60);
11812
+ const min = totalMin % 60;
11813
+ if (totalHour < 24) {
11814
+ return min === 0 ? `${totalHour}h` : `${totalHour}h${min}m`;
11815
+ }
11816
+ const day = Math.floor(totalHour / 24);
11817
+ const hour = totalHour % 24;
11818
+ return hour === 0 ? `${day}d` : `${day}d${hour}h`;
11819
+ }
11820
+ function formatClock(ts) {
11821
+ const d = new Date(ts);
11822
+ return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
11823
+ }
11824
+ function formatTimeoutLabel(ms) {
11825
+ if (!Number.isFinite(ms) || ms <= 0)
11826
+ return "0m";
11827
+ return formatElapsed(ms);
11828
+ }
11829
+ function truncateTitle(raw) {
11830
+ const s = raw.trim();
11831
+ if (s.length <= MAX_TITLE_CHARS)
11832
+ return s;
11833
+ return `${s.slice(0, MAX_TITLE_CHARS)}…`;
11834
+ }
11835
+ function pickString(v) {
11836
+ return typeof v === "string" && v.length > 0 ? v : undefined;
11837
+ }
11838
+ function pickNumber(v) {
11839
+ return typeof v === "number" && Number.isFinite(v) ? v : undefined;
11840
+ }
11841
+ function buildTelemetryNoticeText(type, payload, now = Date.now()) {
11842
+ const p = payload ?? {};
11843
+ const taskId = pickNumber(p.associatedTaskId);
11844
+ const bgId = pickString(p.backgroundTaskId);
11845
+ const desc = truncateTitle(pickString(p.description) ?? "后台任务");
11846
+ let label;
11847
+ if (taskId !== undefined)
11848
+ label = `Task #${taskId}`;
11849
+ else if (bgId)
11850
+ label = bgId;
11851
+ else
11852
+ label = "后台任务";
11853
+ const head = type === "task.background.started" ? `\uD83D\uDD14 ${label}「${desc}」已启动` : `\uD83D\uDD04 ${label}「${desc}」后台运行中`;
11854
+ const parts = [];
11855
+ const startedAt = pickNumber(p.startedAt);
11856
+ const timeoutMs = pickNumber(p.timeoutMs);
11857
+ if (type !== "task.background.started" && startedAt !== undefined) {
11858
+ parts.push(`已跑 ${formatElapsed(now - startedAt)}`);
11859
+ }
11860
+ if (startedAt !== undefined && timeoutMs !== undefined) {
11861
+ parts.push(`预计 ${formatClock(startedAt + timeoutMs)} 超时(timeout ${formatTimeoutLabel(timeoutMs)})`);
11862
+ }
11863
+ let text = head;
11864
+ if (parts.length > 0)
11865
+ text += `
11866
+ ${parts.join(",")}`;
11867
+ const progressMessage = pickString(p.progressMessage);
11868
+ if (progressMessage)
11869
+ text += `
11870
+ ${progressMessage}`;
11871
+ return text;
11872
+ }
11873
+ var DEFAULT_SEND_TIMEOUT_MS = 5000;
11874
+ var MAX_IDEMPOTENCY_KEY_LEN = 50;
11875
+ function deriveTelemetryIdempotencyKey(eventId, chatId) {
11876
+ const anchor = pickString(eventId) ?? `ts-${Date.now()}-${Math.random()}`;
11877
+ return deriveIdempotencyKey(["telemetry", anchor, chatId]);
11878
+ }
11879
+ function buildLarkSendArgs(input) {
11880
+ const { chatId, text, profile, idempotencyKey } = input;
11881
+ const args = [
11882
+ "im",
11883
+ "+messages-send",
11884
+ "--chat-id",
11885
+ chatId,
11886
+ "--markdown",
11887
+ normalizeLarkAtMentions(text),
11888
+ "--as",
11889
+ "bot"
11890
+ ];
11891
+ if (idempotencyKey) {
11892
+ args.push("--idempotency-key", idempotencyKey.slice(0, MAX_IDEMPOTENCY_KEY_LEN));
11893
+ }
11894
+ if (profile)
11895
+ args.unshift("--profile", profile);
11896
+ return args;
11897
+ }
11898
+ function sendLarkChatMarkdown(options) {
11899
+ const { chatId, text, profile, idempotencyKey } = options;
11900
+ const timeoutMs = options.timeoutMs ?? DEFAULT_SEND_TIMEOUT_MS;
11901
+ if (!chatId) {
11902
+ return Promise.reject(new Error("sendLarkChatMarkdown: empty chatId"));
11903
+ }
11904
+ return new Promise((resolve, reject) => {
11905
+ let settled = false;
11906
+ let timer;
11907
+ const finish = (err) => {
11908
+ if (settled)
11909
+ return;
11910
+ settled = true;
11911
+ if (timer)
11912
+ clearTimeout(timer);
11913
+ if (err)
11914
+ reject(err);
11915
+ else
11916
+ resolve();
11917
+ };
11918
+ const args = buildLarkSendArgs({ chatId, text, profile, idempotencyKey });
11919
+ let proc;
11920
+ try {
11921
+ proc = spawn("lark-cli", args, {
11922
+ shell: false,
11923
+ stdio: ["ignore", "pipe", "pipe"]
11924
+ });
11925
+ } catch (err) {
11926
+ finish(err instanceof Error ? err : new Error(String(err)));
11927
+ return;
11928
+ }
11929
+ let stderr = "";
11930
+ proc.stderr?.on("data", (chunk) => {
11931
+ stderr += chunk.toString();
11932
+ });
11933
+ timer = setTimeout(() => {
11934
+ try {
11935
+ proc.kill("SIGKILL");
11936
+ } catch {}
11937
+ finish(new Error(`lark-cli timed out after ${timeoutMs}ms`));
11938
+ }, timeoutMs);
11939
+ timer.unref?.();
11940
+ proc.on("close", (code) => {
11941
+ if (code === 0)
11942
+ finish();
11943
+ else
11944
+ finish(new Error(`lark-cli exited with code ${code}: ${stderr}`));
11945
+ });
11946
+ proc.on("error", (err) => finish(err));
11947
+ });
11948
+ }
11949
+ function targetFromEventMetadata(metadata) {
11950
+ if (!metadata)
11951
+ return;
11952
+ const replyChannel = metadata.replyChannel;
11953
+ const chatId = pickString(replyChannel?.chatId) ?? pickString(metadata.chatId);
11954
+ if (!chatId)
11955
+ return;
11956
+ const profile = pickString(replyChannel?.profile) ?? pickString(metadata.profile);
11957
+ return profile ? { chatId, profile } : { chatId };
11958
+ }
11959
+ async function targetFromTriggerEvent(eventStore, triggerEventId) {
11960
+ if (!eventStore || !triggerEventId)
11961
+ return;
11962
+ let persisted;
11963
+ try {
11964
+ persisted = await eventStore.getEventById(triggerEventId);
11965
+ } catch {
11966
+ return;
11967
+ }
11968
+ if (!persisted || typeof persisted !== "object")
11969
+ return;
11970
+ const metadata = persisted.metadata;
11971
+ if (!metadata || typeof metadata !== "object")
11972
+ return;
11973
+ return targetFromEventMetadata(metadata);
11974
+ }
11975
+
11976
+ // src/commands/shared/self-echo-filter.ts
11977
+ var BOT_SENDER_TYPES = new Set(["bot", "app"]);
11978
+ function pickString2(v) {
11979
+ return typeof v === "string" && v.length > 0 ? v : undefined;
11980
+ }
11981
+ function extractSenderIdentity(event) {
11982
+ if (!event)
11983
+ return {};
11984
+ const metadata = event.metadata;
11985
+ const payload = event.payload;
11986
+ const payloadMeta = payload?.metadata;
11987
+ const rawEvent = payload?.rawEvent;
11988
+ const innerEvent = rawEvent?.event;
11989
+ const innerSender = innerEvent?.sender;
11990
+ const topSender = rawEvent?.sender;
11991
+ const senderType = pickString2(metadata?.senderType) ?? pickString2(payloadMeta?.senderType) ?? pickString2(rawEvent?.sender_type) ?? pickString2(innerSender?.sender_type) ?? pickString2(topSender?.sender_type) ?? pickString2(payload?.senderType);
11992
+ const nestedSenderId = innerSender?.sender_id ?? topSender?.sender_id;
11993
+ const senderId = pickString2(metadata?.senderId) ?? pickString2(payloadMeta?.senderId) ?? pickString2(payload?.senderId) ?? pickString2(rawEvent?.sender_id) ?? (typeof nestedSenderId === "string" ? pickString2(nestedSenderId) : pickString2(nestedSenderId?.open_id) ?? pickString2(nestedSenderId?.user_id) ?? pickString2(nestedSenderId?.union_id));
11994
+ return {
11995
+ ...senderType ? { senderType } : {},
11996
+ ...senderId ? { senderId } : {}
11997
+ };
11998
+ }
11999
+ function isSelfEchoEvent(event, options = {}) {
12000
+ const { senderType, senderId } = extractSenderIdentity(event);
12001
+ if (options.enabled === false) {
12002
+ return {
12003
+ skip: false,
12004
+ reason: "disabled",
12005
+ ...senderType ? { senderType } : {},
12006
+ ...senderId ? { senderId } : {}
12007
+ };
12008
+ }
12009
+ if (senderType && BOT_SENDER_TYPES.has(senderType.toLowerCase())) {
12010
+ return {
12011
+ skip: true,
12012
+ reason: "bot-sender",
12013
+ ...senderType ? { senderType } : {},
12014
+ ...senderId ? { senderId } : {}
12015
+ };
12016
+ }
12017
+ if (senderId && options.selfBotIds && options.selfBotIds.length > 0) {
12018
+ if (options.selfBotIds.includes(senderId)) {
12019
+ return {
12020
+ skip: true,
12021
+ reason: "self-bot-id",
12022
+ ...senderType ? { senderType } : {},
12023
+ senderId
12024
+ };
12025
+ }
12026
+ }
12027
+ return {
12028
+ skip: false,
12029
+ ...senderType ? { senderType } : {},
12030
+ ...senderId ? { senderId } : {}
12031
+ };
12032
+ }
12033
+ var SELF_ECHO_FILTER_ENABLED_ENV = "ROY_AGENT_SELF_ECHO_FILTER";
12034
+ function readSelfEchoFilterEnabled(env = process.env) {
12035
+ const raw = env[SELF_ECHO_FILTER_ENABLED_ENV];
12036
+ if (raw === undefined)
12037
+ return true;
12038
+ const v = raw.trim().toLowerCase();
12039
+ return !(v === "0" || v === "false" || v === "off" || v === "no");
12040
+ }
12041
+
12042
+ // src/commands/shared/event-handler.ts
11700
12043
  function parseLarkPerTaskChatId(contextJson) {
11701
12044
  if (!contextJson)
11702
12045
  return;
@@ -11716,6 +12059,8 @@ var NOTIFY_V2_TERMINAL_TYPES = new Set([
11716
12059
  ]);
11717
12060
 
11718
12061
  class EventHandler {
12062
+ static DEFAULT_LANE_KEY = "__default__";
12063
+ static DEFAULT_MAX_CONCURRENT_SESSIONS = 4;
11719
12064
  env;
11720
12065
  eventTypes;
11721
12066
  onEvent;
@@ -11723,15 +12068,39 @@ class EventHandler {
11723
12068
  formatter;
11724
12069
  isIdle;
11725
12070
  idleCheckInterval;
11726
- queue = [];
11727
- isProcessing = false;
12071
+ isSessionIdleFn = null;
12072
+ maxConcurrentSessions;
12073
+ lanes = new Map;
12074
+ runningLanes = new Set;
12075
+ laneTails = new Map;
12076
+ get isProcessing() {
12077
+ return this.runningLanes.size > 0;
12078
+ }
12079
+ chatSessionCache = new Map;
12080
+ inFlightCount = 0;
12081
+ legacyQueue = [];
12082
+ get queue() {
12083
+ return this.legacyQueue;
12084
+ }
11728
12085
  isStopped = false;
11729
12086
  unsubscribe = null;
12087
+ activeSlots = 0;
12088
+ slotWaiters = [];
12089
+ sessionChains = new Map;
11730
12090
  sessionComponent = null;
11731
12091
  activeEventSessionId = null;
11732
12092
  resolvedSessionByTrigger = new Map;
11733
12093
  eventStore = null;
11734
12094
  getHandlerForEsType = null;
12095
+ telemetrySender = (notice) => sendLarkChatMarkdown(notice);
12096
+ selfEchoFilter = {
12097
+ enabled: true,
12098
+ selfBotIds: []
12099
+ };
12100
+ selfEchoDroppedCount = 0;
12101
+ getSelfEchoDroppedCount() {
12102
+ return this.selfEchoDroppedCount;
12103
+ }
11735
12104
  constructor(options) {
11736
12105
  this.env = options.env;
11737
12106
  this.eventTypes = options.eventTypes;
@@ -11739,10 +12108,18 @@ class EventHandler {
11739
12108
  this.onEventWithEnv = options.onEventWithEnv;
11740
12109
  this.formatter = options.formatter ?? new EventMessageFormatter;
11741
12110
  this.isIdle = options.isIdle ?? (() => true);
12111
+ this.isSessionIdleFn = options.isSessionIdle ?? null;
12112
+ const maxSessions = options.maxConcurrentSessions;
12113
+ this.maxConcurrentSessions = typeof maxSessions === "number" && Number.isFinite(maxSessions) && maxSessions >= 1 ? Math.floor(maxSessions) : EventHandler.DEFAULT_MAX_CONCURRENT_SESSIONS;
11742
12114
  this.idleCheckInterval = options.idleCheckInterval ?? 100;
11743
12115
  this.sessionComponent = options.sessionComponent ?? null;
11744
12116
  this.eventStore = options.eventStore ?? null;
11745
12117
  this.getHandlerForEsType = options.getHandlerForEsType ?? null;
12118
+ this.telemetrySender = options.telemetrySender ?? ((notice) => sendLarkChatMarkdown(notice));
12119
+ this.selfEchoFilter = {
12120
+ enabled: options.selfEchoFilter?.enabled ?? readSelfEchoFilterEnabled(),
12121
+ selfBotIds: options.selfEchoFilter?.selfBotIds ?? []
12122
+ };
11746
12123
  }
11747
12124
  start() {
11748
12125
  this.isStopped = false;
@@ -11776,17 +12153,58 @@ class EventHandler {
11776
12153
  this.unsubscribe();
11777
12154
  this.unsubscribe = null;
11778
12155
  }
11779
- this.queue = [];
11780
- this.isProcessing = false;
12156
+ this.lanes.clear();
12157
+ this.legacyQueue = [];
12158
+ this.laneTails.clear();
12159
+ const waiters = this.slotWaiters.splice(0, this.slotWaiters.length);
12160
+ for (const w of waiters)
12161
+ w();
12162
+ this.activeSlots = 0;
11781
12163
  this.clearActiveSession();
11782
12164
  }
11783
12165
  async handleEvent(event) {
11784
12166
  if (this.isStopped) {
11785
12167
  return;
11786
12168
  }
12169
+ if (this.selfEchoFilter.enabled) {
12170
+ const verdict = isSelfEchoEvent(event, {
12171
+ selfBotIds: this.selfEchoFilter.selfBotIds
12172
+ });
12173
+ if (verdict.skip) {
12174
+ this.selfEchoDroppedCount += 1;
12175
+ console.error(`[EventHandler] self-echo event dropped (reason=${verdict.reason}` + `, senderType=${verdict.senderType ?? "-"}` + `, senderId=${verdict.senderId ?? "-"}` + `, type=${event.type})`);
12176
+ return;
12177
+ }
12178
+ }
12179
+ if (isTelemetryEvent(event.type)) {
12180
+ this.sendTelemetryNotice(event);
12181
+ return;
12182
+ }
11787
12183
  const formatted = await this.formatEvent(event);
11788
12184
  this.enqueue(formatted);
11789
12185
  }
12186
+ async sendTelemetryNotice(event) {
12187
+ try {
12188
+ const metadata = event.metadata;
12189
+ let target = targetFromEventMetadata(metadata);
12190
+ if (!target) {
12191
+ const triggerEventId = typeof metadata?.triggerEventId === "string" ? metadata.triggerEventId : undefined;
12192
+ target = await targetFromTriggerEvent(this.eventStore, triggerEventId);
12193
+ }
12194
+ if (!target) {
12195
+ return;
12196
+ }
12197
+ const text = buildTelemetryNoticeText(event.type, event.payload);
12198
+ await this.telemetrySender({
12199
+ chatId: target.chatId,
12200
+ ...target.profile ? { profile: target.profile } : {},
12201
+ text,
12202
+ idempotencyKey: deriveTelemetryIdempotencyKey(event.id, target.chatId)
12203
+ });
12204
+ } catch (err) {
12205
+ console.error(`[EventHandler] telemetry notice failed (swallowed) type=${event.type}`, err);
12206
+ }
12207
+ }
11790
12208
  async formatEvent(event) {
11791
12209
  if (NOTIFY_V2_TERMINAL_TYPES.has(event.type) && event.metadata?.triggerEventId && this.eventStore && this.getHandlerForEsType) {
11792
12210
  try {
@@ -11899,77 +12317,218 @@ class EventHandler {
11899
12317
  await this.onEvent(formatted.message);
11900
12318
  }
11901
12319
  }
12320
+ laneKeyFor(item) {
12321
+ if (item.chatId)
12322
+ return item.chatId;
12323
+ if (item.triggerSessionId)
12324
+ return `trigger:${item.triggerSessionId}`;
12325
+ return EventHandler.DEFAULT_LANE_KEY;
12326
+ }
11902
12327
  enqueue(formattedEvent) {
11903
12328
  if (this.isStopped) {
11904
12329
  return;
11905
12330
  }
11906
- this.queue.push(formattedEvent);
11907
- if (!this.isProcessing) {
11908
- this.processQueue();
12331
+ const item = { ...formattedEvent };
12332
+ if (!item.envContext) {
12333
+ item.envContext = getEnvContext();
11909
12334
  }
12335
+ const laneKey = this.laneKeyFor(item);
12336
+ const lane = this.lanes.get(laneKey);
12337
+ if (lane) {
12338
+ lane.push(item);
12339
+ } else {
12340
+ this.lanes.set(laneKey, [item]);
12341
+ }
12342
+ this.scheduleLane(laneKey);
11910
12343
  }
11911
12344
  getQueueLength() {
11912
- return this.queue.length;
12345
+ let total = this.legacyQueue.length + this.inFlightCount;
12346
+ for (const lane of this.lanes.values()) {
12347
+ total += lane.length;
12348
+ }
12349
+ return total;
11913
12350
  }
11914
12351
  isQueueEmpty() {
11915
- return this.queue.length === 0;
12352
+ return this.getQueueLength() === 0;
11916
12353
  }
11917
- async processQueue() {
11918
- this.isProcessing = true;
11919
- const chatSessionCache = new Map;
11920
- while (!this.isStopped && this.queue.length > 0) {
11921
- await this.waitForIdle();
11922
- if (this.isStopped) {
11923
- break;
12354
+ isSessionIdle(laneKey) {
12355
+ return !this.runningLanes.has(laneKey);
12356
+ }
12357
+ getRunningLaneKeys() {
12358
+ return Array.from(this.runningLanes);
12359
+ }
12360
+ scheduleLane(laneKey) {
12361
+ if (this.isStopped)
12362
+ return;
12363
+ if (this.runningLanes.has(laneKey))
12364
+ return;
12365
+ this.runningLanes.add(laneKey);
12366
+ const tail = this.runLane(laneKey).catch((err) => {
12367
+ console.error(`[EventHandler] lane ${laneKey} 处理异常:`, err);
12368
+ }).finally(() => {
12369
+ this.runningLanes.delete(laneKey);
12370
+ this.laneTails.delete(laneKey);
12371
+ const rest = this.lanes.get(laneKey);
12372
+ if (rest && rest.length > 0) {
12373
+ this.scheduleLane(laneKey);
11924
12374
  }
11925
- const item = this.queue.shift();
11926
- if (!item) {
12375
+ });
12376
+ this.laneTails.set(laneKey, tail);
12377
+ }
12378
+ async runLane(laneKey) {
12379
+ while (!this.isStopped) {
12380
+ const lane = this.lanes.get(laneKey);
12381
+ if (!lane || lane.length === 0) {
11927
12382
  break;
11928
12383
  }
11929
- let sessionId = null;
11930
- if (item.chatId && this.sessionComponent) {
11931
- const cached = chatSessionCache.get(item.chatId);
11932
- if (cached) {
11933
- sessionId = cached;
11934
- } else {
11935
- sessionId = await this.ensureChatSession(item.chatId);
11936
- if (sessionId) {
11937
- chatSessionCache.set(item.chatId, sessionId);
12384
+ const head = lane[0];
12385
+ const sessionId = await runWithEnvContextAsync(head.envContext, () => this.resolveSessionForItem(laneKey, head));
12386
+ if (this.isStopped)
12387
+ break;
12388
+ const canonicalKey = sessionId ?? laneKey;
12389
+ if (canonicalKey !== laneKey) {
12390
+ const mine = this.lanes.get(laneKey) ?? [];
12391
+ if (mine.length > 0) {
12392
+ const target = this.lanes.get(canonicalKey);
12393
+ if (target) {
12394
+ target.push(...mine);
12395
+ } else {
12396
+ this.lanes.set(canonicalKey, mine);
11938
12397
  }
12398
+ this.lanes.delete(laneKey);
12399
+ this.scheduleLane(canonicalKey);
11939
12400
  }
11940
- if (!sessionId) {
11941
- console.warn(`[EventHandler] Failed to ensure chat-keyed session (chatId=${item.chatId}), ` + `falling back to trigger_session_id path`);
11942
- sessionId = await this.ensureActiveSession(item.triggerSessionId);
11943
- }
11944
- } else {
11945
- sessionId = await this.ensureActiveSession(item.triggerSessionId);
12401
+ return;
11946
12402
  }
12403
+ await this.waitForIdle(sessionId ?? laneKey);
12404
+ if (this.isStopped)
12405
+ break;
12406
+ const claimKey = sessionId ?? laneKey;
12407
+ const releaseClaim = await this.claimSession(claimKey);
11947
12408
  if (this.isStopped) {
12409
+ releaseClaim();
11948
12410
  break;
11949
12411
  }
11950
- if (sessionId) {
11951
- item.eventSessionId = sessionId;
12412
+ await this.acquireSlot();
12413
+ if (this.isStopped) {
12414
+ releaseClaim();
12415
+ this.releaseSlot();
12416
+ break;
11952
12417
  }
11953
12418
  try {
12419
+ const item = lane.shift();
12420
+ if (!item)
12421
+ break;
12422
+ this.inFlightCount++;
12423
+ try {
12424
+ await this.processItem(item, sessionId);
12425
+ } finally {
12426
+ this.inFlightCount--;
12427
+ }
12428
+ } finally {
12429
+ releaseClaim();
12430
+ this.releaseSlot();
12431
+ }
12432
+ }
12433
+ const rest = this.lanes.get(laneKey);
12434
+ if (!rest || rest.length === 0) {
12435
+ this.lanes.delete(laneKey);
12436
+ }
12437
+ }
12438
+ async resolveSessionForItem(laneKey, item) {
12439
+ if (item.sessionResolved) {
12440
+ return item.resolvedSessionId ?? null;
12441
+ }
12442
+ const resolved = await this.resolveSessionForItemUncached(laneKey, item);
12443
+ item.sessionResolved = true;
12444
+ item.resolvedSessionId = resolved;
12445
+ return resolved;
12446
+ }
12447
+ async resolveSessionForItemUncached(laneKey, item) {
12448
+ if (item.chatId && this.sessionComponent) {
12449
+ const cached = this.chatSessionCache.get(item.chatId);
12450
+ if (cached)
12451
+ return cached;
12452
+ let resolved = await this.ensureChatSession(item.chatId);
12453
+ if (!resolved) {
12454
+ console.warn(`[EventHandler] Failed to ensure chat-keyed session (chatId=${item.chatId}), ` + `falling back to trigger_session_id path`);
12455
+ resolved = await this.ensureActiveSession(item.triggerSessionId);
12456
+ } else {
12457
+ this.chatSessionCache.set(item.chatId, resolved);
12458
+ }
12459
+ return resolved;
12460
+ }
12461
+ return this.ensureActiveSession(item.triggerSessionId);
12462
+ }
12463
+ async processItem(item, sessionId) {
12464
+ if (sessionId) {
12465
+ item.eventSessionId = sessionId;
12466
+ }
12467
+ try {
12468
+ await runWithEnvContextAsync(item.envContext, async () => {
11954
12469
  if (this.onEventWithEnv) {
11955
12470
  await this.onEventWithEnv(item);
11956
- continue;
12471
+ return;
11957
12472
  }
11958
12473
  await this.onEvent(item.message);
11959
- } catch (error) {
11960
- console.error(`[EventHandler] 处理事件失败:`, error);
11961
- try {
11962
- await notifyReactFailure2({
11963
- error,
11964
- envEvent: item.envEvent,
11965
- sessionId
11966
- });
11967
- } catch (notifyErr) {
11968
- console.error(`[EventHandler] 失败说明回飞书异常:`, notifyErr);
11969
- }
12474
+ });
12475
+ } catch (error) {
12476
+ console.error(`[EventHandler] 处理事件失败:`, error);
12477
+ try {
12478
+ await notifyReactFailure2({
12479
+ error,
12480
+ envEvent: item.envEvent,
12481
+ sessionId: sessionId ?? undefined
12482
+ });
12483
+ } catch (notifyErr) {
12484
+ console.error(`[EventHandler] 失败说明回飞书异常:`, notifyErr);
11970
12485
  }
11971
12486
  }
11972
- this.isProcessing = false;
12487
+ }
12488
+ async processQueue() {
12489
+ if (this.isStopped)
12490
+ return;
12491
+ if (this.legacyQueue.length > 0) {
12492
+ const pending = this.legacyQueue.splice(0, this.legacyQueue.length);
12493
+ for (const item of pending) {
12494
+ if (!item.envContext) {
12495
+ item.envContext = getEnvContext();
12496
+ }
12497
+ const laneKey = this.laneKeyFor(item);
12498
+ const lane = this.lanes.get(laneKey);
12499
+ if (lane)
12500
+ lane.push(item);
12501
+ else
12502
+ this.lanes.set(laneKey, [item]);
12503
+ }
12504
+ }
12505
+ for (const [laneKey, lane] of Array.from(this.lanes.entries())) {
12506
+ if (lane.length > 0)
12507
+ this.scheduleLane(laneKey);
12508
+ }
12509
+ const tails = Array.from(this.laneTails.values());
12510
+ if (tails.length > 0) {
12511
+ await Promise.all(tails.map((t) => t.catch(() => {
12512
+ return;
12513
+ })));
12514
+ }
12515
+ }
12516
+ async acquireSlot() {
12517
+ if (this.activeSlots < this.maxConcurrentSessions) {
12518
+ this.activeSlots++;
12519
+ return;
12520
+ }
12521
+ await new Promise((resolve) => {
12522
+ this.slotWaiters.push(resolve);
12523
+ });
12524
+ this.activeSlots++;
12525
+ }
12526
+ releaseSlot() {
12527
+ if (this.activeSlots > 0)
12528
+ this.activeSlots--;
12529
+ const next = this.slotWaiters.shift();
12530
+ if (next)
12531
+ next();
11973
12532
  }
11974
12533
  async ensureActiveSession(triggerSessionIdOverride) {
11975
12534
  if (!this.sessionComponent)
@@ -12079,6 +12638,7 @@ class EventHandler {
12079
12638
  clearActiveSession() {
12080
12639
  this.activeEventSessionId = null;
12081
12640
  this.resolvedSessionByTrigger.clear();
12641
+ this.chatSessionCache.clear();
12082
12642
  }
12083
12643
  cacheResolvedSession(triggerSessionIdOverride, sessionId) {
12084
12644
  if (triggerSessionIdOverride && sessionId) {
@@ -12088,11 +12648,38 @@ class EventHandler {
12088
12648
  getActiveEventSessionId() {
12089
12649
  return this.activeEventSessionId;
12090
12650
  }
12091
- async waitForIdle() {
12092
- while (!this.isStopped && !this.isIdle()) {
12651
+ async waitForIdle(sessionId) {
12652
+ while (!this.isStopped && !this.isLaneIdle(sessionId)) {
12093
12653
  await this.sleep(this.idleCheckInterval);
12094
12654
  }
12095
12655
  }
12656
+ isLaneIdle(sessionId) {
12657
+ if (this.isSessionIdleFn && sessionId) {
12658
+ return this.isSessionIdleFn(sessionId);
12659
+ }
12660
+ return this.isIdle();
12661
+ }
12662
+ claimSession(sessionKey) {
12663
+ const prev = this.sessionChains.get(sessionKey) ?? Promise.resolve();
12664
+ let releaseFn;
12665
+ const current = new Promise((resolve) => {
12666
+ releaseFn = resolve;
12667
+ });
12668
+ const tail = prev.then(() => current);
12669
+ this.sessionChains.set(sessionKey, tail);
12670
+ return prev.then(() => {
12671
+ let released = false;
12672
+ return () => {
12673
+ if (released)
12674
+ return;
12675
+ released = true;
12676
+ releaseFn();
12677
+ if (this.sessionChains.get(sessionKey) === tail) {
12678
+ this.sessionChains.delete(sessionKey);
12679
+ }
12680
+ };
12681
+ });
12682
+ }
12096
12683
  sleep(ms) {
12097
12684
  return new Promise((resolve) => setTimeout(resolve, ms));
12098
12685
  }
@@ -12105,6 +12692,14 @@ __legacyDecorateClassTS([
12105
12692
  ]),
12106
12693
  __legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
12107
12694
  ], EventHandler.prototype, "handleEvent", null);
12695
+ __legacyDecorateClassTS([
12696
+ TracedAs5("event_handler.telemetry_notice", { recordParams: true, recordResult: false }),
12697
+ __legacyMetadataTS("design:type", Function),
12698
+ __legacyMetadataTS("design:paramtypes", [
12699
+ typeof EnvEvent === "undefined" ? Object : EnvEvent
12700
+ ]),
12701
+ __legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
12702
+ ], EventHandler.prototype, "sendTelemetryNotice", null);
12108
12703
  __legacyDecorateClassTS([
12109
12704
  TracedAs5("cli.event-handler.formatEvent", { recordParams: true, recordResult: false }),
12110
12705
  __legacyMetadataTS("design:type", Function),
@@ -12187,12 +12782,24 @@ function createEventHandlerForInteractive(options) {
12187
12782
  const dbPath = options.dbPath ?? createDefaultEventStoreDbPath();
12188
12783
  const eventStore = getOrCreateEventStore(dbPath);
12189
12784
  const getHandlerForEsType = getHandlerForEsTypeForEnv(options.env);
12785
+ const maxConcurrentSessions = options.maxConcurrentSessions ?? readMaxConcurrentSessionsFromEnv();
12190
12786
  return new EventHandler({
12191
12787
  ...options,
12788
+ ...maxConcurrentSessions !== undefined ? { maxConcurrentSessions } : {},
12192
12789
  eventStore,
12193
12790
  getHandlerForEsType
12194
12791
  });
12195
12792
  }
12793
+ var MAX_CONCURRENT_SESSIONS_ENV = "ROY_AGENT_MAX_CONCURRENT_SESSIONS";
12794
+ function readMaxConcurrentSessionsFromEnv(env = process.env) {
12795
+ const raw = env[MAX_CONCURRENT_SESSIONS_ENV];
12796
+ if (raw === undefined || raw.trim() === "")
12797
+ return;
12798
+ const parsed = Number(raw);
12799
+ if (!Number.isFinite(parsed) || parsed < 1)
12800
+ return;
12801
+ return Math.max(1, Math.floor(parsed));
12802
+ }
12196
12803
 
12197
12804
  // src/commands/shared/esc-active-descendant.ts
12198
12805
  function hasActiveDescendants(env, parentSessionId) {
@@ -12865,7 +13472,8 @@ var USER_PROMPT = COLORS.accent("❯ ");
12865
13472
  class REPL {
12866
13473
  rl;
12867
13474
  options;
12868
- isExecuting = false;
13475
+ executingSessions = new Map;
13476
+ static LEGACY_SESSION_KEY = "__legacy_isExecuting__";
12869
13477
  replMode = "ready";
12870
13478
  statusBarCtx = {};
12871
13479
  isAltEnterPending = false;
@@ -13058,7 +13666,10 @@ ${COLORS.system("[正在中断...]")}`);
13058
13666
  }
13059
13667
  }
13060
13668
  abortStream();
13061
- this.isExecuting = false;
13669
+ for (const sid of this.getExecutingSessionIds()) {
13670
+ abortStream(sid);
13671
+ }
13672
+ this.abortAllExecutingSessions();
13062
13673
  console.log(`${COLORS.system("[已中断,可以继续输入]")}`);
13063
13674
  } else {
13064
13675
  console.log(`
@@ -13143,7 +13754,8 @@ ${COLORS.system("[没有正在执行的请求]")}`);
13143
13754
  this.inkApi = api;
13144
13755
  },
13145
13756
  onSubmit: async (input) => {
13146
- this.isExecuting = true;
13757
+ const inkSessionId = this.options.sessionId;
13758
+ this.markSessionExecuting(inkSessionId, true);
13147
13759
  inkApiRef?.setMode("executing");
13148
13760
  try {
13149
13761
  await this.options.onExecute(input);
@@ -13161,7 +13773,7 @@ ${COLORS.system("[没有正在执行的请求]")}`);
13161
13773
  inkApiRef?.appendMessage({ role: "system", content: `[Error] ${msg}` });
13162
13774
  throw err;
13163
13775
  } finally {
13164
- this.isExecuting = false;
13776
+ this.markSessionExecuting(inkSessionId, false);
13165
13777
  }
13166
13778
  },
13167
13779
  onCommand: async (cmd) => {
@@ -13314,7 +13926,8 @@ ${COLORS.error("❓ 未知命令: /" + cmd + "}")}`);
13314
13926
  }
13315
13927
  }
13316
13928
  async handleMessage(message) {
13317
- if (this.isExecuting) {
13929
+ const inputSessionId = this.options.sessionId;
13930
+ if (!this.isSessionIdle(inputSessionId)) {
13318
13931
  console.log(`
13319
13932
  ${COLORS.system("⏳ 上一次请求尚未完成,请稍候...")}
13320
13933
  `);
@@ -13381,7 +13994,7 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13381
13994
  };
13382
13995
  }
13383
13996
  }
13384
- this.currentAgentContext = {
13997
+ const agentContext = {
13385
13998
  agentType: agentFromEvent,
13386
13999
  pluginEnabled,
13387
14000
  envEvent: envEventForAgent,
@@ -13389,9 +14002,12 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13389
14002
  eventSessionId,
13390
14003
  systemPrompt: envEvent.metadata?.systemPrompt
13391
14004
  };
13392
- await this.executeInternal(message, { showPrompt: true });
14005
+ await this.executeInternal(message, {
14006
+ showPrompt: true,
14007
+ sessionId: eventSessionId,
14008
+ agentContext
14009
+ });
13393
14010
  }
13394
- currentAgentContext = undefined;
13395
14011
  lastReplyChannel = undefined;
13396
14012
  lastReplyChannelByChat = new Map;
13397
14013
  static REPLY_CHANNEL_CHAT_CACHE_MAX = 100;
@@ -13410,7 +14026,56 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13410
14026
  }
13411
14027
  }
13412
14028
  isIdle() {
13413
- return !this.isExecuting;
14029
+ return !this.anySessionBusy();
14030
+ }
14031
+ anySessionBusy() {
14032
+ for (const count of this.execSet().values()) {
14033
+ if (count > 0)
14034
+ return true;
14035
+ }
14036
+ return false;
14037
+ }
14038
+ execSet() {
14039
+ if (!this.executingSessions)
14040
+ this.executingSessions = new Map;
14041
+ return this.executingSessions;
14042
+ }
14043
+ get isExecuting() {
14044
+ return this.anySessionBusy();
14045
+ }
14046
+ set isExecuting(executing) {
14047
+ if (!executing) {
14048
+ this.execSet().clear();
14049
+ return;
14050
+ }
14051
+ this.execSet().set(REPL.LEGACY_SESSION_KEY, 1);
14052
+ }
14053
+ markSessionExecuting(sessionId, executing) {
14054
+ const counts = this.execSet();
14055
+ if (executing) {
14056
+ counts.set(sessionId, (counts.get(sessionId) ?? 0) + 1);
14057
+ return;
14058
+ }
14059
+ const current = counts.get(sessionId) ?? 0;
14060
+ if (current <= 1) {
14061
+ counts.delete(sessionId);
14062
+ } else {
14063
+ counts.set(sessionId, current - 1);
14064
+ }
14065
+ }
14066
+ isSessionIdle(sessionId) {
14067
+ return (this.execSet().get(sessionId) ?? 0) <= 0;
14068
+ }
14069
+ getExecutingSessionIds() {
14070
+ const ids = [];
14071
+ for (const [sid, count] of this.execSet().entries()) {
14072
+ if (count > 0)
14073
+ ids.push(sid);
14074
+ }
14075
+ return ids;
14076
+ }
14077
+ abortAllExecutingSessions() {
14078
+ this.execSet().clear();
13414
14079
  }
13415
14080
  setMode(mode, detail) {
13416
14081
  this.replMode = mode;
@@ -13433,30 +14098,37 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13433
14098
  }));
13434
14099
  }
13435
14100
  async executeInternal(message, options) {
13436
- this.isExecuting = true;
13437
- this.setMode("executing");
13438
- const agentContext = this.currentAgentContext;
13439
- this.currentAgentContext = undefined;
14101
+ const execSessionId = options.sessionId ?? this.options.sessionId;
14102
+ const agentContext = {
14103
+ ...options.agentContext,
14104
+ eventSessionId: options.agentContext?.eventSessionId ?? execSessionId
14105
+ };
13440
14106
  resetStreamAbort();
13441
14107
  const provider = getTracerProvider3();
13442
14108
  const tracer = provider.getTracer("roy-tracer");
13443
- const existingContext = provider.getGlobalContext();
14109
+ const payloadTraceContext = agentContext?.envEvent?.metadata?.traceContext;
14110
+ const existingContext = payloadTraceContext ?? provider.getGlobalContext();
13444
14111
  let rootSpan;
13445
14112
  let traceId;
13446
- if (existingContext) {
13447
- rootSpan = tracer.startSpan("interactive.execute", {
13448
- parent: existingContext
13449
- });
13450
- traceId = existingContext.traceId;
13451
- } else {
13452
- rootSpan = tracer.startSpan("interactive.execute");
13453
- traceId = rootSpan.spanContext.traceId;
13454
- provider.setGlobalContext(rootSpan.spanContext);
13455
- }
13456
- console.log(`${COLORS.system("[Trace] " + traceId)}`);
13457
- console.log(`${COLORS.progress("[thinking...]")}`);
14113
+ let runTraceContext = existingContext;
13458
14114
  try {
13459
- await this.options.onExecute(message, traceId, agentContext);
14115
+ this.markSessionExecuting(execSessionId, true);
14116
+ this.setMode("executing");
14117
+ await provider.withGlobalContext(runTraceContext, async () => {
14118
+ if (existingContext) {
14119
+ rootSpan = tracer.startSpan("interactive.execute", {
14120
+ parent: existingContext
14121
+ });
14122
+ traceId = existingContext.traceId;
14123
+ } else {
14124
+ rootSpan = tracer.startSpan("interactive.execute");
14125
+ traceId = rootSpan.spanContext.traceId;
14126
+ runTraceContext = rootSpan.spanContext;
14127
+ }
14128
+ console.log(`${COLORS.system("[Trace] " + traceId)}`);
14129
+ console.log(`${COLORS.progress("[thinking...]")}`);
14130
+ await this.options.onExecute(message, traceId, agentContext);
14131
+ });
13460
14132
  if (this.options.onAfterExecute) {
13461
14133
  try {
13462
14134
  await this.options.onAfterExecute(this.lastUsage);
@@ -13472,7 +14144,7 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13472
14144
  console.error(`
13473
14145
  ${COLORS.error("❌ 执行失败: " + error)}
13474
14146
  `);
13475
- rootSpan.setAttribute("error", String(error));
14147
+ rootSpan?.setAttribute("error", String(error));
13476
14148
  try {
13477
14149
  await notifyReactFailure3({
13478
14150
  error,
@@ -13484,14 +14156,15 @@ ${COLORS.error("❌ 执行失败: " + error)}
13484
14156
  }
13485
14157
  }
13486
14158
  } finally {
13487
- rootSpan.end();
13488
- if (existingContext) {
13489
- provider.setGlobalContext(existingContext);
13490
- } else {
13491
- provider.setGlobalContext(undefined);
14159
+ try {
14160
+ provider.runInGlobalContextSync(runTraceContext, () => rootSpan?.end());
14161
+ } catch (endErr) {
14162
+ logger6.warn("root span end failed (ignored)", { error: String(endErr) });
14163
+ }
14164
+ this.markSessionExecuting(execSessionId, false);
14165
+ if (this.isIdle()) {
14166
+ this.setMode("ready");
13492
14167
  }
13493
- this.isExecuting = false;
13494
- this.setMode("ready");
13495
14168
  if (options.showPrompt) {
13496
14169
  this.redrawInputArea();
13497
14170
  if (this.rl) {
@@ -13661,6 +14334,9 @@ ${COLORS.error("❌ 执行失败: " + error)}
13661
14334
  }
13662
14335
  }
13663
14336
  abortStream();
14337
+ for (const sid of this.getExecutingSessionIds()) {
14338
+ abortStream(sid);
14339
+ }
13664
14340
  if (this.inkApi && typeof this.inkApi.finishAssistantStream === "function") {
13665
14341
  try {
13666
14342
  this.inkApi.finishAssistantStream();
@@ -13687,7 +14363,7 @@ ${COLORS.error("❌ 执行失败: " + error)}
13687
14363
  }
13688
14364
  }
13689
14365
  if (isExecuting) {
13690
- this.isExecuting = false;
14366
+ this.abortAllExecutingSessions();
13691
14367
  }
13692
14368
  this.setMode("ready");
13693
14369
  if (this.rl) {
@@ -13890,6 +14566,57 @@ function createInteractiveCommand(externalEnvService) {
13890
14566
  quiet: true,
13891
14567
  silent: useInk
13892
14568
  });
14569
+ const MAX_SESSION_EXECUTORS = 16;
14570
+ const sessionExecutors = new Map;
14571
+ const runningTurns = new Set;
14572
+ let sessionChunkCallback = undefined;
14573
+ const getSessionExecutor = (sid) => {
14574
+ if (!sid)
14575
+ return queryExecutor;
14576
+ const cached = sessionExecutors.get(sid);
14577
+ if (cached) {
14578
+ sessionExecutors.delete(sid);
14579
+ sessionExecutors.set(sid, cached);
14580
+ return cached;
14581
+ }
14582
+ const created = new QueryExecutor({
14583
+ env,
14584
+ sessionComponent,
14585
+ output,
14586
+ quiet: true,
14587
+ silent: true
14588
+ });
14589
+ created.initStreamOutput(llmComponent);
14590
+ created.setSessionScope(sid);
14591
+ created.setAcceptsSidelessEvents(false);
14592
+ created.setChunkCallback(sessionChunkCallback);
14593
+ sessionExecutors.set(sid, created);
14594
+ while (sessionExecutors.size > MAX_SESSION_EXECUTORS) {
14595
+ let victim;
14596
+ for (const key of sessionExecutors.keys()) {
14597
+ if (!runningTurns.has(key)) {
14598
+ victim = key;
14599
+ break;
14600
+ }
14601
+ }
14602
+ if (victim === undefined)
14603
+ break;
14604
+ const evicted = sessionExecutors.get(victim);
14605
+ sessionExecutors.delete(victim);
14606
+ try {
14607
+ evicted?.dispose();
14608
+ } catch {}
14609
+ }
14610
+ return created;
14611
+ };
14612
+ const disposeSessionExecutors = () => {
14613
+ for (const ex of sessionExecutors.values()) {
14614
+ try {
14615
+ ex.dispose();
14616
+ } catch {}
14617
+ }
14618
+ sessionExecutors.clear();
14619
+ };
13893
14620
  let currentAgentContext = undefined;
13894
14621
  if (args.agent && args.agent !== "default") {
13895
14622
  const agentComponent = env.getComponent("agent");
@@ -13992,7 +14719,8 @@ function createInteractiveCommand(externalEnvService) {
13992
14719
  version: getPackageVersion(),
13993
14720
  useInk,
13994
14721
  onExecute: async (message, traceId, agentContext) => {
13995
- resetStreamAbort();
14722
+ const runSessionId = agentContext?.eventSessionId;
14723
+ resetStreamAbort(runSessionId);
13996
14724
  const preloadedPrompts = eventSourceSystemPrompts;
13997
14725
  const eventSourceSystemPrompt = preloadedPrompts.length > 0 ? joinResolvedSystemPrompts(preloadedPrompts) : undefined;
13998
14726
  const mergedContext = {
@@ -14007,16 +14735,27 @@ function createInteractiveCommand(externalEnvService) {
14007
14735
  }
14008
14736
  };
14009
14737
  const effectiveSessionId = mergedContext.eventSessionId ?? sessionId;
14010
- const result = await queryExecutor.execute(message, effectiveSessionId, {
14011
- showReasoning: args.reasoning,
14012
- showToolCalls: args.toolCalls,
14013
- showToolResults: args.toolResults
14014
- }, traceId, mergedContext);
14015
- if (result && !queryExecutor.getFullText()) {
14016
- console.log(result);
14738
+ const isEventDrivenTurn = !!runSessionId && runSessionId !== sessionId;
14739
+ const exec2 = isEventDrivenTurn ? getSessionExecutor(effectiveSessionId) : queryExecutor;
14740
+ runningTurns.add(effectiveSessionId);
14741
+ try {
14742
+ const result = await exec2.execute(message, effectiveSessionId, {
14743
+ showReasoning: args.reasoning,
14744
+ showToolCalls: args.toolCalls,
14745
+ showToolResults: args.toolResults
14746
+ }, traceId, mergedContext);
14747
+ if (result && !exec2.getFullText()) {
14748
+ console.log(result);
14749
+ } else if (isEventDrivenTurn) {
14750
+ const full = exec2.getFullText();
14751
+ if (full)
14752
+ console.log(full);
14753
+ }
14754
+ repl.setLastUsage(exec2.getLastUsage());
14755
+ return result;
14756
+ } finally {
14757
+ runningTurns.delete(effectiveSessionId);
14017
14758
  }
14018
- repl.setLastUsage(queryExecutor.getLastUsage());
14019
- return result;
14020
14759
  },
14021
14760
  onAfterExecute: (usage) => {
14022
14761
  if (useInk)
@@ -14041,6 +14780,7 @@ function createInteractiveCommand(externalEnvService) {
14041
14780
  onSwitchSession: async (newSessionId) => {
14042
14781
  const result = await sessionManager.switchSession(newSessionId);
14043
14782
  queryExecutor.dispose();
14783
+ disposeSessionExecutors();
14044
14784
  return result;
14045
14785
  },
14046
14786
  onCompact: async () => {
@@ -14090,6 +14830,7 @@ function createInteractiveCommand(externalEnvService) {
14090
14830
  eventTypes: ["task.background.*", "event-source.event.*"],
14091
14831
  formatter: new EventMessageFormatter({ prefix: "[通知]" }),
14092
14832
  isIdle: () => repl.isIdle(),
14833
+ isSessionIdle: (sid) => repl.isSessionIdle(sid),
14093
14834
  sessionComponent,
14094
14835
  onEvent: async (_message) => {},
14095
14836
  onEventWithEnv: async (formattedEvent) => {
@@ -14123,6 +14864,20 @@ function createInteractiveCommand(externalEnvService) {
14123
14864
  repl.sessionManager = sessionManager;
14124
14865
  repl.sessionComponent = sessionComponent;
14125
14866
  repl._queryExecutor = queryExecutor;
14867
+ {
14868
+ let inkCb = null;
14869
+ Object.defineProperty(repl, "inkChunkCallback", {
14870
+ configurable: true,
14871
+ get: () => inkCb,
14872
+ set: (cb) => {
14873
+ inkCb = cb;
14874
+ sessionChunkCallback = cb ?? undefined;
14875
+ for (const ex of sessionExecutors.values()) {
14876
+ ex.setChunkCallback(sessionChunkCallback);
14877
+ }
14878
+ }
14879
+ });
14880
+ }
14126
14881
  await repl.start();
14127
14882
  process.removeListener("SIGINT", sigintHandler);
14128
14883
  console.log("正在清理资源...");
@@ -14134,6 +14889,7 @@ function createInteractiveCommand(externalEnvService) {
14134
14889
  }
14135
14890
  eventHandler.stop();
14136
14891
  queryExecutor.dispose();
14892
+ disposeSessionExecutors();
14137
14893
  const esComponent = env.getComponent("event-source");
14138
14894
  if (esComponent) {
14139
14895
  const sources = esComponent.list?.();
@@ -15501,6 +16257,7 @@ async function loadHandleQuery(options, hooks) {
15501
16257
  const llmComponent = envService.getLLM();
15502
16258
  queryExecutor.initStreamOutput(llmComponent ?? null);
15503
16259
  const handleQuery = async (message, envEvent) => {
16260
+ const eventTraceContext = envEvent?.metadata?.traceContext;
15504
16261
  const mergedContext = {
15505
16262
  ...agentContext,
15506
16263
  model: options.model,
@@ -15515,7 +16272,7 @@ async function loadHandleQuery(options, hooks) {
15515
16272
  showReasoning: options.reasoning ?? true,
15516
16273
  showToolCalls: options.toolCalls ?? true,
15517
16274
  showToolResults: options.toolResults ?? false
15518
- }, undefined, mergedContext);
16275
+ }, eventTraceContext?.traceId, mergedContext);
15519
16276
  return String(result ?? "");
15520
16277
  };
15521
16278
  const taskEventHandler = new TaskEventHandler({
@@ -15592,6 +16349,7 @@ function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext
15592
16349
  break;
15593
16350
  }
15594
16351
  });
16352
+ const eventTraceContext = envEvent?.metadata?.traceContext;
15595
16353
  const mergedContext = {
15596
16354
  ...agentContext,
15597
16355
  model: options.model,
@@ -15606,7 +16364,7 @@ function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext
15606
16364
  showReasoning: options.reasoning ?? true,
15607
16365
  showToolCalls: true,
15608
16366
  showToolResults: true
15609
- }, undefined, mergedContext);
16367
+ }, eventTraceContext?.traceId, mergedContext);
15610
16368
  return String(result ?? "");
15611
16369
  };
15612
16370
  }