@ai-setting/roy-agent-cli 1.7.55 → 1.7.57

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);
377
383
  }
378
- function abortStream() {
379
- streamAbortSignal.aborted = true;
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);
397
+ }
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.55",
9642
+ version: "1.7.57",
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,6 +11779,10 @@ 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";
11701
11788
  function parseLarkPerTaskChatId(contextJson) {
@@ -11717,6 +11804,8 @@ var NOTIFY_V2_TERMINAL_TYPES = new Set([
11717
11804
  ]);
11718
11805
 
11719
11806
  class EventHandler {
11807
+ static DEFAULT_LANE_KEY = "__default__";
11808
+ static DEFAULT_MAX_CONCURRENT_SESSIONS = 4;
11720
11809
  env;
11721
11810
  eventTypes;
11722
11811
  onEvent;
@@ -11724,10 +11813,25 @@ class EventHandler {
11724
11813
  formatter;
11725
11814
  isIdle;
11726
11815
  idleCheckInterval;
11727
- queue = [];
11728
- isProcessing = false;
11816
+ isSessionIdleFn = null;
11817
+ maxConcurrentSessions;
11818
+ lanes = new Map;
11819
+ runningLanes = new Set;
11820
+ laneTails = new Map;
11821
+ get isProcessing() {
11822
+ return this.runningLanes.size > 0;
11823
+ }
11824
+ chatSessionCache = new Map;
11825
+ inFlightCount = 0;
11826
+ legacyQueue = [];
11827
+ get queue() {
11828
+ return this.legacyQueue;
11829
+ }
11729
11830
  isStopped = false;
11730
11831
  unsubscribe = null;
11832
+ activeSlots = 0;
11833
+ slotWaiters = [];
11834
+ sessionChains = new Map;
11731
11835
  sessionComponent = null;
11732
11836
  activeEventSessionId = null;
11733
11837
  resolvedSessionByTrigger = new Map;
@@ -11740,6 +11844,9 @@ class EventHandler {
11740
11844
  this.onEventWithEnv = options.onEventWithEnv;
11741
11845
  this.formatter = options.formatter ?? new EventMessageFormatter;
11742
11846
  this.isIdle = options.isIdle ?? (() => true);
11847
+ this.isSessionIdleFn = options.isSessionIdle ?? null;
11848
+ const maxSessions = options.maxConcurrentSessions;
11849
+ this.maxConcurrentSessions = typeof maxSessions === "number" && Number.isFinite(maxSessions) && maxSessions >= 1 ? Math.floor(maxSessions) : EventHandler.DEFAULT_MAX_CONCURRENT_SESSIONS;
11743
11850
  this.idleCheckInterval = options.idleCheckInterval ?? 100;
11744
11851
  this.sessionComponent = options.sessionComponent ?? null;
11745
11852
  this.eventStore = options.eventStore ?? null;
@@ -11777,8 +11884,13 @@ class EventHandler {
11777
11884
  this.unsubscribe();
11778
11885
  this.unsubscribe = null;
11779
11886
  }
11780
- this.queue = [];
11781
- this.isProcessing = false;
11887
+ this.lanes.clear();
11888
+ this.legacyQueue = [];
11889
+ this.laneTails.clear();
11890
+ const waiters = this.slotWaiters.splice(0, this.slotWaiters.length);
11891
+ for (const w of waiters)
11892
+ w();
11893
+ this.activeSlots = 0;
11782
11894
  this.clearActiveSession();
11783
11895
  }
11784
11896
  async handleEvent(event) {
@@ -11900,77 +12012,218 @@ class EventHandler {
11900
12012
  await this.onEvent(formatted.message);
11901
12013
  }
11902
12014
  }
12015
+ laneKeyFor(item) {
12016
+ if (item.chatId)
12017
+ return item.chatId;
12018
+ if (item.triggerSessionId)
12019
+ return `trigger:${item.triggerSessionId}`;
12020
+ return EventHandler.DEFAULT_LANE_KEY;
12021
+ }
11903
12022
  enqueue(formattedEvent) {
11904
12023
  if (this.isStopped) {
11905
12024
  return;
11906
12025
  }
11907
- this.queue.push(formattedEvent);
11908
- if (!this.isProcessing) {
11909
- this.processQueue();
12026
+ const item = { ...formattedEvent };
12027
+ if (!item.envContext) {
12028
+ item.envContext = getEnvContext();
12029
+ }
12030
+ const laneKey = this.laneKeyFor(item);
12031
+ const lane = this.lanes.get(laneKey);
12032
+ if (lane) {
12033
+ lane.push(item);
12034
+ } else {
12035
+ this.lanes.set(laneKey, [item]);
11910
12036
  }
12037
+ this.scheduleLane(laneKey);
11911
12038
  }
11912
12039
  getQueueLength() {
11913
- return this.queue.length;
12040
+ let total = this.legacyQueue.length + this.inFlightCount;
12041
+ for (const lane of this.lanes.values()) {
12042
+ total += lane.length;
12043
+ }
12044
+ return total;
11914
12045
  }
11915
12046
  isQueueEmpty() {
11916
- return this.queue.length === 0;
12047
+ return this.getQueueLength() === 0;
11917
12048
  }
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;
12049
+ isSessionIdle(laneKey) {
12050
+ return !this.runningLanes.has(laneKey);
12051
+ }
12052
+ getRunningLaneKeys() {
12053
+ return Array.from(this.runningLanes);
12054
+ }
12055
+ scheduleLane(laneKey) {
12056
+ if (this.isStopped)
12057
+ return;
12058
+ if (this.runningLanes.has(laneKey))
12059
+ return;
12060
+ this.runningLanes.add(laneKey);
12061
+ const tail = this.runLane(laneKey).catch((err) => {
12062
+ console.error(`[EventHandler] lane ${laneKey} 处理异常:`, err);
12063
+ }).finally(() => {
12064
+ this.runningLanes.delete(laneKey);
12065
+ this.laneTails.delete(laneKey);
12066
+ const rest = this.lanes.get(laneKey);
12067
+ if (rest && rest.length > 0) {
12068
+ this.scheduleLane(laneKey);
11925
12069
  }
11926
- const item = this.queue.shift();
11927
- if (!item) {
12070
+ });
12071
+ this.laneTails.set(laneKey, tail);
12072
+ }
12073
+ async runLane(laneKey) {
12074
+ while (!this.isStopped) {
12075
+ const lane = this.lanes.get(laneKey);
12076
+ if (!lane || lane.length === 0) {
11928
12077
  break;
11929
12078
  }
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);
12079
+ const head = lane[0];
12080
+ const sessionId = await runWithEnvContextAsync(head.envContext, () => this.resolveSessionForItem(laneKey, head));
12081
+ if (this.isStopped)
12082
+ break;
12083
+ const canonicalKey = sessionId ?? laneKey;
12084
+ if (canonicalKey !== laneKey) {
12085
+ const mine = this.lanes.get(laneKey) ?? [];
12086
+ if (mine.length > 0) {
12087
+ const target = this.lanes.get(canonicalKey);
12088
+ if (target) {
12089
+ target.push(...mine);
12090
+ } else {
12091
+ this.lanes.set(canonicalKey, mine);
11939
12092
  }
12093
+ this.lanes.delete(laneKey);
12094
+ this.scheduleLane(canonicalKey);
11940
12095
  }
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);
12096
+ return;
11947
12097
  }
12098
+ await this.waitForIdle(sessionId ?? laneKey);
12099
+ if (this.isStopped)
12100
+ break;
12101
+ const claimKey = sessionId ?? laneKey;
12102
+ const releaseClaim = await this.claimSession(claimKey);
11948
12103
  if (this.isStopped) {
12104
+ releaseClaim();
11949
12105
  break;
11950
12106
  }
11951
- if (sessionId) {
11952
- item.eventSessionId = sessionId;
12107
+ await this.acquireSlot();
12108
+ if (this.isStopped) {
12109
+ releaseClaim();
12110
+ this.releaseSlot();
12111
+ break;
11953
12112
  }
11954
12113
  try {
12114
+ const item = lane.shift();
12115
+ if (!item)
12116
+ break;
12117
+ this.inFlightCount++;
12118
+ try {
12119
+ await this.processItem(item, sessionId);
12120
+ } finally {
12121
+ this.inFlightCount--;
12122
+ }
12123
+ } finally {
12124
+ releaseClaim();
12125
+ this.releaseSlot();
12126
+ }
12127
+ }
12128
+ const rest = this.lanes.get(laneKey);
12129
+ if (!rest || rest.length === 0) {
12130
+ this.lanes.delete(laneKey);
12131
+ }
12132
+ }
12133
+ async resolveSessionForItem(laneKey, item) {
12134
+ if (item.sessionResolved) {
12135
+ return item.resolvedSessionId ?? null;
12136
+ }
12137
+ const resolved = await this.resolveSessionForItemUncached(laneKey, item);
12138
+ item.sessionResolved = true;
12139
+ item.resolvedSessionId = resolved;
12140
+ return resolved;
12141
+ }
12142
+ async resolveSessionForItemUncached(laneKey, item) {
12143
+ if (item.chatId && this.sessionComponent) {
12144
+ const cached = this.chatSessionCache.get(item.chatId);
12145
+ if (cached)
12146
+ return cached;
12147
+ let resolved = await this.ensureChatSession(item.chatId);
12148
+ if (!resolved) {
12149
+ console.warn(`[EventHandler] Failed to ensure chat-keyed session (chatId=${item.chatId}), ` + `falling back to trigger_session_id path`);
12150
+ resolved = await this.ensureActiveSession(item.triggerSessionId);
12151
+ } else {
12152
+ this.chatSessionCache.set(item.chatId, resolved);
12153
+ }
12154
+ return resolved;
12155
+ }
12156
+ return this.ensureActiveSession(item.triggerSessionId);
12157
+ }
12158
+ async processItem(item, sessionId) {
12159
+ if (sessionId) {
12160
+ item.eventSessionId = sessionId;
12161
+ }
12162
+ try {
12163
+ await runWithEnvContextAsync(item.envContext, async () => {
11955
12164
  if (this.onEventWithEnv) {
11956
12165
  await this.onEventWithEnv(item);
11957
- continue;
12166
+ return;
11958
12167
  }
11959
12168
  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
- }
12169
+ });
12170
+ } catch (error) {
12171
+ console.error(`[EventHandler] 处理事件失败:`, error);
12172
+ try {
12173
+ await notifyReactFailure2({
12174
+ error,
12175
+ envEvent: item.envEvent,
12176
+ sessionId: sessionId ?? undefined
12177
+ });
12178
+ } catch (notifyErr) {
12179
+ console.error(`[EventHandler] 失败说明回飞书异常:`, notifyErr);
11971
12180
  }
11972
12181
  }
11973
- this.isProcessing = false;
12182
+ }
12183
+ async processQueue() {
12184
+ if (this.isStopped)
12185
+ return;
12186
+ if (this.legacyQueue.length > 0) {
12187
+ const pending = this.legacyQueue.splice(0, this.legacyQueue.length);
12188
+ for (const item of pending) {
12189
+ if (!item.envContext) {
12190
+ item.envContext = getEnvContext();
12191
+ }
12192
+ const laneKey = this.laneKeyFor(item);
12193
+ const lane = this.lanes.get(laneKey);
12194
+ if (lane)
12195
+ lane.push(item);
12196
+ else
12197
+ this.lanes.set(laneKey, [item]);
12198
+ }
12199
+ }
12200
+ for (const [laneKey, lane] of Array.from(this.lanes.entries())) {
12201
+ if (lane.length > 0)
12202
+ this.scheduleLane(laneKey);
12203
+ }
12204
+ const tails = Array.from(this.laneTails.values());
12205
+ if (tails.length > 0) {
12206
+ await Promise.all(tails.map((t) => t.catch(() => {
12207
+ return;
12208
+ })));
12209
+ }
12210
+ }
12211
+ async acquireSlot() {
12212
+ if (this.activeSlots < this.maxConcurrentSessions) {
12213
+ this.activeSlots++;
12214
+ return;
12215
+ }
12216
+ await new Promise((resolve) => {
12217
+ this.slotWaiters.push(resolve);
12218
+ });
12219
+ this.activeSlots++;
12220
+ }
12221
+ releaseSlot() {
12222
+ if (this.activeSlots > 0)
12223
+ this.activeSlots--;
12224
+ const next = this.slotWaiters.shift();
12225
+ if (next)
12226
+ next();
11974
12227
  }
11975
12228
  async ensureActiveSession(triggerSessionIdOverride) {
11976
12229
  if (!this.sessionComponent)
@@ -12080,6 +12333,7 @@ class EventHandler {
12080
12333
  clearActiveSession() {
12081
12334
  this.activeEventSessionId = null;
12082
12335
  this.resolvedSessionByTrigger.clear();
12336
+ this.chatSessionCache.clear();
12083
12337
  }
12084
12338
  cacheResolvedSession(triggerSessionIdOverride, sessionId) {
12085
12339
  if (triggerSessionIdOverride && sessionId) {
@@ -12089,11 +12343,38 @@ class EventHandler {
12089
12343
  getActiveEventSessionId() {
12090
12344
  return this.activeEventSessionId;
12091
12345
  }
12092
- async waitForIdle() {
12093
- while (!this.isStopped && !this.isIdle()) {
12346
+ async waitForIdle(sessionId) {
12347
+ while (!this.isStopped && !this.isLaneIdle(sessionId)) {
12094
12348
  await this.sleep(this.idleCheckInterval);
12095
12349
  }
12096
12350
  }
12351
+ isLaneIdle(sessionId) {
12352
+ if (this.isSessionIdleFn && sessionId) {
12353
+ return this.isSessionIdleFn(sessionId);
12354
+ }
12355
+ return this.isIdle();
12356
+ }
12357
+ claimSession(sessionKey) {
12358
+ const prev = this.sessionChains.get(sessionKey) ?? Promise.resolve();
12359
+ let releaseFn;
12360
+ const current = new Promise((resolve) => {
12361
+ releaseFn = resolve;
12362
+ });
12363
+ const tail = prev.then(() => current);
12364
+ this.sessionChains.set(sessionKey, tail);
12365
+ return prev.then(() => {
12366
+ let released = false;
12367
+ return () => {
12368
+ if (released)
12369
+ return;
12370
+ released = true;
12371
+ releaseFn();
12372
+ if (this.sessionChains.get(sessionKey) === tail) {
12373
+ this.sessionChains.delete(sessionKey);
12374
+ }
12375
+ };
12376
+ });
12377
+ }
12097
12378
  sleep(ms) {
12098
12379
  return new Promise((resolve) => setTimeout(resolve, ms));
12099
12380
  }
@@ -12188,12 +12469,24 @@ function createEventHandlerForInteractive(options) {
12188
12469
  const dbPath = options.dbPath ?? createDefaultEventStoreDbPath();
12189
12470
  const eventStore = getOrCreateEventStore(dbPath);
12190
12471
  const getHandlerForEsType = getHandlerForEsTypeForEnv(options.env);
12472
+ const maxConcurrentSessions = options.maxConcurrentSessions ?? readMaxConcurrentSessionsFromEnv();
12191
12473
  return new EventHandler({
12192
12474
  ...options,
12475
+ ...maxConcurrentSessions !== undefined ? { maxConcurrentSessions } : {},
12193
12476
  eventStore,
12194
12477
  getHandlerForEsType
12195
12478
  });
12196
12479
  }
12480
+ var MAX_CONCURRENT_SESSIONS_ENV = "ROY_AGENT_MAX_CONCURRENT_SESSIONS";
12481
+ function readMaxConcurrentSessionsFromEnv(env = process.env) {
12482
+ const raw = env[MAX_CONCURRENT_SESSIONS_ENV];
12483
+ if (raw === undefined || raw.trim() === "")
12484
+ return;
12485
+ const parsed = Number(raw);
12486
+ if (!Number.isFinite(parsed) || parsed < 1)
12487
+ return;
12488
+ return Math.max(1, Math.floor(parsed));
12489
+ }
12197
12490
 
12198
12491
  // src/commands/shared/esc-active-descendant.ts
12199
12492
  function hasActiveDescendants(env, parentSessionId) {
@@ -12866,7 +13159,8 @@ var USER_PROMPT = COLORS.accent("❯ ");
12866
13159
  class REPL {
12867
13160
  rl;
12868
13161
  options;
12869
- isExecuting = false;
13162
+ executingSessions = new Map;
13163
+ static LEGACY_SESSION_KEY = "__legacy_isExecuting__";
12870
13164
  replMode = "ready";
12871
13165
  statusBarCtx = {};
12872
13166
  isAltEnterPending = false;
@@ -13059,7 +13353,10 @@ ${COLORS.system("[正在中断...]")}`);
13059
13353
  }
13060
13354
  }
13061
13355
  abortStream();
13062
- this.isExecuting = false;
13356
+ for (const sid of this.getExecutingSessionIds()) {
13357
+ abortStream(sid);
13358
+ }
13359
+ this.abortAllExecutingSessions();
13063
13360
  console.log(`${COLORS.system("[已中断,可以继续输入]")}`);
13064
13361
  } else {
13065
13362
  console.log(`
@@ -13144,7 +13441,8 @@ ${COLORS.system("[没有正在执行的请求]")}`);
13144
13441
  this.inkApi = api;
13145
13442
  },
13146
13443
  onSubmit: async (input) => {
13147
- this.isExecuting = true;
13444
+ const inkSessionId = this.options.sessionId;
13445
+ this.markSessionExecuting(inkSessionId, true);
13148
13446
  inkApiRef?.setMode("executing");
13149
13447
  try {
13150
13448
  await this.options.onExecute(input);
@@ -13162,7 +13460,7 @@ ${COLORS.system("[没有正在执行的请求]")}`);
13162
13460
  inkApiRef?.appendMessage({ role: "system", content: `[Error] ${msg}` });
13163
13461
  throw err;
13164
13462
  } finally {
13165
- this.isExecuting = false;
13463
+ this.markSessionExecuting(inkSessionId, false);
13166
13464
  }
13167
13465
  },
13168
13466
  onCommand: async (cmd) => {
@@ -13315,7 +13613,8 @@ ${COLORS.error("❓ 未知命令: /" + cmd + "}")}`);
13315
13613
  }
13316
13614
  }
13317
13615
  async handleMessage(message) {
13318
- if (this.isExecuting) {
13616
+ const inputSessionId = this.options.sessionId;
13617
+ if (!this.isSessionIdle(inputSessionId)) {
13319
13618
  console.log(`
13320
13619
  ${COLORS.system("⏳ 上一次请求尚未完成,请稍候...")}
13321
13620
  `);
@@ -13382,7 +13681,7 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13382
13681
  };
13383
13682
  }
13384
13683
  }
13385
- this.currentAgentContext = {
13684
+ const agentContext = {
13386
13685
  agentType: agentFromEvent,
13387
13686
  pluginEnabled,
13388
13687
  envEvent: envEventForAgent,
@@ -13390,9 +13689,12 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13390
13689
  eventSessionId,
13391
13690
  systemPrompt: envEvent.metadata?.systemPrompt
13392
13691
  };
13393
- await this.executeInternal(message, { showPrompt: true });
13692
+ await this.executeInternal(message, {
13693
+ showPrompt: true,
13694
+ sessionId: eventSessionId,
13695
+ agentContext
13696
+ });
13394
13697
  }
13395
- currentAgentContext = undefined;
13396
13698
  lastReplyChannel = undefined;
13397
13699
  lastReplyChannelByChat = new Map;
13398
13700
  static REPLY_CHANNEL_CHAT_CACHE_MAX = 100;
@@ -13411,7 +13713,56 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13411
13713
  }
13412
13714
  }
13413
13715
  isIdle() {
13414
- return !this.isExecuting;
13716
+ return !this.anySessionBusy();
13717
+ }
13718
+ anySessionBusy() {
13719
+ for (const count of this.execSet().values()) {
13720
+ if (count > 0)
13721
+ return true;
13722
+ }
13723
+ return false;
13724
+ }
13725
+ execSet() {
13726
+ if (!this.executingSessions)
13727
+ this.executingSessions = new Map;
13728
+ return this.executingSessions;
13729
+ }
13730
+ get isExecuting() {
13731
+ return this.anySessionBusy();
13732
+ }
13733
+ set isExecuting(executing) {
13734
+ if (!executing) {
13735
+ this.execSet().clear();
13736
+ return;
13737
+ }
13738
+ this.execSet().set(REPL.LEGACY_SESSION_KEY, 1);
13739
+ }
13740
+ markSessionExecuting(sessionId, executing) {
13741
+ const counts = this.execSet();
13742
+ if (executing) {
13743
+ counts.set(sessionId, (counts.get(sessionId) ?? 0) + 1);
13744
+ return;
13745
+ }
13746
+ const current = counts.get(sessionId) ?? 0;
13747
+ if (current <= 1) {
13748
+ counts.delete(sessionId);
13749
+ } else {
13750
+ counts.set(sessionId, current - 1);
13751
+ }
13752
+ }
13753
+ isSessionIdle(sessionId) {
13754
+ return (this.execSet().get(sessionId) ?? 0) <= 0;
13755
+ }
13756
+ getExecutingSessionIds() {
13757
+ const ids = [];
13758
+ for (const [sid, count] of this.execSet().entries()) {
13759
+ if (count > 0)
13760
+ ids.push(sid);
13761
+ }
13762
+ return ids;
13763
+ }
13764
+ abortAllExecutingSessions() {
13765
+ this.execSet().clear();
13415
13766
  }
13416
13767
  setMode(mode, detail) {
13417
13768
  this.replMode = mode;
@@ -13434,30 +13785,37 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13434
13785
  }));
13435
13786
  }
13436
13787
  async executeInternal(message, options) {
13437
- this.isExecuting = true;
13438
- this.setMode("executing");
13439
- const agentContext = this.currentAgentContext;
13440
- this.currentAgentContext = undefined;
13788
+ const execSessionId = options.sessionId ?? this.options.sessionId;
13789
+ const agentContext = {
13790
+ ...options.agentContext,
13791
+ eventSessionId: options.agentContext?.eventSessionId ?? execSessionId
13792
+ };
13441
13793
  resetStreamAbort();
13442
13794
  const provider = getTracerProvider3();
13443
13795
  const tracer = provider.getTracer("roy-tracer");
13444
- const existingContext = provider.getGlobalContext();
13796
+ const payloadTraceContext = agentContext?.envEvent?.metadata?.traceContext;
13797
+ const existingContext = payloadTraceContext ?? provider.getGlobalContext();
13445
13798
  let rootSpan;
13446
13799
  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...]")}`);
13800
+ let runTraceContext = existingContext;
13459
13801
  try {
13460
- await this.options.onExecute(message, traceId, agentContext);
13802
+ this.markSessionExecuting(execSessionId, true);
13803
+ this.setMode("executing");
13804
+ await provider.withGlobalContext(runTraceContext, async () => {
13805
+ if (existingContext) {
13806
+ rootSpan = tracer.startSpan("interactive.execute", {
13807
+ parent: existingContext
13808
+ });
13809
+ traceId = existingContext.traceId;
13810
+ } else {
13811
+ rootSpan = tracer.startSpan("interactive.execute");
13812
+ traceId = rootSpan.spanContext.traceId;
13813
+ runTraceContext = rootSpan.spanContext;
13814
+ }
13815
+ console.log(`${COLORS.system("[Trace] " + traceId)}`);
13816
+ console.log(`${COLORS.progress("[thinking...]")}`);
13817
+ await this.options.onExecute(message, traceId, agentContext);
13818
+ });
13461
13819
  if (this.options.onAfterExecute) {
13462
13820
  try {
13463
13821
  await this.options.onAfterExecute(this.lastUsage);
@@ -13473,7 +13831,7 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13473
13831
  console.error(`
13474
13832
  ${COLORS.error("❌ 执行失败: " + error)}
13475
13833
  `);
13476
- rootSpan.setAttribute("error", String(error));
13834
+ rootSpan?.setAttribute("error", String(error));
13477
13835
  try {
13478
13836
  await notifyReactFailure3({
13479
13837
  error,
@@ -13485,14 +13843,15 @@ ${COLORS.error("❌ 执行失败: " + error)}
13485
13843
  }
13486
13844
  }
13487
13845
  } finally {
13488
- rootSpan.end();
13489
- if (existingContext) {
13490
- provider.setGlobalContext(existingContext);
13491
- } else {
13492
- provider.setGlobalContext(undefined);
13846
+ try {
13847
+ provider.runInGlobalContextSync(runTraceContext, () => rootSpan?.end());
13848
+ } catch (endErr) {
13849
+ logger6.warn("root span end failed (ignored)", { error: String(endErr) });
13850
+ }
13851
+ this.markSessionExecuting(execSessionId, false);
13852
+ if (this.isIdle()) {
13853
+ this.setMode("ready");
13493
13854
  }
13494
- this.isExecuting = false;
13495
- this.setMode("ready");
13496
13855
  if (options.showPrompt) {
13497
13856
  this.redrawInputArea();
13498
13857
  if (this.rl) {
@@ -13662,6 +14021,9 @@ ${COLORS.error("❌ 执行失败: " + error)}
13662
14021
  }
13663
14022
  }
13664
14023
  abortStream();
14024
+ for (const sid of this.getExecutingSessionIds()) {
14025
+ abortStream(sid);
14026
+ }
13665
14027
  if (this.inkApi && typeof this.inkApi.finishAssistantStream === "function") {
13666
14028
  try {
13667
14029
  this.inkApi.finishAssistantStream();
@@ -13688,7 +14050,7 @@ ${COLORS.error("❌ 执行失败: " + error)}
13688
14050
  }
13689
14051
  }
13690
14052
  if (isExecuting) {
13691
- this.isExecuting = false;
14053
+ this.abortAllExecutingSessions();
13692
14054
  }
13693
14055
  this.setMode("ready");
13694
14056
  if (this.rl) {
@@ -13891,6 +14253,57 @@ function createInteractiveCommand(externalEnvService) {
13891
14253
  quiet: true,
13892
14254
  silent: useInk
13893
14255
  });
14256
+ const MAX_SESSION_EXECUTORS = 16;
14257
+ const sessionExecutors = new Map;
14258
+ const runningTurns = new Set;
14259
+ let sessionChunkCallback = undefined;
14260
+ const getSessionExecutor = (sid) => {
14261
+ if (!sid)
14262
+ return queryExecutor;
14263
+ const cached = sessionExecutors.get(sid);
14264
+ if (cached) {
14265
+ sessionExecutors.delete(sid);
14266
+ sessionExecutors.set(sid, cached);
14267
+ return cached;
14268
+ }
14269
+ const created = new QueryExecutor({
14270
+ env,
14271
+ sessionComponent,
14272
+ output,
14273
+ quiet: true,
14274
+ silent: true
14275
+ });
14276
+ created.initStreamOutput(llmComponent);
14277
+ created.setSessionScope(sid);
14278
+ created.setAcceptsSidelessEvents(false);
14279
+ created.setChunkCallback(sessionChunkCallback);
14280
+ sessionExecutors.set(sid, created);
14281
+ while (sessionExecutors.size > MAX_SESSION_EXECUTORS) {
14282
+ let victim;
14283
+ for (const key of sessionExecutors.keys()) {
14284
+ if (!runningTurns.has(key)) {
14285
+ victim = key;
14286
+ break;
14287
+ }
14288
+ }
14289
+ if (victim === undefined)
14290
+ break;
14291
+ const evicted = sessionExecutors.get(victim);
14292
+ sessionExecutors.delete(victim);
14293
+ try {
14294
+ evicted?.dispose();
14295
+ } catch {}
14296
+ }
14297
+ return created;
14298
+ };
14299
+ const disposeSessionExecutors = () => {
14300
+ for (const ex of sessionExecutors.values()) {
14301
+ try {
14302
+ ex.dispose();
14303
+ } catch {}
14304
+ }
14305
+ sessionExecutors.clear();
14306
+ };
13894
14307
  let currentAgentContext = undefined;
13895
14308
  if (args.agent && args.agent !== "default") {
13896
14309
  const agentComponent = env.getComponent("agent");
@@ -13993,7 +14406,8 @@ function createInteractiveCommand(externalEnvService) {
13993
14406
  version: getPackageVersion(),
13994
14407
  useInk,
13995
14408
  onExecute: async (message, traceId, agentContext) => {
13996
- resetStreamAbort();
14409
+ const runSessionId = agentContext?.eventSessionId;
14410
+ resetStreamAbort(runSessionId);
13997
14411
  const preloadedPrompts = eventSourceSystemPrompts;
13998
14412
  const eventSourceSystemPrompt = preloadedPrompts.length > 0 ? joinResolvedSystemPrompts(preloadedPrompts) : undefined;
13999
14413
  const mergedContext = {
@@ -14008,16 +14422,27 @@ function createInteractiveCommand(externalEnvService) {
14008
14422
  }
14009
14423
  };
14010
14424
  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);
14425
+ const isEventDrivenTurn = !!runSessionId && runSessionId !== sessionId;
14426
+ const exec2 = isEventDrivenTurn ? getSessionExecutor(effectiveSessionId) : queryExecutor;
14427
+ runningTurns.add(effectiveSessionId);
14428
+ try {
14429
+ const result = await exec2.execute(message, effectiveSessionId, {
14430
+ showReasoning: args.reasoning,
14431
+ showToolCalls: args.toolCalls,
14432
+ showToolResults: args.toolResults
14433
+ }, traceId, mergedContext);
14434
+ if (result && !exec2.getFullText()) {
14435
+ console.log(result);
14436
+ } else if (isEventDrivenTurn) {
14437
+ const full = exec2.getFullText();
14438
+ if (full)
14439
+ console.log(full);
14440
+ }
14441
+ repl.setLastUsage(exec2.getLastUsage());
14442
+ return result;
14443
+ } finally {
14444
+ runningTurns.delete(effectiveSessionId);
14018
14445
  }
14019
- repl.setLastUsage(queryExecutor.getLastUsage());
14020
- return result;
14021
14446
  },
14022
14447
  onAfterExecute: (usage) => {
14023
14448
  if (useInk)
@@ -14042,6 +14467,7 @@ function createInteractiveCommand(externalEnvService) {
14042
14467
  onSwitchSession: async (newSessionId) => {
14043
14468
  const result = await sessionManager.switchSession(newSessionId);
14044
14469
  queryExecutor.dispose();
14470
+ disposeSessionExecutors();
14045
14471
  return result;
14046
14472
  },
14047
14473
  onCompact: async () => {
@@ -14091,6 +14517,7 @@ function createInteractiveCommand(externalEnvService) {
14091
14517
  eventTypes: ["task.background.*", "event-source.event.*"],
14092
14518
  formatter: new EventMessageFormatter({ prefix: "[通知]" }),
14093
14519
  isIdle: () => repl.isIdle(),
14520
+ isSessionIdle: (sid) => repl.isSessionIdle(sid),
14094
14521
  sessionComponent,
14095
14522
  onEvent: async (_message) => {},
14096
14523
  onEventWithEnv: async (formattedEvent) => {
@@ -14124,6 +14551,20 @@ function createInteractiveCommand(externalEnvService) {
14124
14551
  repl.sessionManager = sessionManager;
14125
14552
  repl.sessionComponent = sessionComponent;
14126
14553
  repl._queryExecutor = queryExecutor;
14554
+ {
14555
+ let inkCb = null;
14556
+ Object.defineProperty(repl, "inkChunkCallback", {
14557
+ configurable: true,
14558
+ get: () => inkCb,
14559
+ set: (cb) => {
14560
+ inkCb = cb;
14561
+ sessionChunkCallback = cb ?? undefined;
14562
+ for (const ex of sessionExecutors.values()) {
14563
+ ex.setChunkCallback(sessionChunkCallback);
14564
+ }
14565
+ }
14566
+ });
14567
+ }
14127
14568
  await repl.start();
14128
14569
  process.removeListener("SIGINT", sigintHandler);
14129
14570
  console.log("正在清理资源...");
@@ -14135,6 +14576,7 @@ function createInteractiveCommand(externalEnvService) {
14135
14576
  }
14136
14577
  eventHandler.stop();
14137
14578
  queryExecutor.dispose();
14579
+ disposeSessionExecutors();
14138
14580
  const esComponent = env.getComponent("event-source");
14139
14581
  if (esComponent) {
14140
14582
  const sources = esComponent.list?.();
@@ -15502,6 +15944,7 @@ async function loadHandleQuery(options, hooks) {
15502
15944
  const llmComponent = envService.getLLM();
15503
15945
  queryExecutor.initStreamOutput(llmComponent ?? null);
15504
15946
  const handleQuery = async (message, envEvent) => {
15947
+ const eventTraceContext = envEvent?.metadata?.traceContext;
15505
15948
  const mergedContext = {
15506
15949
  ...agentContext,
15507
15950
  model: options.model,
@@ -15516,7 +15959,7 @@ async function loadHandleQuery(options, hooks) {
15516
15959
  showReasoning: options.reasoning ?? true,
15517
15960
  showToolCalls: options.toolCalls ?? true,
15518
15961
  showToolResults: options.toolResults ?? false
15519
- }, undefined, mergedContext);
15962
+ }, eventTraceContext?.traceId, mergedContext);
15520
15963
  return String(result ?? "");
15521
15964
  };
15522
15965
  const taskEventHandler = new TaskEventHandler({
@@ -15593,6 +16036,7 @@ function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext
15593
16036
  break;
15594
16037
  }
15595
16038
  });
16039
+ const eventTraceContext = envEvent?.metadata?.traceContext;
15596
16040
  const mergedContext = {
15597
16041
  ...agentContext,
15598
16042
  model: options.model,
@@ -15607,7 +16051,7 @@ function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext
15607
16051
  showReasoning: options.reasoning ?? true,
15608
16052
  showToolCalls: true,
15609
16053
  showToolResults: true
15610
- }, undefined, mergedContext);
16054
+ }, eventTraceContext?.traceId, mergedContext);
15611
16055
  return String(result ?? "");
15612
16056
  };
15613
16057
  }