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