@openacp/cli 2026.405.2 → 2026.406.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/cli.js CHANGED
@@ -1104,7 +1104,7 @@ Declare in \`permissions\` array. Only request what you need.
1104
1104
 
1105
1105
  Calling a method without the required permission throws \`PluginPermissionError\`.
1106
1106
 
1107
- ## Middleware Hooks (20 total)
1107
+ ## Middleware Hooks (19 total)
1108
1108
 
1109
1109
  Register with \`ctx.registerMiddleware(hook, { priority?, handler })\`. Return \`null\` to block the flow, call \`next()\` to continue.
1110
1110
 
@@ -1757,6 +1757,104 @@ var init_security_guard = __esm({
1757
1757
  }
1758
1758
  });
1759
1759
 
1760
+ // src/core/events.ts
1761
+ var Hook, BusEvent, SessionEv;
1762
+ var init_events = __esm({
1763
+ "src/core/events.ts"() {
1764
+ "use strict";
1765
+ Hook = {
1766
+ // --- Message flow ---
1767
+ /** Incoming message from any adapter — modifiable, can block. */
1768
+ MESSAGE_INCOMING: "message:incoming",
1769
+ /** Outgoing message before it reaches the adapter — modifiable, can block. */
1770
+ MESSAGE_OUTGOING: "message:outgoing",
1771
+ // --- Agent / turn lifecycle ---
1772
+ /** Before a user prompt is sent to the agent — modifiable, can block. */
1773
+ AGENT_BEFORE_PROMPT: "agent:beforePrompt",
1774
+ /** Before an agent event is dispatched — modifiable, can block. */
1775
+ AGENT_BEFORE_EVENT: "agent:beforeEvent",
1776
+ /** After an agent event is dispatched — read-only, fire-and-forget. */
1777
+ AGENT_AFTER_EVENT: "agent:afterEvent",
1778
+ /** Before the current prompt is cancelled — modifiable, can block. */
1779
+ AGENT_BEFORE_CANCEL: "agent:beforeCancel",
1780
+ /** Before the agent is switched — modifiable, can block. */
1781
+ AGENT_BEFORE_SWITCH: "agent:beforeSwitch",
1782
+ /** After the agent has been switched — read-only, fire-and-forget. */
1783
+ AGENT_AFTER_SWITCH: "agent:afterSwitch",
1784
+ // --- Turn boundaries ---
1785
+ /** Turn started — read-only, fire-and-forget. */
1786
+ TURN_START: "turn:start",
1787
+ /** Turn ended (always fires, even on error) — read-only, fire-and-forget. */
1788
+ TURN_END: "turn:end",
1789
+ // --- Session lifecycle ---
1790
+ /** Before a new session is created — modifiable, can block. */
1791
+ SESSION_BEFORE_CREATE: "session:beforeCreate",
1792
+ /** After a session is destroyed — read-only, fire-and-forget. */
1793
+ SESSION_AFTER_DESTROY: "session:afterDestroy",
1794
+ // --- Permissions ---
1795
+ /** Before a permission request is shown to the user — modifiable, can block. */
1796
+ PERMISSION_BEFORE_REQUEST: "permission:beforeRequest",
1797
+ /** After a permission request is resolved — read-only, fire-and-forget. */
1798
+ PERMISSION_AFTER_RESOLVE: "permission:afterResolve",
1799
+ // --- Config ---
1800
+ /** Before config options change — modifiable, can block. */
1801
+ CONFIG_BEFORE_CHANGE: "config:beforeChange",
1802
+ // --- Filesystem (agent-level) ---
1803
+ /** Before a file read operation — modifiable. */
1804
+ FS_BEFORE_READ: "fs:beforeRead",
1805
+ /** Before a file write operation — modifiable. */
1806
+ FS_BEFORE_WRITE: "fs:beforeWrite",
1807
+ // --- Terminal ---
1808
+ /** Before a terminal session is created — modifiable, can block. */
1809
+ TERMINAL_BEFORE_CREATE: "terminal:beforeCreate",
1810
+ /** After a terminal session exits — read-only, fire-and-forget. */
1811
+ TERMINAL_AFTER_EXIT: "terminal:afterExit"
1812
+ };
1813
+ BusEvent = {
1814
+ // --- Session lifecycle ---
1815
+ SESSION_CREATED: "session:created",
1816
+ SESSION_UPDATED: "session:updated",
1817
+ SESSION_DELETED: "session:deleted",
1818
+ SESSION_ENDED: "session:ended",
1819
+ SESSION_NAMED: "session:named",
1820
+ SESSION_THREAD_READY: "session:threadReady",
1821
+ SESSION_CONFIG_CHANGED: "session:configChanged",
1822
+ SESSION_AGENT_SWITCH: "session:agentSwitch",
1823
+ // --- Agent ---
1824
+ AGENT_EVENT: "agent:event",
1825
+ AGENT_PROMPT: "agent:prompt",
1826
+ // --- Permissions ---
1827
+ PERMISSION_REQUEST: "permission:request",
1828
+ PERMISSION_RESOLVED: "permission:resolved",
1829
+ // --- Message visibility ---
1830
+ MESSAGE_QUEUED: "message:queued",
1831
+ MESSAGE_PROCESSING: "message:processing",
1832
+ // --- System lifecycle ---
1833
+ KERNEL_BOOTED: "kernel:booted",
1834
+ SYSTEM_READY: "system:ready",
1835
+ SYSTEM_SHUTDOWN: "system:shutdown",
1836
+ SYSTEM_COMMANDS_READY: "system:commands-ready",
1837
+ // --- Plugin lifecycle ---
1838
+ PLUGIN_LOADED: "plugin:loaded",
1839
+ PLUGIN_FAILED: "plugin:failed",
1840
+ PLUGIN_DISABLED: "plugin:disabled",
1841
+ PLUGIN_UNLOADED: "plugin:unloaded",
1842
+ // --- Usage ---
1843
+ USAGE_RECORDED: "usage:recorded"
1844
+ };
1845
+ SessionEv = {
1846
+ AGENT_EVENT: "agent_event",
1847
+ PERMISSION_REQUEST: "permission_request",
1848
+ SESSION_END: "session_end",
1849
+ STATUS_CHANGE: "status_change",
1850
+ NAMED: "named",
1851
+ ERROR: "error",
1852
+ PROMPT_COUNT_CHANGED: "prompt_count_changed",
1853
+ TURN_STARTED: "turn_started"
1854
+ };
1855
+ }
1856
+ });
1857
+
1760
1858
  // src/plugins/security/index.ts
1761
1859
  var security_exports = {};
1762
1860
  __export(security_exports, {
@@ -1855,7 +1953,7 @@ function createSecurityPlugin() {
1855
1953
  };
1856
1954
  };
1857
1955
  const guard = new SecurityGuard(getSecurityConfig, core.sessionManager);
1858
- ctx.registerMiddleware("message:incoming", {
1956
+ ctx.registerMiddleware(Hook.MESSAGE_INCOMING, {
1859
1957
  handler: async (payload, next) => {
1860
1958
  const access2 = await guard.checkAccess(payload);
1861
1959
  if (!access2.allowed) {
@@ -1875,6 +1973,7 @@ var init_security = __esm({
1875
1973
  "src/plugins/security/index.ts"() {
1876
1974
  "use strict";
1877
1975
  init_security_guard();
1976
+ init_events();
1878
1977
  security_default = createSecurityPlugin();
1879
1978
  }
1880
1979
  });
@@ -3427,7 +3526,7 @@ var init_history_recorder = __esm({
3427
3526
  }
3428
3527
  states = /* @__PURE__ */ new Map();
3429
3528
  debounceTimers = /* @__PURE__ */ new Map();
3430
- onBeforePrompt(sessionId, text6, attachments) {
3529
+ onBeforePrompt(sessionId, text6, attachments, sourceAdapterId) {
3431
3530
  let state = this.states.get(sessionId);
3432
3531
  if (!state) {
3433
3532
  state = {
@@ -3445,6 +3544,9 @@ var init_history_recorder = __esm({
3445
3544
  if (attachments && attachments.length > 0) {
3446
3545
  userTurn.attachments = attachments.map(toHistoryAttachment);
3447
3546
  }
3547
+ if (sourceAdapterId) {
3548
+ userTurn.sourceAdapterId = sourceAdapterId;
3549
+ }
3448
3550
  state.history.turns.push(userTurn);
3449
3551
  const assistantTurn = {
3450
3552
  index: state.history.turns.length,
@@ -3729,6 +3831,7 @@ var init_context = __esm({
3729
3831
  init_history_provider();
3730
3832
  init_history_recorder();
3731
3833
  init_history_store();
3834
+ init_events();
3732
3835
  contextPlugin = {
3733
3836
  name: "@openacp/context",
3734
3837
  version: "1.0.0",
@@ -3772,35 +3875,35 @@ var init_context = __esm({
3772
3875
  manager.setHistoryStore(store);
3773
3876
  manager.registerFlusher((sessionId) => recorder.flush(sessionId));
3774
3877
  ctx.registerService("context", manager);
3775
- ctx.registerMiddleware("agent:beforePrompt", {
3878
+ ctx.registerMiddleware(Hook.AGENT_BEFORE_PROMPT, {
3776
3879
  priority: 200,
3777
3880
  handler: async (payload, next) => {
3778
- recorder.onBeforePrompt(payload.sessionId, payload.text, payload.attachments);
3881
+ recorder.onBeforePrompt(payload.sessionId, payload.text, payload.attachments, payload.sourceAdapterId);
3779
3882
  return next();
3780
3883
  }
3781
3884
  });
3782
- ctx.registerMiddleware("agent:afterEvent", {
3885
+ ctx.registerMiddleware(Hook.AGENT_AFTER_EVENT, {
3783
3886
  priority: 200,
3784
3887
  handler: async (payload, next) => {
3785
3888
  recorder.onAfterEvent(payload.sessionId, payload.event);
3786
3889
  return next();
3787
3890
  }
3788
3891
  });
3789
- ctx.registerMiddleware("turn:end", {
3892
+ ctx.registerMiddleware(Hook.TURN_END, {
3790
3893
  priority: 200,
3791
3894
  handler: async (payload, next) => {
3792
3895
  await recorder.onTurnEnd(payload.sessionId, payload.stopReason);
3793
3896
  return next();
3794
3897
  }
3795
3898
  });
3796
- ctx.registerMiddleware("permission:afterResolve", {
3899
+ ctx.registerMiddleware(Hook.PERMISSION_AFTER_RESOLVE, {
3797
3900
  priority: 200,
3798
3901
  handler: async (payload, next) => {
3799
3902
  recorder.onPermissionResolved(payload.sessionId, payload.requestId, payload.decision);
3800
3903
  return next();
3801
3904
  }
3802
3905
  });
3803
- ctx.registerMiddleware("session:afterDestroy", {
3906
+ ctx.registerMiddleware(Hook.SESSION_AFTER_DESTROY, {
3804
3907
  priority: 200,
3805
3908
  handler: async (payload, next) => {
3806
3909
  await recorder.onSessionDestroy(payload.sessionId);
@@ -7886,6 +7989,7 @@ var MAX_SSE_CONNECTIONS, SSEManager;
7886
7989
  var init_sse_manager = __esm({
7887
7990
  "src/plugins/api-server/sse-manager.ts"() {
7888
7991
  "use strict";
7992
+ init_events();
7889
7993
  MAX_SSE_CONNECTIONS = 50;
7890
7994
  SSEManager = class {
7891
7995
  constructor(eventBus, getSessionStats, startedAt) {
@@ -7900,14 +8004,14 @@ var init_sse_manager = __esm({
7900
8004
  setup() {
7901
8005
  if (!this.eventBus) return;
7902
8006
  const events = [
7903
- "session:created",
7904
- "session:updated",
7905
- "session:deleted",
7906
- "agent:event",
7907
- "permission:request",
7908
- "permission:resolved",
7909
- "message:queued",
7910
- "message:processing"
8007
+ BusEvent.SESSION_CREATED,
8008
+ BusEvent.SESSION_UPDATED,
8009
+ BusEvent.SESSION_DELETED,
8010
+ BusEvent.AGENT_EVENT,
8011
+ BusEvent.PERMISSION_REQUEST,
8012
+ BusEvent.PERMISSION_RESOLVED,
8013
+ BusEvent.MESSAGE_QUEUED,
8014
+ BusEvent.MESSAGE_PROCESSING
7911
8015
  ];
7912
8016
  for (const eventName of events) {
7913
8017
  const handler = (data) => {
@@ -7970,12 +8074,12 @@ data: ${JSON.stringify(data)}
7970
8074
 
7971
8075
  `;
7972
8076
  const sessionEvents = [
7973
- "agent:event",
7974
- "permission:request",
7975
- "permission:resolved",
7976
- "session:updated",
7977
- "message:queued",
7978
- "message:processing"
8077
+ BusEvent.AGENT_EVENT,
8078
+ BusEvent.PERMISSION_REQUEST,
8079
+ BusEvent.PERMISSION_RESOLVED,
8080
+ BusEvent.SESSION_UPDATED,
8081
+ BusEvent.MESSAGE_QUEUED,
8082
+ BusEvent.MESSAGE_PROCESSING
7979
8083
  ];
