@openacp/cli 2026.413.1 → 2026.414.2

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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as Attachment, O as OutgoingMessage, I as IChannelAdapter, T as TurnMeta, a as AgentEvent, S as StopReason, P as PermissionRequest, N as NotificationMessage, b as SessionStatus, U as UsageRecord, c as AgentCapabilities, d as AgentDefinition, M as McpServerConfig, e as SetConfigOptionValue, f as InstalledAgent, R as RegistryAgent, g as AgentListItem, h as AvailabilityResult, i as InstallProgress, j as InstallResult, k as TurnContext, C as ConfigOption, l as AgentSwitchEntry, m as AgentCommand, n as TurnRouting, o as SessionRecord, p as UsageRecordEvent, q as IncomingMessage, D as DisplayVerbosity, r as ChannelConfig, s as AdapterCapabilities, t as ToolCallMeta, V as ViewerLinks, u as OutputMode, v as PlanEntry } from './channel-Dg1nGCYa.js';
2
- export { w as AgentDistribution, x as AuthMethod, y as AuthenticateRequest, z as ChannelAdapter, B as ConfigSelectChoice, E as ConfigSelectGroup, F as ContentBlock, K as KIND_ICONS, G as ModelInfo, H as NewSessionResponse, J as PermissionOption, L as PromptResponse, Q as RegistryBinaryTarget, W as RegistryDistribution, X as STATUS_ICONS, Y as SessionListItem, Z as SessionListResponse, _ as SessionMode, $ as SessionModeState, a0 as SessionModelState, a1 as TelegramPlatformData, a2 as ToolUpdateMeta, a3 as createTurnContext, a4 as getEffectiveTarget, a5 as isSystemEvent } from './channel-Dg1nGCYa.js';
1
+ import { A as Attachment, O as OutgoingMessage, I as IChannelAdapter, T as TurnMeta, a as AgentEvent, S as StopReason, P as PermissionRequest, N as NotificationMessage, b as SessionStatus, U as UsageRecord, c as AgentCapabilities, d as AgentDefinition, M as McpServerConfig, e as SetConfigOptionValue, f as InstalledAgent, R as RegistryAgent, g as AgentListItem, h as AvailabilityResult, i as InstallProgress, j as InstallResult, k as TurnContext, C as ConfigOption, l as AgentSwitchEntry, m as AgentCommand, n as TurnRouting, o as SessionRecord, p as UsageRecordEvent, q as TurnSender, r as IncomingMessage, D as DisplayVerbosity, s as ChannelConfig, t as AdapterCapabilities, u as ToolCallMeta, V as ViewerLinks, v as OutputMode, w as PlanEntry } from './channel-bWC2vDOv.js';
2
+ export { x as AgentDistribution, y as AuthMethod, z as AuthenticateRequest, B as ChannelAdapter, E as ConfigSelectChoice, F as ConfigSelectGroup, G as ContentBlock, K as KIND_ICONS, H as ModelInfo, J as NewSessionResponse, L as PermissionOption, Q as PromptResponse, W as RegistryBinaryTarget, X as RegistryDistribution, Y as STATUS_ICONS, Z as SessionListItem, _ as SessionListResponse, $ as SessionMode, a0 as SessionModeState, a1 as SessionModelState, a2 as TelegramPlatformData, a3 as ToolUpdateMeta, a4 as createTurnContext, a5 as getEffectiveTarget, a6 as isSystemEvent } from './channel-bWC2vDOv.js';
3
3
  import pino from 'pino';
4
4
  import * as zod from 'zod';
5
5
  import { ZodSchema, z } from 'zod';
@@ -559,6 +559,9 @@ interface MiddlewarePayloadMap {
559
559
  promptNumber: number;
560
560
  turnId: string;
561
561
  meta?: TurnMeta;
562
+ userPrompt?: string;
563
+ sourceAdapterId?: string;
564
+ responseAdapterId?: string | null;
562
565
  };
