@openacp/cli 2026.410.3 → 2026.413.1

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
@@ -793,6 +793,8 @@ var init_events = __esm({
793
793
  TURN_START: "turn:start",
794
794
  /** Turn ended (always fires, even on error) — read-only, fire-and-forget. */
795
795
  TURN_END: "turn:end",
796
+ /** After a turn completes — full assembled agent text, read-only, fire-and-forget. */
797
+ AGENT_AFTER_TURN: "agent:afterTurn",
796
798
  // --- Session lifecycle ---
797
799
  /** Before a new session is created — modifiable, can block. */
798
800
  SESSION_BEFORE_CREATE: "session:beforeCreate",
@@ -870,7 +872,22 @@ var init_events = __esm({
870
872
  PLUGIN_UNLOADED: "plugin:unloaded",
871
873
  // --- Usage ---
872
874
  /** Fired when a token usage record is captured (consumed by usage plugin). */
873
- USAGE_RECORDED: "usage:recorded"
875
+ USAGE_RECORDED: "usage:recorded",
876
+ // --- Identity lifecycle ---
877
+ /** Fired when a new user+identity record is created. */
878
+ IDENTITY_CREATED: "identity:created",
879
+ /** Fired when user profile fields change. */
880
+ IDENTITY_UPDATED: "identity:updated",
881
+ /** Fired when two identities are linked (same person). */
882
+ IDENTITY_LINKED: "identity:linked",
883
+ /** Fired when an identity is unlinked into a new user. */
884
+ IDENTITY_UNLINKED: "identity:unlinked",
885
+ /** Fired when two user records are merged during a link operation. */
886
+ IDENTITY_USER_MERGED: "identity:userMerged",
887
+ /** Fired when a user's role changes. */
888
+ IDENTITY_ROLE_CHANGED: "identity:roleChanged",
889
+ /** Fired when a user is seen (throttled). */
890
+ IDENTITY_SEEN: "identity:seen"
874
891
  };
875
892
  SessionEv = {
876
893
  /** Agent produced an event (text, tool_call, etc.) during a turn. */
@@ -2829,14 +2846,20 @@ var init_api_client = __esm({
2829
2846
  });
2830
2847
 
2831
2848
  // src/plugins/notifications/notification.ts
2832
- var NotificationManager;
2849
+ var NotificationService;
2833
2850
  var init_notification = __esm({
2834
2851
  "src/plugins/notifications/notification.ts"() {
2835
2852
  "use strict";
2836
- NotificationManager = class {
2853
+ NotificationService = class {
2837
2854
  constructor(adapters) {
2838
2855
  this.adapters = adapters;
2839
2856
  }
2857
+ identityResolver;
2858
+ /** Inject identity resolver for user-targeted notifications. */
2859
+ setIdentityResolver(resolver) {
2860
+ this.identityResolver = resolver;
2861
+ }
2862
+ // --- Legacy API (backward compat with NotificationManager) ---
2840
2863
  /**
2841
2864
  * Send a notification to a specific channel adapter.
2842
2865
  *
@@ -2865,6 +2888,62 @@ var init_notification = __esm({
2865
2888
  }
2866
2889
  }
2867
2890
  }
2891
+ // --- New user-targeted API ---
2892
+ /**
2893
+ * Send a notification to a user across all their linked platforms.
2894
+ * Fire-and-forget — never throws, swallows all errors.
2895
+ */
2896
+ async notifyUser(target, message, options) {
2897
+ try {
2898
+ await this._resolveAndDeliver(target, message, options);
2899
+ } catch {
2900
+ }
2901
+ }
2902
+ async _resolveAndDeliver(target, message, options) {
2903
+ if ("channelId" in target && "platformId" in target) {
2904
+ const adapter = this.adapters.get(target.channelId);
2905
+ if (!adapter?.sendUserNotification) return;
2906
+ await adapter.sendUserNotification(target.platformId, message, {
2907
+ via: options?.via,
2908
+ topicId: options?.topicId,
2909
+ sessionId: options?.sessionId
2910
+ });
2911
+ return;
2912
+ }
2913
+ if (!this.identityResolver) return;
2914
+ let identities = [];
2915
+ if ("identityId" in target) {
2916
+ const identity = await this.identityResolver.getIdentity(target.identityId);
2917
+ if (!identity) return;
2918
+ const user = await this.identityResolver.getUser(identity.userId);
2919
+ if (!user) return;
2920
+ identities = await this.identityResolver.getIdentitiesFor(user.userId);
2921
+ } else if ("userId" in target) {
2922
+ identities = await this.identityResolver.getIdentitiesFor(target.userId);
2923
+ }
2924
+ if (options?.onlyPlatforms) {
2925
+ identities = identities.filter((i) => options.onlyPlatforms.includes(i.source));
2926
+ }
2927
+ if (options?.excludePlatforms) {
2928
+ identities = identities.filter((i) => !options.excludePlatforms.includes(i.source));
2929
+ }
2930
+ for (const identity of identities) {
2931
+ const adapter = this.adapters.get(identity.source);
2932
+ if (!adapter?.sendUserNotification) continue;
2933
+ try {
2934
+ await adapter.sendUserNotification(identity.platformId, message, {
2935
+ via: options?.via,
2936
+ topicId: options?.topicId,
2937
+ sessionId: options?.sessionId,
2938
+ platformMention: {
2939
+ platformUsername: identity.platformUsername,
2940
+ platformId: identity.platformId
2941
+ }
2942
+ });
2943
+ } catch {
2944
+ }
2945
+ }
2946
+ }
2868
2947
  };
2869
2948
  }
2870
2949
  });
@@ -3356,9 +3435,16 @@ async function createApiServer(options) {
3356
3435
  });
3357
3436
  const authPreHandler = createAuthPreHandler(options.getSecret, options.getJwtSecret, options.tokenStore);
3358
3437
  app.decorateRequest("auth", null, []);
3438
+ let booted = false;
3439
+ app.addHook("onReady", async () => {
3440
+ booted = true;
3441
+ });
3359
3442
  return {
3360
3443
  app,
3361
3444
  registerPlugin(prefix, plugin, opts) {
3445
+ if (booted) {
3446
+ return;
3447
+ }
3362
3448
  const wrappedPlugin = async (pluginApp, pluginOpts) => {
3363
3449
  if (opts?.auth !== false) {
3364
3450
  pluginApp.addHook("onRequest", authPreHandler);
@@ -10148,10 +10234,19 @@ var init_adapter = __esm({
10148
10234
  });
10149
10235
  } catch {
10150
10236
  }
10151
- } else if (response.type === "text" || response.type === "error") {
10152
- const text3 = response.type === "text" ? response.text : `\u274C ${response.message}`;
10237
+ } else if (response.type === "text" || response.type === "error" || response.type === "adaptive") {
10238
+ let text3;
10239
+ let parseMode;
10240
+ if (response.type === "adaptive") {
10241
+ const variant = response.variants?.["telegram"];
10242
+ text3 = variant?.text ?? response.fallback;
10243
+ parseMode = variant?.parse_mode;
10244
+ } else {
10245
+ text3 = response.type === "text" ? response.text : `\u274C ${response.message}`;
10246
+ parseMode = "Markdown";
10247
+ }
10153
10248
  try {
10154
- await ctx.editMessageText(text3, { parse_mode: "Markdown" });
10249
+ await ctx.editMessageText(text3, { ...parseMode && { parse_mode: parseMode } });
10155
10250
  } catch {
10156
10251
  }
10157
10252
  }
@@ -10433,6 +10528,15 @@ OpenACP will automatically retry until this is resolved.`;
10433
10528
  message_thread_id: topicId
10434
10529
  });
10435
10530
  break;
10531
+ case "adaptive": {
10532
+ const variant = response.variants?.["telegram"];
10533
+ const text3 = variant?.text ?? response.fallback;
10534
+ await this.bot.api.sendMessage(chatId, text3, {
10535
+ message_thread_id: topicId,
10536
+ ...variant?.parse_mode && { parse_mode: variant.parse_mode }
10537
+ });
10538
+ break;
10539
+ }
10436
10540
  case "error":
10437
10541
  await this.bot.api.sendMessage(
10438
10542
  chatId,
@@ -10541,12 +10645,25 @@ ${lines.join("\n")}`;
10541
10645
  }
10542
10646
  ctx.replyWithChatAction("typing").catch(() => {
10543
10647
  });
10544
- this.core.handleMessage({
10545
- channelId: "telegram",
10546
- threadId: String(threadId),
10547
- userId: String(ctx.from.id),
10548
- text: forwardText
10549
- }).catch((err) => log36.error({ err }, "handleMessage error"));
10648
+ const fromName = [ctx.from.first_name, ctx.from.last_name].filter(Boolean).join(" ") || void 0;
10649
+ this.core.handleMessage(
10650
+ {
10651
+ channelId: "telegram",
10652
+ threadId: String(threadId),
10653
+ userId: String(ctx.from.id),
10654
+ text: forwardText
10655
+ },
10656
+ // Inject structured channel user info into TurnMeta so plugins can identify
10657
+ // the sender by name without adapter-specific fields on IncomingMessage.
10658
+ {
10659
+ channelUser: {
10660
+ channelId: "telegram",
10661
+ userId: String(ctx.from.id),
10662
+ displayName: fromName,
10663
+ username: ctx.from.username
10664
+ }
10665
+ }
10666
+ ).catch((err) => log36.error({ err }, "handleMessage error"));
10550
10667
  });
10551
10668
  this.bot.on("message:photo", async (ctx) => {
10552
10669
  const threadId = ctx.message.message_thread_id;
@@ -11259,6 +11376,8 @@ var ChannelAdapter = class {
11259
11376
  }
11260
11377
  async archiveSessionTopic(_sessionId) {
11261
11378
  }
11379
+ async sendUserNotification(_platformId, _message, _options) {
11380
+ }
11262
11381
  };
11263
11382
 
11264
11383
  // src/core/utils/streams.ts
@@ -12714,16 +12833,16 @@ var PromptQueue = class {
12714
12833
  * immediately. Otherwise, it's buffered and the returned promise resolves
12715
12834
  * only after the prompt finishes processing.
12716
12835
  */
12717
- async enqueue(text3, attachments, routing, turnId) {
12836
+ async enqueue(text3, attachments, routing, turnId, meta) {
12718
12837
  if (this.processing) {
12719
12838
  return new Promise((resolve7) => {
12720
- this.queue.push({ text: text3, attachments, routing, turnId, resolve: resolve7 });
12839
+ this.queue.push({ text: text3, attachments, routing, turnId, meta, resolve: resolve7 });
12721
12840
  });
12722
12841
  }
12723
- await this.process(text3, attachments, routing, turnId);
12842
+ await this.process(text3, attachments, routing, turnId, meta);
12724
12843
  }
12725
12844
  /** Run a single prompt through the processor, then drain the next queued item. */
12726
- async process(text3, attachments, routing, turnId) {
12845
+ async process(text3, attachments, routing, turnId, meta) {
12727
12846
  this.processing = true;
12728
12847
  this.abortController = new AbortController();
12729
12848
  const { signal } = this.abortController;
@@ -12733,7 +12852,7 @@ var PromptQueue = class {
12733
12852
  });
12734
12853
  try {
12735
12854
  await Promise.race([
12736
- this.processor(text3, attachments, routing, turnId),
12855
+ this.processor(text3, attachments, routing, turnId, meta),
12737
12856
  new Promise((_, reject) => {
12738
12857
  signal.addEventListener("abort", () => reject(new Error("Prompt aborted")), { once: true });
12739
12858
  })
@@ -12754,7 +12873,7 @@ var PromptQueue = class {
12754
12873
  drainNext() {
12755
12874
  const next = this.queue.shift();
12756
12875
  if (next) {
12757
- this.process(next.text, next.attachments, next.routing, next.turnId).then(next.resolve);
12876
+ this.process(next.text, next.attachments, next.routing, next.turnId, next.meta).then(next.resolve);
12758
12877
  }
12759
12878
  }
12760
12879
  /**
@@ -12962,7 +13081,7 @@ var Session = class extends TypedEmitter {
12962
13081
  this.log = createSessionLogger(this.id, moduleLog);
12963
13082
  this.log.info({ agentName: this.agentName }, "Session created");
12964
13083
  this.queue = new PromptQueue(
12965
- (text3, attachments, routing, turnId) => this.processPrompt(text3, attachments, routing, turnId),
13084
+ (text3, attachments, routing, turnId, meta) => this.processPrompt(text3, attachments, routing, turnId, meta),
12966
13085
  (err) => {
12967
13086
  this.log.error({ err }, "Prompt execution failed");
12968
13087
  const message = err instanceof Error ? err.message : String(err);
@@ -13061,19 +13180,20 @@ var Session = class extends TypedEmitter {
13061
13180
  * then adds it to the PromptQueue. Returns a turnId that callers can use to correlate
13062
13181
  * queued/processing events before the prompt actually runs.
13063
13182
  */
13064
- async enqueuePrompt(text3, attachments, routing, externalTurnId) {
13183
+ async enqueuePrompt(text3, attachments, routing, externalTurnId, meta) {
13065
13184
  const turnId = externalTurnId ?? nanoid2(8);
13185
+ const turnMeta = meta ?? { turnId };
13066
13186
  if (this.middlewareChain) {
13067
- const payload = { text: text3, attachments, sessionId: this.id, sourceAdapterId: routing?.sourceAdapterId };
13187
+ const payload = { text: text3, attachments, sessionId: this.id, sourceAdapterId: routing?.sourceAdapterId, meta: turnMeta };
13068
13188
  const result = await this.middlewareChain.execute(Hook.AGENT_BEFORE_PROMPT, payload, async (p) => p);
13069
13189
  if (!result) return turnId;
13070
13190
  text3 = result.text;
13071
13191
  attachments = result.attachments;
13072
13192
  }
13073
- await this.queue.enqueue(text3, attachments, routing, turnId);
13193
+ await this.queue.enqueue(text3, attachments, routing, turnId, turnMeta);
13074
13194
  return turnId;
13075
13195
  }
13076
- async processPrompt(text3, attachments, routing, turnId) {
13196
+ async processPrompt(text3, attachments, routing, turnId, meta) {
13077
13197
  if (this._status === "finished") return;
13078
13198
  this.activeTurnContext = createTurnContext(
13079
13199
  routing?.sourceAdapterId ?? this.channelId,
@@ -13113,6 +13233,13 @@ ${text3}`;
13113
13233
  if (accumulatorListener) {
13114
13234
  this.on(SessionEv.AGENT_EVENT, accumulatorListener);
13115
13235
  }
13236
+ const turnTextBuffer = [];
13237
+ const turnTextListener = (event) => {
13238
+ if (event.type === "text" && typeof event.content === "string") {
13239
+ turnTextBuffer.push(event.content);
13240
+ }
13241
+ };
13242
+ this.on(SessionEv.AGENT_EVENT, turnTextListener);
13116
13243
  const mw = this.middlewareChain;
13117
13244
  const afterEventListener = mw ? (event) => {
13118
13245
  mw.execute(Hook.AGENT_AFTER_EVENT, { sessionId: this.id, event, outgoingMessage: { type: "text", text: "" } }, async (e) => e).catch(() => {
@@ -13122,7 +13249,7 @@ ${text3}`;
13122
13249
  this.agentInstance.on(SessionEv.AGENT_EVENT, afterEventListener);
13123
13250
  }
13124
13251
  if (this.middlewareChain) {
13125
- this.middlewareChain.execute(Hook.TURN_START, { sessionId: this.id, promptText: processed.text, promptNumber: this.promptCount }, async (p) => p).catch(() => {
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(() => {
13126
13253
  });
13127
13254
  }
13128
13255
  let stopReason = "end_turn";
@@ -13148,8 +13275,20 @@ ${text3}`;
13148
13275
  if (afterEventListener) {
13149
13276
  this.agentInstance.off(SessionEv.AGENT_EVENT, afterEventListener);
13150
13277
  }
13278
+ this.off(SessionEv.AGENT_EVENT, turnTextListener);
13279
+ const finalTurnId = this.activeTurnContext?.turnId ?? turnId ?? "";
13151
13280
  if (this.middlewareChain) {
13152
- this.middlewareChain.execute(Hook.TURN_END, { sessionId: this.id, stopReason, durationMs: Date.now() - promptStart }, async (p) => p).catch(() => {
13281
+ this.middlewareChain.execute(Hook.TURN_END, { sessionId: this.id, stopReason, durationMs: Date.now() - promptStart, turnId: finalTurnId, meta }, async (p) => p).catch(() => {
13282
+ });
13283
+ }
13284
+ if (this.middlewareChain) {
13285
+ this.middlewareChain.execute(Hook.AGENT_AFTER_TURN, {
13286
+ sessionId: this.id,
13287
+ turnId: finalTurnId,
13288
+ fullText: turnTextBuffer.join(""),
13289
+ stopReason,
13290
+ meta
13291
+ }, async (p) => p).catch(() => {
13153
13292
  });
13154
13293
  }
13155
13294
  this.activeTurnContext = null;
@@ -14631,8 +14770,7 @@ var SessionFactory = class {
14631
14770
  const payload = {
14632
14771
  agentName: params.agentName,
14633
14772
  workingDir: params.workingDirectory,
14634
- userId: "",
14635
- // userId is not part of SessionCreateParams — resolved upstream
14773
+ userId: params.userId ?? "",
14636
14774
  channelId: params.channelId,
14637
14775
  threadId: ""
14638
14776
  // threadId is assigned after session creation
@@ -14734,7 +14872,8 @@ var SessionFactory = class {
14734
14872
  this.eventBus.emit(BusEvent.SESSION_CREATED, {
14735
14873
  sessionId: session.id,
14736
14874
  agent: session.agentName,
14737
- status: session.status
14875
+ status: session.status,
14876
+ userId: createParams.userId
14738
14877
  });
14739
14878
  }
14740
14879
  return session;
@@ -16102,11 +16241,55 @@ var PluginStorageImpl = class {
16102
16241
  async list() {
16103
16242
  return Object.keys(this.readKv());
16104
16243
  }
16244
+ async keys(prefix) {
16245
+ const all = Object.keys(this.readKv());
16246
+ return prefix ? all.filter((k) => k.startsWith(prefix)) : all;
16247
+ }
16248
+ async clear() {
16249
+ this.writeChain = this.writeChain.then(() => {
16250
+ this.writeKv({});
16251
+ });
16252
+ return this.writeChain;
16253
+ }
16105
16254
  /** Returns the plugin's data directory, creating it lazily on first access. */
16106
16255
  getDataDir() {
16107
16256
  fs14.mkdirSync(this.dataDir, { recursive: true });
16108
16257
  return this.dataDir;
16109
16258
  }
16259
+ /**
16260
+ * Creates a namespaced storage instance scoped to a session.
16261
+ * Keys are prefixed with `session:{sessionId}:` to isolate session data
16262
+ * from global plugin storage in the same backing file.
16263
+ */
16264
+ forSession(sessionId) {
16265
+ const prefix = `session:${sessionId}:`;
16266
+ return {
16267
+ get: (key) => this.get(`${prefix}${key}`),
16268
+ set: (key, value) => this.set(`${prefix}${key}`, value),
16269
+ delete: (key) => this.delete(`${prefix}${key}`),
16270
+ list: async () => {
16271
+ const all = await this.keys(prefix);
16272
+ return all.map((k) => k.slice(prefix.length));
16273
+ },
16274
+ keys: async (p) => {
16275
+ const full = p ? `${prefix}${p}` : prefix;
16276
+ const all = await this.keys(full);
16277
+ return all.map((k) => k.slice(prefix.length));
16278
+ },
16279
+ clear: async () => {
16280
+ this.writeChain = this.writeChain.then(() => {
16281
+ const data = this.readKv();
16282
+ for (const key of Object.keys(data)) {
16283
+ if (key.startsWith(prefix)) delete data[key];
16284
+ }
16285
+ this.writeKv(data);
16286
+ });
16287
+ return this.writeChain;
16288
+ },
16289
+ getDataDir: () => this.getDataDir(),
16290
+ forSession: (nestedId) => this.forSession(`${sessionId}:${nestedId}`)
16291
+ };
16292
+ }
16110
16293
  };
16111
16294
 
16112
16295
  // src/core/plugin/plugin-context.ts
@@ -16170,9 +16353,52 @@ function createPluginContext(opts) {
16170
16353
  requirePermission(permissions, "storage:read", "storage.list");
16171
16354
  return storageImpl.list();
16172
16355
  },
16356
+ async keys(prefix) {
16357
+ requirePermission(permissions, "storage:read", "storage.keys");
16358
+ return storageImpl.keys(prefix);
16359
+ },
16360
+ async clear() {
16361
+ requirePermission(permissions, "storage:write", "storage.clear");
16362
+ return storageImpl.clear();
16363
+ },
16173
16364
  getDataDir() {
16174
16365
  requirePermission(permissions, "storage:read", "storage.getDataDir");
16175
16366
  return storageImpl.getDataDir();
16367
+ },
16368
+ forSession(sessionId) {
16369
+ requirePermission(permissions, "storage:read", "storage.forSession");
16370
+ const scoped = storageImpl.forSession(sessionId);
16371
+ return {
16372
+ get: (key) => {
16373
+ requirePermission(permissions, "storage:read", "storage.get");
16374
+ return scoped.get(key);
16375
+ },
16376
+ set: (key, value) => {
16377
+ requirePermission(permissions, "storage:write", "storage.set");
16378
+ return scoped.set(key, value);
16379
+ },
16380
+ delete: (key) => {
16381
+ requirePermission(permissions, "storage:write", "storage.delete");
16382
+ return scoped.delete(key);
16383
+ },
16384
+ list: () => {
16385
+ requirePermission(permissions, "storage:read", "storage.list");
16386
+ return scoped.list();
16387
+ },
16388
+ keys: (prefix) => {
16389
+ requirePermission(permissions, "storage:read", "storage.keys");
16390
+ return scoped.keys(prefix);
16391
+ },
16392
+ clear: () => {
16393
+ requirePermission(permissions, "storage:write", "storage.clear");
16394
+ return scoped.clear();
16395
+ },
16396
+ getDataDir: () => {
16397
+ requirePermission(permissions, "storage:read", "storage.getDataDir");
16398
+ return scoped.getDataDir();
16399
+ },
16400
+ forSession: (nestedId) => storage.forSession(`${sessionId}:${nestedId}`)
16401
+ };
16176
16402
  }
16177
16403
  };
16178
16404
  const ctx = {
@@ -16223,6 +16449,26 @@ function createPluginContext(opts) {
16223
16449
  await router.send(_sessionId, _content);
16224
16450
  }
16225
16451
  },
16452
+ notify(target, message, options) {
16453
+ requirePermission(permissions, "notifications:send", "notify()");
16454
+ const svc = serviceRegistry.get("notifications");
16455
+ if (svc?.notifyUser) {
16456
+ svc.notifyUser(target, message, options).catch(() => {
16457
+ });
16458
+ }
16459
+ },
16460
+ defineHook(_name) {
16461
+ },
16462
+ async emitHook(name, payload) {
16463
+ const qualifiedName = `plugin:${pluginName}:${name}`;
16464
+ return middlewareChain.execute(qualifiedName, payload, (p) => p);
16465
+ },
16466
+ async getSessionInfo(sessionId) {
16467
+ requirePermission(permissions, "sessions:read", "getSessionInfo()");
16468
+ const sessionMgr = serviceRegistry.get("session-info");
16469
+ if (!sessionMgr) return void 0;
16470
+ return sessionMgr.getSessionInfo(sessionId);
16471
+ },
16226
16472
  registerMenuItem(item) {
16227
16473
  requirePermission(permissions, "commands:register", "registerMenuItem()");
16228
16474
  const menuRegistry = serviceRegistry.get("menu-registry");
@@ -17272,7 +17518,7 @@ var OpenACPCore = class {
17272
17518
  *
17273
17519
  * If no session is found, the user is told to start one with /new.
17274
17520
  */
17275
- async handleMessage(message) {
17521
+ async handleMessage(message, initialMeta) {
17276
17522
  log16.debug(
17277
17523
  {
17278
17524
  channelId: message.channelId,
@@ -17281,10 +17527,12 @@ var OpenACPCore = class {
17281
17527
  },
17282
17528
  "Incoming message"
17283
17529
  );
17530
+ const turnId = nanoid3(8);
17531
+ const meta = { turnId, ...initialMeta };
17284
17532
  if (this.lifecycleManager?.middlewareChain) {
17285
17533
  const result = await this.lifecycleManager.middlewareChain.execute(
17286
17534
  Hook.MESSAGE_INCOMING,
17287
- message,
17535
+ { ...message, meta },
17288
17536
  async (msg) => msg
17289
17537
  );
17290
17538
  if (!result) return;
@@ -17336,8 +17584,8 @@ ${text3}`;
17336
17584
  }
17337
17585
  const sourceAdapterId = message.routing?.sourceAdapterId ?? message.channelId;
17338
17586
  const routing = sourceAdapterId !== message.routing?.sourceAdapterId ? { ...message.routing, sourceAdapterId } : message.routing;
17587
+ const enrichedMeta = message.meta ?? meta;
17339
17588
  if (sourceAdapterId && sourceAdapterId !== "sse" && sourceAdapterId !== "api") {
17340
- const turnId = nanoid3(8);
17341
17589
  this.eventBus.emit(BusEvent.MESSAGE_QUEUED, {
17342
17590
  sessionId: session.id,
17343
17591
  turnId,
@@ -17347,10 +17595,50 @@ ${text3}`;
17347
17595
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
17348
17596
  queueDepth: session.queueDepth
17349
17597
  });
17350
- await session.enqueuePrompt(text3, message.attachments, routing, turnId);
17598
+ await session.enqueuePrompt(text3, message.attachments, routing, turnId, enrichedMeta);
17351
17599
  } else {
17352
- await session.enqueuePrompt(text3, message.attachments, routing);
17600
+ await session.enqueuePrompt(text3, message.attachments, routing, turnId, enrichedMeta);
17601
+ }
17602
+ }
17603
+ /**
17604
+ * Send a message to a known session, running the full message:incoming → agent:beforePrompt
17605
+ * middleware chain (same as handleMessage) but without the threadId-based session lookup.
17606
+ *
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).
17609
+ *
17610
+ * @param session The target session — caller is responsible for validating its status.
17611
+ * @param message Sender context and message content.
17612
+ * @param initialMeta Optional adapter-specific context to seed the TurnMeta bag
17613
+ * (e.g. channelUser with display name/username).
17614
+ */
17615
+ async handleMessageInSession(session, message, initialMeta) {
17616
+ const turnId = nanoid3(8);
17617
+ const meta = { turnId, ...initialMeta };
17618
+ let text3 = message.text;
17619
+ let { attachments } = message;
17620
+ let enrichedMeta = meta;
17621
+ if (this.lifecycleManager?.middlewareChain) {
17622
+ const payload = {
17623
+ channelId: message.channelId,
17624
+ threadId: session.id,
17625
+ userId: message.userId,
17626
+ text: text3,
17627
+ attachments,
17628
+ meta
17629
+ };
17630
+ const result = await this.lifecycleManager.middlewareChain.execute(
17631
+ Hook.MESSAGE_INCOMING,
17632
+ payload,
17633
+ async (p) => p
17634
+ );
17635
+ if (!result) return;
17636
+ text3 = result.text;
17637
+ attachments = result.attachments;
17638
+ enrichedMeta = result.meta ?? meta;
17353
17639
  }
17640
+ const routing = { sourceAdapterId: message.channelId };
17641
+ await session.enqueuePrompt(text3, attachments, routing, turnId, enrichedMeta);
17354
17642
  }
17355
17643
  // --- Unified Session Creation Pipeline ---
17356
17644
  /**
@@ -19792,7 +20080,7 @@ export {
19792
20080
  MenuRegistry,
19793
20081
  MessageTransformer,
19794
20082
  MessagingAdapter,
19795
- NotificationManager,
20083
+ NotificationService as NotificationManager,
19796
20084
  OpenACPCore,
19797
20085
  OutputModeResolver,
19798
20086
  PRODUCT_GUIDE,