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