563
566
  'turn:end': {
564
567
  sessionId: string;
@@ -1868,6 +1871,11 @@ declare class Session extends TypedEmitter<SessionEvents> {
1868
1871
  get queueDepth(): number;
1869
1872
  /** Whether a prompt is currently being processed by the agent */
1870
1873
  get promptRunning(): boolean;
1874
+ /** Snapshot of queued (not yet processing) items — for inspection by API consumers. */
1875
+ get queueItems(): {
1876
+ userPrompt: string;
1877
+ turnId?: string;
1878
+ }[];
1871
1879
  /** Store context markdown to be prepended to the next prompt (used for session resume with history). */
1872
1880
  setContext(markdown: string): void;
1873
1881
  /** Set TTS mode: "off" = disabled, "next" = one-shot (auto-resets after prompt), "on" = persistent. */
@@ -1938,13 +1946,13 @@ declare class PromptQueue {
1938
1946
  private abortController;
1939
1947
  /** Set when abort is triggered; drainNext waits for the current processor to settle before starting the next item. */
1940
1948
  private processorSettled;
1941
- constructor(processor: (text: string, attachments?: Attachment[], routing?: TurnRouting, turnId?: string, meta?: TurnMeta) => Promise<void>, onError?: ((err: unknown) => void) | undefined);
1949
+ constructor(processor: (text: string, userPrompt: string, attachments?: Attachment[], routing?: TurnRouting, turnId?: string, meta?: TurnMeta) => Promise<void>, onError?: ((err: unknown) => void) | undefined);
1942
1950
  /**
1943
1951
  * Add a prompt to the queue. If no prompt is currently processing, it runs
1944
1952
  * immediately. Otherwise, it's buffered and the returned promise resolves
1945
1953
  * only after the prompt finishes processing.
1946
1954
  */
1947
- enqueue(text: string, attachments?: Attachment[], routing?: TurnRouting, turnId?: string, meta?: TurnMeta): Promise<void>;
1955
+ enqueue(text: string, userPrompt: string, attachments?: Attachment[], routing?: TurnRouting, turnId?: string, meta?: TurnMeta): Promise<void>;
1948
1956
  /** Run a single prompt through the processor, then drain the next queued item. */
1949
1957
  private process;
1950
1958
  /** Dequeue and process the next pending prompt, if any. Called after each prompt completes. */
@@ -1956,6 +1964,11 @@ declare class PromptQueue {
1956
1964
  clear(): void;
1957
1965
  get pending(): number;
1958
1966
  get isProcessing(): boolean;
1967
+ /** Snapshot of queued (not yet processing) items — used for queue inspection by callers. */
1968
+ get pendingItems(): Array<{
1969
+ userPrompt: string;
1970
+ turnId?: string;
1971
+ }>;
1959
1972
  }
1960
1973
 
1961
1974
  /**
@@ -2036,6 +2049,7 @@ interface EventBusEvents {
2036
2049
  }) => void;
2037
2050
  "agent:event": (data: {
2038
2051
  sessionId: string;
2052
+ turnId: string;
2039
2053
  event: AgentEvent;
2040
2054
  }) => void;
2041
2055
  "permission:request": (data: {
@@ -2056,6 +2070,8 @@ interface EventBusEvents {
2056
2070
  commands: Array<{
2057
2071
  name: string;
2058
2072
  description: string;
2073
+ category?: string;
2074
+ usage?: string;
2059
2075
  }>;
2060
2076
  }) => void;
2061
2077
  "plugin:loaded": (data: {
@@ -2103,13 +2119,24 @@ interface EventBusEvents {
2103
2119
  attachments?: unknown[];
2104
2120
  timestamp: string;
2105
2121
  queueDepth: number;
2122
+ sender?: TurnSender | null;
2106
2123
  }) => void;
2107
2124
  "message:processing": (data: {
2108
2125
  sessionId: string;
2109
2126
  turnId: string;
2110
2127
  sourceAdapterId: string;
2128
+ userPrompt: string;
2129
+ finalPrompt: string;
2130
+ attachments?: unknown[];
2131
+ sender?: TurnSender | null;
2111
2132
  timestamp: string;
2112
2133
  }) => void;
2134
+ /** Fired when a queued message is rejected before processing (e.g. blocked by middleware). */
2135
+ "message:failed": (data: {
2136
+ sessionId: string;
2137
+ turnId: string;
2138
+ reason: string;
2139
+ }) => void;
2113
2140
  "session:agentSwitch": (data: {
2114
2141
  sessionId: string;
2115
2142
  fromAgent: string;
@@ -3325,24 +3352,37 @@ declare class OpenACPCore {
3325
3352
  * If no session is found, the user is told to start one with /new.
3326
3353
  */
3327
3354
  handleMessage(message: IncomingMessage, initialMeta?: Record<string, unknown>): Promise<void>;
3355
+ /**
3356
+ * Shared dispatch path for sending a prompt to a session.
3357
+ * Called by both handleMessage (Telegram) and handleMessageInSession (SSE/API)
3358
+ * after their respective middleware/enrichment steps.
3359
+ */
3360
+ private _dispatchToSession;
3328
3361
  /**
3329
3362
  * Send a message to a known session, running the full message:incoming → agent:beforePrompt
3330
3363
  * middleware chain (same as handleMessage) but without the threadId-based session lookup.
3331
3364
  *
3332
- * Used by channels that already hold a direct session reference (e.g. SSE adapter), where
3333
- * looking up by channelId+threadId is unreliable (API sessions may have no threadId).
3365
+ * Used by channels that already hold a direct session reference (e.g. SSE adapter, api-server),
3366
+ * where looking up by channelId+threadId is unreliable (API sessions may have no threadId).
3334
3367
  *
3335
3368
  * @param session The target session — caller is responsible for validating its status.
3336
3369
  * @param message Sender context and message content.
3337
3370
  * @param initialMeta Optional adapter-specific context to seed the TurnMeta bag
3338
3371
  * (e.g. channelUser with display name/username).
3372
+ * @param options Optional turnId override and response routing.
3339
3373
  */
3340
3374
  handleMessageInSession(session: Session, message: {
3341
3375
  channelId: string;
3342
3376
  userId: string;
3343
3377
  text: string;
3344
3378
  attachments?: Attachment[];
3345
- }, initialMeta?: Record<string, unknown>): Promise<void>;
3379
+ }, initialMeta?: Record<string, unknown>, options?: {
3380
+ externalTurnId?: string;
3381
+ responseAdapterId?: string | null;
3382
+ }): Promise<{
3383
+ turnId: string;
3384
+ queueDepth: number;
3385
+ }>;
3346
3386
  /**
3347
3387
  * Create (or resume) a session with full wiring: agent, adapter thread, bridge, persistence.
3348
3388
  *
@@ -3780,6 +3820,13 @@ interface StoredToken {
3780
3820
  revoked: boolean;
3781
3821
  /** User ID from identity system. Null until user completes /identity/setup. */
3782
3822
  userId?: string;
3823
+ /**
3824
+ * Per-token secret for identity re-linking on reconnect.
3825
+ * Never embedded in the JWT — only returned at token creation/exchange time.
3826
+ * The App stores this in its workspace store and uses it to silently re-link
3827
+ * a new token to the same user after the old token's refresh deadline expires.
3828
+ */
3829
+ identitySecret: string;
3783
3830
  }
3784
3831
  interface CreateTokenOpts {
3785
3832
  role: string;
@@ -3860,6 +3907,14 @@ declare class TokenStore {
3860
3907
  setUserId(tokenId: string, userId: string): void;
3861
3908
  /** Get the user ID associated with a token. */
3862
3909
  getUserId(tokenId: string): string | undefined;
3910
+ /**
3911
+ * Looks up a non-revoked token by its identity secret.
3912
+ *
3913
+ * Used by the identity re-link flow: the App sends the old token's identitySecret
3914
+ * to prove continuity of identity when reconnecting with a new JWT.
3915
+ * Returns undefined if no match, or if the matching token is revoked.
3916
+ */
3917
+ getByIdentitySecret(secret: string): StoredToken | undefined;
3863
3918
  /**
3864
3919
  * Generates a one-time authorization code that can be exchanged for a JWT.
3865
3920
  *
@@ -4931,10 +4986,11 @@ declare class TelegramAdapter extends MessagingAdapter {
4931
4986
  */
4932
4987
  private retryWithBackoff;
4933
4988
  /**
4934
- * Register Telegram commands in the background with retries.
4935
- * Non-critical bot works fine without autocomplete commands.
4989
+ * Sync Telegram autocomplete commands after all plugins are ready.
4990
+ * Merges STATIC_COMMANDS (hardcoded system commands) with plugin commands
4991
+ * from the registry, deduplicating by command name. Non-critical.
4936
4992
  */
4937
- private registerCommandsWithRetry;
4993
+ private syncCommandsWithRetry;
4938
4994
  private initTopicDependentFeatures;
4939
4995
  private startPrerequisiteWatcher;
4940
4996
  /**
package/dist/index.js CHANGED
@@ -852,6 +852,8 @@ var init_events = __esm({
852
852
  MESSAGE_QUEUED: "message:queued",
853
853
  /** Fired when a queued message starts processing. */
854
854
  MESSAGE_PROCESSING: "message:processing",
855
+ /** Fired when a queued message is rejected (e.g. blocked by middleware). */
856
+ MESSAGE_FAILED: "message:failed",
855
857
  // --- System lifecycle ---
856
858
  /** Fired after kernel (core + plugin infrastructure) has booted. */
857
859
  KERNEL_BOOTED: "kernel:booted",
@@ -3538,7 +3540,8 @@ var init_sse_manager = __esm({
3538
3540
  BusEvent.PERMISSION_REQUEST,
3539
3541
  BusEvent.PERMISSION_RESOLVED,
3540
3542
  BusEvent.MESSAGE_QUEUED,
3541
- BusEvent.MESSAGE_PROCESSING
3543
+ BusEvent.MESSAGE_PROCESSING,
3544
+ BusEvent.MESSAGE_FAILED
3542
3545
  ];
3543
3546
  for (const eventName of events) {
3544
3547
  const handler = (data) => {
@@ -3620,7 +3623,8 @@ data: ${JSON.stringify(data)}
3620
3623
  BusEvent.PERMISSION_RESOLVED,
3621
3624
  BusEvent.SESSION_UPDATED,
3622
3625
  BusEvent.MESSAGE_QUEUED,
3623
- BusEvent.MESSAGE_PROCESSING
3626
+ BusEvent.MESSAGE_PROCESSING,
3627
+ BusEvent.MESSAGE_FAILED
3624
3628
  ];
3625
3629
  for (const res of this.sseConnections) {
3626
3630
  const filter = res.sessionFilter;
@@ -10110,7 +10114,11 @@ var init_adapter = __esm({
10110
10114
  }
10111
10115
  return prev(method, payload, signal);
10112
10116
  });
10113
- this.registerCommandsWithRetry();
10117
+ const onCommandsReady = ({ commands }) => {
10118
+ this.core.eventBus.off(BusEvent.SYSTEM_COMMANDS_READY, onCommandsReady);
10119
+ this.syncCommandsWithRetry(commands);
10120
+ };
10121
+ this.core.eventBus.on(BusEvent.SYSTEM_COMMANDS_READY, onCommandsReady);
10114
10122
  this.bot.use((ctx, next) => {
10115
10123
  const chatId = ctx.chat?.id ?? ctx.callbackQuery?.message?.chat?.id;
10116
10124
  if (chatId !== this.telegramConfig.chatId) return;
@@ -10336,12 +10344,16 @@ ${p}` : p;
10336
10344
  throw new Error("unreachable");
10337
10345
  }
10338
10346
  /**
10339
- * Register Telegram commands in the background with retries.
10340
- * Non-critical bot works fine without autocomplete commands.
10347
+ * Sync Telegram autocomplete commands after all plugins are ready.
10348
+ * Merges STATIC_COMMANDS (hardcoded system commands) with plugin commands
10349
+ * from the registry, deduplicating by command name. Non-critical.
10341
10350
  */
10342
- registerCommandsWithRetry() {
10351
+ syncCommandsWithRetry(registryCommands) {
10352
+ const staticNames = new Set(STATIC_COMMANDS.map((c2) => c2.command));
10353
+ const pluginCommands = registryCommands.filter((c2) => c2.category === "plugin" && !staticNames.has(c2.name) && /^[a-z0-9_]+$/.test(c2.name)).map((c2) => ({ command: c2.name, description: c2.description.slice(0, 256) }));
10354
+ const allCommands = [...STATIC_COMMANDS, ...pluginCommands].slice(0, 100);
10343
10355
  this.retryWithBackoff(
10344
- () => this.bot.api.setMyCommands(STATIC_COMMANDS, {
10356
+ () => this.bot.api.setMyCommands(allCommands, {
10345
10357
  scope: { type: "chat", chat_id: this.telegramConfig.chatId }
10346
10358
  }),
10347
10359
  "setMyCommands"
@@ -12833,16 +12845,16 @@ var PromptQueue = class {
12833
12845
  * immediately. Otherwise, it's buffered and the returned promise resolves
12834
12846
  * only after the prompt finishes processing.
12835
12847
  */
12836
- async enqueue(text3, attachments, routing, turnId, meta) {
12848
+ async enqueue(text3, userPrompt, attachments, routing, turnId, meta) {
12837
12849
  if (this.processing) {
12838
12850
  return new Promise((resolve7) => {
12839
- this.queue.push({ text: text3, attachments, routing, turnId, meta, resolve: resolve7 });
12851
+ this.queue.push({ text: text3, userPrompt, attachments, routing, turnId, meta, resolve: resolve7 });
12840
12852
  });
12841
12853
  }
12842
- await this.process(text3, attachments, routing, turnId, meta);
12854
+ await this.process(text3, userPrompt, attachments, routing, turnId, meta);
12843
12855
  }
12844
12856
  /** Run a single prompt through the processor, then drain the next queued item. */
12845
- async process(text3, attachments, routing, turnId, meta) {
12857
+ async process(text3, userPrompt, attachments, routing, turnId, meta) {
12846
12858
  this.processing = true;
12847
12859
  this.abortController = new AbortController();
12848
12860
  const { signal } = this.abortController;
@@ -12852,7 +12864,7 @@ var PromptQueue = class {
12852
12864
  });
12853
12865
  try {
12854
12866
  await Promise.race([
12855
- this.processor(text3, attachments, routing, turnId, meta),
12867
+ this.processor(text3, userPrompt, attachments, routing, turnId, meta),
12856
12868
  new Promise((_, reject) => {
12857
12869
  signal.addEventListener("abort", () => reject(new Error("Prompt aborted")), { once: true });
12858
12870
  })
@@ -12873,7 +12885,7 @@ var PromptQueue = class {
12873
12885
  drainNext() {
12874
12886
  const next = this.queue.shift();
12875
12887
  if (next) {
12876
- this.process(next.text, next.attachments, next.routing, next.turnId, next.meta).then(next.resolve);
12888
+ this.process(next.text, next.userPrompt, next.attachments, next.routing, next.turnId, next.meta).then(next.resolve);
12877
12889
  }
12878
12890
  }
12879
12891
  /**
@@ -12895,6 +12907,13 @@ var PromptQueue = class {
12895
12907
  get isProcessing() {
12896
12908
  return this.processing;
12897
12909
  }
12910
+ /** Snapshot of queued (not yet processing) items — used for queue inspection by callers. */
12911
+ get pendingItems() {
12912
+ return this.queue.map((item) => ({
12913
+ userPrompt: item.userPrompt,
12914
+ turnId: item.turnId
12915
+ }));
12916
+ }
12898
12917
  };
12899
12918
 
12900
12919
  // src/core/sessions/permission-gate.ts
@@ -12976,17 +12995,31 @@ import * as fs8 from "fs";
12976
12995
 
12977
12996
  // src/core/sessions/turn-context.ts
12978
12997
  import { nanoid } from "nanoid";
12979
- function createTurnContext(sourceAdapterId, responseAdapterId, turnId) {
12998
+ function extractSender(meta) {
12999
+ const identity = meta?.identity;
13000
+ if (!identity || !identity.userId || !identity.identityId) return null;
12980
13001
  return {
12981
- turnId: turnId ?? nanoid(8),
12982
- sourceAdapterId,
12983
- responseAdapterId
13002
+ userId: identity.userId,
13003
+ identityId: identity.identityId,
13004
+ displayName: identity.displayName,
13005
+ username: identity.username
12984
13006
  };
12985
13007
  }
12986
13008
  function getEffectiveTarget(ctx) {
12987
13009
  if (ctx.responseAdapterId === null) return null;
12988
13010
  return ctx.responseAdapterId ?? ctx.sourceAdapterId;
12989
13011
  }
13012
+ function createTurnContext(sourceAdapterId, responseAdapterId, turnId, userPrompt, finalPrompt, attachments, meta) {
13013
+ return {
13014
+ turnId: turnId ?? nanoid(8),
13015
+ sourceAdapterId,
13016
+ responseAdapterId,
13017
+ userPrompt,
13018
+ finalPrompt,
13019
+ attachments,
13020
+ meta
13021
+ };
13022
+ }
12990
13023
  var SYSTEM_EVENT_TYPES = /* @__PURE__ */ new Set([
12991
13024
  "session_end",
12992
13025
  "system_message",
@@ -13081,7 +13114,7 @@ var Session = class extends TypedEmitter {
13081
13114
  this.log = createSessionLogger(this.id, moduleLog);
13082
13115
  this.log.info({ agentName: this.agentName }, "Session created");
13083
13116
  this.queue = new PromptQueue(
13084
- (text3, attachments, routing, turnId, meta) => this.processPrompt(text3, attachments, routing, turnId, meta),
13117
+ (text3, userPrompt, attachments, routing, turnId, meta) => this.processPrompt(text3, userPrompt, attachments, routing, turnId, meta),
13085
13118
  (err) => {
13086
13119
  this.log.error({ err }, "Prompt execution failed");
13087
13120
  const message = err instanceof Error ? err.message : String(err);
@@ -13161,6 +13194,10 @@ var Session = class extends TypedEmitter {
13161
13194
  get promptRunning() {
13162
13195
  return this.queue.isProcessing;
13163
13196
  }
13197
+ /** Snapshot of queued (not yet processing) items — for inspection by API consumers. */
13198
+ get queueItems() {
13199
+ return this.queue.pendingItems;
13200
+ }
13164
13201
  // --- Context Injection ---
13165
13202
  /** Store context markdown to be prepended to the next prompt (used for session resume with history). */
13166
13203
  setContext(markdown) {
@@ -13183,22 +13220,28 @@ var Session = class extends TypedEmitter {
13183
13220
  async enqueuePrompt(text3, attachments, routing, externalTurnId, meta) {
13184
13221
  const turnId = externalTurnId ?? nanoid2(8);
13185
13222
  const turnMeta = meta ?? { turnId };
13223
+ const userPrompt = text3;
13186
13224
  if (this.middlewareChain) {
13187
13225
  const payload = { text: text3, attachments, sessionId: this.id, sourceAdapterId: routing?.sourceAdapterId, meta: turnMeta };
13188
13226
  const result = await this.middlewareChain.execute(Hook.AGENT_BEFORE_PROMPT, payload, async (p) => p);
13189
- if (!result) return turnId;
13227
+ if (!result) throw new Error("PROMPT_BLOCKED");
13190
13228
  text3 = result.text;
13191
13229
  attachments = result.attachments;
13192
13230
  }
13193
- await this.queue.enqueue(text3, attachments, routing, turnId, turnMeta);
13231
+ await this.queue.enqueue(text3, userPrompt, attachments, routing, turnId, turnMeta);
13194
13232
  return turnId;
13195
13233
  }
13196
- async processPrompt(text3, attachments, routing, turnId, meta) {
13234
+ async processPrompt(text3, userPrompt, attachments, routing, turnId, meta) {
13197
13235
  if (this._status === "finished") return;
13198
13236
  this.activeTurnContext = createTurnContext(
13199
13237
  routing?.sourceAdapterId ?? this.channelId,
13200
13238
  routing?.responseAdapterId,
13201
- turnId
13239
+ turnId,
13240
+ userPrompt,
13241
+ text3,
13242
+ // finalPrompt (after middleware transformations)
13243
+ attachments,
13244
+ meta
13202
13245
  );
13203
13246
  this.emit(SessionEv.TURN_STARTED, this.activeTurnContext);
13204
13247
  this.promptCount++;
@@ -13249,7 +13292,16 @@ ${text3}`;
13249
13292
  this.agentInstance.on(SessionEv.AGENT_EVENT, afterEventListener);
13250
13293
  }
13251
13294
  if (this.middlewareChain) {
13252
- this.middlewareChain.execute(Hook.TURN_START, { sessionId: this.id, promptText: processed.text, promptNumber: this.promptCount, turnId: this.activeTurnContext?.turnId ?? turnId ?? "", meta }, async (p) => p).catch(() => {
13295
+ this.middlewareChain.execute(Hook.TURN_START, {
13296
+ sessionId: this.id,
13297
+ promptText: processed.text,
13298
+ promptNumber: this.promptCount,
13299
+ turnId: this.activeTurnContext?.turnId ?? turnId ?? "",
13300
+ meta,
13301
+ userPrompt: this.activeTurnContext?.userPrompt,
13302
+ sourceAdapterId: this.activeTurnContext?.sourceAdapterId,
13303
+ responseAdapterId: this.activeTurnContext?.responseAdapterId
13304
+ }, async (p) => p).catch(() => {
13253
13305
  });
13254
13306
  }
13255
13307
  let stopReason = "end_turn";
@@ -14418,7 +14470,7 @@ var SessionBridge = class {
14418
14470
  if (this.shouldForward(event)) {
14419
14471
  this.dispatchAgentEvent(event);
14420
14472
  } else {
14421
- this.deps.eventBus?.emit(BusEvent.AGENT_EVENT, { sessionId: this.session.id, event });
14473
+ this.deps.eventBus?.emit(BusEvent.AGENT_EVENT, { sessionId: this.session.id, turnId: "", event });
14422
14474
  }
14423
14475
  });
14424
14476
  if (!this.session.agentInstance.onPermissionRequest || this.session.agentInstance.onPermissionRequest.__bridgeId === void 0) {
@@ -14471,14 +14523,16 @@ var SessionBridge = class {
14471
14523
  this.deps.sessionManager.patchRecord(this.session.id, { currentPromptCount: count });
14472
14524
  });
14473
14525
  this.listen(this.session, SessionEv.TURN_STARTED, (ctx) => {
14474
- if (ctx.sourceAdapterId !== "sse") {
14475
- this.deps.eventBus?.emit(BusEvent.MESSAGE_PROCESSING, {
14476
- sessionId: this.session.id,
14477
- turnId: ctx.turnId,
14478
- sourceAdapterId: ctx.sourceAdapterId,
14479
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
14480
- });
14481
- }
14526
+ this.deps.eventBus?.emit(BusEvent.MESSAGE_PROCESSING, {
14527
+ sessionId: this.session.id,
14528
+ turnId: ctx.turnId,
14529
+ sourceAdapterId: ctx.sourceAdapterId,
14530
+ userPrompt: ctx.userPrompt,
14531
+ finalPrompt: ctx.finalPrompt,
14532
+ attachments: ctx.attachments,
14533
+ sender: extractSender(ctx.meta),
14534
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
14535
+ });
14482
14536
  });
14483
14537
  if (this.session.latestCommands !== null) {
14484
14538
  this.session.emit(SessionEv.AGENT_EVENT, { type: "commands_update", commands: this.session.latestCommands });
@@ -14644,6 +14698,7 @@ var SessionBridge = class {
14644
14698
  }
14645
14699
  this.deps.eventBus?.emit(BusEvent.AGENT_EVENT, {
14646
14700
  sessionId: this.session.id,
14701
+ turnId: this.session.activeTurnContext?.turnId ?? "",
14647
14702
  event
14648
14703
  });
14649
14704
  return outgoing;
@@ -14844,6 +14899,7 @@ var SessionFactory = class {
14844
14899
  const failedSessionId = createParams.existingSessionId ?? `failed-${Date.now()}`;
14845
14900
  this.eventBus.emit(BusEvent.AGENT_EVENT, {
14846
14901
  sessionId: failedSessionId,
14902
+ turnId: "",
14847
14903
  event: guidance
14848
14904
  });
14849
14905
  throw err;
@@ -15393,7 +15449,7 @@ var AgentSwitchHandler = class {
15393
15449
  message: `Switching from ${fromAgent} to ${toAgent}...`
15394
15450
  };
15395
15451
  session.emit(SessionEv.AGENT_EVENT, startEvent);
15396
- eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, event: startEvent });
15452
+ eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, turnId: "", event: startEvent });
15397
15453
  eventBus.emit(BusEvent.SESSION_AGENT_SWITCH, {
15398
15454
  sessionId,
15399
15455
  fromAgent,
@@ -15456,7 +15512,7 @@ var AgentSwitchHandler = class {
15456
15512
  message: resumed ? `Switched to ${toAgent} (resumed previous session).` : `Switched to ${toAgent} (new session).`
15457
15513
  };
15458
15514
  session.emit(SessionEv.AGENT_EVENT, successEvent);
15459
- eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, event: successEvent });
15515
+ eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, turnId: "", event: successEvent });
15460
15516
  eventBus.emit(BusEvent.SESSION_AGENT_SWITCH, {
15461
15517
  sessionId,
15462
15518
  fromAgent,
@@ -15471,7 +15527,7 @@ var AgentSwitchHandler = class {
15471
15527
  message: `Failed to switch to ${toAgent}: ${errorMessage}`
15472
15528
  };
15473
15529
  session.emit(SessionEv.AGENT_EVENT, failedEvent);
15474
- eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, event: failedEvent });
15530
+ eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, turnId: "", event: failedEvent });
15475
15531
  eventBus.emit(BusEvent.SESSION_AGENT_SWITCH, {
15476
15532
  sessionId,
15477
15533
  fromAgent,
@@ -17567,9 +17623,6 @@ var OpenACPCore = class {
17567
17623
  }
17568
17624
  return;
17569
17625
  }
17570
- this.sessionManager.patchRecord(session.id, {
17571
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
17572
- });
17573
17626
  let text3 = message.text;
17574
17627
  if (this.assistantManager?.isAssistant(session.id)) {
17575
17628
  const pending = this.assistantManager.consumePendingSystemPrompt(message.channelId);
@@ -17582,38 +17635,56 @@ User message:
17582
17635
  ${text3}`;
17583
17636
  }
17584
17637
  }
17585
- const sourceAdapterId = message.routing?.sourceAdapterId ?? message.channelId;
17586
- const routing = sourceAdapterId !== message.routing?.sourceAdapterId ? { ...message.routing, sourceAdapterId } : message.routing;
17587
17638
  const enrichedMeta = message.meta ?? meta;
17588
- if (sourceAdapterId && sourceAdapterId !== "sse" && sourceAdapterId !== "api") {
17589
- this.eventBus.emit(BusEvent.MESSAGE_QUEUED, {
17639
+ await this._dispatchToSession(session, text3, message.attachments, {
17640
+ sourceAdapterId: message.routing?.sourceAdapterId ?? message.channelId,
17641
+ responseAdapterId: message.routing?.responseAdapterId
17642
+ }, turnId, enrichedMeta);
17643
+ }
17644
+ /**
17645
+ * Shared dispatch path for sending a prompt to a session.
17646
+ * Called by both handleMessage (Telegram) and handleMessageInSession (SSE/API)
17647
+ * after their respective middleware/enrichment steps.
17648
+ */
17649
+ async _dispatchToSession(session, text3, attachments, routing, turnId, meta) {
17650
+ this.sessionManager.patchRecord(session.id, {
17651
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
17652
+ });
17653
+ this.eventBus.emit(BusEvent.MESSAGE_QUEUED, {
17654
+ sessionId: session.id,
17655
+ turnId,
17656
+ text: text3,
17657
+ sourceAdapterId: routing.sourceAdapterId,
17658
+ attachments,
17659
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
17660
+ queueDepth: session.queueDepth + 1,
17661
+ sender: extractSender(meta)
17662
+ });
17663
+ session.enqueuePrompt(text3, attachments, routing, turnId, meta).catch((err) => {
17664
+ const reason = err instanceof Error ? err.message : String(err);
17665
+ log16.warn({ err, sessionId: session.id, turnId, reason }, "enqueuePrompt failed \u2014 emitting message:failed");
17666
+ this.eventBus.emit(BusEvent.MESSAGE_FAILED, {
17590
17667
  sessionId: session.id,
17591
17668
  turnId,
17592
- text: text3,
17593
- sourceAdapterId,
17594
- attachments: message.attachments,
17595
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
17596
- queueDepth: session.queueDepth
17669
+ reason
17597
17670
  });
17598
- await session.enqueuePrompt(text3, message.attachments, routing, turnId, enrichedMeta);
17599
- } else {
17600
- await session.enqueuePrompt(text3, message.attachments, routing, turnId, enrichedMeta);
17601
- }
17671
+ });
17602
17672
  }
17603
17673
  /**
17604
17674
  * Send a message to a known session, running the full message:incoming → agent:beforePrompt
17605
17675
  * middleware chain (same as handleMessage) but without the threadId-based session lookup.
17606
17676
  *
17607
- * Used by channels that already hold a direct session reference (e.g. SSE adapter), where
17608
- * looking up by channelId+threadId is unreliable (API sessions may have no threadId).
17677
+ * Used by channels that already hold a direct session reference (e.g. SSE adapter, api-server),
17678
+ * where looking up by channelId+threadId is unreliable (API sessions may have no threadId).
17609
17679
  *
17610
17680
  * @param session The target session — caller is responsible for validating its status.
17611
17681
  * @param message Sender context and message content.
17612
17682
  * @param initialMeta Optional adapter-specific context to seed the TurnMeta bag
17613
17683
  * (e.g. channelUser with display name/username).
17684
+ * @param options Optional turnId override and response routing.
17614
17685
  */
17615
- async handleMessageInSession(session, message, initialMeta) {
17616
- const turnId = nanoid3(8);
17686
+ async handleMessageInSession(session, message, initialMeta, options) {
17687
+ const turnId = options?.externalTurnId ?? nanoid3(8);
17617
17688
  const meta = { turnId, ...initialMeta };
17618
17689
  let text3 = message.text;
17619
17690
  let { attachments } = message;
@@ -17632,13 +17703,17 @@ ${text3}`;
17632
17703
  payload,
17633
17704
  async (p) => p
17634
17705
  );
17635
- if (!result) return;
17706
+ if (!result) return { turnId, queueDepth: session.queueDepth };
17636
17707
  text3 = result.text;
17637
17708
  attachments = result.attachments;
17638
17709
  enrichedMeta = result.meta ?? meta;
17639
17710
  }
17640
- const routing = { sourceAdapterId: message.channelId };
17641
- await session.enqueuePrompt(text3, attachments, routing, turnId, enrichedMeta);
17711
+ const routing = {
17712
+ sourceAdapterId: message.channelId,
17713
+ responseAdapterId: options?.responseAdapterId
17714
+ };
17715
+ await this._dispatchToSession(session, text3, attachments, routing, turnId, enrichedMeta);
17716
+ return { turnId, queueDepth: session.queueDepth };
17642
17717
  }
17643
17718
  // --- Unified Session Creation Pipeline ---
17644
17719
  /**
@@ -17750,7 +17825,7 @@ ${text3}`;
17750
17825
  } else if (processedEvent.type === "error") {
17751
17826
  session.fail(processedEvent.message);
17752
17827
  }
17753
- this.eventBus.emit(BusEvent.AGENT_EVENT, { sessionId: session.id, event: processedEvent });
17828
+ this.eventBus.emit(BusEvent.AGENT_EVENT, { sessionId: session.id, turnId: session.activeTurnContext?.turnId ?? "", event: processedEvent });
17754
17829
  });
17755
17830
  session.on(SessionEv.STATUS_CHANGE, (_from, to) => {
17756
17831
  this.sessionManager.patchRecord(session.id, {
@@ -17762,6 +17837,18 @@ ${text3}`;
17762
17837
  session.on(SessionEv.PROMPT_COUNT_CHANGED, (count) => {
17763
17838
  this.sessionManager.patchRecord(session.id, { currentPromptCount: count });
17764
17839
  });
17840
+ session.on(SessionEv.TURN_STARTED, (ctx) => {
17841
+ this.eventBus.emit(BusEvent.MESSAGE_PROCESSING, {
17842
+ sessionId: session.id,
17843
+ turnId: ctx.turnId,
17844
+ sourceAdapterId: ctx.sourceAdapterId,
17845
+ userPrompt: ctx.userPrompt,
17846
+ finalPrompt: ctx.finalPrompt,
17847
+ attachments: ctx.attachments,
17848
+ sender: extractSender(ctx.meta),
17849
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17850
+ });
17851
+ });
17765
17852
  }
17766
17853
  this.sessionFactory.wireSideEffects(session, {
17767
17854
  eventBus: this.eventBus,