7980
8084
  for (const res of this.sseConnections) {
7981
8085
  const filter = res.sessionFilter;
@@ -8167,10 +8271,7 @@ var init_sessions = __esm({
8167
8271
  limit: z.coerce.number().int().min(1).max(100).default(50),
8168
8272
  offset: z.coerce.number().int().min(0).default(0)
8169
8273
  });
8170
- WorkspaceNameSchema = z.string().optional().refine(
8171
- (v) => v === void 0 || !v.startsWith("/") && !v.startsWith("~"),
8172
- { message: "workspace must be a relative name, not an absolute path" }
8173
- );
8274
+ WorkspaceNameSchema = z.string().optional();
8174
8275
  CreateSessionBodySchema = z.object({
8175
8276
  agent: z.string().max(200).optional(),
8176
8277
  workspace: WorkspaceNameSchema,
@@ -8185,7 +8286,7 @@ var init_sessions = __esm({
8185
8286
  channel: z.string().max(200).optional()
8186
8287
  });
8187
8288
  AttachmentInputSchema = z.object({
8188
- fileName: z.string().regex(/^[a-zA-Z0-9._-]+$/, "fileName must contain only alphanumeric, dot, dash, or underscore characters").max(255),
8289
+ fileName: z.string().min(1).max(255),
8189
8290
  mimeType: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9!#$&\-^_]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-^_.+]*$/, "mimeType must be a valid MIME type").max(200),
8190
8291
  data: z.string().max(15e6)
8191
8292
  // ~10 MB base64 ≈ 13.3 MB string
@@ -8317,15 +8418,22 @@ async function sessionRoutes(app, deps) {
8317
8418
  const channelId = adapterId ?? "api";
8318
8419
  const resolvedAgent = body.agent || deps.core.configManager.get().defaultAgent;
8319
8420
  const agentDef = deps.core.agentCatalog.resolve(resolvedAgent);
8320
- const resolvedWorkspace = deps.core.configManager.resolveWorkspace(
8321
- body.workspace || agentDef?.workingDirectory
8322
- );
8421
+ let resolvedWorkspace;
8422
+ try {
8423
+ resolvedWorkspace = deps.core.configManager.resolveWorkspace(
8424
+ body.workspace || agentDef?.workingDirectory
8425
+ );
8426
+ } catch (err) {
8427
+ throw new BadRequestError(
8428
+ "INVALID_WORKSPACE",
8429
+ err instanceof Error ? err.message : "Invalid workspace path"
8430
+ );
8431
+ }
8323
8432
  const session = await deps.core.createSession({
8324
8433
  channelId,
8325
8434
  agentName: resolvedAgent,
8326
8435
  workingDirectory: resolvedWorkspace,
8327
- createThread: !!adapter,
8328
- initialName: `\u{1F504} ${resolvedAgent} \u2014 New Session`
8436
+ createThread: !!adapter
8329
8437
  });
8330
8438
  return {
8331
8439
  sessionId: session.id,
@@ -8833,6 +8941,7 @@ var init_config2 = __esm({
8833
8941
  defaultAgent: z4.string(),
8834
8942
  workspace: z4.object({
8835
8943
  baseDir: z4.string().default("~/openacp-workspace"),
8944
+ allowExternalWorkspaces: z4.boolean().default(true),
8836
8945
  security: z4.object({
8837
8946
  allowedPaths: z4.array(z4.string()).default([]),
8838
8947
  envWhitelist: z4.array(z4.string()).default([])
@@ -8953,13 +9062,22 @@ var init_config2 = __esm({
8953
9062
  if (input2.startsWith("/") || input2.startsWith("~")) {
8954
9063
  const resolved2 = expandHome3(input2);
8955
9064
  const base = expandHome3(this.config.workspace.baseDir);
8956
- if (resolved2 === base || resolved2.startsWith(base + path26.sep)) {
8957
- fs22.mkdirSync(resolved2, { recursive: true });
9065
+ const isInternal = resolved2 === base || resolved2.startsWith(base + path26.sep);
9066
+ if (!isInternal) {
9067
+ if (!this.config.workspace.allowExternalWorkspaces) {
9068
+ throw new Error(
9069
+ `Workspace path "${input2}" is outside base directory "${this.config.workspace.baseDir}". Set allowExternalWorkspaces: true to allow this.`
9070
+ );
9071
+ }
9072
+ if (!fs22.existsSync(resolved2)) {
9073
+ throw new Error(
9074
+ `Workspace path "${input2}" does not exist.`
9075
+ );
9076
+ }
8958
9077
  return resolved2;
8959
9078
  }
8960
- throw new Error(
8961
- `Workspace path "${input2}" is outside base directory "${this.config.workspace.baseDir}".`
8962
- );
9079
+ fs22.mkdirSync(resolved2, { recursive: true });
9080
+ return resolved2;
8963
9081
  }
8964
9082
  const name = input2.replace(/[^a-zA-Z0-9_-]/g, "");
8965
9083
  if (name !== input2) {
@@ -10183,7 +10301,7 @@ function createApiServerPlugin() {
10183
10301
  );
10184
10302
  ctx.registerService("api-server", apiService);
10185
10303
  cleanupInterval = setInterval(() => tokenStore.cleanup(), 60 * 60 * 1e3);
10186
- ctx.on("system:ready", async () => {
10304
+ ctx.on(BusEvent.SYSTEM_READY, async () => {
10187
10305
  log15.info(
10188
10306
  { configPort: apiConfig.port, configHost: apiConfig.host },
10189
10307
  "API server starting..."
@@ -10235,6 +10353,7 @@ var log15, cachedVersion, api_server_default;
10235
10353
  var init_api_server = __esm({
10236
10354
  "src/plugins/api-server/index.ts"() {
10237
10355
  "use strict";
10356
+ init_events();
10238
10357
  init_log();
10239
10358
  log15 = createChildLogger({ module: "api-server" });
10240
10359
  api_server_default = createApiServerPlugin();
@@ -10679,6 +10798,7 @@ var init_sse_adapter = __esm({
10679
10798
  init_event_buffer();
10680
10799
  init_adapter();
10681
10800
  init_routes();
10801
+ init_events();
10682
10802
  _adapter = null;
10683
10803
  _connectionManager = null;
10684
10804
  plugin = {
@@ -10705,11 +10825,11 @@ var init_sse_adapter = __esm({
10705
10825
  _connectionManager = connectionManager;
10706
10826
  ctx.registerService("adapter:sse", adapter);
10707
10827
  const commandRegistry = ctx.getService("command-registry");
10708
- ctx.on("session:deleted", (data) => {
10828
+ ctx.on(BusEvent.SESSION_DELETED, (data) => {
10709
10829
  const { sessionId } = data;
10710
10830
  eventBuffer.cleanup(sessionId);
10711
10831
  });
10712
- ctx.on("session:ended", (data) => {
10832
+ ctx.on(BusEvent.SESSION_ENDED, (data) => {
10713
10833
  const { sessionId } = data;
10714
10834
  eventBuffer.cleanup(sessionId);
10715
10835
  });
@@ -12968,7 +13088,6 @@ var menu_exports = {};
12968
13088
  __export(menu_exports, {
12969
13089
  buildMenuKeyboard: () => buildMenuKeyboard,
12970
13090
  buildSkillMessages: () => buildSkillMessages,
12971
- handleClear: () => handleClear,
12972
13091
  handleHelp: () => handleHelp,
12973
13092
  handleMenu: () => handleMenu
12974
13093
  });
@@ -13029,31 +13148,11 @@ Each session gets its own topic \u2014 chat there to work with the agent.
13029
13148
  /bypass_permissions \u2014 Toggle bypass permissions
13030
13149
  /handoff \u2014 Continue session in terminal
13031
13150
  /archive \u2014 Archive session topic
13032
- /clear \u2014 Clear assistant history
13033
13151
 
13034
13152
  \u{1F4AC} Need help? Just ask me in this topic!`,
13035
13153
  { parse_mode: "HTML" }
13036
13154
  );
13037
13155
  }
13038
- async function handleClear(ctx, assistant) {
13039
- if (!assistant) {
13040
- await ctx.reply("\u26A0\uFE0F Assistant is not available.", { parse_mode: "HTML" });
13041
- return;
13042
- }
13043
- const threadId = ctx.message?.message_thread_id;
13044
- if (threadId !== assistant.topicId) {
13045
- await ctx.reply("\u2139\uFE0F /clear only works in the Assistant topic.", { parse_mode: "HTML" });
13046
- return;
13047
- }
13048
- await ctx.reply("\u{1F504} Clearing assistant history...", { parse_mode: "HTML" });
13049
- try {
13050
- await assistant.respawn();
13051
- await ctx.reply("\u2705 Assistant history cleared.", { parse_mode: "HTML" });
13052
- } catch (err) {
13053
- const message = err instanceof Error ? err.message : String(err);
13054
- await ctx.reply(`\u274C Failed to clear: <code>${message}</code>`, { parse_mode: "HTML" });
13055
- }
13056
- }
13057
13156
  function buildSkillMessages(commands) {
13058
13157
  const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
13059
13158
  const header2 = "\u{1F6E0} <b>Available Skills</b>\n";
@@ -14578,7 +14677,10 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
14578
14677
  const menuRegistry = core.lifecycleManager?.serviceRegistry?.get("menu-registry");
14579
14678
  if (!menuRegistry) return;
14580
14679
  const item = menuRegistry.getItem(itemId);
14581
- if (!item) return;
14680
+ if (!item) {
14681
+ log25.warn({ itemId }, "Menu item not found in registry");
14682
+ return;
14683
+ }
14582
14684
  const topicId = ctx.callbackQuery.message?.message_thread_id;
14583
14685
  const registry = core.lifecycleManager?.serviceRegistry?.get("command-registry");
14584
14686
  switch (item.action.type) {
@@ -14662,7 +14764,7 @@ ${lines}`, { parse_mode: "HTML" }).catch(() => {
14662
14764
  }
14663
14765
  });
14664
14766
  }
14665
- var STATIC_COMMANDS;
14767
+ var log25, STATIC_COMMANDS;
14666
14768
  var init_commands3 = __esm({
14667
14769
  "src/plugins/telegram/commands/index.ts"() {
14668
14770
  "use strict";
@@ -14675,6 +14777,7 @@ var init_commands3 = __esm({
14675
14777
  init_tunnel4();
14676
14778
  init_switch();
14677
14779
  init_telegram_overrides();
14780
+ init_log();
14678
14781
  init_menu();
14679
14782
  init_menu();
14680
14783
  init_telegram_overrides();
@@ -14686,6 +14789,7 @@ var init_commands3 = __esm({
14686
14789
  init_settings();
14687
14790
  init_doctor2();
14688
14791
  init_resume();
14792
+ log25 = createChildLogger({ module: "telegram-menu-callbacks" });
14689
14793
  STATIC_COMMANDS = [
14690
14794
  { command: "new", description: "Create new session" },
14691
14795
  { command: "newchat", description: "New chat, same agent & workspace" },
@@ -14698,7 +14802,6 @@ var init_commands3 = __esm({
14698
14802
  { command: "menu", description: "Show menu" },
14699
14803
  { command: "integrate", description: "Manage agent integrations" },
14700
14804
  { command: "handoff", description: "Continue this session in your terminal" },
14701
- { command: "clear", description: "Clear assistant history" },
14702
14805
  { command: "restart", description: "Restart OpenACP" },
14703
14806
  { command: "update", description: "Update to latest version and restart" },
14704
14807
  { command: "doctor", description: "Run system diagnostics" },
@@ -14721,14 +14824,14 @@ var init_commands3 = __esm({
14721
14824
  // src/plugins/telegram/permissions.ts
14722
14825
  import { InlineKeyboard as InlineKeyboard11 } from "grammy";
14723
14826
  import { nanoid as nanoid2 } from "nanoid";
14724
- var log25, PermissionHandler;
14827
+ var log26, PermissionHandler;
14725
14828
  var init_permissions = __esm({
14726
14829
  "src/plugins/telegram/permissions.ts"() {
14727
14830
  "use strict";
14728
14831
  init_formatting();
14729
14832
  init_topics2();
14730
14833
  init_log();
14731
- log25 = createChildLogger({ module: "telegram-permissions" });
14834
+ log26 = createChildLogger({ module: "telegram-permissions" });
14732
14835
  PermissionHandler = class {
14733
14836
  constructor(bot, chatId, getSession, sendNotification) {
14734
14837
  this.bot = bot;
@@ -14772,11 +14875,11 @@ ${escapeHtml4(request.description)}`,
14772
14875
  });
14773
14876
  }
14774
14877
  setupCallbackHandler() {
14775
- this.bot.on("callback_query:data", async (ctx) => {
14878
+ this.bot.on("callback_query:data", async (ctx, next) => {
14776
14879
  const data = ctx.callbackQuery.data;
14777
- if (!data.startsWith("p:")) return;
14880
+ if (!data.startsWith("p:")) return next();
14778
14881
  const parts = data.split(":");
14779
- if (parts.length < 3) return;
14882
+ if (parts.length < 3) return next();
14780
14883
  const [, callbackKey, optionId] = parts;
14781
14884
  const pending = this.pending.get(callbackKey);
14782
14885
  if (!pending) {
@@ -14788,7 +14891,7 @@ ${escapeHtml4(request.description)}`,
14788
14891
  }
14789
14892
  const session = this.getSession(pending.sessionId);
14790
14893
  const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
14791
- log25.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
14894
+ log26.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
14792
14895
  if (session?.permissionGate.requestId === pending.requestId) {
14793
14896
  session.permissionGate.resolve(optionId);
14794
14897
  }
@@ -15242,7 +15345,7 @@ var init_display_spec_builder = __esm({
15242
15345
  });
15243
15346
 
15244
15347
  // src/plugins/telegram/activity.ts
15245
- var log26, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker;
15348
+ var log27, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker;
15246
15349
  var init_activity = __esm({
15247
15350
  "src/plugins/telegram/activity.ts"() {
15248
15351
  "use strict";
@@ -15252,7 +15355,7 @@ var init_activity = __esm({
15252
15355
  init_stream_accumulator();
15253
15356
  init_stream_accumulator();
15254
15357
  init_display_spec_builder();
15255
- log26 = createChildLogger({ module: "telegram:activity" });
15358
+ log27 = createChildLogger({ module: "telegram:activity" });
15256
15359
  THINKING_REFRESH_MS = 15e3;
15257
15360
  THINKING_MAX_MS = 3 * 60 * 1e3;
15258
15361
  ThinkingIndicator = class {
@@ -15293,7 +15396,7 @@ var init_activity = __esm({
15293
15396
  }
15294
15397
  }
15295
15398
  } catch (err) {
15296
- log26.warn({ err }, "ThinkingIndicator.show() failed");
15399
+ log27.warn({ err }, "ThinkingIndicator.show() failed");
15297
15400
  } finally {
15298
15401
  this.sending = false;
15299
15402
  }
@@ -15461,7 +15564,7 @@ var init_activity = __esm({
15461
15564
  this.tracer?.log("telegram", { action: "telegram:delete:overflow", sessionId: this.sessionId, msgId: staleId });
15462
15565
  }
15463
15566
  } catch (err) {
15464
- log26.warn({ err }, "[ToolCard] send/edit failed");
15567
+ log27.warn({ err }, "[ToolCard] send/edit failed");
15465
15568
  }
15466
15569
  }
15467
15570
  };
@@ -15565,7 +15668,7 @@ var init_activity = __esm({
15565
15668
  const entry = this.toolStateMap.merge(id, status, rawInput, content, viewerLinks, diffStats);
15566
15669
  if (!existed || !entry) return;
15567
15670
  if (viewerLinks || entry.viewerLinks) {
15568
- log26.debug({ toolId: id, status, hasIncomingLinks: !!viewerLinks, hasEntryLinks: !!entry.viewerLinks, entryLinks: entry.viewerLinks }, "toolUpdate: viewer links trace");
15671
+ log27.debug({ toolId: id, status, hasIncomingLinks: !!viewerLinks, hasEntryLinks: !!entry.viewerLinks, entryLinks: entry.viewerLinks }, "toolUpdate: viewer links trace");
15569
15672
  }
15570
15673
  const spec = this.specBuilder.buildToolSpec(entry, this._outputMode, this.sessionContext);
15571
15674
  this.toolCard.updateFromSpec(spec);
@@ -15992,13 +16095,13 @@ var init_draft_manager = __esm({
15992
16095
  });
15993
16096
 
15994
16097
  // src/plugins/telegram/skill-command-manager.ts
15995
- var log27, SkillCommandManager;
16098
+ var log28, SkillCommandManager;
15996
16099
  var init_skill_command_manager = __esm({
15997
16100
  "src/plugins/telegram/skill-command-manager.ts"() {
15998
16101
  "use strict";
15999
16102
  init_commands3();
16000
16103
  init_log();
16001
- log27 = createChildLogger({ module: "skill-commands" });
16104
+ log28 = createChildLogger({ module: "skill-commands" });
16002
16105
  SkillCommandManager = class {
16003
16106
  // sessionId → pinned msgId
16004
16107
  constructor(bot, chatId, sendQueue, sessionManager) {
@@ -16064,7 +16167,7 @@ var init_skill_command_manager = __esm({
16064
16167
  disable_notification: true
16065
16168
  });
16066
16169
  } catch (err) {
16067
- log27.error({ err, sessionId }, "Failed to send skill commands");
16170
+ log28.error({ err, sessionId }, "Failed to send skill commands");
16068
16171
  }
16069
16172
  }
16070
16173
  async cleanup(sessionId) {
@@ -16458,10 +16561,11 @@ function patchedFetch(input2, init) {
16458
16561
  }
16459
16562
  return fetch(input2, init);
16460
16563
  }
16461
- var log28, TelegramAdapter;
16564
+ var log29, TelegramAdapter;
16462
16565
  var init_adapter2 = __esm({
16463
16566
  "src/plugins/telegram/adapter.ts"() {
16464
16567
  "use strict";
16568
+ init_events();
16465
16569
  init_log();
16466
16570
  init_topics2();
16467
16571
  init_commands3();
@@ -16477,7 +16581,7 @@ var init_adapter2 = __esm({
16477
16581
  init_messaging_adapter();
16478
16582
  init_renderer2();
16479
16583
  init_output_mode_resolver();
16480
- log28 = createChildLogger({ module: "telegram" });
16584
+ log29 = createChildLogger({ module: "telegram" });
16481
16585
  TelegramAdapter = class extends MessagingAdapter {
16482
16586
  name = "telegram";
16483
16587
  renderer = new TelegramRenderer();
@@ -16604,7 +16708,7 @@ var init_adapter2 = __esm({
16604
16708
  );
16605
16709
  this.bot.catch((err) => {
16606
16710
  const rootCause = err.error instanceof Error ? err.error : err;
16607
- log28.error({ err: rootCause }, "Telegram bot error");
16711
+ log29.error({ err: rootCause }, "Telegram bot error");
16608
16712
  });
16609
16713
  this.bot.api.config.use(async (prev, method, payload, signal) => {
16610
16714
  const maxRetries = 3;
@@ -16622,7 +16726,7 @@ var init_adapter2 = __esm({
16622
16726
  if (rateLimitedMethods.includes(method)) {
16623
16727
  this.sendQueue.onRateLimited();
16624
16728
  }
16625
- log28.warn(
16729
+ log29.warn(
16626
16730
  { method, retryAfter, attempt: attempt + 1 },
16627
16731
  "Rate limited by Telegram, retrying"
16628
16732
  );
@@ -16810,9 +16914,9 @@ ${p2}` : p2;
16810
16914
  this.setupRoutes();
16811
16915
  this.bot.start({
16812
16916
  allowed_updates: ["message", "callback_query"],
16813
- onStart: () => log28.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
16917
+ onStart: () => log29.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
16814
16918
  });
16815
- log28.info(
16919
+ log29.info(
16816
16920
  {
16817
16921
  chatId: this.telegramConfig.chatId,
16818
16922
  notificationTopicId: this.telegramConfig.notificationTopicId,
@@ -16826,12 +16930,12 @@ ${p2}` : p2;
16826
16930
  this.telegramConfig.chatId
16827
16931
  );
16828
16932
  if (prereqResult.ok) {
16829
- log28.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
16933
+ log29.info("Telegram adapter: prerequisites OK, initializing topic-dependent features");
16830
16934
  await this.initTopicDependentFeatures();
16831
16935
  } else {
16832
- log28.warn({ issues: prereqResult.issues }, "Telegram adapter: prerequisites NOT met, starting watcher");
16936
+ log29.warn({ issues: prereqResult.issues }, "Telegram adapter: prerequisites NOT met, starting watcher");
16833
16937
  for (const issue of prereqResult.issues) {
16834
- log28.warn({ issue }, "Telegram prerequisite not met");
16938
+ log29.warn({ issue }, "Telegram prerequisite not met");
16835
16939
  }
16836
16940
  this.startPrerequisiteWatcher(prereqResult.issues);
16837
16941
  }
@@ -16847,7 +16951,7 @@ ${p2}` : p2;
16847
16951
  } catch (err) {
16848
16952
  if (attempt === maxRetries) throw err;
16849
16953
  const delay = baseDelayMs * Math.pow(2, attempt - 1);
16850
- log28.warn(
16954
+ log29.warn(
16851
16955
  { err, attempt, maxRetries, delayMs: delay, operation: label },
16852
16956
  `${label} failed, retrying in ${delay}ms`
16853
16957
  );
@@ -16867,12 +16971,12 @@ ${p2}` : p2;
16867
16971
  }),
16868
16972
  "setMyCommands"
16869
16973
  ).catch((err) => {
16870
- log28.warn({ err }, "Failed to register Telegram commands after retries (non-critical)");
16974
+ log29.warn({ err }, "Failed to register Telegram commands after retries (non-critical)");
16871
16975
  });
16872
16976
  }
16873
16977
  async initTopicDependentFeatures() {
16874
16978
  if (this._topicsInitialized) return;
16875
- log28.info(
16979
+ log29.info(
16876
16980
  { notificationTopicId: this.telegramConfig.notificationTopicId, assistantTopicId: this.telegramConfig.assistantTopicId },
16877
16981
  "initTopicDependentFeatures: starting (existing IDs in config)"
16878
16982
  );
@@ -16897,7 +17001,7 @@ ${p2}` : p2;
16897
17001
  this.assistantTopicId = topics.assistantTopicId;
16898
17002
  this._systemTopicIds.notificationTopicId = topics.notificationTopicId;
16899
17003
  this._systemTopicIds.assistantTopicId = topics.assistantTopicId;
16900
- log28.info(
17004
+ log29.info(
16901
17005
  { notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId },
16902
17006
  "initTopicDependentFeatures: topics ready"
16903
17007
  );
@@ -16928,16 +17032,16 @@ ${p2}` : p2;
16928
17032
  ).then((msg) => {
16929
17033
  if (msg) this.storeControlMsgId(sessionId, msg.message_id);
16930
17034
  }).catch((err) => {
16931
- log28.warn({ err, sessionId }, "Failed to send initial messages for new session");
17035
+ log29.warn({ err, sessionId }, "Failed to send initial messages for new session");
16932
17036
  });
16933
17037
  };
16934
- this.core.eventBus.on("session:threadReady", this._threadReadyHandler);
17038
+ this.core.eventBus.on(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
16935
17039
  this._configChangedHandler = ({ sessionId }) => {
16936
17040
  this.updateControlMessage(sessionId).catch(() => {
16937
17041
  });
16938
17042
  };
16939
17043
  this.core.eventBus.on("session:configChanged", this._configChangedHandler);
16940
- log28.info({ assistantTopicId: this.assistantTopicId }, "initTopicDependentFeatures: sending welcome message");
17044
+ log29.info({ assistantTopicId: this.assistantTopicId }, "initTopicDependentFeatures: sending welcome message");
16941
17045
  try {
16942
17046
  const config = this.core.configManager.get();
16943
17047
  const agents = this.core.agentManager.getAvailableAgents();
@@ -16959,17 +17063,17 @@ ${p2}` : p2;
16959
17063
  this.core.lifecycleManager?.serviceRegistry?.get("menu-registry")
16960
17064
  )
16961
17065
  });
16962
- log28.info("initTopicDependentFeatures: welcome message sent");
17066
+ log29.info("initTopicDependentFeatures: welcome message sent");
16963
17067
  } catch (err) {
16964
- log28.warn({ err }, "Failed to send welcome message");
17068
+ log29.warn({ err }, "Failed to send welcome message");
16965
17069
  }
16966
17070
  try {
16967
- await this.core.assistantManager.spawn("telegram", String(this.assistantTopicId));
17071
+ await this.core.assistantManager.getOrSpawn("telegram", String(this.assistantTopicId));
16968
17072
  } catch (err) {
16969
- log28.error({ err }, "Failed to spawn assistant");
17073
+ log29.error({ err }, "Failed to spawn assistant");
16970
17074
  }
16971
17075
  this._topicsInitialized = true;
16972
- log28.info("Telegram adapter fully initialized");
17076
+ log29.info("Telegram adapter fully initialized");
16973
17077
  }
16974
17078
  startPrerequisiteWatcher(issues) {
16975
17079
  const setupMessage = `\u26A0\uFE0F <b>OpenACP needs setup before it can start.</b>
@@ -16980,7 +17084,7 @@ OpenACP will automatically retry until this is resolved.`;
16980
17084
  this.bot.api.sendMessage(this.telegramConfig.chatId, setupMessage, {
16981
17085
  parse_mode: "HTML"
16982
17086
  }).catch((err) => {
16983
- log28.warn({ err }, "Failed to send setup guidance to General topic");
17087
+ log29.warn({ err }, "Failed to send setup guidance to General topic");
16984
17088
  });
16985
17089
  const schedule = [5e3, 1e4, 3e4];
16986
17090
  let attempt = 1;
@@ -16993,7 +17097,7 @@ OpenACP will automatically retry until this is resolved.`;
16993
17097
  );
16994
17098
  if (result.ok) {
16995
17099
  this._prerequisiteWatcher = null;
16996
- log28.info("Prerequisites met \u2014 completing Telegram adapter initialization");
17100
+ log29.info("Prerequisites met \u2014 completing Telegram adapter initialization");
16997
17101
  try {
16998
17102
  await this.initTopicDependentFeatures();
16999
17103
  await this.bot.api.sendMessage(
@@ -17002,11 +17106,11 @@ OpenACP will automatically retry until this is resolved.`;
17002
17106
  { parse_mode: "HTML" }
17003
17107
  );
17004
17108
  } catch (err) {
17005
- log28.error({ err }, "Failed to complete initialization after prerequisites met");
17109
+ log29.error({ err }, "Failed to complete initialization after prerequisites met");
17006
17110
  }
17007
17111
  return;
17008
17112
  }
17009
- log28.debug({ issues: result.issues }, "Prerequisites not yet met, retrying");
17113
+ log29.debug({ issues: result.issues }, "Prerequisites not yet met, retrying");
17010
17114
  const delay = schedule[Math.min(attempt, schedule.length - 1)];
17011
17115
  attempt++;
17012
17116
  this._prerequisiteWatcher = setTimeout(retry, delay);
@@ -17023,7 +17127,7 @@ OpenACP will automatically retry until this is resolved.`;
17023
17127
  }
17024
17128
  this.sessionTrackers.clear();
17025
17129
  if (this._threadReadyHandler) {
17026
- this.core.eventBus.off("session:threadReady", this._threadReadyHandler);
17130
+ this.core.eventBus.off(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
17027
17131
  this._threadReadyHandler = void 0;
17028
17132
  }
17029
17133
  if (this._configChangedHandler) {
@@ -17032,7 +17136,7 @@ OpenACP will automatically retry until this is resolved.`;
17032
17136
  }
17033
17137
  this.sendQueue.clear();
17034
17138
  await this.bot.stop();
17035
- log28.info("Telegram bot stopped");
17139
+ log29.info("Telegram bot stopped");
17036
17140
  }
17037
17141
  // --- CommandRegistry response rendering ---
17038
17142
  async renderCommandResponse(response, chatId, topicId) {
@@ -17117,6 +17221,13 @@ ${lines.join("\n")}`;
17117
17221
  }
17118
17222
  setupRoutes() {
17119
17223
  this.bot.on("message:text", async (ctx) => {
17224
+ if (!this._topicsInitialized) {
17225
+ await ctx.reply(
17226
+ "\u23F3 OpenACP is still setting up. Check the General topic for instructions."
17227
+ ).catch(() => {
17228
+ });
17229
+ return;
17230
+ }
17120
17231
  const threadId = ctx.message.message_thread_id;
17121
17232
  const text6 = ctx.message.text;
17122
17233
  if (!threadId) {
@@ -17149,7 +17260,7 @@ ${lines.join("\n")}`;
17149
17260
  threadId: String(threadId),
17150
17261
  userId: String(ctx.from.id),
17151
17262
  text: forwardText
17152
- }).catch((err) => log28.error({ err }, "handleMessage error"));
17263
+ }).catch((err) => log29.error({ err }, "handleMessage error"));
17153
17264
  });
17154
17265
  this.bot.on("message:photo", async (ctx) => {
17155
17266
  const threadId = ctx.message.message_thread_id;
@@ -17238,7 +17349,7 @@ ${lines.join("\n")}`;
17238
17349
  if (session.archiving) return;
17239
17350
  const threadId = Number(session.threadId);
17240
17351
  if (!threadId || isNaN(threadId)) {
17241
- log28.warn(
17352
+ log29.warn(
17242
17353
  { sessionId, threadId: session.threadId },
17243
17354
  "Session has no valid threadId, skipping message"
17244
17355
  );
@@ -17254,7 +17365,7 @@ ${lines.join("\n")}`;
17254
17365
  this._sessionThreadIds.delete(sessionId);
17255
17366
  }
17256
17367
  }).catch((err) => {
17257
- log28.warn({ err, sessionId }, "Dispatch queue error");
17368
+ log29.warn({ err, sessionId }, "Dispatch queue error");
17258
17369
  });
17259
17370
  this._dispatchQueues.set(sessionId, next);
17260
17371
  await next;
@@ -17376,7 +17487,7 @@ ${lines.join("\n")}`;
17376
17487
  );
17377
17488
  usageMsgId = result?.message_id;
17378
17489
  } catch (err) {
17379
- log28.warn({ err, sessionId }, "Failed to send usage message");
17490
+ log29.warn({ err, sessionId }, "Failed to send usage message");
17380
17491
  }
17381
17492
  if (this.notificationTopicId && sessionId !== this.core.assistantManager?.get("telegram")?.id) {
17382
17493
  const sess = this.core.sessionManager.getSession(sessionId);
@@ -17404,7 +17515,7 @@ Task completed.
17404
17515
  if (!content.attachment) return;
17405
17516
  const { attachment } = content;
17406
17517
  if (attachment.size > 50 * 1024 * 1024) {
17407
- log28.warn(
17518
+ log29.warn(
17408
17519
  {
17409
17520
  sessionId,
17410
17521
  fileName: attachment.fileName,
@@ -17448,7 +17559,7 @@ Task completed.
17448
17559
  );
17449
17560
  }
17450
17561
  } catch (err) {
17451
- log28.error(
17562
+ log29.error(
17452
17563
  { err, sessionId, fileName: attachment.fileName },
17453
17564
  "Failed to send attachment"
17454
17565
  );
@@ -17547,7 +17658,7 @@ Task completed.
17547
17658
  }
17548
17659
  async sendPermissionRequest(sessionId, request) {
17549
17660
  this.getTracer(sessionId)?.log("telegram", { action: "permission:send", sessionId, requestId: request.id, description: request.description });
17550
- log28.info({ sessionId, requestId: request.id }, "Permission request sent");
17661
+ log29.info({ sessionId, requestId: request.id }, "Permission request sent");
17551
17662
  const session = this.core.sessionManager.getSession(sessionId);
17552
17663
  if (!session) return;
17553
17664
  await this.sendQueue.enqueue(
@@ -17557,7 +17668,7 @@ Task completed.
17557
17668
  async sendNotification(notification) {
17558
17669
  this.getTracer(notification.sessionId)?.log("telegram", { action: "notification:send", sessionId: notification.sessionId, type: notification.type });
17559
17670
  if (notification.sessionId === this.core.assistantManager?.get("telegram")?.id) return;
17560
- log28.info(
17671
+ log29.info(
17561
17672
  { sessionId: notification.sessionId, type: notification.type },
17562
17673
  "Notification sent"
17563
17674
  );
@@ -17596,7 +17707,7 @@ Task completed.
17596
17707
  }
17597
17708
  async createSessionThread(sessionId, name) {
17598
17709
  this.getTracer(sessionId)?.log("telegram", { action: "thread:create", sessionId, name });
17599
- log28.info({ sessionId, name }, "Session topic created");
17710
+ log29.info({ sessionId, name }, "Session topic created");
17600
17711
  return String(
17601
17712
  await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
17602
17713
  );
@@ -17607,7 +17718,7 @@ Task completed.
17607
17718
  if (!session) return;
17608
17719
  const threadId = Number(session.threadId);
17609
17720
  if (!threadId) {
17610
- log28.debug({ sessionId, newName }, "Cannot rename thread \u2014 threadId not set yet");
17721
+ log29.debug({ sessionId, newName }, "Cannot rename thread \u2014 threadId not set yet");
17611
17722
  return;
17612
17723
  }
17613
17724
  await renameSessionTopic(
@@ -17626,7 +17737,7 @@ Task completed.
17626
17737
  try {
17627
17738
  await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
17628
17739
  } catch (err) {
17629
- log28.warn(
17740
+ log29.warn(
17630
17741
  { err, sessionId, topicId },
17631
17742
  "Failed to delete forum topic (may already be deleted)"
17632
17743
  );
@@ -17670,7 +17781,7 @@ Task completed.
17670
17781
  const buffer = Buffer.from(await response.arrayBuffer());
17671
17782
  return { buffer, filePath: file.file_path };
17672
17783
  } catch (err) {
17673
- log28.error({ err }, "Failed to download file from Telegram");
17784
+ log29.error({ err }, "Failed to download file from Telegram");
17674
17785
  return null;
17675
17786
  }
17676
17787
  }
@@ -17691,7 +17802,7 @@ Task completed.
17691
17802
  try {
17692
17803
  buffer = await this.fileService.convertOggToWav(buffer);
17693
17804
  } catch (err) {
17694
- log28.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
17805
+ log29.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
17695
17806
  fileName = "voice.ogg";
17696
17807
  mimeType = "audio/ogg";
17697
17808
  originalFilePath = void 0;
@@ -17723,7 +17834,7 @@ Task completed.
17723
17834
  userId: String(userId),
17724
17835
  text: text6,
17725
17836
  attachments: [att]
17726
- }).catch((err) => log28.error({ err }, "handleMessage error"));
17837
+ }).catch((err) => log29.error({ err }, "handleMessage error"));
17727
17838
  }
17728
17839
  async cleanupSkillCommands(sessionId) {
17729
17840
  this._pendingSkillCommands.delete(sessionId);
@@ -18820,6 +18931,47 @@ var init_typed_emitter = __esm({
18820
18931
  }
18821
18932
  });
18822
18933
 
18934
+ // src/core/agents/attachment-blocks.ts
18935
+ function buildAttachmentNote(att, capabilities) {
18936
+ const tooLarge = att.size > MAX_ATTACHMENT_SIZE;
18937
+ if (tooLarge) {
18938
+ const sizeMB = Math.round(att.size / 1024 / 1024);
18939
+ return `[Attachment skipped: "${att.fileName}" is too large (${sizeMB}MB > 10MB limit)]`;
18940
+ }
18941
+ if (att.type === "image") {
18942
+ if (!capabilities.image) {
18943
+ return null;
18944
+ }
18945
+ if (!SUPPORTED_IMAGE_MIMES.has(att.mimeType)) {
18946
+ return `[Attachment skipped: image format not supported (${att.mimeType})]`;
18947
+ }
18948
+ return null;
18949
+ }
18950
+ if (att.type === "audio") {
18951
+ if (!capabilities.audio) {
18952
+ return null;
18953
+ }
18954
+ return null;
18955
+ }
18956
+ return null;
18957
+ }
18958
+ var SUPPORTED_IMAGE_MIMES, MAX_ATTACHMENT_SIZE;
18959
+ var init_attachment_blocks = __esm({
18960
+ "src/core/agents/attachment-blocks.ts"() {
18961
+ "use strict";
18962
+ SUPPORTED_IMAGE_MIMES = /* @__PURE__ */ new Set([
18963
+ "image/jpeg",
18964
+ "image/png",
18965
+ "image/gif",
18966
+ "image/webp",
18967
+ "image/avif",
18968
+ "image/heic",
18969
+ "image/heif"
18970
+ ]);
18971
+ MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024;
18972
+ }
18973
+ });
18974
+
18823
18975
  // src/core/sessions/terminal-manager.ts
18824
18976
  import { spawn as spawn7 } from "child_process";
18825
18977
  import { randomUUID } from "crypto";
@@ -18828,6 +18980,7 @@ var init_terminal_manager = __esm({
18828
18980
  "src/core/sessions/terminal-manager.ts"() {
18829
18981
  "use strict";
18830
18982
  init_env_filter();
18983
+ init_events();
18831
18984
  TerminalManager = class {
18832
18985
  terminals = /* @__PURE__ */ new Map();
18833
18986
  maxOutputBytes;
@@ -18844,7 +18997,7 @@ var init_terminal_manager = __esm({
18844
18997
  for (const ev of termEnvArr) {
18845
18998
  envRecord[ev.name] = ev.value;
18846
18999
  }
18847
- const result = await middlewareChain.execute("terminal:beforeCreate", {
19000
+ const result = await middlewareChain.execute(Hook.TERMINAL_BEFORE_CREATE, {
18848
19001
  sessionId,
18849
19002
  command: termCommand,
18850
19003
  args: termArgs,
@@ -18898,7 +19051,7 @@ var init_terminal_manager = __esm({
18898
19051
  childProcess.on("exit", (code, signal) => {
18899
19052
  state.exitStatus = { exitCode: code, signal };
18900
19053
  if (middlewareChain) {
18901
- middlewareChain.execute("terminal:afterExit", {
19054
+ middlewareChain.execute(Hook.TERMINAL_AFTER_EXIT, {
18902
19055
  sessionId,
18903
19056
  terminalId,
18904
19057
  command: state.command,
@@ -19102,7 +19255,7 @@ function resolveAgentCommand(cmd) {
19102
19255
  }
19103
19256
  return { command: cmd, args: [] };
19104
19257
  }
19105
- var log30, AgentInstance;
19258
+ var log31, AgentInstance;
19106
19259
  var init_agent_instance = __esm({
19107
19260
  "src/core/agents/agent-instance.ts"() {
19108
19261
  "use strict";
@@ -19112,11 +19265,13 @@ var init_agent_instance = __esm({
19112
19265
  init_stderr_capture();
19113
19266
  init_typed_emitter();
19114
19267
  init_read_text_file();
19268
+ init_attachment_blocks();
19115
19269
  init_terminal_manager();
19116
19270
  init_mcp_manager();
19117
19271
  init_debug_tracer();
19118
19272
  init_log();
19119
- log30 = createChildLogger({ module: "agent-instance" });
19273
+ init_events();
19274
+ log31 = createChildLogger({ module: "agent-instance" });
19120
19275
  AgentInstance = class _AgentInstance extends TypedEmitter {
19121
19276
  connection;
19122
19277
  child;
@@ -19143,10 +19298,10 @@ var init_agent_instance = __esm({
19143
19298
  super();
19144
19299
  this.agentName = agentName;
19145
19300
  }
19146
- static async spawnSubprocess(agentDef, workingDirectory) {
19301
+ static async spawnSubprocess(agentDef, workingDirectory, allowedPaths = []) {
19147
19302
  const instance = new _AgentInstance(agentDef.name);
19148
19303
  const resolved = resolveAgentCommand(agentDef.command);
19149
- log30.debug(
19304
+ log31.debug(
19150
19305
  {
19151
19306
  agentName: agentDef.name,
19152
19307
  command: resolved.command,
@@ -19157,10 +19312,7 @@ var init_agent_instance = __esm({
19157
19312
  const ignorePatterns = PathGuard.loadIgnoreFile(workingDirectory);
19158
19313
  instance.pathGuard = new PathGuard({
19159
19314
  cwd: workingDirectory,
19160
- // allowedPaths is wired from workspace.security.allowedPaths config;
19161
- // spawnSubprocess would need to receive a SecurityConfig param to use it.
19162
- // Tracked as follow-up: pass workspace security config through spawn/resume call chain.
19163
- allowedPaths: [],
19315
+ allowedPaths,
19164
19316
  ignorePatterns
19165
19317
  });
19166
19318
  instance.child = spawn8(
@@ -19231,14 +19383,14 @@ var init_agent_instance = __esm({
19231
19383
  }
19232
19384
  });
19233
19385
  if (initResponse.protocolVersion !== PROTOCOL_VERSION) {
19234
- log30.warn(
19386
+ log31.warn(
19235
19387
  { expected: PROTOCOL_VERSION, got: initResponse.protocolVersion },
19236
19388
  "ACP protocol version mismatch \u2014 some features may not work correctly"
19237
19389
  );
19238
19390
  }
19239
19391
  instance.promptCapabilities = initResponse.agentCapabilities?.promptCapabilities;
19240
19392
  instance.agentCapabilities = initResponse.agentCapabilities;
19241
- log30.info(
19393
+ log31.info(
19242
19394
  { promptCapabilities: instance.promptCapabilities ?? {} },
19243
19395
  "Agent prompt capabilities"
19244
19396
  );
@@ -19247,14 +19399,14 @@ var init_agent_instance = __esm({
19247
19399
  setupCrashDetection() {
19248
19400
  this.child.on("exit", (code, signal) => {
19249
19401
  if (this._destroying) return;
19250
- log30.info(
19402
+ log31.info(
19251
19403
  { sessionId: this.sessionId, exitCode: code, signal },
19252
19404
  "Agent process exited"
19253
19405
  );
19254
19406
  if (signal === "SIGINT" || signal === "SIGTERM") return;
19255
19407
  if (code !== 0 && code !== null || signal) {
19256
19408
  const stderr = this.stderrCapture.getLastLines();
19257
- this.emit("agent_event", {
19409
+ this.emit(SessionEv.AGENT_EVENT, {
19258
19410
  type: "error",
19259
19411
  message: signal ? `Agent killed by signal ${signal}
19260
19412
  ${stderr}` : `Agent crashed (exit code ${code})
@@ -19263,30 +19415,31 @@ ${stderr}`
19263
19415
  }
19264
19416
  });
19265
19417
  this.connection.closed.then(() => {
19266
- log30.debug({ sessionId: this.sessionId }, "ACP connection closed");
19418
+ log31.debug({ sessionId: this.sessionId }, "ACP connection closed");
19267
19419
  });
19268
19420
  }
19269
- static async spawn(agentDef, workingDirectory, mcpServers) {
19270
- log30.debug(
19421
+ static async spawn(agentDef, workingDirectory, mcpServers, allowedPaths) {
19422
+ log31.debug(
19271
19423
  { agentName: agentDef.name, command: agentDef.command },
19272
19424
  "Spawning agent"
19273
19425
  );
19274
19426
  const spawnStart = Date.now();
19275
19427
  const instance = await _AgentInstance.spawnSubprocess(
19276
19428
  agentDef,
19277
- workingDirectory
19429
+ workingDirectory,
19430
+ allowedPaths
19278
19431
  );
19279
19432
  const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
19280
19433
  const response = await instance.connection.newSession({
19281
19434
  cwd: workingDirectory,
19282
19435
  mcpServers: resolvedMcp
19283
19436
  });
19284
- log30.info(response, "newSession response");
19437
+ log31.info(response, "newSession response");
19285
19438
  instance.sessionId = response.sessionId;
19286
19439
  instance.initialSessionResponse = response;
19287
19440
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
19288
19441
  instance.setupCrashDetection();
19289
- log30.info(
19442
+ log31.info(
19290
19443
  {
19291
19444
  sessionId: response.sessionId,
19292
19445
  durationMs: Date.now() - spawnStart,
@@ -19297,12 +19450,13 @@ ${stderr}`
19297
19450
  );
19298
19451
  return instance;
19299
19452
  }
19300
- static async resume(agentDef, workingDirectory, agentSessionId, mcpServers) {
19301
- log30.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
19453
+ static async resume(agentDef, workingDirectory, agentSessionId, mcpServers, allowedPaths) {
19454
+ log31.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
19302
19455
  const spawnStart = Date.now();
19303
19456
  const instance = await _AgentInstance.spawnSubprocess(
19304
19457
  agentDef,
19305
- workingDirectory
19458
+ workingDirectory,
19459
+ allowedPaths
19306
19460
  );
19307
19461
  const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
19308
19462
  try {
@@ -19315,7 +19469,7 @@ ${stderr}`
19315
19469
  instance.sessionId = agentSessionId;
19316
19470
  instance.initialSessionResponse = response;
19317
19471
  instance.debugTracer = createDebugTracer(agentSessionId, workingDirectory);
19318
- log30.info(
19472
+ log31.info(
19319
19473
  {
19320
19474
  sessionId: agentSessionId,
19321
19475
  durationMs: Date.now() - spawnStart,
@@ -19331,7 +19485,7 @@ ${stderr}`
19331
19485
  instance.sessionId = response.sessionId;
19332
19486
  instance.initialSessionResponse = response;
19333
19487
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
19334
- log30.info(
19488
+ log31.info(
19335
19489
  {
19336
19490
  sessionId: response.sessionId,
19337
19491
  durationMs: Date.now() - spawnStart,
@@ -19341,7 +19495,7 @@ ${stderr}`
19341
19495
  );
19342
19496
  }
19343
19497
  } catch (err) {
19344
- log30.warn(
19498
+ log31.warn(
19345
19499
  { err, agentSessionId },
19346
19500
  "Resume failed, falling back to new session"
19347
19501
  );
@@ -19352,7 +19506,7 @@ ${stderr}`
19352
19506
  instance.sessionId = response.sessionId;
19353
19507
  instance.initialSessionResponse = response;
19354
19508
  instance.debugTracer = createDebugTracer(response.sessionId, workingDirectory);
19355
- log30.info(
19509
+ log31.info(
19356
19510
  { sessionId: response.sessionId, durationMs: Date.now() - spawnStart },
19357
19511
  "Agent fallback spawn complete"
19358
19512
  );
@@ -19490,7 +19644,7 @@ ${stderr}`
19490
19644
  return;
19491
19645
  }
19492
19646
  if (event !== null) {
19493
- self.emit("agent_event", event);
19647
+ self.emit(SessionEv.AGENT_EVENT, event);
19494
19648
  }
19495
19649
  },
19496
19650
  // ── Permission requests ──────────────────────────────────────────────
@@ -19517,7 +19671,7 @@ ${stderr}`
19517
19671
  throw new Error(`[Access denied] ${pathCheck.reason}`);
19518
19672
  }
19519
19673
  if (self.middlewareChain) {
19520
- const result = await self.middlewareChain.execute("fs:beforeRead", { sessionId: self.sessionId, path: p2.path, line: p2.line, limit: p2.limit }, async (r) => r);
19674
+ const result = await self.middlewareChain.execute(Hook.FS_BEFORE_READ, { sessionId: self.sessionId, path: p2.path, line: p2.line, limit: p2.limit }, async (r) => r);
19521
19675
  if (!result) return { content: "" };
19522
19676
  p2.path = result.path;
19523
19677
  }
@@ -19535,7 +19689,7 @@ ${stderr}`
19535
19689
  throw new Error(`[Access denied] ${pathCheck.reason}`);
19536
19690
  }
19537
19691
  if (self.middlewareChain) {
19538
- const result = await self.middlewareChain.execute("fs:beforeWrite", { sessionId: self.sessionId, path: writePath, content: writeContent }, async (r) => r);
19692
+ const result = await self.middlewareChain.execute(Hook.FS_BEFORE_WRITE, { sessionId: self.sessionId, path: writePath, content: writeContent }, async (r) => r);
19539
19693
  if (!result) return {};
19540
19694
  writePath = result.path;
19541
19695
  writeContent = result.content;
@@ -19632,10 +19786,16 @@ ${stderr}`
19632
19786
  // ── Prompt & lifecycle ──────────────────────────────────────────────
19633
19787
  async prompt(text6, attachments) {
19634
19788
  const contentBlocks = [{ type: "text", text: text6 }];
19635
- const SUPPORTED_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
19789
+ const capabilities = this.promptCapabilities ?? {};
19636
19790
  for (const att of attachments ?? []) {
19637
- const tooLarge = att.size > 10 * 1024 * 1024;
19638
- if (att.type === "image" && this.promptCapabilities?.image && !tooLarge && SUPPORTED_IMAGE_MIMES.has(att.mimeType)) {
19791
+ const skipNote = buildAttachmentNote(att, capabilities);
19792
+ if (skipNote !== null) {
19793
+ contentBlocks[0].text += `
19794
+
19795
+ ${skipNote}`;
19796
+ continue;
19797
+ }
19798
+ if (att.type === "image" && capabilities.image && SUPPORTED_IMAGE_MIMES.has(att.mimeType)) {
19639
19799
  const attCheck = this.pathGuard.validatePath(att.filePath, "read");
19640
19800
  if (!attCheck.allowed) {
19641
19801
  contentBlocks[0].text += `
@@ -19645,7 +19805,7 @@ ${stderr}`
19645
19805
  }
19646
19806
  const data = await fs40.promises.readFile(att.filePath);
19647
19807
  contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
19648
- } else if (att.type === "audio" && this.promptCapabilities?.audio && !tooLarge) {
19808
+ } else if (att.type === "audio" && capabilities.audio) {
19649
19809
  const attCheck = this.pathGuard.validatePath(att.filePath, "read");
19650
19810
  if (!attCheck.allowed) {
19651
19811
  contentBlocks[0].text += `
@@ -19656,9 +19816,9 @@ ${stderr}`
19656
19816
  const data = await fs40.promises.readFile(att.filePath);
19657
19817
  contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
19658
19818
  } else {
19659
- if ((att.type === "image" || att.type === "audio") && !tooLarge) {
19660
- log30.debug(
19661
- { type: att.type, capabilities: this.promptCapabilities ?? {} },
19819
+ if (att.type === "image" || att.type === "audio") {
19820
+ log31.debug(
19821
+ { type: att.type, capabilities },
19662
19822
  "Agent does not support %s content, falling back to file path",
19663
19823
  att.type
19664
19824
  );
@@ -19721,15 +19881,15 @@ var init_agent_manager = __esm({
19721
19881
  getAgent(name) {
19722
19882
  return this.catalog.resolve(name);
19723
19883
  }
19724
- async spawn(agentName, workingDirectory) {
19884
+ async spawn(agentName, workingDirectory, allowedPaths) {
19725
19885
  const agentDef = this.getAgent(agentName);
19726
19886
  if (!agentDef) throw new Error(`Agent "${agentName}" is not installed. Run "openacp agents install ${agentName}" to add it.`);
19727
- return AgentInstance.spawn(agentDef, workingDirectory);
19887
+ return AgentInstance.spawn(agentDef, workingDirectory, void 0, allowedPaths);
19728
19888
  }
19729
- async resume(agentName, workingDirectory, agentSessionId) {
19889
+ async resume(agentName, workingDirectory, agentSessionId, allowedPaths) {
19730
19890
  const agentDef = this.getAgent(agentName);
19731
19891
  if (!agentDef) throw new Error(`Agent "${agentName}" is not installed. Run "openacp agents install ${agentName}" to add it.`);
19732
- return AgentInstance.resume(agentDef, workingDirectory, agentSessionId);
19892
+ return AgentInstance.resume(agentDef, workingDirectory, agentSessionId, void 0, allowedPaths);
19733
19893
  }
19734
19894
  };
19735
19895
  }
@@ -19926,6 +20086,7 @@ var init_session2 = __esm({
19926
20086
  init_permission_gate();
19927
20087
  init_log();
19928
20088
  init_turn_context();
20089
+ init_events();
19929
20090
  moduleLog = createChildLogger({ module: "session" });
19930
20091
  TTS_PROMPT_INSTRUCTION = `
19931
20092
 
@@ -19954,7 +20115,15 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
19954
20115
  }
19955
20116
  agentName;
19956
20117
  workingDirectory;
19957
- agentInstance;
20118
+ _agentInstance;
20119
+ get agentInstance() {
20120
+ return this._agentInstance;
20121
+ }
20122
+ set agentInstance(agent) {
20123
+ this._agentInstance = agent;
20124
+ this.wireAgentRelay();
20125
+ this.wireCommandsBuffer();
20126
+ }
19958
20127
  agentSessionId = "";
19959
20128
  _status = "initializing";
19960
20129
  name;
@@ -19978,9 +20147,6 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
19978
20147
  threadIds = /* @__PURE__ */ new Map();
19979
20148
  /** Active turn context — sealed on prompt dequeue, cleared on turn end */
19980
20149
  activeTurnContext = null;
19981
- /** The agentInstance for which the agent→session event relay is wired (prevents duplicate relays from multiple bridges).
19982
- * When the agent is swapped, the relay must be re-wired to the new instance. */
19983
- agentRelaySource = null;
19984
20150
  permissionGate = new PermissionGate();
19985
20151
  queue;
19986
20152
  speechService;
@@ -20004,23 +20170,37 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20004
20170
  this.log.error({ err }, "Prompt execution failed");
20005
20171
  const message = err instanceof Error ? err.message : String(err);
20006
20172
  this.fail(message);
20007
- this.emit("agent_event", { type: "error", message: `Prompt execution failed: ${message}` });
20173
+ this.emit(SessionEv.AGENT_EVENT, { type: "error", message: `Prompt execution failed: ${message}` });
20008
20174
  }
20009
20175
  );
20010
- this.wireCommandsBuffer();
20176
+ }
20177
+ /** Wire the agent→session event relay on the current agentInstance.
20178
+ * Removes any previous relay first to avoid duplicates on agent switch.
20179
+ * This relay ensures session.emit("agent_event") fires for ALL sessions,
20180
+ * including headless API sessions that have no SessionBridge attached. */
20181
+ agentRelayCleanup;
20182
+ wireAgentRelay() {
20183
+ this.agentRelayCleanup?.();
20184
+ const instance = this._agentInstance;
20185
+ const handler = (event) => {
20186
+ this.emit(SessionEv.AGENT_EVENT, event);
20187
+ };
20188
+ instance.on(SessionEv.AGENT_EVENT, handler);
20189
+ this.agentRelayCleanup = () => instance.off(SessionEv.AGENT_EVENT, handler);
20011
20190
  }
20012
20191
  /** Wire a listener on the current agentInstance to buffer commands_update events.
20013
20192
  * Must be called after every agentInstance replacement (constructor + switchAgent). */
20014
20193
  commandsBufferCleanup;
20015
20194
  wireCommandsBuffer() {
20016
20195
  this.commandsBufferCleanup?.();
20196
+ const instance = this._agentInstance;
20017
20197
  const handler = (event) => {
20018
20198
  if (event.type === "commands_update") {
20019
20199
  this.latestCommands = event.commands;
20020
20200
  }
20021
20201
  };
20022
- this.agentInstance.on("agent_event", handler);
20023
- this.commandsBufferCleanup = () => this.agentInstance.off("agent_event", handler);
20202
+ instance.on(SessionEv.AGENT_EVENT, handler);
20203
+ this.commandsBufferCleanup = () => instance.off(SessionEv.AGENT_EVENT, handler);
20024
20204
  }
20025
20205
  // --- State Machine ---
20026
20206
  get status() {
@@ -20034,12 +20214,12 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20034
20214
  fail(reason) {
20035
20215
  if (this._status === "error") return;
20036
20216
  this.transition("error");
20037
- this.emit("error", new Error(reason));
20217
+ this.emit(SessionEv.ERROR, new Error(reason));
20038
20218
  }
20039
20219
  /** Transition to finished — from active only. Emits session_end for backward compat. */
20040
20220
  finish(reason) {
20041
20221
  this.transition("finished");
20042
- this.emit("session_end", reason ?? "completed");
20222
+ this.emit(SessionEv.SESSION_END, reason ?? "completed");
20043
20223
  }
20044
20224
  /** Transition to cancelled — from active only (terminal session cancel) */
20045
20225
  markCancelled() {
@@ -20055,7 +20235,7 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20055
20235
  }
20056
20236
  this._status = to;
20057
20237
  this.log.debug({ from, to }, "Session status transition");
20058
- this.emit("status_change", from, to);
20238
+ this.emit(SessionEv.STATUS_CHANGE, from, to);
20059
20239
  }
20060
20240
  /** Number of prompts waiting in queue */
20061
20241
  get queueDepth() {
@@ -20077,8 +20257,8 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20077
20257
  async enqueuePrompt(text6, attachments, routing, externalTurnId) {
20078
20258
  const turnId = externalTurnId ?? nanoid4(8);
20079
20259
  if (this.middlewareChain) {
20080
- const payload = { text: text6, attachments, sessionId: this.id };
20081
- const result = await this.middlewareChain.execute("agent:beforePrompt", payload, async (p2) => p2);
20260
+ const payload = { text: text6, attachments, sessionId: this.id, sourceAdapterId: routing?.sourceAdapterId };
20261
+ const result = await this.middlewareChain.execute(Hook.AGENT_BEFORE_PROMPT, payload, async (p2) => p2);
20082
20262
  if (!result) return turnId;
20083
20263
  text6 = result.text;
20084
20264
  attachments = result.attachments;
@@ -20093,9 +20273,9 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20093
20273
  routing?.responseAdapterId,
20094
20274
  turnId
20095
20275
  );
20096
- this.emit("turn_started", this.activeTurnContext);
20276
+ this.emit(SessionEv.TURN_STARTED, this.activeTurnContext);
20097
20277
  this.promptCount++;
20098
- this.emit("prompt_count_changed", this.promptCount);
20278
+ this.emit(SessionEv.PROMPT_COUNT_CHANGED, this.promptCount);
20099
20279
  if (this._status === "initializing" || this._status === "cancelled" || this._status === "error") {
20100
20280
  this.activate();
20101
20281
  }
@@ -20124,10 +20304,18 @@ ${text6}`;
20124
20304
  }
20125
20305
  } : null;
20126
20306
  if (accumulatorListener) {
20127
- this.on("agent_event", accumulatorListener);
20307
+ this.on(SessionEv.AGENT_EVENT, accumulatorListener);
20308
+ }
20309
+ const mw = this.middlewareChain;
20310
+ const afterEventListener = mw ? (event) => {
20311
+ mw.execute(Hook.AGENT_AFTER_EVENT, { sessionId: this.id, event, outgoingMessage: { type: "text", text: "" } }, async (e) => e).catch(() => {
20312
+ });
20313
+ } : null;
20314
+ if (afterEventListener) {
20315
+ this.agentInstance.on(SessionEv.AGENT_EVENT, afterEventListener);
20128
20316
  }
20129
20317
  if (this.middlewareChain) {
20130
- this.middlewareChain.execute("turn:start", { sessionId: this.id, promptText: processed.text, promptNumber: this.promptCount }, async (p2) => p2).catch(() => {
20318
+ this.middlewareChain.execute(Hook.TURN_START, { sessionId: this.id, promptText: processed.text, promptNumber: this.promptCount }, async (p2) => p2).catch(() => {
20131
20319
  });
20132
20320
  }
20133
20321
  let stopReason = "end_turn";
@@ -20148,10 +20336,13 @@ ${text6}`;
20148
20336
  promptError = err;
20149
20337
  } finally {
20150
20338
  if (accumulatorListener) {
20151
- this.off("agent_event", accumulatorListener);
20339
+ this.off(SessionEv.AGENT_EVENT, accumulatorListener);
20340
+ }
20341
+ if (afterEventListener) {
20342
+ this.agentInstance.off(SessionEv.AGENT_EVENT, afterEventListener);
20152
20343
  }
20153
20344
  if (this.middlewareChain) {
20154
- this.middlewareChain.execute("turn:end", { sessionId: this.id, stopReason, durationMs: Date.now() - promptStart }, async (p2) => p2).catch(() => {
20345
+ this.middlewareChain.execute(Hook.TURN_END, { sessionId: this.id, stopReason, durationMs: Date.now() - promptStart }, async (p2) => p2).catch(() => {
20155
20346
  });
20156
20347
  }
20157
20348
  this.activeTurnContext = null;
@@ -20196,7 +20387,7 @@ ${text6}`;
20196
20387
  const audioBuffer = await fs41.promises.readFile(audioPath);
20197
20388
  const result = await this.speechService.transcribe(audioBuffer, audioMime);
20198
20389
  this.log.info({ provider: "stt", duration: result.duration }, "Voice transcribed");
20199
- this.emit("agent_event", {
20390
+ this.emit(SessionEv.AGENT_EVENT, {
20200
20391
  type: "system_message",
20201
20392
  message: `\u{1F3A4} You said: ${result.text}`
20202
20393
  });
@@ -20205,7 +20396,7 @@ ${text6}`;
20205
20396
  ${result.text}` : result.text;
20206
20397
  } catch (err) {
20207
20398
  this.log.warn({ err }, "STT transcription failed, keeping audio attachment");
20208
- this.emit("agent_event", {
20399
+ this.emit(SessionEv.AGENT_EVENT, {
20209
20400
  type: "error",
20210
20401
  message: `Voice transcription failed: ${err.message}`
20211
20402
  });
@@ -20239,12 +20430,12 @@ ${result.text}` : result.text;
20239
20430
  timeoutPromise
20240
20431
  ]);
20241
20432
  const base64 = result.audioBuffer.toString("base64");
20242
- this.emit("agent_event", {
20433
+ this.emit(SessionEv.AGENT_EVENT, {
20243
20434
  type: "audio_content",
20244
20435
  data: base64,
20245
20436
  mimeType: result.mimeType
20246
20437
  });
20247
- this.emit("agent_event", { type: "tts_strip" });
20438
+ this.emit(SessionEv.AGENT_EVENT, { type: "tts_strip" });
20248
20439
  this.log.info("TTS synthesis completed");
20249
20440
  } finally {
20250
20441
  clearTimeout(ttsTimer);
@@ -20259,19 +20450,19 @@ ${result.text}` : result.text;
20259
20450
  const captureHandler = (event) => {
20260
20451
  if (event.type === "text") title += event.content;
20261
20452
  };
20262
- this.pause((event) => event !== "agent_event");
20263
- this.agentInstance.on("agent_event", captureHandler);
20453
+ this.pause((event) => event !== SessionEv.AGENT_EVENT);
20454
+ this.agentInstance.on(SessionEv.AGENT_EVENT, captureHandler);
20264
20455
  try {
20265
20456
  await this.agentInstance.prompt(
20266
20457
  "Summarize this conversation in max 5 words for a topic title. Reply ONLY with the title, nothing else."
20267
20458
  );
20268
20459
  this.name = title.trim().slice(0, 50) || `Session ${this.id.slice(0, 6)}`;
20269
20460
  this.log.info({ name: this.name }, "Session auto-named");
20270
- this.emit("named", this.name);
20461
+ this.emit(SessionEv.NAMED, this.name);
20271
20462
  } catch {
20272
20463
  this.name = `Session ${this.id.slice(0, 6)}`;
20273
20464
  } finally {
20274
- this.agentInstance.off("agent_event", captureHandler);
20465
+ this.agentInstance.off(SessionEv.AGENT_EVENT, captureHandler);
20275
20466
  this.clearBuffer();
20276
20467
  this.resume();
20277
20468
  }
@@ -20333,7 +20524,7 @@ ${result.text}` : result.text;
20333
20524
  /** Set session name explicitly and emit 'named' event */
20334
20525
  setName(name) {
20335
20526
  this.name = name;
20336
- this.emit("named", name);
20527
+ this.emit(SessionEv.NAMED, name);
20337
20528
  }
20338
20529
  /** Send a config option change to the agent and update local state from the response. */
20339
20530
  async setConfigOption(configId, value) {
@@ -20349,7 +20540,7 @@ ${result.text}` : result.text;
20349
20540
  }
20350
20541
  async updateConfigOptions(options) {
20351
20542
  if (this.middlewareChain) {
20352
- const result = await this.middlewareChain.execute("config:beforeChange", { sessionId: this.id, configId: "options", oldValue: this.configOptions, newValue: options }, async (p2) => p2);
20543
+ const result = await this.middlewareChain.execute(Hook.CONFIG_BEFORE_CHANGE, { sessionId: this.id, configId: "options", oldValue: this.configOptions, newValue: options }, async (p2) => p2);
20353
20544
  if (!result) return;
20354
20545
  }
20355
20546
  this.configOptions = options;
@@ -20369,7 +20560,7 @@ ${result.text}` : result.text;
20369
20560
  /** Cancel the current prompt and clear the queue. Stays in active state. */
20370
20561
  async abortPrompt() {
20371
20562
  if (this.middlewareChain) {
20372
- const result = await this.middlewareChain.execute("agent:beforeCancel", { sessionId: this.id }, async (p2) => p2);
20563
+ const result = await this.middlewareChain.execute(Hook.AGENT_BEFORE_CANCEL, { sessionId: this.id }, async (p2) => p2);
20373
20564
  if (!result) return;
20374
20565
  }
20375
20566
  this.queue.clear();
@@ -20410,7 +20601,6 @@ ${result.text}` : result.text;
20410
20601
  this.configOptions = [];
20411
20602
  this.latestCommands = null;
20412
20603
  this.applySpawnResponse(newAgent.initialSessionResponse, newAgent.agentCapabilities);
20413
- this.wireCommandsBuffer();
20414
20604
  this.log.info({ from: this.agentSwitchHistory.at(-1).agentName, to: agentName }, "Agent switched");
20415
20605
  }
20416
20606
  async destroy() {
@@ -20432,6 +20622,7 @@ var init_session_manager = __esm({
20432
20622
  "src/core/sessions/session-manager.ts"() {
20433
20623
  "use strict";
20434
20624
  init_session2();
20625
+ init_events();
20435
20626
  SessionManager = class {
20436
20627
  sessions = /* @__PURE__ */ new Map();
20437
20628
  store;
@@ -20536,18 +20727,18 @@ var init_session_manager = __esm({
20536
20727
  }
20537
20728
  }
20538
20729
  if (this.middlewareChain) {
20539
- this.middlewareChain.execute("session:afterDestroy", { sessionId }, async (p2) => p2).catch(() => {
20730
+ this.middlewareChain.execute(Hook.SESSION_AFTER_DESTROY, { sessionId }, async (p2) => p2).catch(() => {
20540
20731
  });
20541
20732
  }
20542
20733
  }
20543
20734
  listSessions(channelId) {
20544
- const all = Array.from(this.sessions.values());
20735
+ const all = Array.from(this.sessions.values()).filter((s) => !s.isAssistant);
20545
20736
  if (channelId) return all.filter((s) => s.channelId === channelId);
20546
20737
  return all;
20547
20738
  }
20548
20739
  listAllSessions(channelId) {
20549
20740
  if (this.store) {
20550
- let records = this.store.list();
20741
+ let records = this.store.list().filter((r) => !r.isAssistant);
20551
20742
  if (channelId) records = records.filter((r) => r.channelId === channelId);
20552
20743
  return records.map((record) => {
20553
20744
  const live2 = this.sessions.get(record.sessionId);
@@ -20587,7 +20778,7 @@ var init_session_manager = __esm({
20587
20778
  };
20588
20779
  });
20589
20780
  }
20590
- let live = Array.from(this.sessions.values());
20781
+ let live = Array.from(this.sessions.values()).filter((s) => !s.isAssistant);
20591
20782
  if (channelId) live = live.filter((s) => s.channelId === channelId);
20592
20783
  return live.map((s) => ({
20593
20784
  id: s.id,
@@ -20608,7 +20799,7 @@ var init_session_manager = __esm({
20608
20799
  }
20609
20800
  listRecords(filter) {
20610
20801
  if (!this.store) return [];
20611
- let records = this.store.list();
20802
+ let records = this.store.list().filter((r) => !r.isAssistant);
20612
20803
  if (filter?.statuses?.length) {
20613
20804
  records = records.filter((r) => filter.statuses.includes(r.status));
20614
20805
  }
@@ -20617,7 +20808,7 @@ var init_session_manager = __esm({
20617
20808
  async removeRecord(sessionId) {
20618
20809
  if (!this.store) return;
20619
20810
  await this.store.remove(sessionId);
20620
- this.eventBus?.emit("session:deleted", { sessionId });
20811
+ this.eventBus?.emit(BusEvent.SESSION_DELETED, { sessionId });
20621
20812
  }
20622
20813
  /**
20623
20814
  * Graceful shutdown: persist session state without killing agent subprocesses.
@@ -20665,7 +20856,7 @@ var init_session_manager = __esm({
20665
20856
  this.sessions.clear();
20666
20857
  if (this.middlewareChain) {
20667
20858
  for (const sessionId of sessionIds) {
20668
- this.middlewareChain.execute("session:afterDestroy", { sessionId }, async (p2) => p2).catch(() => {
20859
+ this.middlewareChain.execute(Hook.SESSION_AFTER_DESTROY, { sessionId }, async (p2) => p2).catch(() => {
20669
20860
  });
20670
20861
  }
20671
20862
  }
@@ -20675,14 +20866,15 @@ var init_session_manager = __esm({
20675
20866
  });
20676
20867
 
20677
20868
  // src/core/sessions/session-bridge.ts
20678
- var log31, SessionBridge;
20869
+ var log32, SessionBridge;
20679
20870
  var init_session_bridge = __esm({
20680
20871
  "src/core/sessions/session-bridge.ts"() {
20681
20872
  "use strict";
20682
20873
  init_log();
20683
20874
  init_bypass_detection();
20684
20875
  init_turn_context();
20685
- log31 = createChildLogger({ module: "session-bridge" });
20876
+ init_events();
20877
+ log32 = createChildLogger({ module: "session-bridge" });
20686
20878
  SessionBridge = class {
20687
20879
  constructor(session, adapter, deps, adapterId) {
20688
20880
  this.session = session;
@@ -20706,21 +20898,21 @@ var init_session_bridge = __esm({
20706
20898
  try {
20707
20899
  const mw = this.deps.middlewareChain;
20708
20900
  if (mw) {
20709
- const result = await mw.execute("message:outgoing", { sessionId, message }, async (m) => m);
20901
+ const result = await mw.execute(Hook.MESSAGE_OUTGOING, { sessionId, message }, async (m) => m);
20710
20902
  this.tracer?.log("core", { step: "middleware:outgoing", sessionId, hook: "message:outgoing", blocked: !result });
20711
20903
  if (!result) return;
20712
20904
  this.tracer?.log("core", { step: "dispatch", sessionId, message: result.message });
20713
20905
  this.adapter.sendMessage(sessionId, result.message).catch((err) => {
20714
- log31.error({ err, sessionId }, "Failed to send message to adapter");
20906
+ log32.error({ err, sessionId }, "Failed to send message to adapter");
20715
20907
  });
20716
20908
  } else {
20717
20909
  this.tracer?.log("core", { step: "dispatch", sessionId, message });
20718
20910
  this.adapter.sendMessage(sessionId, message).catch((err) => {
20719
- log31.error({ err, sessionId }, "Failed to send message to adapter");
20911
+ log32.error({ err, sessionId }, "Failed to send message to adapter");
20720
20912
  });
20721
20913
  }
20722
20914
  } catch (err) {
20723
- log31.error({ err, sessionId }, "Error in sendMessage middleware");
20915
+ log32.error({ err, sessionId }, "Error in sendMessage middleware");
20724
20916
  }
20725
20917
  }
20726
20918
  /** Determine if this bridge should forward the given event based on turn routing. */
@@ -20735,17 +20927,11 @@ var init_session_bridge = __esm({
20735
20927
  connect() {
20736
20928
  if (this.connected) return;
20737
20929
  this.connected = true;
20738
- if (this.session.agentRelaySource !== this.session.agentInstance) {
20739
- this.listen(this.session.agentInstance, "agent_event", (event) => {
20740
- this.session.emit("agent_event", event);
20741
- });
20742
- this.session.agentRelaySource = this.session.agentInstance;
20743
- }
20744
- this.listen(this.session, "agent_event", (event) => {
20930
+ this.listen(this.session, SessionEv.AGENT_EVENT, (event) => {
20745
20931
  if (this.shouldForward(event)) {
20746
20932
  this.dispatchAgentEvent(event);
20747
20933
  } else {
20748
- this.deps.eventBus?.emit("agent:event", { sessionId: this.session.id, event });
20934
+ this.deps.eventBus?.emit(BusEvent.AGENT_EVENT, { sessionId: this.session.id, event });
20749
20935
  }
20750
20936
  });
20751
20937
  if (!this.session.agentInstance.onPermissionRequest || this.session.agentInstance.onPermissionRequest.__bridgeId === void 0) {
@@ -20755,47 +20941,51 @@ var init_session_bridge = __esm({
20755
20941
  handler.__bridgeId = this.adapterId;
20756
20942
  this.session.agentInstance.onPermissionRequest = handler;
20757
20943
  }
20758
- this.listen(this.session, "permission_request", async (request) => {
20944
+ this.listen(this.session, SessionEv.PERMISSION_REQUEST, async (request) => {
20759
20945
  const current = this.session.agentInstance.onPermissionRequest;
20760
20946
  if (current?.__bridgeId === this.adapterId) return;
20761
20947
  if (!this.session.permissionGate.isPending) return;
20762
20948
  try {
20763
20949
  await this.adapter.sendPermissionRequest(this.session.id, request);
20764
20950
  } catch (err) {
20765
- log31.error({ err, sessionId: this.session.id, adapterId: this.adapterId }, "Failed to send permission request to adapter");
20951
+ log32.error({ err, sessionId: this.session.id, adapterId: this.adapterId }, "Failed to send permission request to adapter");
20766
20952
  }
20767
20953
  });
20768
- this.listen(this.session, "status_change", (from, to) => {
20954
+ this.listen(this.session, SessionEv.STATUS_CHANGE, (from, to) => {
20769
20955
  this.deps.sessionManager.patchRecord(this.session.id, {
20770
20956
  status: to,
20771
20957
  lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
20772
20958
  });
20773
- this.deps.eventBus?.emit("session:updated", {
20774
- sessionId: this.session.id,
20775
- status: to
20776
- });
20959
+ if (!this.session.isAssistant) {
20960
+ this.deps.eventBus?.emit(BusEvent.SESSION_UPDATED, {
20961
+ sessionId: this.session.id,
20962
+ status: to
20963
+ });
20964
+ }
20777
20965
  if (to === "finished") {
20778
20966
  queueMicrotask(() => this.disconnect());
20779
20967
  }
20780
20968
  });
20781
- this.listen(this.session, "named", async (name) => {
20969
+ this.listen(this.session, SessionEv.NAMED, async (name) => {
20782
20970
  const record = this.deps.sessionManager.getSessionRecord(this.session.id);
20783
20971
  const alreadyNamed = !!record?.name;
20784
20972
  await this.deps.sessionManager.patchRecord(this.session.id, { name });
20785
- this.deps.eventBus?.emit("session:updated", {
20786
- sessionId: this.session.id,
20787
- name
20788
- });
20973
+ if (!this.session.isAssistant) {
20974
+ this.deps.eventBus?.emit(BusEvent.SESSION_UPDATED, {
20975
+ sessionId: this.session.id,
20976
+ name
20977
+ });
20978
+ }
20789
20979
  if (!alreadyNamed) {
20790
20980
  await this.adapter.renameSessionThread(this.session.id, name);
20791
20981
  }
20792
20982
  });
20793
- this.listen(this.session, "prompt_count_changed", (count) => {
20983
+ this.listen(this.session, SessionEv.PROMPT_COUNT_CHANGED, (count) => {
20794
20984
  this.deps.sessionManager.patchRecord(this.session.id, { currentPromptCount: count });
20795
20985
  });
20796
- this.listen(this.session, "turn_started", (ctx) => {
20986
+ this.listen(this.session, SessionEv.TURN_STARTED, (ctx) => {
20797
20987
  if (ctx.sourceAdapterId !== "sse" && ctx.sourceAdapterId !== "api") {
20798
- this.deps.eventBus?.emit("message:processing", {
20988
+ this.deps.eventBus?.emit(BusEvent.MESSAGE_PROCESSING, {
20799
20989
  sessionId: this.session.id,
20800
20990
  turnId: ctx.turnId,
20801
20991
  sourceAdapterId: ctx.sourceAdapterId,
@@ -20804,10 +20994,10 @@ var init_session_bridge = __esm({
20804
20994
  }
20805
20995
  });
20806
20996
  if (this.session.latestCommands !== null) {
20807
- this.session.emit("agent_event", { type: "commands_update", commands: this.session.latestCommands });
20997
+ this.session.emit(SessionEv.AGENT_EVENT, { type: "commands_update", commands: this.session.latestCommands });
20808
20998
  }
20809
20999
  if (this.session.configOptions.length > 0) {
20810
- this.session.emit("agent_event", { type: "config_option_update", options: this.session.configOptions });
21000
+ this.session.emit(SessionEv.AGENT_EVENT, { type: "config_option_update", options: this.session.configOptions });
20811
21001
  }
20812
21002
  }
20813
21003
  disconnect() {
@@ -20827,29 +21017,23 @@ var init_session_bridge = __esm({
20827
21017
  const mw = this.deps.middlewareChain;
20828
21018
  if (mw) {
20829
21019
  try {
20830
- const result = await mw.execute("agent:beforeEvent", { sessionId: this.session.id, event }, async (e) => e);
21020
+ const result = await mw.execute(Hook.AGENT_BEFORE_EVENT, { sessionId: this.session.id, event }, async (e) => e);
20831
21021
  this.tracer?.log("core", { step: "middleware:before", sessionId: this.session.id, hook: "agent:beforeEvent", blocked: !result });
20832
21022
  if (!result) return;
20833
21023
  const transformedEvent = result.event;
20834
- const outgoing = this.handleAgentEvent(transformedEvent);
20835
- mw.execute("agent:afterEvent", {
20836
- sessionId: this.session.id,
20837
- event: transformedEvent,
20838
- outgoingMessage: outgoing ?? { type: "text", text: "" }
20839
- }, async (e) => e).catch(() => {
20840
- });
21024
+ this.handleAgentEvent(transformedEvent);
20841
21025
  } catch {
20842
21026
  try {
20843
21027
  this.handleAgentEvent(event);
20844
21028
  } catch (err) {
20845
- log31.error({ err, sessionId: this.session.id }, "Error handling agent event (middleware fallback)");
21029
+ log32.error({ err, sessionId: this.session.id }, "Error handling agent event (middleware fallback)");
20846
21030
  }
20847
21031
  }
20848
21032
  } else {
20849
21033
  try {
20850
21034
  this.handleAgentEvent(event);
20851
21035
  } catch (err) {
20852
- log31.error({ err, sessionId: this.session.id }, "Error handling agent event");
21036
+ log32.error({ err, sessionId: this.session.id }, "Error handling agent event");
20853
21037
  }
20854
21038
  }
20855
21039
  }
@@ -20913,7 +21097,7 @@ var init_session_bridge = __esm({
20913
21097
  text: "",
20914
21098
  attachment: att
20915
21099
  });
20916
- }).catch((err) => log31.error({ err }, "Failed to save agent image"));
21100
+ }).catch((err) => log32.error({ err }, "Failed to save agent image"));
20917
21101
  }
20918
21102
  break;
20919
21103
  }
@@ -20930,12 +21114,12 @@ var init_session_bridge = __esm({
20930
21114
  text: "",
20931
21115
  attachment: att
20932
21116
  });
20933
- }).catch((err) => log31.error({ err }, "Failed to save agent audio"));
21117
+ }).catch((err) => log32.error({ err }, "Failed to save agent audio"));
20934
21118
  }
20935
21119
  break;
20936
21120
  }
20937
21121
  case "commands_update":
20938
- log31.debug({ commands: event.commands }, "Commands available");
21122
+ log32.debug({ commands: event.commands }, "Commands available");
20939
21123
  this.adapter.sendSkillCommands?.(this.session.id, event.commands);
20940
21124
  break;
20941
21125
  case "system_message":
@@ -20970,7 +21154,7 @@ var init_session_bridge = __esm({
20970
21154
  this.adapter.stripTTSBlock?.(this.session.id);
20971
21155
  break;
20972
21156
  }
20973
- this.deps.eventBus?.emit("agent:event", {
21157
+ this.deps.eventBus?.emit(BusEvent.AGENT_EVENT, {
20974
21158
  sessionId: this.session.id,
20975
21159
  event
20976
21160
  });
@@ -20989,7 +21173,7 @@ var init_session_bridge = __esm({
20989
21173
  let permReq = request;
20990
21174
  if (mw) {
20991
21175
  const payload = { sessionId: this.session.id, request, autoResolve: void 0 };
20992
- const result = await mw.execute("permission:beforeRequest", payload, async (r) => r);
21176
+ const result = await mw.execute(Hook.PERMISSION_BEFORE_REQUEST, payload, async (r) => r);
20993
21177
  if (!result) return "";
20994
21178
  permReq = result.request;
20995
21179
  if (result.autoResolve) {
@@ -20997,21 +21181,21 @@ var init_session_bridge = __esm({
20997
21181
  return result.autoResolve;
20998
21182
  }
20999
21183
  }
21000
- this.deps.eventBus?.emit("permission:request", {
21184
+ this.deps.eventBus?.emit(BusEvent.PERMISSION_REQUEST, {
21001
21185
  sessionId: this.session.id,
21002
21186
  permission: permReq
21003
21187
  });
21004
21188
  const autoDecision = this.checkAutoApprove(permReq);
21005
21189
  if (autoDecision) {
21006
- this.session.emit("permission_request", permReq);
21190
+ this.session.emit(SessionEv.PERMISSION_REQUEST, permReq);
21007
21191
  this.emitAfterResolve(mw, permReq.id, autoDecision, "system", startTime);
21008
21192
  return autoDecision;
21009
21193
  }
21010
21194
  const promise = this.session.permissionGate.setPending(permReq);
21011
- this.session.emit("permission_request", permReq);
21195
+ this.session.emit(SessionEv.PERMISSION_REQUEST, permReq);
21012
21196
  await this.adapter.sendPermissionRequest(this.session.id, permReq);
21013
21197
  const optionId = await promise;
21014
- this.deps.eventBus?.emit("permission:resolved", {
21198
+ this.deps.eventBus?.emit(BusEvent.PERMISSION_RESOLVED, {
21015
21199
  sessionId: this.session.id,
21016
21200
  requestId: permReq.id,
21017
21201
  decision: optionId,
@@ -21031,7 +21215,7 @@ var init_session_bridge = __esm({
21031
21215
  if (isAgentBypass || isClientBypass) {
21032
21216
  const allowOption = request.options.find((o) => o.isAllow);
21033
21217
  if (allowOption) {
21034
- log31.info(
21218
+ log32.info(
21035
21219
  { sessionId: this.session.id, requestId: request.id, optionId: allowOption.id, agentBypass: !!isAgentBypass, clientBypass: !!isClientBypass },
21036
21220
  "Bypass mode: auto-approving permission"
21037
21221
  );
@@ -21043,7 +21227,7 @@ var init_session_bridge = __esm({
21043
21227
  /** Emit permission:afterResolve middleware hook (fire-and-forget) */
21044
21228
  emitAfterResolve(mw, requestId, decision, userId, startTime) {
21045
21229
  if (mw) {
21046
- mw.execute("permission:afterResolve", {
21230
+ mw.execute(Hook.PERMISSION_AFTER_RESOLVE, {
21047
21231
  sessionId: this.session.id,
21048
21232
  requestId,
21049
21233
  decision,
@@ -21204,13 +21388,13 @@ function computeLineDiff(oldStr, newStr) {
21204
21388
  removed: Math.max(0, oldLines.length - prefixLen - suffixLen)
21205
21389
  };
21206
21390
  }
21207
- var log32, BINARY_VIEWER_EXTENSIONS, MessageTransformer;
21391
+ var log33, BINARY_VIEWER_EXTENSIONS, MessageTransformer;
21208
21392
  var init_message_transformer = __esm({
21209
21393
  "src/core/message-transformer.ts"() {
21210
21394
  "use strict";
21211
21395
  init_extract_file_info();
21212
21396
  init_log();
21213
- log32 = createChildLogger({ module: "message-transformer" });
21397
+ log33 = createChildLogger({ module: "message-transformer" });
21214
21398
  BINARY_VIEWER_EXTENSIONS = /* @__PURE__ */ new Set([
21215
21399
  ".wav",
21216
21400
  ".ogg",
@@ -21413,14 +21597,14 @@ var init_message_transformer = __esm({
21413
21597
  }
21414
21598
  }
21415
21599
  if (!this.tunnelService || !sessionContext) {
21416
- log32.debug(
21600
+ log33.debug(
21417
21601
  { hasTunnel: !!this.tunnelService, hasCtx: !!sessionContext, kind },
21418
21602
  "enrichWithViewerLinks: skipping (no tunnel or session context)"
21419
21603
  );
21420
21604
  return;
21421
21605
  }
21422
21606
  const name = "name" in event ? event.name || "" : "";
21423
- log32.debug(
21607
+ log33.debug(
21424
21608
  { name, kind, status: event.status, hasContent: !!event.content, hasRawInput: !!event.rawInput },
21425
21609
  "enrichWithViewerLinks: inspecting event"
21426
21610
  );
@@ -21432,7 +21616,7 @@ var init_message_transformer = __esm({
21432
21616
  event.meta
21433
21617
  );
21434
21618
  if (!fileInfo) {
21435
- log32.debug(
21619
+ log33.debug(
21436
21620
  { name, kind, hasContent: !!event.content, hasRawInput: !!event.rawInput, hasMeta: !!event.meta },
21437
21621
  "enrichWithViewerLinks: extractFileInfo returned null"
21438
21622
  );
@@ -21440,15 +21624,15 @@ var init_message_transformer = __esm({
21440
21624
  }
21441
21625
  const fileExt = path45.extname(fileInfo.filePath).toLowerCase();
21442
21626
  if (BINARY_VIEWER_EXTENSIONS.has(fileExt)) {
21443
- log32.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping binary file");
21627
+ log33.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping binary file");
21444
21628
  return;
21445
21629
  }
21446
21630
  const publicUrl = this.tunnelService.getPublicUrl();
21447
21631
  if (publicUrl.startsWith("http://localhost") || publicUrl.startsWith("http://127.0.0.1")) {
21448
- log32.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping (no public tunnel URL)");
21632
+ log33.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping (no public tunnel URL)");
21449
21633
  return;
21450
21634
  }
21451
- log32.info(
21635
+ log33.info(
21452
21636
  {
21453
21637
  name,
21454
21638
  kind,
@@ -21494,12 +21678,12 @@ var init_message_transformer = __esm({
21494
21678
  // src/core/sessions/session-store.ts
21495
21679
  import fs42 from "fs";
21496
21680
  import path46 from "path";
21497
- var log33, DEBOUNCE_MS3, JsonFileSessionStore;
21681
+ var log34, DEBOUNCE_MS3, JsonFileSessionStore;
21498
21682
  var init_session_store = __esm({
21499
21683
  "src/core/sessions/session-store.ts"() {
21500
21684
  "use strict";
21501
21685
  init_log();
21502
- log33 = createChildLogger({ module: "session-store" });
21686
+ log34 = createChildLogger({ module: "session-store" });
21503
21687
  DEBOUNCE_MS3 = 2e3;
21504
21688
  JsonFileSessionStore = class {
21505
21689
  records = /* @__PURE__ */ new Map();
@@ -21551,6 +21735,14 @@ var init_session_store = __esm({
21551
21735
  }
21552
21736
  return void 0;
21553
21737
  }
21738
+ findAssistant(channelId) {
21739
+ for (const record of this.records.values()) {
21740
+ if (record.isAssistant === true && record.channelId === channelId) {
21741
+ return record;
21742
+ }
21743
+ }
21744
+ return void 0;
21745
+ }
21554
21746
  list(channelId) {
21555
21747
  const all = [...this.records.values()];
21556
21748
  if (channelId) return all.filter((r) => r.channelId === channelId);
@@ -21593,7 +21785,7 @@ var init_session_store = __esm({
21593
21785
  fs42.readFileSync(this.filePath, "utf-8")
21594
21786
  );
21595
21787
  if (raw.version !== 1) {
21596
- log33.warn(
21788
+ log34.warn(
21597
21789
  { version: raw.version },
21598
21790
  "Unknown session store version, skipping load"
21599
21791
  );
@@ -21602,9 +21794,9 @@ var init_session_store = __esm({
21602
21794
  for (const [id, record] of Object.entries(raw.sessions)) {
21603
21795
  this.records.set(id, this.migrateRecord(record));
21604
21796
  }
21605
- log33.debug({ count: this.records.size }, "Loaded session records");
21797
+ log34.debug({ count: this.records.size }, "Loaded session records");
21606
21798
  } catch (err) {
21607
- log33.error({ err }, "Failed to load session store, backing up corrupt file");
21799
+ log34.error({ err }, "Failed to load session store, backing up corrupt file");
21608
21800
  try {
21609
21801
  fs42.renameSync(this.filePath, `${this.filePath}.bak`);
21610
21802
  } catch {
@@ -21630,6 +21822,8 @@ var init_session_store = __esm({
21630
21822
  for (const [id, record] of this.records) {
21631
21823
  if (record.status === "active" || record.status === "initializing")
21632
21824
  continue;
21825
+ if (record.isAssistant === true)
21826
+ continue;
21633
21827
  const raw = record.lastActiveAt;
21634
21828
  if (!raw) continue;
21635
21829
  const lastActive = new Date(raw).getTime();
@@ -21640,7 +21834,7 @@ var init_session_store = __esm({
21640
21834
  }
21641
21835
  }
21642
21836
  if (removed > 0) {
21643
- log33.info({ removed }, "Cleaned up expired session records");
21837
+ log34.info({ removed }, "Cleaned up expired session records");
21644
21838
  this.scheduleDiskWrite();
21645
21839
  }
21646
21840
  }
@@ -21655,13 +21849,14 @@ var init_session_store = __esm({
21655
21849
  });
21656
21850
 
21657
21851
  // src/core/sessions/session-factory.ts
21658
- var log34, SessionFactory;
21852
+ var log35, SessionFactory;
21659
21853
  var init_session_factory = __esm({
21660
21854
  "src/core/sessions/session-factory.ts"() {
21661
21855
  "use strict";
21662
21856
  init_session2();
21663
21857
  init_log();
21664
- log34 = createChildLogger({ module: "session-factory" });
21858
+ init_events();
21859
+ log35 = createChildLogger({ module: "session-factory" });
21665
21860
  SessionFactory = class {
21666
21861
  constructor(agentManager, sessionManager, speechServiceAccessor, eventBus, instanceRoot) {
21667
21862
  this.agentManager = agentManager;
@@ -21703,7 +21898,7 @@ var init_session_factory = __esm({
21703
21898
  threadId: ""
21704
21899
  // threadId is assigned after session creation
21705
21900
  };
21706
- const result = await this.middlewareChain.execute("session:beforeCreate", payload, async (p2) => p2);
21901
+ const result = await this.middlewareChain.execute(Hook.SESSION_BEFORE_CREATE, payload, async (p2) => p2);
21707
21902
  if (!result) throw new Error("Session creation blocked by middleware");
21708
21903
  createParams = {
21709
21904
  ...params,
@@ -21712,6 +21907,7 @@ var init_session_factory = __esm({
21712
21907
  channelId: result.channelId
21713
21908
  };
21714
21909
  }
21910
+ const configAllowedPaths = this.configManager?.get().workspace?.security?.allowedPaths ?? [];
21715
21911
  let agentInstance;
21716
21912
  try {
21717
21913
  if (createParams.resumeAgentSessionId) {
@@ -21719,22 +21915,25 @@ var init_session_factory = __esm({
21719
21915
  agentInstance = await this.agentManager.resume(
21720
21916
  createParams.agentName,
21721
21917
  createParams.workingDirectory,
21722
- createParams.resumeAgentSessionId
21918
+ createParams.resumeAgentSessionId,
21919
+ configAllowedPaths
21723
21920
  );
21724
21921
  } catch (resumeErr) {
21725
- log34.warn(
21922
+ log35.warn(
21726
21923
  { agentName: createParams.agentName, resumeErr },
21727
21924
  "Agent session resume failed, falling back to fresh spawn"
21728
21925
  );
21729
21926
  agentInstance = await this.agentManager.spawn(
21730
21927
  createParams.agentName,
21731
- createParams.workingDirectory
21928
+ createParams.workingDirectory,
21929
+ configAllowedPaths
21732
21930
  );
21733
21931
  }
21734
21932
  } else {
21735
21933
  agentInstance = await this.agentManager.spawn(
21736
21934
  createParams.agentName,
21737
- createParams.workingDirectory
21935
+ createParams.workingDirectory,
21936
+ configAllowedPaths
21738
21937
  );
21739
21938
  }
21740
21939
  } catch (err) {
@@ -21766,7 +21965,7 @@ var init_session_factory = __esm({
21766
21965
  message: guidanceLines.join("\n")
21767
21966
  };
21768
21967
  const failedSessionId = createParams.existingSessionId ?? `failed-${Date.now()}`;
21769
- this.eventBus.emit("agent:event", {
21968
+ this.eventBus.emit(BusEvent.AGENT_EVENT, {
21770
21969
  sessionId: failedSessionId,
21771
21970
  event: guidance
21772
21971
  });
@@ -21792,11 +21991,13 @@ var init_session_factory = __esm({
21792
21991
  }
21793
21992
  session.applySpawnResponse(agentInstance.initialSessionResponse, agentInstance.agentCapabilities);
21794
21993
  this.sessionManager.registerSession(session);
21795
- this.eventBus.emit("session:created", {
21796
- sessionId: session.id,
21797
- agent: session.agentName,
21798
- status: session.status
21799
- });
21994
+ if (!session.isAssistant) {
21995
+ this.eventBus.emit(BusEvent.SESSION_CREATED, {
21996
+ sessionId: session.id,
21997
+ agent: session.agentName,
21998
+ status: session.status
21999
+ });
22000
+ }
21800
22001
  return session;
21801
22002
  }
21802
22003
  /**
@@ -21814,6 +22015,7 @@ var init_session_factory = __esm({
21814
22015
  if (!this.sessionStore || !this.createFullSession) return null;
21815
22016
  const record = this.sessionStore.get(sessionId);
21816
22017
  if (!record) return null;
22018
+ if (record.isAssistant) return null;
21817
22019
  if (record.status === "error" || record.status === "cancelled") return null;
21818
22020
  const existing = this.resumeLocks.get(sessionId);
21819
22021
  if (existing) return existing;
@@ -21855,10 +22057,10 @@ var init_session_factory = __esm({
21855
22057
  session.setAgentCapabilities(record.acpState.agentCapabilities);
21856
22058
  }
21857
22059
  }
21858
- log34.info({ sessionId }, "Lazy resume by ID successful");
22060
+ log35.info({ sessionId }, "Lazy resume by ID successful");
21859
22061
  return session;
21860
22062
  } catch (err) {
21861
- log34.error({ err, sessionId }, "Lazy resume by ID failed");
22063
+ log35.error({ err, sessionId }, "Lazy resume by ID failed");
21862
22064
  return null;
21863
22065
  } finally {
21864
22066
  this.resumeLocks.delete(sessionId);
@@ -21878,18 +22080,19 @@ var init_session_factory = __esm({
21878
22080
  (p2) => String(p2.topicId) === threadId || String(p2.threadId ?? "") === threadId
21879
22081
  );
21880
22082
  if (!record) {
21881
- log34.debug({ threadId, channelId }, "No session record found for thread");
22083
+ log35.debug({ threadId, channelId }, "No session record found for thread");
21882
22084
  return null;
21883
22085
  }
22086
+ if (record.isAssistant) return null;
21884
22087
  if (record.status === "error" || record.status === "cancelled") {
21885
- log34.warn(
22088
+ log35.warn(
21886
22089
  { threadId, sessionId: record.sessionId, status: record.status },
21887
22090
  "Session record found but skipped (status: %s) \u2014 use /new to start a fresh session",
21888
22091
  record.status
21889
22092
  );
21890
22093
  return null;
21891
22094
  }
21892
- log34.info({ threadId, sessionId: record.sessionId, status: record.status }, "Lazy resume: found record, attempting resume");
22095
+ log35.info({ threadId, sessionId: record.sessionId, status: record.status }, "Lazy resume: found record, attempting resume");
21893
22096
  const resumePromise = (async () => {
21894
22097
  try {
21895
22098
  const session = await this.createFullSession({
@@ -21930,7 +22133,7 @@ var init_session_factory = __esm({
21930
22133
  }
21931
22134
  const resumeFalledBack = record.agentSessionId && session.agentSessionId !== record.agentSessionId;
21932
22135
  if (resumeFalledBack) {
21933
- log34.info({ sessionId: session.id }, "Resume fell back to fresh spawn \u2014 injecting conversation history");
22136
+ log35.info({ sessionId: session.id }, "Resume fell back to fresh spawn \u2014 injecting conversation history");
21934
22137
  const contextManager = this.getContextManager?.();
21935
22138
  if (contextManager) {
21936
22139
  try {
@@ -21947,10 +22150,10 @@ var init_session_factory = __esm({
21947
22150
  }
21948
22151
  }
21949
22152
  }
21950
- log34.info({ sessionId: session.id, threadId }, "Lazy resume successful");
22153
+ log35.info({ sessionId: session.id, threadId }, "Lazy resume successful");
21951
22154
  return session;
21952
22155
  } catch (err) {
21953
- log34.error({ err, record }, "Lazy resume failed");
22156
+ log35.error({ err, record }, "Lazy resume failed");
21954
22157
  const adapter = this.adapters?.get(channelId);
21955
22158
  if (adapter) {
21956
22159
  try {
@@ -21975,7 +22178,7 @@ var init_session_factory = __esm({
21975
22178
  }
21976
22179
  const config = this.configManager.get();
21977
22180
  const resolvedAgent = agentName || config.defaultAgent;
21978
- log34.info({ channelId, agentName: resolvedAgent }, "New session request");
22181
+ log35.info({ channelId, agentName: resolvedAgent }, "New session request");
21979
22182
  const agentDef = this.agentCatalog.resolve(resolvedAgent);
21980
22183
  const resolvedWorkspace = this.configManager.resolveWorkspace(
21981
22184
  workspacePath || agentDef?.workingDirectory
@@ -22024,7 +22227,7 @@ var init_session_factory = __esm({
22024
22227
  params.contextOptions
22025
22228
  );
22026
22229
  } catch (err) {
22027
- log34.warn({ err }, "Context building failed, proceeding without context");
22230
+ log35.warn({ err }, "Context building failed, proceeding without context");
22028
22231
  }
22029
22232
  }
22030
22233
  const session = await this.createFullSession({
@@ -22040,9 +22243,9 @@ var init_session_factory = __esm({
22040
22243
  return { session, contextResult };
22041
22244
  }
22042
22245
  wireSideEffects(session, deps) {
22043
- session.on("agent_event", (event) => {
22246
+ session.on(SessionEv.AGENT_EVENT, (event) => {
22044
22247
  if (event.type !== "usage") return;
22045
- deps.eventBus.emit("usage:recorded", {
22248
+ deps.eventBus.emit(BusEvent.USAGE_RECORDED, {
22046
22249
  sessionId: session.id,
22047
22250
  agentName: session.agentName,
22048
22251
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -22051,7 +22254,7 @@ var init_session_factory = __esm({
22051
22254
  cost: event.cost
22052
22255
  });
22053
22256
  });
22054
- session.on("status_change", (_from, to) => {
22257
+ session.on(SessionEv.STATUS_CHANGE, (_from, to) => {
22055
22258
  if ((to === "finished" || to === "cancelled") && deps.tunnelService) {
22056
22259
  deps.tunnelService.stopBySession(session.id).then((stopped) => {
22057
22260
  for (const entry of stopped) {
@@ -22073,13 +22276,14 @@ var init_session_factory = __esm({
22073
22276
  });
22074
22277
 
22075
22278
  // src/core/agent-switch-handler.ts
22076
- var log35, AgentSwitchHandler;
22279
+ var log36, AgentSwitchHandler;
22077
22280
  var init_agent_switch_handler = __esm({
22078
22281
  "src/core/agent-switch-handler.ts"() {
22079
22282
  "use strict";
22080
22283
  init_agent_registry();
22081
22284
  init_log();
22082
- log35 = createChildLogger({ module: "agent-switch" });
22285
+ init_events();
22286
+ log36 = createChildLogger({ module: "agent-switch" });
22083
22287
  AgentSwitchHandler = class {
22084
22288
  constructor(deps) {
22085
22289
  this.deps = deps;
@@ -22104,7 +22308,7 @@ var init_agent_switch_handler = __esm({
22104
22308
  if (!agentDef) throw new Error(`Agent "${toAgent}" is not installed`);
22105
22309
  const fromAgent = session.agentName;
22106
22310
  const middlewareChain = this.deps.getMiddlewareChain();
22107
- const result = await middlewareChain?.execute("agent:beforeSwitch", {
22311
+ const result = await middlewareChain?.execute(Hook.AGENT_BEFORE_SWITCH, {
22108
22312
  sessionId,
22109
22313
  fromAgent,
22110
22314
  toAgent
@@ -22118,9 +22322,9 @@ var init_agent_switch_handler = __esm({
22118
22322
  type: "system_message",
22119
22323
  message: `Switching from ${fromAgent} to ${toAgent}...`
22120
22324
  };
22121
- session.emit("agent_event", startEvent);
22122
- eventBus.emit("agent:event", { sessionId, event: startEvent });
22123
- eventBus.emit("session:agentSwitch", {
22325
+ session.emit(SessionEv.AGENT_EVENT, startEvent);
22326
+ eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, event: startEvent });
22327
+ eventBus.emit(BusEvent.SESSION_AGENT_SWITCH, {
22124
22328
  sessionId,
22125
22329
  fromAgent,
22126
22330
  toAgent,
@@ -22144,19 +22348,20 @@ var init_agent_switch_handler = __esm({
22144
22348
  }
22145
22349
  const fromAgentSessionId = session.agentSessionId;
22146
22350
  const fileService = this.deps.getService("file-service");
22351
+ const configAllowedPaths = configManager.get().workspace?.security?.allowedPaths ?? [];
22147
22352
  try {
22148
22353
  await session.switchAgent(toAgent, async () => {
22149
22354
  if (canResume) {
22150
22355
  try {
22151
- const instance2 = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId);
22356
+ const instance2 = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId, configAllowedPaths);
22152
22357
  if (fileService) instance2.addAllowedPath(fileService.baseDir);
22153
22358
  resumed = true;
22154
22359
  return instance2;
22155
22360
  } catch {
22156
- log35.warn({ sessionId, toAgent }, "Resume failed, falling back to new agent with context injection");
22361
+ log36.warn({ sessionId, toAgent }, "Resume failed, falling back to new agent with context injection");
22157
22362
  }
22158
22363
  }
22159
- const instance = await agentManager.spawn(toAgent, session.workingDirectory);
22364
+ const instance = await agentManager.spawn(toAgent, session.workingDirectory, configAllowedPaths);
22160
22365
  if (fileService) instance.addAllowedPath(fileService.baseDir);
22161
22366
  try {
22162
22367
  const contextService = this.deps.getService("context");
@@ -22180,9 +22385,9 @@ var init_agent_switch_handler = __esm({
22180
22385
  type: "system_message",
22181
22386
  message: resumed ? `Switched to ${toAgent} (resumed previous session).` : `Switched to ${toAgent} (new session).`
22182
22387
  };
22183
- session.emit("agent_event", successEvent);
22184
- eventBus.emit("agent:event", { sessionId, event: successEvent });
22185
- eventBus.emit("session:agentSwitch", {
22388
+ session.emit(SessionEv.AGENT_EVENT, successEvent);
22389
+ eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, event: successEvent });
22390
+ eventBus.emit(BusEvent.SESSION_AGENT_SWITCH, {
22186
22391
  sessionId,
22187
22392
  fromAgent,
22188
22393
  toAgent,
@@ -22195,9 +22400,9 @@ var init_agent_switch_handler = __esm({
22195
22400
  type: "system_message",
22196
22401
  message: `Failed to switch to ${toAgent}: ${errorMessage}`
22197
22402
  };
22198
- session.emit("agent_event", failedEvent);
22199
- eventBus.emit("agent:event", { sessionId, event: failedEvent });
22200
- eventBus.emit("session:agentSwitch", {
22403
+ session.emit(SessionEv.AGENT_EVENT, failedEvent);
22404
+ eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, event: failedEvent });
22405
+ eventBus.emit(BusEvent.SESSION_AGENT_SWITCH, {
22201
22406
  sessionId,
22202
22407
  fromAgent,
22203
22408
  toAgent,
@@ -22222,10 +22427,10 @@ var init_agent_switch_handler = __esm({
22222
22427
  createBridge(session, adapter, adapterId).connect();
22223
22428
  }
22224
22429
  }
22225
- log35.warn({ sessionId, fromAgent, toAgent, err }, "Agent switch failed, rolled back to previous agent");
22430
+ log36.warn({ sessionId, fromAgent, toAgent, err }, "Agent switch failed, rolled back to previous agent");
22226
22431
  } catch (rollbackErr) {
22227
22432
  session.fail(`Switch failed and rollback failed: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`);
22228
- log35.error({ sessionId, fromAgent, toAgent, err, rollbackErr }, "Agent switch failed and rollback also failed");
22433
+ log36.error({ sessionId, fromAgent, toAgent, err, rollbackErr }, "Agent switch failed and rollback also failed");
22229
22434
  }
22230
22435
  throw err;
22231
22436
  }
@@ -22235,7 +22440,7 @@ var init_agent_switch_handler = __esm({
22235
22440
  if (adapter) {
22236
22441
  createBridge(session, adapter, adapterId).connect();
22237
22442
  } else {
22238
- log35.warn({ sessionId, adapterId }, "Adapter not available during switch reconnect, skipping bridge");
22443
+ log36.warn({ sessionId, adapterId }, "Adapter not available during switch reconnect, skipping bridge");
22239
22444
  }
22240
22445
  }
22241
22446
  }
@@ -22246,7 +22451,7 @@ var init_agent_switch_handler = __esm({
22246
22451
  currentPromptCount: 0,
22247
22452
  agentSwitchHistory: session.agentSwitchHistory
22248
22453
  });
22249
- middlewareChain?.execute("agent:afterSwitch", {
22454
+ middlewareChain?.execute(Hook.AGENT_AFTER_SWITCH, {
22250
22455
  sessionId,
22251
22456
  fromAgent,
22252
22457
  toAgent,
@@ -22268,12 +22473,12 @@ import * as fs43 from "fs";
22268
22473
  import * as path47 from "path";
22269
22474
  import * as os20 from "os";
22270
22475
  import { z as z10 } from "zod";
22271
- var log36, InstalledAgentSchema, AgentStoreSchema, AgentStore;
22476
+ var log37, InstalledAgentSchema, AgentStoreSchema, AgentStore;
22272
22477
  var init_agent_store = __esm({
22273
22478
  "src/core/agents/agent-store.ts"() {
22274
22479
  "use strict";
22275
22480
  init_log();
22276
- log36 = createChildLogger({ module: "agent-store" });
22481
+ log37 = createChildLogger({ module: "agent-store" });
22277
22482
  InstalledAgentSchema = z10.object({
22278
22483
  registryId: z10.string().nullable(),
22279
22484
  name: z10.string(),
@@ -22307,11 +22512,11 @@ var init_agent_store = __esm({
22307
22512
  if (result.success) {
22308
22513
  this.data = result.data;
22309
22514
  } else {
22310
- log36.warn({ errors: result.error.issues }, "Invalid agents.json, starting fresh");
22515
+ log37.warn({ errors: result.error.issues }, "Invalid agents.json, starting fresh");
22311
22516
  this.data = { version: 1, installed: {} };
22312
22517
  }
22313
22518
  } catch (err) {
22314
- log36.warn({ err }, "Failed to read agents.json, starting fresh");
22519
+ log37.warn({ err }, "Failed to read agents.json, starting fresh");
22315
22520
  this.data = { version: 1, installed: {} };
22316
22521
  }
22317
22522
  }
@@ -22353,7 +22558,7 @@ __export(agent_catalog_exports, {
22353
22558
  import * as fs44 from "fs";
22354
22559
  import * as path48 from "path";
22355
22560
  import * as os21 from "os";
22356
- var log37, REGISTRY_URL2, DEFAULT_TTL_HOURS, AgentCatalog;
22561
+ var log38, REGISTRY_URL2, DEFAULT_TTL_HOURS, AgentCatalog;
22357
22562
  var init_agent_catalog = __esm({
22358
22563
  "src/core/agents/agent-catalog.ts"() {
22359
22564
  "use strict";
@@ -22361,7 +22566,7 @@ var init_agent_catalog = __esm({
22361
22566
  init_agent_installer();
22362
22567
  init_agent_dependencies();
22363
22568
  init_log();
22364
- log37 = createChildLogger({ module: "agent-catalog" });
22569
+ log38 = createChildLogger({ module: "agent-catalog" });
22365
22570
  REGISTRY_URL2 = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
22366
22571
  DEFAULT_TTL_HOURS = 24;
22367
22572
  AgentCatalog = class {
@@ -22382,7 +22587,7 @@ var init_agent_catalog = __esm({
22382
22587
  // --- Registry ---
22383
22588
  async fetchRegistry() {
22384
22589
  try {
22385
- log37.info("Fetching agent registry from CDN...");
22590
+ log38.info("Fetching agent registry from CDN...");
22386
22591
  const response = await fetch(REGISTRY_URL2);
22387
22592
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
22388
22593
  const data = await response.json();
@@ -22394,9 +22599,9 @@ var init_agent_catalog = __esm({
22394
22599
  };
22395
22600
  fs44.mkdirSync(path48.dirname(this.cachePath), { recursive: true });
22396
22601
  fs44.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), { mode: 384 });
22397
- log37.info({ count: this.registryAgents.length }, "Registry updated");
22602
+ log38.info({ count: this.registryAgents.length }, "Registry updated");
22398
22603
  } catch (err) {
22399
- log37.warn({ err }, "Failed to fetch registry, using cached data");
22604
+ log38.warn({ err }, "Failed to fetch registry, using cached data");
22400
22605
  }
22401
22606
  }
22402
22607
  async refreshRegistryIfStale() {
@@ -22557,7 +22762,7 @@ var init_agent_catalog = __esm({
22557
22762
  }
22558
22763
  }
22559
22764
  if (changed) {
22560
- log37.info("Enriched installed agents with registry data");
22765
+ log38.info("Enriched installed agents with registry data");
22561
22766
  }
22562
22767
  }
22563
22768
  isCacheStale() {
@@ -22577,11 +22782,11 @@ var init_agent_catalog = __esm({
22577
22782
  const raw = JSON.parse(fs44.readFileSync(this.cachePath, "utf-8"));
22578
22783
  if (raw.data?.agents) {
22579
22784
  this.registryAgents = raw.data.agents;
22580
- log37.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
22785
+ log38.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
22581
22786
  return;
22582
22787
  }
22583
22788
  } catch {
22584
- log37.warn("Failed to load registry cache");
22789
+ log38.warn("Failed to load registry cache");
22585
22790
  }
22586
22791
  }
22587
22792
  try {
@@ -22594,13 +22799,13 @@ var init_agent_catalog = __esm({
22594
22799
  if (fs44.existsSync(candidate)) {
22595
22800
  const raw = JSON.parse(fs44.readFileSync(candidate, "utf-8"));
22596
22801
  this.registryAgents = raw.agents ?? [];
22597
- log37.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
22802
+ log38.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
22598
22803
  return;
22599
22804
  }
22600
22805
  }
22601
- log37.warn("No registry data available (no cache, no snapshot)");
22806
+ log38.warn("No registry data available (no cache, no snapshot)");
22602
22807
  } catch {
22603
- log37.warn("Failed to load bundled registry snapshot");
22808
+ log38.warn("Failed to load bundled registry snapshot");
22604
22809
  }
22605
22810
  }
22606
22811
  };
@@ -22986,7 +23191,7 @@ function createPluginContext(opts) {
22986
23191
  }
22987
23192
  };
22988
23193
  const baseLog = opts.log ?? noopLog;
22989
- const log46 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
23194
+ const log47 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
22990
23195
  const storageImpl = new PluginStorageImpl(storagePath);
22991
23196
  const storage = {
22992
23197
  async get(key) {
@@ -23013,7 +23218,7 @@ function createPluginContext(opts) {
23013
23218
  const ctx = {
23014
23219
  pluginName,
23015
23220
  pluginConfig,
23016
- log: log46,
23221
+ log: log47,
23017
23222
  storage,
23018
23223
  on(event, handler) {
23019
23224
  requirePermission(permissions, "events:read", "on()");
@@ -23048,7 +23253,7 @@ function createPluginContext(opts) {
23048
23253
  const registry = serviceRegistry.get("command-registry");
23049
23254
  if (registry && typeof registry.register === "function") {
23050
23255
  registry.register(def, pluginName);
23051
- log46.debug(`Command '/${def.name}' registered`);
23256
+ log47.debug(`Command '/${def.name}' registered`);
23052
23257
  }
23053
23258
  },
23054
23259
  async sendMessage(_sessionId, _content) {
@@ -23091,7 +23296,7 @@ function createPluginContext(opts) {
23091
23296
  const registry = serviceRegistry.get("field-registry");
23092
23297
  if (registry && typeof registry.register === "function") {
23093
23298
  registry.register(pluginName, fields);
23094
- log46.debug(`Registered ${fields.length} editable field(s) for ${pluginName}`);
23299
+ log47.debug(`Registered ${fields.length} editable field(s) for ${pluginName}`);
23095
23300
  }
23096
23301
  },
23097
23302
  get sessions() {
@@ -23197,6 +23402,7 @@ var init_lifecycle_manager = __esm({
23197
23402
  init_middleware_chain();
23198
23403
  init_error_tracker();
23199
23404
  init_plugin_context();
23405
+ init_events();
23200
23406
  SETUP_TIMEOUT_MS = 3e4;
23201
23407
  TEARDOWN_TIMEOUT_MS = 1e4;
23202
23408
  LifecycleManager = class {
@@ -23303,7 +23509,7 @@ var init_lifecycle_manager = __esm({
23303
23509
  }
23304
23510
  const registryEntry = this.pluginRegistry?.get(plugin2.name);
23305
23511
  if (registryEntry && registryEntry.enabled === false) {
23306
- this.eventBus?.emit("plugin:disabled", { name: plugin2.name });
23512
+ this.eventBus?.emit(BusEvent.PLUGIN_DISABLED, { name: plugin2.name });
23307
23513
  continue;
23308
23514
  }
23309
23515
  if (registryEntry && plugin2.migrate && registryEntry.version !== plugin2.version && this.settingsManager) {
@@ -23346,7 +23552,7 @@ var init_lifecycle_manager = __esm({
23346
23552
  if (!validation.valid) {
23347
23553
  this._failed.add(plugin2.name);
23348
23554
  this.getPluginLogger(plugin2.name).error(`Settings validation failed: ${validation.errors?.join("; ")}`);
23349
- this.eventBus?.emit("plugin:failed", { name: plugin2.name, error: `Settings validation failed: ${validation.errors?.join("; ")}` });
23555
+ this.eventBus?.emit(BusEvent.PLUGIN_FAILED, { name: plugin2.name, error: `Settings validation failed: ${validation.errors?.join("; ")}` });
23350
23556
  continue;
23351
23557
  }
23352
23558
  }
@@ -23369,13 +23575,13 @@ var init_lifecycle_manager = __esm({
23369
23575
  await withTimeout(plugin2.setup(ctx), SETUP_TIMEOUT_MS, `${plugin2.name}.setup()`);
23370
23576
  this.contexts.set(plugin2.name, ctx);
23371
23577
  this._loaded.add(plugin2.name);
23372
- this.eventBus?.emit("plugin:loaded", { name: plugin2.name, version: plugin2.version });
23578
+ this.eventBus?.emit(BusEvent.PLUGIN_LOADED, { name: plugin2.name, version: plugin2.version });
23373
23579
  } catch (err) {
23374
23580
  this._failed.add(plugin2.name);
23375
23581
  ctx.cleanup();
23376
23582
  console.error(`[lifecycle] Plugin ${plugin2.name} setup() FAILED:`, err);
23377
23583
  this.getPluginLogger(plugin2.name).error(`setup() failed: ${err}`);
23378
- this.eventBus?.emit("plugin:failed", { name: plugin2.name, error: String(err) });
23584
+ this.eventBus?.emit(BusEvent.PLUGIN_FAILED, { name: plugin2.name, error: String(err) });
23379
23585
  }
23380
23586
  }
23381
23587
  }
@@ -23396,7 +23602,7 @@ var init_lifecycle_manager = __esm({
23396
23602
  this._loaded.delete(name);
23397
23603
  this._failed.delete(name);
23398
23604
  this.loadOrder = this.loadOrder.filter((p2) => p2.name !== name);
23399
- this.eventBus?.emit("plugin:unloaded", { name });
23605
+ this.eventBus?.emit(BusEvent.PLUGIN_UNLOADED, { name });
23400
23606
  }
23401
23607
  async shutdown() {
23402
23608
  const reversed = [...this.loadOrder].reverse();
@@ -23413,7 +23619,7 @@ var init_lifecycle_manager = __esm({
23413
23619
  ctx.cleanup();
23414
23620
  this.contexts.delete(plugin2.name);
23415
23621
  }
23416
- this.eventBus?.emit("plugin:unloaded", { name: plugin2.name });
23622
+ this.eventBus?.emit(BusEvent.PLUGIN_UNLOADED, { name: plugin2.name });
23417
23623
  }
23418
23624
  this._loaded.clear();
23419
23625
  this.loadOrder = [];
@@ -23423,12 +23629,12 @@ var init_lifecycle_manager = __esm({
23423
23629
  });
23424
23630
 
23425
23631
  // src/core/menu-registry.ts
23426
- var log38, MenuRegistry;
23632
+ var log39, MenuRegistry;
23427
23633
  var init_menu_registry = __esm({
23428
23634
  "src/core/menu-registry.ts"() {
23429
23635
  "use strict";
23430
23636
  init_log();
23431
- log38 = createChildLogger({ module: "menu-registry" });
23637
+ log39 = createChildLogger({ module: "menu-registry" });
23432
23638
  MenuRegistry = class {
23433
23639
  items = /* @__PURE__ */ new Map();
23434
23640
  register(item) {
@@ -23447,7 +23653,7 @@ var init_menu_registry = __esm({
23447
23653
  try {
23448
23654
  return item.visible();
23449
23655
  } catch (err) {
23450
- log38.warn({ err, id: item.id }, "MenuItem visible() threw, hiding item");
23656
+ log39.warn({ err, id: item.id }, "MenuItem visible() threw, hiding item");
23451
23657
  return false;
23452
23658
  }
23453
23659
  }).sort((a, b) => a.priority - b.priority);
@@ -23520,13 +23726,13 @@ Talk to users like a helpful assistant, not a CLI manual.`;
23520
23726
  });
23521
23727
 
23522
23728
  // src/core/assistant/assistant-registry.ts
23523
- var log39, AssistantRegistry;
23729
+ var log40, AssistantRegistry;
23524
23730
  var init_assistant_registry = __esm({
23525
23731
  "src/core/assistant/assistant-registry.ts"() {
23526
23732
  "use strict";
23527
23733
  init_log();
23528
23734
  init_prompt_constants();
23529
- log39 = createChildLogger({ module: "assistant-registry" });
23735
+ log40 = createChildLogger({ module: "assistant-registry" });
23530
23736
  AssistantRegistry = class {
23531
23737
  sections = /* @__PURE__ */ new Map();
23532
23738
  _instanceRoot = "";
@@ -23536,7 +23742,7 @@ var init_assistant_registry = __esm({
23536
23742
  }
23537
23743
  register(section) {
23538
23744
  if (this.sections.has(section.id)) {
23539
- log39.warn({ id: section.id }, "Assistant section overwritten");
23745
+ log40.warn({ id: section.id }, "Assistant section overwritten");
23540
23746
  }
23541
23747
  this.sections.set(section.id, section);
23542
23748
  }
@@ -23561,7 +23767,7 @@ ${context}`);
23561
23767
  parts.push("```bash\n" + cmds + "\n```");
23562
23768
  }
23563
23769
  } catch (err) {
23564
- log39.warn({ err, sectionId: section.id }, "Assistant section buildContext() failed, skipping");
23770
+ log40.warn({ err, sectionId: section.id }, "Assistant section buildContext() failed, skipping");
23565
23771
  }
23566
23772
  }
23567
23773
  parts.push(buildAssistantGuidelines(this._instanceRoot));
@@ -23572,33 +23778,37 @@ ${context}`);
23572
23778
  });
23573
23779
 
23574
23780
  // src/core/assistant/assistant-manager.ts
23575
- var log40, AssistantManager;
23781
+ var log41, AssistantManager;
23576
23782
  var init_assistant_manager = __esm({
23577
23783
  "src/core/assistant/assistant-manager.ts"() {
23578
23784
  "use strict";
23579
23785
  init_log();
23580
- log40 = createChildLogger({ module: "assistant-manager" });
23786
+ log41 = createChildLogger({ module: "assistant-manager" });
23581
23787
  AssistantManager = class {
23582
23788
  constructor(core, registry) {
23583
23789
  this.core = core;
23584
23790
  this.registry = registry;
23585
23791
  }
23586
23792
  sessions = /* @__PURE__ */ new Map();
23587
- respawning = /* @__PURE__ */ new Set();
23588
23793
  pendingSystemPrompts = /* @__PURE__ */ new Map();
23589
- async spawn(channelId, threadId) {
23794
+ async getOrSpawn(channelId, threadId) {
23795
+ const existing = this.core.sessionStore?.findAssistant(channelId);
23590
23796
  const session = await this.core.createSession({
23591
23797
  channelId,
23592
23798
  agentName: this.core.configManager.get().defaultAgent,
23593
23799
  workingDirectory: this.core.configManager.resolveWorkspace(),
23594
23800
  initialName: "Assistant",
23595
23801
  isAssistant: true,
23596
- threadId
23802
+ threadId,
23803
+ existingSessionId: existing?.sessionId
23597
23804
  });
23598
23805
  this.sessions.set(channelId, session);
23599
23806
  const systemPrompt = this.registry.buildSystemPrompt(channelId);
23600
23807
  this.pendingSystemPrompts.set(channelId, systemPrompt);
23601
- log40.info({ sessionId: session.id, channelId }, "Assistant spawned (system prompt deferred)");
23808
+ log41.info(
23809
+ { sessionId: session.id, channelId, reused: !!existing },
23810
+ existing ? "Assistant session reused (system prompt deferred)" : "Assistant spawned (system prompt deferred)"
23811
+ );
23602
23812
  return session;
23603
23813
  }
23604
23814
  get(channelId) {
@@ -23619,19 +23829,6 @@ var init_assistant_manager = __esm({
23619
23829
  }
23620
23830
  return false;
23621
23831
  }
23622
- async respawn(channelId, threadId) {
23623
- if (this.respawning.has(channelId)) {
23624
- return this.sessions.get(channelId);
23625
- }
23626
- this.respawning.add(channelId);
23627
- try {
23628
- const old = this.sessions.get(channelId);
23629
- if (old) await old.destroy();
23630
- return await this.spawn(channelId, threadId);
23631
- } finally {
23632
- this.respawning.delete(channelId);
23633
- }
23634
- }
23635
23832
  };
23636
23833
  }
23637
23834
  });
@@ -23844,7 +24041,7 @@ var init_core_items = __esm({
23844
24041
  import path51 from "path";
23845
24042
  import os23 from "os";
23846
24043
  import { nanoid as nanoid5 } from "nanoid";
23847
- var log41, OpenACPCore;
24044
+ var log42, OpenACPCore;
23848
24045
  var init_core = __esm({
23849
24046
  "src/core/core.ts"() {
23850
24047
  "use strict";
@@ -23868,7 +24065,8 @@ var init_core = __esm({
23868
24065
  init_middleware_chain();
23869
24066
  init_error_tracker();
23870
24067
  init_log();
23871
- log41 = createChildLogger({ module: "core" });
24068
+ init_events();
24069
+ log42 = createChildLogger({ module: "core" });
23872
24070
  OpenACPCore = class {
23873
24071
  configManager;
23874
24072
  agentCatalog;
@@ -23983,7 +24181,7 @@ var init_core = __esm({
23983
24181
  if (configPath === "logging.level" && typeof value === "string") {
23984
24182
  const { setLogLevel: setLogLevel2 } = await Promise.resolve().then(() => (init_log(), log_exports));
23985
24183
  setLogLevel2(value);
23986
- log41.info({ level: value }, "Log level changed at runtime");
24184
+ log42.info({ level: value }, "Log level changed at runtime");
23987
24185
  }
23988
24186
  if (configPath.startsWith("speech.")) {
23989
24187
  const speechSvc = this.lifecycleManager.serviceRegistry.get("speech");
@@ -24007,7 +24205,7 @@ var init_core = __esm({
24007
24205
  }
24008
24206
  };
24009
24207
  speechSvc.refreshProviders(newSpeechConfig);
24010
- log41.info("Speech service config updated at runtime (from plugin settings)");
24208
+ log42.info("Speech service config updated at runtime (from plugin settings)");
24011
24209
  }
24012
24210
  }
24013
24211
  }
@@ -24037,14 +24235,14 @@ var init_core = __esm({
24037
24235
  }
24038
24236
  async start() {
24039
24237
  this.agentCatalog.refreshRegistryIfStale().catch((err) => {
24040
- log41.warn({ err }, "Background registry refresh failed");
24238
+ log42.warn({ err }, "Background registry refresh failed");
24041
24239
  });
24042
24240
  const failures = [];
24043
24241
  for (const [name, adapter] of this.adapters.entries()) {
24044
24242
  try {
24045
24243
  await adapter.start();
24046
24244
  } catch (err) {
24047
- log41.error({ err, adapter: name }, `Adapter "${name}" failed to start`);
24245
+ log42.error({ err, adapter: name }, `Adapter "${name}" failed to start`);
24048
24246
  failures.push({ name, error: err });
24049
24247
  }
24050
24248
  }
@@ -24092,7 +24290,7 @@ var init_core = __esm({
24092
24290
  }
24093
24291
  // --- Message Routing ---
24094
24292
  async handleMessage(message) {
24095
- log41.debug(
24293
+ log42.debug(
24096
24294
  {
24097
24295
  channelId: message.channelId,
24098
24296
  threadId: message.threadId,
@@ -24102,7 +24300,7 @@ var init_core = __esm({
24102
24300
  );
24103
24301
  if (this.lifecycleManager?.middlewareChain) {
24104
24302
  const result = await this.lifecycleManager.middlewareChain.execute(
24105
- "message:incoming",
24303
+ Hook.MESSAGE_INCOMING,
24106
24304
  message,
24107
24305
  async (msg) => msg
24108
24306
  );
@@ -24111,7 +24309,7 @@ var init_core = __esm({
24111
24309
  }
24112
24310
  const access2 = await this.securityGuard.checkAccess(message);
24113
24311
  if (!access2.allowed) {
24114
- log41.warn({ userId: message.userId, reason: access2.reason }, "Access denied");
24312
+ log42.warn({ userId: message.userId, reason: access2.reason }, "Access denied");
24115
24313
  if (access2.reason.includes("Session limit")) {
24116
24314
  const adapter = this.adapters.get(message.channelId);
24117
24315
  if (adapter) {
@@ -24125,7 +24323,7 @@ var init_core = __esm({
24125
24323
  }
24126
24324
  let session = await this.sessionFactory.getOrResume(message.channelId, message.threadId);
24127
24325
  if (!session) {
24128
- log41.warn(
24326
+ log42.warn(
24129
24327
  { channelId: message.channelId, threadId: message.threadId },
24130
24328
  "No session found for thread (in-memory miss + lazy resume returned null)"
24131
24329
  );
@@ -24154,9 +24352,10 @@ ${text6}`;
24154
24352
  }
24155
24353
  }
24156
24354
  const sourceAdapterId = message.routing?.sourceAdapterId ?? message.channelId;
24355
+ const routing = sourceAdapterId !== message.routing?.sourceAdapterId ? { ...message.routing, sourceAdapterId } : message.routing;
24157
24356
  if (sourceAdapterId && sourceAdapterId !== "sse" && sourceAdapterId !== "api") {
24158
24357
  const turnId = nanoid5(8);
24159
- this.eventBus.emit("message:queued", {
24358
+ this.eventBus.emit(BusEvent.MESSAGE_QUEUED, {
24160
24359
  sessionId: session.id,
24161
24360
  turnId,
24162
24361
  text: text6,
@@ -24165,9 +24364,9 @@ ${text6}`;
24165
24364
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
24166
24365
  queueDepth: session.queueDepth
24167
24366
  });
24168
- await session.enqueuePrompt(text6, message.attachments, message.routing, turnId);
24367
+ await session.enqueuePrompt(text6, message.attachments, routing, turnId);
24169
24368
  } else {
24170
- await session.enqueuePrompt(text6, message.attachments, message.routing);
24369
+ await session.enqueuePrompt(text6, message.attachments, routing);
24171
24370
  }
24172
24371
  }
24173
24372
  // --- Unified Session Creation Pipeline ---
@@ -24211,6 +24410,7 @@ ${text6}`;
24211
24410
  createdAt: session.createdAt.toISOString(),
24212
24411
  lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
24213
24412
  name: session.name,
24413
+ isAssistant: params.isAssistant,
24214
24414
  platform: platform2,
24215
24415
  platforms,
24216
24416
  firstAgent: session.firstAgent,
@@ -24223,10 +24423,10 @@ ${text6}`;
24223
24423
  const bridge = this.createBridge(session, adapter, session.channelId);
24224
24424
  bridge.connect();
24225
24425
  adapter.flushPendingSkillCommands?.(session.id).catch((err) => {
24226
- log41.warn({ err, sessionId: session.id }, "Failed to flush pending skill commands");
24426
+ log42.warn({ err, sessionId: session.id }, "Failed to flush pending skill commands");
24227
24427
  });
24228
24428
  if (params.createThread && session.threadId) {
24229
- this.eventBus.emit("session:threadReady", {
24429
+ this.eventBus.emit(BusEvent.SESSION_THREAD_READY, {
24230
24430
  sessionId: session.id,
24231
24431
  channelId: params.channelId,
24232
24432
  threadId: session.threadId
@@ -24237,26 +24437,56 @@ ${text6}`;
24237
24437
  session.agentInstance.onPermissionRequest = async (permRequest) => {
24238
24438
  const allowOption = permRequest.options.find((o) => o.isAllow);
24239
24439
  if (!allowOption) {
24240
- log41.warn(
24440
+ log42.warn(
24241
24441
  { sessionId: session.id, permissionId: permRequest.id, description: permRequest.description },
24242
24442
  "Headless session has no allow option for permission request \u2014 skipping auto-approve, will time out"
24243
24443
  );
24244
24444
  return new Promise(() => {
24245
24445
  });
24246
24446
  }
24247
- log41.warn(
24447
+ log42.warn(
24248
24448
  { sessionId: session.id, permissionId: permRequest.id, option: allowOption.id },
24249
24449
  `Auto-approving permission "${permRequest.description}" for headless session \u2014 no adapter connected`
24250
24450
  );
24251
24451
  return allowOption.id;
24252
24452
  };
24453
+ session.on(SessionEv.NAMED, async (name) => {
24454
+ await this.sessionManager.patchRecord(session.id, { name });
24455
+ this.eventBus.emit(BusEvent.SESSION_UPDATED, { sessionId: session.id, name });
24456
+ });
24457
+ const mw = () => this.lifecycleManager?.middlewareChain;
24458
+ session.on(SessionEv.AGENT_EVENT, async (event) => {
24459
+ let processedEvent = event;
24460
+ const chain = mw();
24461
+ if (chain) {
24462
+ const result = await chain.execute(Hook.AGENT_BEFORE_EVENT, { sessionId: session.id, event }, async (e) => e);
24463
+ if (!result) return;
24464
+ processedEvent = result.event;
24465
+ }
24466
+ if (processedEvent.type === "session_end") {
24467
+ session.finish(processedEvent.reason);
24468
+ } else if (processedEvent.type === "error") {
24469
+ session.fail(processedEvent.message);
24470
+ }
24471
+ this.eventBus.emit(BusEvent.AGENT_EVENT, { sessionId: session.id, event: processedEvent });
24472
+ });
24473
+ session.on(SessionEv.STATUS_CHANGE, (_from, to) => {
24474
+ this.sessionManager.patchRecord(session.id, {
24475
+ status: to,
24476
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
24477
+ });
24478
+ this.eventBus.emit(BusEvent.SESSION_UPDATED, { sessionId: session.id, status: to });
24479
+ });
24480
+ session.on(SessionEv.PROMPT_COUNT_CHANGED, (count) => {
24481
+ this.sessionManager.patchRecord(session.id, { currentPromptCount: count });
24482
+ });
24253
24483
  }
24254
24484
  this.sessionFactory.wireSideEffects(session, {
24255
24485
  eventBus: this.eventBus,
24256
24486
  notificationManager: this.notificationManager,
24257
24487
  tunnelService: this._tunnelService
24258
24488
  });
24259
- log41.info(
24489
+ log42.info(
24260
24490
  { sessionId: session.id, agentName: params.agentName },
24261
24491
  "Session created via pipeline"
24262
24492
  );
@@ -24706,18 +24936,6 @@ Prompts: ${session.promptCount}` };
24706
24936
  return { type: "list", title: "\u{1F4CB} Sessions", items };
24707
24937
  }
24708
24938
  });
24709
- registry.register({
24710
- name: "clear",
24711
- description: "Clear session history",
24712
- category: "system",
24713
- handler: async (args2) => {
24714
- if (!core.assistantManager) return { type: "error", message: "Assistant not available" };
24715
- const assistant = core.assistantManager.get(args2.channelId);
24716
- if (!assistant) return { type: "error", message: "No assistant session for this channel." };
24717
- await core.assistantManager.respawn(args2.channelId, assistant.threadId);
24718
- return { type: "text", text: "\u2705 Assistant history cleared." };
24719
- }
24720
- });
24721
24939
  registry.register({
24722
24940
  name: "newchat",
24723
24941
  description: "New chat, same agent & workspace",
@@ -25244,10 +25462,10 @@ function registerCategoryCommand(registry, core, category, commandName) {
25244
25462
  }
25245
25463
  try {
25246
25464
  await session.setConfigOption(configOption.id, { type: "select", value: raw });
25247
- core.eventBus.emit("session:configChanged", { sessionId: session.id });
25465
+ core.eventBus.emit(BusEvent.SESSION_CONFIG_CHANGED, { sessionId: session.id });
25248
25466
  return { type: "text", text: labels.successMsg(match.name, configOption.name) };
25249
25467
  } catch (err) {
25250
- log42.error({ err, commandName, configId: configOption.id }, "setConfigOption failed");
25468
+ log43.error({ err, commandName, configId: configOption.id }, "setConfigOption failed");
25251
25469
  const msg = err instanceof Error ? err.message : typeof err === "object" && err !== null && typeof err.message === "string" ? err.message : String(err);
25252
25470
  return { type: "error", message: `Could not change ${commandName}: ${msg}` };
25253
25471
  }
@@ -25306,13 +25524,13 @@ function registerDangerousCommand(registry, core) {
25306
25524
  try {
25307
25525
  const targetValue = wantOn ? bypassValue : nonBypassDefault;
25308
25526
  await session.setConfigOption(modeConfig.id, { type: "select", value: targetValue });
25309
- core.eventBus.emit("session:configChanged", { sessionId: session.id });
25527
+ core.eventBus.emit(BusEvent.SESSION_CONFIG_CHANGED, { sessionId: session.id });
25310
25528
  return {
25311
25529
  type: "text",
25312
25530
  text: wantOn ? "\u2620\uFE0F **Bypass Permissions enabled** \u2014 all permission requests will be auto-approved. The agent can run any action without asking." : "\u{1F510} **Bypass Permissions disabled** \u2014 you will be asked to approve risky actions."
25313
25531
  };
25314
25532
  } catch (err) {
25315
- log42.error({ err }, "setConfigOption failed (bypass toggle)");
25533
+ log43.error({ err }, "setConfigOption failed (bypass toggle)");
25316
25534
  const msg = err instanceof Error ? err.message : typeof err === "object" && err !== null && typeof err.message === "string" ? err.message : String(err);
25317
25535
  return { type: "error", message: `Could not toggle bypass: ${msg}` };
25318
25536
  }
@@ -25321,7 +25539,7 @@ function registerDangerousCommand(registry, core) {
25321
25539
  await core.sessionManager.patchRecord(session.id, {
25322
25540
  clientOverrides: { ...session.clientOverrides }
25323
25541
  });
25324
- core.eventBus.emit("session:configChanged", { sessionId: session.id });
25542
+ core.eventBus.emit(BusEvent.SESSION_CONFIG_CHANGED, { sessionId: session.id });
25325
25543
  return {
25326
25544
  type: "text",
25327
25545
  text: wantOn ? "\u2620\uFE0F **Bypass Permissions enabled** (client-side) \u2014 all permission requests will be auto-approved.\n\n_Note: This agent doesn't natively support bypass mode, so OpenACP will auto-approve on your behalf._" : "\u{1F510} **Bypass Permissions disabled** \u2014 you will be asked to approve risky actions."
@@ -25336,14 +25554,15 @@ function registerConfigCommands(registry, _core) {
25336
25554
  registerCategoryCommand(registry, core, "thought_level", "thought");
25337
25555
  registerDangerousCommand(registry, core);
25338
25556
  }
25339
- var log42, CATEGORY_LABELS;
25557
+ var log43, CATEGORY_LABELS;
25340
25558
  var init_config6 = __esm({
25341
25559
  "src/core/commands/config.ts"() {
25342
25560
  "use strict";
25343
25561
  init_log();
25562
+ init_events();
25344
25563
  init_bypass_detection();
25345
25564
  init_bypass_detection();
25346
- log42 = createChildLogger({ module: "commands/config" });
25565
+ log43 = createChildLogger({ module: "commands/config" });
25347
25566
  CATEGORY_LABELS = {
25348
25567
  mode: {
25349
25568
  menuTitle: (cur) => `Choose session mode (current: ${cur})`,
@@ -25755,7 +25974,7 @@ function installAutoStart(logDir2) {
25755
25974
  fs46.mkdirSync(dir, { recursive: true });
25756
25975
  fs46.writeFileSync(LAUNCHD_PLIST_PATH, plist);
25757
25976
  execFileSync8("launchctl", ["load", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
25758
- log43.info("LaunchAgent installed");
25977
+ log44.info("LaunchAgent installed");
25759
25978
  return { success: true };
25760
25979
  }
25761
25980
  if (process.platform === "linux") {
@@ -25765,13 +25984,13 @@ function installAutoStart(logDir2) {
25765
25984
  fs46.writeFileSync(SYSTEMD_SERVICE_PATH, unit);
25766
25985
  execFileSync8("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
25767
25986
  execFileSync8("systemctl", ["--user", "enable", "openacp"], { stdio: "pipe" });
25768
- log43.info("systemd user service installed");
25987
+ log44.info("systemd user service installed");
25769
25988
  return { success: true };
25770
25989
  }
25771
25990
  return { success: false, error: "Unsupported platform" };
25772
25991
  } catch (e) {
25773
25992
  const msg = e.message;
25774
- log43.error({ err: msg }, "Failed to install auto-start");
25993
+ log44.error({ err: msg }, "Failed to install auto-start");
25775
25994
  return { success: false, error: msg };
25776
25995
  }
25777
25996
  }
@@ -25787,7 +26006,7 @@ function uninstallAutoStart() {
25787
26006
  } catch {
25788
26007
  }
25789
26008
  fs46.unlinkSync(LAUNCHD_PLIST_PATH);
25790
- log43.info("LaunchAgent removed");
26009
+ log44.info("LaunchAgent removed");
25791
26010
  }
25792
26011
  return { success: true };
25793
26012
  }
@@ -25799,14 +26018,14 @@ function uninstallAutoStart() {
25799
26018
  }
25800
26019
  fs46.unlinkSync(SYSTEMD_SERVICE_PATH);
25801
26020
  execFileSync8("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
25802
- log43.info("systemd user service removed");
26021
+ log44.info("systemd user service removed");
25803
26022
  }
25804
26023
  return { success: true };
25805
26024
  }
25806
26025
  return { success: false, error: "Unsupported platform" };
25807
26026
  } catch (e) {
25808
26027
  const msg = e.message;
25809
- log43.error({ err: msg }, "Failed to uninstall auto-start");
26028
+ log44.error({ err: msg }, "Failed to uninstall auto-start");
25810
26029
  return { success: false, error: msg };
25811
26030
  }
25812
26031
  }
@@ -25819,12 +26038,12 @@ function isAutoStartInstalled() {
25819
26038
  }
25820
26039
  return false;
25821
26040
  }
25822
- var log43, LAUNCHD_LABEL, LAUNCHD_PLIST_PATH, SYSTEMD_SERVICE_PATH;
26041
+ var log44, LAUNCHD_LABEL, LAUNCHD_PLIST_PATH, SYSTEMD_SERVICE_PATH;
25823
26042
  var init_autostart = __esm({
25824
26043
  "src/cli/autostart.ts"() {
25825
26044
  "use strict";
25826
26045
  init_log();
25827
- log43 = createChildLogger({ module: "autostart" });
26046
+ log44 = createChildLogger({ module: "autostart" });
25828
26047
  LAUNCHD_LABEL = "com.openacp.daemon";
25829
26048
  LAUNCHD_PLIST_PATH = path52.join(os24.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
25830
26049
  SYSTEMD_SERVICE_PATH = path52.join(os24.homedir(), ".config", "systemd", "user", "openacp.service");
@@ -25927,7 +26146,7 @@ async function setupIntegrations() {
25927
26146
  if (integration) {
25928
26147
  for (const item of integration.items) {
25929
26148
  const result = await item.install();
25930
- for (const log46 of result.logs) console.log(` ${log46}`);
26149
+ for (const log47 of result.logs) console.log(` ${log47}`);
25931
26150
  }
25932
26151
  }
25933
26152
  console.log("Claude CLI integration installed.\n");
@@ -26620,7 +26839,7 @@ async function runSetup(configManager, opts) {
26620
26839
  const config = {
26621
26840
  instanceName,
26622
26841
  defaultAgent,
26623
- workspace: { ...workspace, security: { allowedPaths: [], envWhitelist: [] } },
26842
+ workspace: { ...workspace, allowExternalWorkspaces: true, security: { allowedPaths: [], envWhitelist: [] } },
26624
26843
  logging: {
26625
26844
  level: "info",
26626
26845
  logDir: path56.join(instanceRoot, "logs"),
@@ -26869,14 +27088,14 @@ async function runPostUpgradeChecks(config) {
26869
27088
  const { ensureCloudflared: ensureCloudflared2 } = await Promise.resolve().then(() => (init_install_cloudflared(), install_cloudflared_exports));
26870
27089
  await ensureCloudflared2();
26871
27090
  } catch (err) {
26872
- log45.warn(
27091
+ log46.warn(
26873
27092
  { err: err.message },
26874
27093
  "Could not install cloudflared. Tunnel may not work."
26875
27094
  );
26876
27095
  }
26877
27096
  } else {
26878
27097
  if (!commandExists(tunnelProvider)) {
26879
- log45.warn(
27098
+ log46.warn(
26880
27099
  `Tunnel provider "${tunnelProvider}" is not installed. Install it or switch to cloudflare (free, auto-installed).`
26881
27100
  );
26882
27101
  }
@@ -26890,7 +27109,7 @@ async function runPostUpgradeChecks(config) {
26890
27109
  if (integration) {
26891
27110
  const allInstalled = integration.items.every((item) => item.isInstalled());
26892
27111
  if (!allInstalled) {
26893
- log45.info(
27112
+ log46.info(
26894
27113
  'Claude CLI integration not installed. Run "openacp integrate claude" for session transfer + tunnel skill.'
26895
27114
  );
26896
27115
  }
@@ -26900,7 +27119,7 @@ async function runPostUpgradeChecks(config) {
26900
27119
  const { ensureJq: ensureJq2 } = await Promise.resolve().then(() => (init_install_jq(), install_jq_exports));
26901
27120
  await ensureJq2();
26902
27121
  } catch (err) {
26903
- log45.warn(
27122
+ log46.warn(
26904
27123
  { err: err.message },
26905
27124
  "Could not install jq. Handoff hooks may not work."
26906
27125
  );
@@ -26910,7 +27129,7 @@ async function runPostUpgradeChecks(config) {
26910
27129
  } catch {
26911
27130
  }
26912
27131
  if (!commandExists("unzip")) {
26913
- log45.warn(
27132
+ log46.warn(
26914
27133
  "unzip is not installed. Some agent installations (binary distribution) may fail. Install: brew install unzip (macOS) or apt install unzip (Linux)"
26915
27134
  );
26916
27135
  }
@@ -26923,20 +27142,20 @@ async function runPostUpgradeChecks(config) {
26923
27142
  (a) => a.distribution === "uvx"
26924
27143
  );
26925
27144
  if (hasUvxAgent && !commandExists("uvx")) {
26926
- log45.warn(
27145
+ log46.warn(
26927
27146
  "uvx is not installed but you have Python-based agents. Install: pip install uv"
26928
27147
  );
26929
27148
  }
26930
27149
  } catch {
26931
27150
  }
26932
27151
  }
26933
- var log45;
27152
+ var log46;
26934
27153
  var init_post_upgrade = __esm({
26935
27154
  "src/cli/post-upgrade.ts"() {
26936
27155
  "use strict";
26937
27156
  init_log();
26938
27157
  init_agent_dependencies();
26939
- log45 = createChildLogger({ module: "post-upgrade" });
27158
+ log46 = createChildLogger({ module: "post-upgrade" });
26940
27159
  }
26941
27160
  });
26942
27161
 
@@ -27069,7 +27288,7 @@ async function startServer(opts) {
27069
27288
  serviceRegistry.register("field-registry", fieldRegistry, "core");
27070
27289
  registerSystemCommands(commandRegistry, core);
27071
27290
  try {
27072
- core.eventBus.emit("kernel:booted");
27291
+ core.eventBus.emit(BusEvent.KERNEL_BOOTED);
27073
27292
  core.lifecycleManager.settingsManager = settingsManager;
27074
27293
  core.lifecycleManager.pluginRegistry = pluginRegistry;
27075
27294
  await core.lifecycleManager.boot(corePlugins);
@@ -27182,8 +27401,8 @@ async function startServer(opts) {
27182
27401
  if (tunnelSvc) {
27183
27402
  core.tunnelService = tunnelSvc;
27184
27403
  }
27185
- core.eventBus.emit("system:commands-ready", { commands: commandRegistry.getAll() });
27186
- core.eventBus.emit("system:ready");
27404
+ core.eventBus.emit(BusEvent.SYSTEM_COMMANDS_READY, { commands: commandRegistry.getAll() });
27405
+ core.eventBus.emit(BusEvent.SYSTEM_READY);
27187
27406
  } catch (err) {
27188
27407
  if (spinner4) {
27189
27408
  spinner4.fail("Plugin boot failed");
@@ -27419,6 +27638,7 @@ var init_main = __esm({
27419
27638
  init_commands4();
27420
27639
  init_instance_registry();
27421
27640
  init_plugin_field_registry();
27641
+ init_events();
27422
27642
  RESTART_EXIT_CODE = 75;
27423
27643
  shuttingDown = false;
27424
27644
  isDirectExecution = process.argv[1]?.endsWith("main.js");
@@ -29165,6 +29385,15 @@ async function uninstallPlugin(name, purge, instanceRoot, json = false) {
29165
29385
  init_api_client();
29166
29386
  init_helpers();
29167
29387
  init_output();
29388
+ function extractApiError(data, fallback = "API request failed") {
29389
+ const err = data.error;
29390
+ if (!err) return fallback;
29391
+ if (typeof err === "string") return err;
29392
+ if (typeof err === "object" && err !== null && "message" in err) {
29393
+ return String(err.message);
29394
+ }
29395
+ return fallback;
29396
+ }
29168
29397
  function printApiHelp() {
29169
29398
  console.log(`
29170
29399
  \x1B[1mopenacp api\x1B[0m \u2014 Interact with the running OpenACP daemon
@@ -29458,8 +29687,8 @@ Shows the version of the currently running daemon process.
29458
29687
  });
29459
29688
  const data = await res.json();
29460
29689
  if (!res.ok) {
29461
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29462
- console.error(`Error: ${data.error}`);
29690
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29691
+ console.error(`Error: ${extractApiError(data)}`);
29463
29692
  process.exit(1);
29464
29693
  }
29465
29694
  if (json) jsonSuccess(data);
@@ -29482,8 +29711,8 @@ Shows the version of the currently running daemon process.
29482
29711
  });
29483
29712
  const data = await res.json();
29484
29713
  if (!res.ok) {
29485
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29486
- console.error(`Error: ${data.error}`);
29714
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29715
+ console.error(`Error: ${extractApiError(data)}`);
29487
29716
  process.exit(1);
29488
29717
  }
29489
29718
  if (json) jsonSuccess({ cancelled: true, sessionId });
@@ -29547,8 +29776,8 @@ Shows the version of the currently running daemon process.
29547
29776
  process.exit(1);
29548
29777
  }
29549
29778
  if (!res.ok) {
29550
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29551
- console.error(`Error: ${data.error}`);
29779
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29780
+ console.error(`Error: ${extractApiError(data)}`);
29552
29781
  process.exit(1);
29553
29782
  }
29554
29783
  if (json) jsonSuccess(data);
@@ -29594,8 +29823,8 @@ Shows the version of the currently running daemon process.
29594
29823
  });
29595
29824
  const data = await res.json();
29596
29825
  if (!res.ok) {
29597
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29598
- console.error(`Error: ${data.error}`);
29826
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29827
+ console.error(`Error: ${extractApiError(data)}`);
29599
29828
  process.exit(1);
29600
29829
  }
29601
29830
  if (json) jsonSuccess(data);
@@ -29610,8 +29839,8 @@ Shows the version of the currently running daemon process.
29610
29839
  const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}`);
29611
29840
  const data = await res.json();
29612
29841
  if (!res.ok) {
29613
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29614
- console.error(`Error: ${data.error}`);
29842
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29843
+ console.error(`Error: ${extractApiError(data)}`);
29615
29844
  process.exit(1);
29616
29845
  }
29617
29846
  if (json) jsonSuccess(data);
@@ -29648,8 +29877,8 @@ Shows the version of the currently running daemon process.
29648
29877
  });
29649
29878
  const data = await res.json();
29650
29879
  if (!res.ok) {
29651
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29652
- console.error(`Error: ${data.error}`);
29880
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29881
+ console.error(`Error: ${extractApiError(data)}`);
29653
29882
  process.exit(1);
29654
29883
  }
29655
29884
  if (json) jsonSuccess(data);
@@ -29659,8 +29888,8 @@ Shows the version of the currently running daemon process.
29659
29888
  const res = await call("/api/health");
29660
29889
  const data = await res.json();
29661
29890
  if (!res.ok) {
29662
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29663
- console.error(`Error: ${data.error}`);
29891
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29892
+ console.error(`Error: ${extractApiError(data)}`);
29664
29893
  process.exit(1);
29665
29894
  }
29666
29895
  if (json) jsonSuccess(data);
@@ -29685,8 +29914,8 @@ Shows the version of the currently running daemon process.
29685
29914
  const res = await call("/api/restart", { method: "POST" });
29686
29915
  const data = await res.json();
29687
29916
  if (!res.ok) {
29688
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29689
- console.error(`Error: ${data.error}`);
29917
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29918
+ console.error(`Error: ${extractApiError(data)}`);
29690
29919
  process.exit(1);
29691
29920
  }
29692
29921
  if (json) jsonSuccess({ restarted: true });
@@ -29698,8 +29927,8 @@ Shows the version of the currently running daemon process.
29698
29927
  const res = await call("/api/config");
29699
29928
  const data = await res.json();
29700
29929
  if (!res.ok) {
29701
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29702
- console.error(`Error: ${data.error}`);
29930
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29931
+ console.error(`Error: ${extractApiError(data)}`);
29703
29932
  process.exit(1);
29704
29933
  }
29705
29934
  if (json) jsonSuccess(data);
@@ -29724,8 +29953,8 @@ Shows the version of the currently running daemon process.
29724
29953
  });
29725
29954
  const data = await res.json();
29726
29955
  if (!res.ok) {
29727
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29728
- console.error(`Error: ${data.error}`);
29956
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29957
+ console.error(`Error: ${extractApiError(data)}`);
29729
29958
  process.exit(1);
29730
29959
  }
29731
29960
  if (json) jsonSuccess(data);
@@ -29757,8 +29986,8 @@ Shows the version of the currently running daemon process.
29757
29986
  const res = await call("/api/tunnel");
29758
29987
  const data = await res.json();
29759
29988
  if (!res.ok) {
29760
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29761
- console.error(`Error: ${data.error}`);
29989
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29990
+ console.error(`Error: ${extractApiError(data)}`);
29762
29991
  process.exit(1);
29763
29992
  }
29764
29993
  if (json) jsonSuccess(data);
@@ -29782,8 +30011,8 @@ Shows the version of the currently running daemon process.
29782
30011
  });
29783
30012
  const data = await res.json();
29784
30013
  if (!res.ok) {
29785
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29786
- console.error(`Error: ${data.error}`);
30014
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30015
+ console.error(`Error: ${extractApiError(data)}`);
29787
30016
  process.exit(1);
29788
30017
  }
29789
30018
  if (json) jsonSuccess({ sent: true });
@@ -29792,8 +30021,8 @@ Shows the version of the currently running daemon process.
29792
30021
  const res = await call("/api/version");
29793
30022
  const data = await res.json();
29794
30023
  if (!res.ok) {
29795
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29796
- console.error(`Error: ${data.error}`);
30024
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30025
+ console.error(`Error: ${extractApiError(data)}`);
29797
30026
  process.exit(1);
29798
30027
  }
29799
30028
  if (json) jsonSuccess(data);
@@ -29810,8 +30039,8 @@ Shows the version of the currently running daemon process.
29810
30039
  const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config`);
29811
30040
  const data = await res.json();
29812
30041
  if (!res.ok) {
29813
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29814
- console.error(`Error: ${data.error}`);
30042
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30043
+ console.error(`Error: ${extractApiError(data)}`);
29815
30044
  process.exit(1);
29816
30045
  }
29817
30046
  if (json) jsonSuccess(data);
@@ -29859,8 +30088,8 @@ Client overrides: ${JSON.stringify(clientOverrides)}`);
29859
30088
  });
29860
30089
  const data = await res.json();
29861
30090
  if (!res.ok) {
29862
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29863
- console.error(`Error: ${data.error}`);
30091
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30092
+ console.error(`Error: ${extractApiError(data)}`);
29864
30093
  process.exit(1);
29865
30094
  }
29866
30095
  if (json) jsonSuccess(data);
@@ -29874,8 +30103,8 @@ Client overrides: ${JSON.stringify(clientOverrides)}`);
29874
30103
  const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config/overrides`);
29875
30104
  const data = await res.json();
29876
30105
  if (!res.ok) {
29877
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29878
- console.error(`Error: ${data.error}`);
30106
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30107
+ console.error(`Error: ${extractApiError(data)}`);
29879
30108
  process.exit(1);
29880
30109
  }
29881
30110
  if (json) jsonSuccess(data);
@@ -29903,8 +30132,8 @@ Client overrides: ${JSON.stringify(clientOverrides)}`);
29903
30132
  });
29904
30133
  const data = await res.json();
29905
30134
  if (!res.ok) {
29906
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29907
- console.error(`Error: ${data.error}`);
30135
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30136
+ console.error(`Error: ${extractApiError(data)}`);
29908
30137
  process.exit(1);
29909
30138
  }
29910
30139
  if (json) jsonSuccess(data);
@@ -29914,8 +30143,8 @@ Client overrides: ${JSON.stringify(clientOverrides)}`);
29914
30143
  const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config/overrides`);
29915
30144
  const data = await res.json();
29916
30145
  if (!res.ok) {
29917
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29918
- console.error(`Error: ${data.error}`);
30146
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30147
+ console.error(`Error: ${extractApiError(data)}`);
29919
30148
  process.exit(1);
29920
30149
  }
29921
30150
  if (json) jsonSuccess(data);
@@ -30591,7 +30820,7 @@ agent-native handoff command such as /openacp:handoff.
30591
30820
  if (uninstall) {
30592
30821
  console.log(`Removing ${agent}/${item.id}...`);
30593
30822
  const result = await item.uninstall();
30594
- for (const log46 of result.logs) console.log(` ${log46}`);
30823
+ for (const log47 of result.logs) console.log(` ${log47}`);
30595
30824
  if (result.success) {
30596
30825
  console.log(` ${item.name} removed.`);
30597
30826
  } else {
@@ -30601,7 +30830,7 @@ agent-native handoff command such as /openacp:handoff.
30601
30830
  } else {
30602
30831
  console.log(`Installing ${agent}/${item.id}...`);
30603
30832
  const result = await item.install();
30604
- for (const log46 of result.logs) console.log(` ${log46}`);
30833
+ for (const log47 of result.logs) console.log(` ${log47}`);
30605
30834
  if (result.success) {
30606
30835
  console.log(` ${item.name} installed.`);
30607
30836
  } else {