@openacp/cli 2026.406.1 → 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";
@@ -14703,7 +14802,6 @@ var init_commands3 = __esm({
14703
14802
  { command: "menu", description: "Show menu" },
14704
14803
  { command: "integrate", description: "Manage agent integrations" },
14705
14804
  { command: "handoff", description: "Continue this session in your terminal" },
14706
- { command: "clear", description: "Clear assistant history" },
14707
14805
  { command: "restart", description: "Restart OpenACP" },
14708
14806
  { command: "update", description: "Update to latest version and restart" },
14709
14807
  { command: "doctor", description: "Run system diagnostics" },
@@ -16467,6 +16565,7 @@ var log29, TelegramAdapter;
16467
16565
  var init_adapter2 = __esm({
16468
16566
  "src/plugins/telegram/adapter.ts"() {
16469
16567
  "use strict";
16568
+ init_events();
16470
16569
  init_log();
16471
16570
  init_topics2();
16472
16571
  init_commands3();
@@ -16936,7 +17035,7 @@ ${p2}` : p2;
16936
17035
  log29.warn({ err, sessionId }, "Failed to send initial messages for new session");
16937
17036
  });
16938
17037
  };
16939
- this.core.eventBus.on("session:threadReady", this._threadReadyHandler);
17038
+ this.core.eventBus.on(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
16940
17039
  this._configChangedHandler = ({ sessionId }) => {
16941
17040
  this.updateControlMessage(sessionId).catch(() => {
16942
17041
  });
@@ -16969,7 +17068,7 @@ ${p2}` : p2;
16969
17068
  log29.warn({ err }, "Failed to send welcome message");
16970
17069
  }
16971
17070
  try {
16972
- await this.core.assistantManager.spawn("telegram", String(this.assistantTopicId));
17071
+ await this.core.assistantManager.getOrSpawn("telegram", String(this.assistantTopicId));
16973
17072
  } catch (err) {
16974
17073
  log29.error({ err }, "Failed to spawn assistant");
16975
17074
  }
@@ -17028,7 +17127,7 @@ OpenACP will automatically retry until this is resolved.`;
17028
17127
  }
17029
17128
  this.sessionTrackers.clear();
17030
17129
  if (this._threadReadyHandler) {
17031
- this.core.eventBus.off("session:threadReady", this._threadReadyHandler);
17130
+ this.core.eventBus.off(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
17032
17131
  this._threadReadyHandler = void 0;
17033
17132
  }
17034
17133
  if (this._configChangedHandler) {
@@ -18832,6 +18931,47 @@ var init_typed_emitter = __esm({
18832
18931
  }
18833
18932
  });
18834
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
+
18835
18975
  // src/core/sessions/terminal-manager.ts
18836
18976
  import { spawn as spawn7 } from "child_process";
18837
18977
  import { randomUUID } from "crypto";
@@ -18840,6 +18980,7 @@ var init_terminal_manager = __esm({
18840
18980
  "src/core/sessions/terminal-manager.ts"() {
18841
18981
  "use strict";
18842
18982
  init_env_filter();
18983
+ init_events();
18843
18984
  TerminalManager = class {
18844
18985
  terminals = /* @__PURE__ */ new Map();
18845
18986
  maxOutputBytes;
@@ -18856,7 +18997,7 @@ var init_terminal_manager = __esm({
18856
18997
  for (const ev of termEnvArr) {
18857
18998
  envRecord[ev.name] = ev.value;
18858
18999
  }
18859
- const result = await middlewareChain.execute("terminal:beforeCreate", {
19000
+ const result = await middlewareChain.execute(Hook.TERMINAL_BEFORE_CREATE, {
18860
19001
  sessionId,
18861
19002
  command: termCommand,
18862
19003
  args: termArgs,
@@ -18910,7 +19051,7 @@ var init_terminal_manager = __esm({
18910
19051
  childProcess.on("exit", (code, signal) => {
18911
19052
  state.exitStatus = { exitCode: code, signal };
18912
19053
  if (middlewareChain) {
18913
- middlewareChain.execute("terminal:afterExit", {
19054
+ middlewareChain.execute(Hook.TERMINAL_AFTER_EXIT, {
18914
19055
  sessionId,
18915
19056
  terminalId,
18916
19057
  command: state.command,
@@ -19124,10 +19265,12 @@ var init_agent_instance = __esm({
19124
19265
  init_stderr_capture();
19125
19266
  init_typed_emitter();
19126
19267
  init_read_text_file();
19268
+ init_attachment_blocks();
19127
19269
  init_terminal_manager();
19128
19270
  init_mcp_manager();
19129
19271
  init_debug_tracer();
19130
19272
  init_log();
19273
+ init_events();
19131
19274
  log31 = createChildLogger({ module: "agent-instance" });
19132
19275
  AgentInstance = class _AgentInstance extends TypedEmitter {
19133
19276
  connection;
@@ -19155,7 +19298,7 @@ var init_agent_instance = __esm({
19155
19298
  super();
19156
19299
  this.agentName = agentName;
19157
19300
  }
19158
- static async spawnSubprocess(agentDef, workingDirectory) {
19301
+ static async spawnSubprocess(agentDef, workingDirectory, allowedPaths = []) {
19159
19302
  const instance = new _AgentInstance(agentDef.name);
19160
19303
  const resolved = resolveAgentCommand(agentDef.command);
19161
19304
  log31.debug(
@@ -19169,10 +19312,7 @@ var init_agent_instance = __esm({
19169
19312
  const ignorePatterns = PathGuard.loadIgnoreFile(workingDirectory);
19170
19313
  instance.pathGuard = new PathGuard({
19171
19314
  cwd: workingDirectory,
19172
- // allowedPaths is wired from workspace.security.allowedPaths config;
19173
- // spawnSubprocess would need to receive a SecurityConfig param to use it.
19174
- // Tracked as follow-up: pass workspace security config through spawn/resume call chain.
19175
- allowedPaths: [],
19315
+ allowedPaths,
19176
19316
  ignorePatterns
19177
19317
  });
19178
19318
  instance.child = spawn8(
@@ -19266,7 +19406,7 @@ var init_agent_instance = __esm({
19266
19406
  if (signal === "SIGINT" || signal === "SIGTERM") return;
19267
19407
  if (code !== 0 && code !== null || signal) {
19268
19408
  const stderr = this.stderrCapture.getLastLines();
19269
- this.emit("agent_event", {
19409
+ this.emit(SessionEv.AGENT_EVENT, {
19270
19410
  type: "error",
19271
19411
  message: signal ? `Agent killed by signal ${signal}
19272
19412
  ${stderr}` : `Agent crashed (exit code ${code})
@@ -19278,7 +19418,7 @@ ${stderr}`
19278
19418
  log31.debug({ sessionId: this.sessionId }, "ACP connection closed");
19279
19419
  });
19280
19420
  }
19281
- static async spawn(agentDef, workingDirectory, mcpServers) {
19421
+ static async spawn(agentDef, workingDirectory, mcpServers, allowedPaths) {
19282
19422
  log31.debug(
19283
19423
  { agentName: agentDef.name, command: agentDef.command },
19284
19424
  "Spawning agent"
@@ -19286,7 +19426,8 @@ ${stderr}`
19286
19426
  const spawnStart = Date.now();
19287
19427
  const instance = await _AgentInstance.spawnSubprocess(
19288
19428
  agentDef,
19289
- workingDirectory
19429
+ workingDirectory,
19430
+ allowedPaths
19290
19431
  );
19291
19432
  const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
19292
19433
  const response = await instance.connection.newSession({
@@ -19309,12 +19450,13 @@ ${stderr}`
19309
19450
  );
19310
19451
  return instance;
19311
19452
  }
19312
- static async resume(agentDef, workingDirectory, agentSessionId, mcpServers) {
19453
+ static async resume(agentDef, workingDirectory, agentSessionId, mcpServers, allowedPaths) {
19313
19454
  log31.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
19314
19455
  const spawnStart = Date.now();
19315
19456
  const instance = await _AgentInstance.spawnSubprocess(
19316
19457
  agentDef,
19317
- workingDirectory
19458
+ workingDirectory,
19459
+ allowedPaths
19318
19460
  );
19319
19461
  const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
19320
19462
  try {
@@ -19502,7 +19644,7 @@ ${stderr}`
19502
19644
  return;
19503
19645
  }
19504
19646
  if (event !== null) {
19505
- self.emit("agent_event", event);
19647
+ self.emit(SessionEv.AGENT_EVENT, event);
19506
19648
  }
19507
19649
  },
19508
19650
  // ── Permission requests ──────────────────────────────────────────────
@@ -19529,7 +19671,7 @@ ${stderr}`
19529
19671
  throw new Error(`[Access denied] ${pathCheck.reason}`);
19530
19672
  }
19531
19673
  if (self.middlewareChain) {
19532
- 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);
19533
19675
  if (!result) return { content: "" };
19534
19676
  p2.path = result.path;
19535
19677
  }
@@ -19547,7 +19689,7 @@ ${stderr}`
19547
19689
  throw new Error(`[Access denied] ${pathCheck.reason}`);
19548
19690
  }
19549
19691
  if (self.middlewareChain) {
19550
- 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);
19551
19693
  if (!result) return {};
19552
19694
  writePath = result.path;
19553
19695
  writeContent = result.content;
@@ -19644,10 +19786,16 @@ ${stderr}`
19644
19786
  // ── Prompt & lifecycle ──────────────────────────────────────────────
19645
19787
  async prompt(text6, attachments) {
19646
19788
  const contentBlocks = [{ type: "text", text: text6 }];
19647
- const SUPPORTED_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
19789
+ const capabilities = this.promptCapabilities ?? {};
19648
19790
  for (const att of attachments ?? []) {
19649
- const tooLarge = att.size > 10 * 1024 * 1024;
19650
- 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)) {
19651
19799
  const attCheck = this.pathGuard.validatePath(att.filePath, "read");
19652
19800
  if (!attCheck.allowed) {
19653
19801
  contentBlocks[0].text += `
@@ -19657,7 +19805,7 @@ ${stderr}`
19657
19805
  }
19658
19806
  const data = await fs40.promises.readFile(att.filePath);
19659
19807
  contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
19660
- } else if (att.type === "audio" && this.promptCapabilities?.audio && !tooLarge) {
19808
+ } else if (att.type === "audio" && capabilities.audio) {
19661
19809
  const attCheck = this.pathGuard.validatePath(att.filePath, "read");
19662
19810
  if (!attCheck.allowed) {
19663
19811
  contentBlocks[0].text += `
@@ -19668,9 +19816,9 @@ ${stderr}`
19668
19816
  const data = await fs40.promises.readFile(att.filePath);
19669
19817
  contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
19670
19818
  } else {
19671
- if ((att.type === "image" || att.type === "audio") && !tooLarge) {
19819
+ if (att.type === "image" || att.type === "audio") {
19672
19820
  log31.debug(
19673
- { type: att.type, capabilities: this.promptCapabilities ?? {} },
19821
+ { type: att.type, capabilities },
19674
19822
  "Agent does not support %s content, falling back to file path",
19675
19823
  att.type
19676
19824
  );
@@ -19733,15 +19881,15 @@ var init_agent_manager = __esm({
19733
19881
  getAgent(name) {
19734
19882
  return this.catalog.resolve(name);
19735
19883
  }
19736
- async spawn(agentName, workingDirectory) {
19884
+ async spawn(agentName, workingDirectory, allowedPaths) {
19737
19885
  const agentDef = this.getAgent(agentName);
19738
19886
  if (!agentDef) throw new Error(`Agent "${agentName}" is not installed. Run "openacp agents install ${agentName}" to add it.`);
19739
- return AgentInstance.spawn(agentDef, workingDirectory);
19887
+ return AgentInstance.spawn(agentDef, workingDirectory, void 0, allowedPaths);
19740
19888
  }
19741
- async resume(agentName, workingDirectory, agentSessionId) {
19889
+ async resume(agentName, workingDirectory, agentSessionId, allowedPaths) {
19742
19890
  const agentDef = this.getAgent(agentName);
19743
19891
  if (!agentDef) throw new Error(`Agent "${agentName}" is not installed. Run "openacp agents install ${agentName}" to add it.`);
19744
- return AgentInstance.resume(agentDef, workingDirectory, agentSessionId);
19892
+ return AgentInstance.resume(agentDef, workingDirectory, agentSessionId, void 0, allowedPaths);
19745
19893
  }
19746
19894
  };
19747
19895
  }
@@ -19938,6 +20086,7 @@ var init_session2 = __esm({
19938
20086
  init_permission_gate();
19939
20087
  init_log();
19940
20088
  init_turn_context();
20089
+ init_events();
19941
20090
  moduleLog = createChildLogger({ module: "session" });
19942
20091
  TTS_PROMPT_INSTRUCTION = `
19943
20092
 
@@ -19966,7 +20115,15 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
19966
20115
  }
19967
20116
  agentName;
19968
20117
  workingDirectory;
19969
- 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
+ }
19970
20127
  agentSessionId = "";
19971
20128
  _status = "initializing";
19972
20129
  name;
@@ -19990,9 +20147,6 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
19990
20147
  threadIds = /* @__PURE__ */ new Map();
19991
20148
  /** Active turn context — sealed on prompt dequeue, cleared on turn end */
19992
20149
  activeTurnContext = null;
19993
- /** The agentInstance for which the agent→session event relay is wired (prevents duplicate relays from multiple bridges).
19994
- * When the agent is swapped, the relay must be re-wired to the new instance. */
19995
- agentRelaySource = null;
19996
20150
  permissionGate = new PermissionGate();
19997
20151
  queue;
19998
20152
  speechService;
@@ -20016,23 +20170,37 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20016
20170
  this.log.error({ err }, "Prompt execution failed");
20017
20171
  const message = err instanceof Error ? err.message : String(err);
20018
20172
  this.fail(message);
20019
- this.emit("agent_event", { type: "error", message: `Prompt execution failed: ${message}` });
20173
+ this.emit(SessionEv.AGENT_EVENT, { type: "error", message: `Prompt execution failed: ${message}` });
20020
20174
  }
20021
20175
  );
20022
- 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);
20023
20190
  }
20024
20191
  /** Wire a listener on the current agentInstance to buffer commands_update events.
20025
20192
  * Must be called after every agentInstance replacement (constructor + switchAgent). */
20026
20193
  commandsBufferCleanup;
20027
20194
  wireCommandsBuffer() {
20028
20195
  this.commandsBufferCleanup?.();
20196
+ const instance = this._agentInstance;
20029
20197
  const handler = (event) => {
20030
20198
  if (event.type === "commands_update") {
20031
20199
  this.latestCommands = event.commands;
20032
20200
  }
20033
20201
  };
20034
- this.agentInstance.on("agent_event", handler);
20035
- this.commandsBufferCleanup = () => this.agentInstance.off("agent_event", handler);
20202
+ instance.on(SessionEv.AGENT_EVENT, handler);
20203
+ this.commandsBufferCleanup = () => instance.off(SessionEv.AGENT_EVENT, handler);
20036
20204
  }
20037
20205
  // --- State Machine ---
20038
20206
  get status() {
@@ -20046,12 +20214,12 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20046
20214
  fail(reason) {
20047
20215
  if (this._status === "error") return;
20048
20216
  this.transition("error");
20049
- this.emit("error", new Error(reason));
20217
+ this.emit(SessionEv.ERROR, new Error(reason));
20050
20218
  }
20051
20219
  /** Transition to finished — from active only. Emits session_end for backward compat. */
20052
20220
  finish(reason) {
20053
20221
  this.transition("finished");
20054
- this.emit("session_end", reason ?? "completed");
20222
+ this.emit(SessionEv.SESSION_END, reason ?? "completed");
20055
20223
  }
20056
20224
  /** Transition to cancelled — from active only (terminal session cancel) */
20057
20225
  markCancelled() {
@@ -20067,7 +20235,7 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20067
20235
  }
20068
20236
  this._status = to;
20069
20237
  this.log.debug({ from, to }, "Session status transition");
20070
- this.emit("status_change", from, to);
20238
+ this.emit(SessionEv.STATUS_CHANGE, from, to);
20071
20239
  }
20072
20240
  /** Number of prompts waiting in queue */
20073
20241
  get queueDepth() {
@@ -20089,8 +20257,8 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20089
20257
  async enqueuePrompt(text6, attachments, routing, externalTurnId) {
20090
20258
  const turnId = externalTurnId ?? nanoid4(8);
20091
20259
  if (this.middlewareChain) {
20092
- const payload = { text: text6, attachments, sessionId: this.id };
20093
- 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);
20094
20262
  if (!result) return turnId;
20095
20263
  text6 = result.text;
20096
20264
  attachments = result.attachments;
@@ -20105,9 +20273,9 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
20105
20273
  routing?.responseAdapterId,
20106
20274
  turnId
20107
20275
  );
20108
- this.emit("turn_started", this.activeTurnContext);
20276
+ this.emit(SessionEv.TURN_STARTED, this.activeTurnContext);
20109
20277
  this.promptCount++;
20110
- this.emit("prompt_count_changed", this.promptCount);
20278
+ this.emit(SessionEv.PROMPT_COUNT_CHANGED, this.promptCount);
20111
20279
  if (this._status === "initializing" || this._status === "cancelled" || this._status === "error") {
20112
20280
  this.activate();
20113
20281
  }
@@ -20136,10 +20304,18 @@ ${text6}`;
20136
20304
  }
20137
20305
  } : null;
20138
20306
  if (accumulatorListener) {
20139
- 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);
20140
20316
  }
20141
20317
  if (this.middlewareChain) {
20142
- 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(() => {
20143
20319
  });
20144
20320
  }
20145
20321
  let stopReason = "end_turn";
@@ -20160,10 +20336,13 @@ ${text6}`;
20160
20336
  promptError = err;
20161
20337
  } finally {
20162
20338
  if (accumulatorListener) {
20163
- this.off("agent_event", accumulatorListener);
20339
+ this.off(SessionEv.AGENT_EVENT, accumulatorListener);
20340
+ }
20341
+ if (afterEventListener) {
20342
+ this.agentInstance.off(SessionEv.AGENT_EVENT, afterEventListener);
20164
20343
  }
20165
20344
  if (this.middlewareChain) {
20166
- 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(() => {
20167
20346
  });
20168
20347
  }
20169
20348
  this.activeTurnContext = null;
@@ -20208,7 +20387,7 @@ ${text6}`;
20208
20387
  const audioBuffer = await fs41.promises.readFile(audioPath);
20209
20388
  const result = await this.speechService.transcribe(audioBuffer, audioMime);
20210
20389
  this.log.info({ provider: "stt", duration: result.duration }, "Voice transcribed");
20211
- this.emit("agent_event", {
20390
+ this.emit(SessionEv.AGENT_EVENT, {
20212
20391
  type: "system_message",
20213
20392
  message: `\u{1F3A4} You said: ${result.text}`
20214
20393
  });
@@ -20217,7 +20396,7 @@ ${text6}`;
20217
20396
  ${result.text}` : result.text;
20218
20397
  } catch (err) {
20219
20398
  this.log.warn({ err }, "STT transcription failed, keeping audio attachment");
20220
- this.emit("agent_event", {
20399
+ this.emit(SessionEv.AGENT_EVENT, {
20221
20400
  type: "error",
20222
20401
  message: `Voice transcription failed: ${err.message}`
20223
20402
  });
@@ -20251,12 +20430,12 @@ ${result.text}` : result.text;
20251
20430
  timeoutPromise
20252
20431
  ]);
20253
20432
  const base64 = result.audioBuffer.toString("base64");
20254
- this.emit("agent_event", {
20433
+ this.emit(SessionEv.AGENT_EVENT, {
20255
20434
  type: "audio_content",
20256
20435
  data: base64,
20257
20436
  mimeType: result.mimeType
20258
20437
  });
20259
- this.emit("agent_event", { type: "tts_strip" });
20438
+ this.emit(SessionEv.AGENT_EVENT, { type: "tts_strip" });
20260
20439
  this.log.info("TTS synthesis completed");
20261
20440
  } finally {
20262
20441
  clearTimeout(ttsTimer);
@@ -20271,19 +20450,19 @@ ${result.text}` : result.text;
20271
20450
  const captureHandler = (event) => {
20272
20451
  if (event.type === "text") title += event.content;
20273
20452
  };
20274
- this.pause((event) => event !== "agent_event");
20275
- this.agentInstance.on("agent_event", captureHandler);
20453
+ this.pause((event) => event !== SessionEv.AGENT_EVENT);
20454
+ this.agentInstance.on(SessionEv.AGENT_EVENT, captureHandler);
20276
20455
  try {
20277
20456
  await this.agentInstance.prompt(
20278
20457
  "Summarize this conversation in max 5 words for a topic title. Reply ONLY with the title, nothing else."
20279
20458
  );
20280
20459
  this.name = title.trim().slice(0, 50) || `Session ${this.id.slice(0, 6)}`;
20281
20460
  this.log.info({ name: this.name }, "Session auto-named");
20282
- this.emit("named", this.name);
20461
+ this.emit(SessionEv.NAMED, this.name);
20283
20462
  } catch {
20284
20463
  this.name = `Session ${this.id.slice(0, 6)}`;
20285
20464
  } finally {
20286
- this.agentInstance.off("agent_event", captureHandler);
20465
+ this.agentInstance.off(SessionEv.AGENT_EVENT, captureHandler);
20287
20466
  this.clearBuffer();
20288
20467
  this.resume();
20289
20468
  }
@@ -20345,7 +20524,7 @@ ${result.text}` : result.text;
20345
20524
  /** Set session name explicitly and emit 'named' event */
20346
20525
  setName(name) {
20347
20526
  this.name = name;
20348
- this.emit("named", name);
20527
+ this.emit(SessionEv.NAMED, name);
20349
20528
  }
20350
20529
  /** Send a config option change to the agent and update local state from the response. */
20351
20530
  async setConfigOption(configId, value) {
@@ -20361,7 +20540,7 @@ ${result.text}` : result.text;
20361
20540
  }
20362
20541
  async updateConfigOptions(options) {
20363
20542
  if (this.middlewareChain) {
20364
- 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);
20365
20544
  if (!result) return;
20366
20545
  }
20367
20546
  this.configOptions = options;
@@ -20381,7 +20560,7 @@ ${result.text}` : result.text;
20381
20560
  /** Cancel the current prompt and clear the queue. Stays in active state. */
20382
20561
  async abortPrompt() {
20383
20562
  if (this.middlewareChain) {
20384
- 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);
20385
20564
  if (!result) return;
20386
20565
  }
20387
20566
  this.queue.clear();
@@ -20422,7 +20601,6 @@ ${result.text}` : result.text;
20422
20601
  this.configOptions = [];
20423
20602
  this.latestCommands = null;
20424
20603
  this.applySpawnResponse(newAgent.initialSessionResponse, newAgent.agentCapabilities);
20425
- this.wireCommandsBuffer();
20426
20604
  this.log.info({ from: this.agentSwitchHistory.at(-1).agentName, to: agentName }, "Agent switched");
20427
20605
  }
20428
20606
  async destroy() {
@@ -20444,6 +20622,7 @@ var init_session_manager = __esm({
20444
20622
  "src/core/sessions/session-manager.ts"() {
20445
20623
  "use strict";
20446
20624
  init_session2();
20625
+ init_events();
20447
20626
  SessionManager = class {
20448
20627
  sessions = /* @__PURE__ */ new Map();
20449
20628
  store;
@@ -20548,18 +20727,18 @@ var init_session_manager = __esm({
20548
20727
  }
20549
20728
  }
20550
20729
  if (this.middlewareChain) {
20551
- this.middlewareChain.execute("session:afterDestroy", { sessionId }, async (p2) => p2).catch(() => {
20730
+ this.middlewareChain.execute(Hook.SESSION_AFTER_DESTROY, { sessionId }, async (p2) => p2).catch(() => {
20552
20731
  });
20553
20732
  }
20554
20733
  }
20555
20734
  listSessions(channelId) {
20556
- const all = Array.from(this.sessions.values());
20735
+ const all = Array.from(this.sessions.values()).filter((s) => !s.isAssistant);
20557
20736
  if (channelId) return all.filter((s) => s.channelId === channelId);
20558
20737
  return all;
20559
20738
  }
20560
20739
  listAllSessions(channelId) {
20561
20740
  if (this.store) {
20562
- let records = this.store.list();
20741
+ let records = this.store.list().filter((r) => !r.isAssistant);
20563
20742
  if (channelId) records = records.filter((r) => r.channelId === channelId);
20564
20743
  return records.map((record) => {
20565
20744
  const live2 = this.sessions.get(record.sessionId);
@@ -20599,7 +20778,7 @@ var init_session_manager = __esm({
20599
20778
  };
20600
20779
  });
20601
20780
  }
20602
- let live = Array.from(this.sessions.values());
20781
+ let live = Array.from(this.sessions.values()).filter((s) => !s.isAssistant);
20603
20782
  if (channelId) live = live.filter((s) => s.channelId === channelId);
20604
20783
  return live.map((s) => ({
20605
20784
  id: s.id,
@@ -20620,7 +20799,7 @@ var init_session_manager = __esm({
20620
20799
  }
20621
20800
  listRecords(filter) {
20622
20801
  if (!this.store) return [];
20623
- let records = this.store.list();
20802
+ let records = this.store.list().filter((r) => !r.isAssistant);
20624
20803
  if (filter?.statuses?.length) {
20625
20804
  records = records.filter((r) => filter.statuses.includes(r.status));
20626
20805
  }
@@ -20629,7 +20808,7 @@ var init_session_manager = __esm({
20629
20808
  async removeRecord(sessionId) {
20630
20809
  if (!this.store) return;
20631
20810
  await this.store.remove(sessionId);
20632
- this.eventBus?.emit("session:deleted", { sessionId });
20811
+ this.eventBus?.emit(BusEvent.SESSION_DELETED, { sessionId });
20633
20812
  }
20634
20813
  /**
20635
20814
  * Graceful shutdown: persist session state without killing agent subprocesses.
@@ -20677,7 +20856,7 @@ var init_session_manager = __esm({
20677
20856
  this.sessions.clear();
20678
20857
  if (this.middlewareChain) {
20679
20858
  for (const sessionId of sessionIds) {
20680
- this.middlewareChain.execute("session:afterDestroy", { sessionId }, async (p2) => p2).catch(() => {
20859
+ this.middlewareChain.execute(Hook.SESSION_AFTER_DESTROY, { sessionId }, async (p2) => p2).catch(() => {
20681
20860
  });
20682
20861
  }
20683
20862
  }
@@ -20694,6 +20873,7 @@ var init_session_bridge = __esm({
20694
20873
  init_log();
20695
20874
  init_bypass_detection();
20696
20875
  init_turn_context();
20876
+ init_events();
20697
20877
  log32 = createChildLogger({ module: "session-bridge" });
20698
20878
  SessionBridge = class {
20699
20879
  constructor(session, adapter, deps, adapterId) {
@@ -20718,7 +20898,7 @@ var init_session_bridge = __esm({
20718
20898
  try {
20719
20899
  const mw = this.deps.middlewareChain;
20720
20900
  if (mw) {
20721
- 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);
20722
20902
  this.tracer?.log("core", { step: "middleware:outgoing", sessionId, hook: "message:outgoing", blocked: !result });
20723
20903
  if (!result) return;
20724
20904
  this.tracer?.log("core", { step: "dispatch", sessionId, message: result.message });
@@ -20747,17 +20927,11 @@ var init_session_bridge = __esm({
20747
20927
  connect() {
20748
20928
  if (this.connected) return;
20749
20929
  this.connected = true;
20750
- if (this.session.agentRelaySource !== this.session.agentInstance) {
20751
- this.listen(this.session.agentInstance, "agent_event", (event) => {
20752
- this.session.emit("agent_event", event);
20753
- });
20754
- this.session.agentRelaySource = this.session.agentInstance;
20755
- }
20756
- this.listen(this.session, "agent_event", (event) => {
20930
+ this.listen(this.session, SessionEv.AGENT_EVENT, (event) => {
20757
20931
  if (this.shouldForward(event)) {
20758
20932
  this.dispatchAgentEvent(event);
20759
20933
  } else {
20760
- this.deps.eventBus?.emit("agent:event", { sessionId: this.session.id, event });
20934
+ this.deps.eventBus?.emit(BusEvent.AGENT_EVENT, { sessionId: this.session.id, event });
20761
20935
  }
20762
20936
  });
20763
20937
  if (!this.session.agentInstance.onPermissionRequest || this.session.agentInstance.onPermissionRequest.__bridgeId === void 0) {
@@ -20767,7 +20941,7 @@ var init_session_bridge = __esm({
20767
20941
  handler.__bridgeId = this.adapterId;
20768
20942
  this.session.agentInstance.onPermissionRequest = handler;
20769
20943
  }
20770
- this.listen(this.session, "permission_request", async (request) => {
20944
+ this.listen(this.session, SessionEv.PERMISSION_REQUEST, async (request) => {
20771
20945
  const current = this.session.agentInstance.onPermissionRequest;
20772
20946
  if (current?.__bridgeId === this.adapterId) return;
20773
20947
  if (!this.session.permissionGate.isPending) return;
@@ -20777,37 +20951,41 @@ var init_session_bridge = __esm({
20777
20951
  log32.error({ err, sessionId: this.session.id, adapterId: this.adapterId }, "Failed to send permission request to adapter");
20778
20952
  }
20779
20953
  });
20780
- this.listen(this.session, "status_change", (from, to) => {
20954
+ this.listen(this.session, SessionEv.STATUS_CHANGE, (from, to) => {
20781
20955
  this.deps.sessionManager.patchRecord(this.session.id, {
20782
20956
  status: to,
20783
20957
  lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
20784
20958
  });
20785
- this.deps.eventBus?.emit("session:updated", {
20786
- sessionId: this.session.id,
20787
- status: to
20788
- });
20959
+ if (!this.session.isAssistant) {
20960
+ this.deps.eventBus?.emit(BusEvent.SESSION_UPDATED, {
20961
+ sessionId: this.session.id,
20962
+ status: to
20963
+ });
20964
+ }
20789
20965
  if (to === "finished") {
20790
20966
  queueMicrotask(() => this.disconnect());
20791
20967
  }
20792
20968
  });
20793
- this.listen(this.session, "named", async (name) => {
20969
+ this.listen(this.session, SessionEv.NAMED, async (name) => {
20794
20970
  const record = this.deps.sessionManager.getSessionRecord(this.session.id);
20795
20971
  const alreadyNamed = !!record?.name;
20796
20972
  await this.deps.sessionManager.patchRecord(this.session.id, { name });
20797
- this.deps.eventBus?.emit("session:updated", {
20798
- sessionId: this.session.id,
20799
- name
20800
- });
20973
+ if (!this.session.isAssistant) {
20974
+ this.deps.eventBus?.emit(BusEvent.SESSION_UPDATED, {
20975
+ sessionId: this.session.id,
20976
+ name
20977
+ });
20978
+ }
20801
20979
  if (!alreadyNamed) {
20802
20980
  await this.adapter.renameSessionThread(this.session.id, name);
20803
20981
  }
20804
20982
  });
20805
- this.listen(this.session, "prompt_count_changed", (count) => {
20983
+ this.listen(this.session, SessionEv.PROMPT_COUNT_CHANGED, (count) => {
20806
20984
  this.deps.sessionManager.patchRecord(this.session.id, { currentPromptCount: count });
20807
20985
  });
20808
- this.listen(this.session, "turn_started", (ctx) => {
20986
+ this.listen(this.session, SessionEv.TURN_STARTED, (ctx) => {
20809
20987
  if (ctx.sourceAdapterId !== "sse" && ctx.sourceAdapterId !== "api") {
20810
- this.deps.eventBus?.emit("message:processing", {
20988
+ this.deps.eventBus?.emit(BusEvent.MESSAGE_PROCESSING, {
20811
20989
  sessionId: this.session.id,
20812
20990
  turnId: ctx.turnId,
20813
20991
  sourceAdapterId: ctx.sourceAdapterId,
@@ -20816,10 +20994,10 @@ var init_session_bridge = __esm({
20816
20994
  }
20817
20995
  });
20818
20996
  if (this.session.latestCommands !== null) {
20819
- 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 });
20820
20998
  }
20821
20999
  if (this.session.configOptions.length > 0) {
20822
- 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 });
20823
21001
  }
20824
21002
  }
20825
21003
  disconnect() {
@@ -20839,17 +21017,11 @@ var init_session_bridge = __esm({
20839
21017
  const mw = this.deps.middlewareChain;
20840
21018
  if (mw) {
20841
21019
  try {
20842
- 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);
20843
21021
  this.tracer?.log("core", { step: "middleware:before", sessionId: this.session.id, hook: "agent:beforeEvent", blocked: !result });
20844
21022
  if (!result) return;
20845
21023
  const transformedEvent = result.event;
20846
- const outgoing = this.handleAgentEvent(transformedEvent);
20847
- mw.execute("agent:afterEvent", {
20848
- sessionId: this.session.id,
20849
- event: transformedEvent,
20850
- outgoingMessage: outgoing ?? { type: "text", text: "" }
20851
- }, async (e) => e).catch(() => {
20852
- });
21024
+ this.handleAgentEvent(transformedEvent);
20853
21025
  } catch {
20854
21026
  try {
20855
21027
  this.handleAgentEvent(event);
@@ -20982,7 +21154,7 @@ var init_session_bridge = __esm({
20982
21154
  this.adapter.stripTTSBlock?.(this.session.id);
20983
21155
  break;
20984
21156
  }
20985
- this.deps.eventBus?.emit("agent:event", {
21157
+ this.deps.eventBus?.emit(BusEvent.AGENT_EVENT, {
20986
21158
  sessionId: this.session.id,
20987
21159
  event
20988
21160
  });
@@ -21001,7 +21173,7 @@ var init_session_bridge = __esm({
21001
21173
  let permReq = request;
21002
21174
  if (mw) {
21003
21175
  const payload = { sessionId: this.session.id, request, autoResolve: void 0 };
21004
- 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);
21005
21177
  if (!result) return "";
21006
21178
  permReq = result.request;
21007
21179
  if (result.autoResolve) {
@@ -21009,21 +21181,21 @@ var init_session_bridge = __esm({
21009
21181
  return result.autoResolve;
21010
21182
  }
21011
21183
  }
21012
- this.deps.eventBus?.emit("permission:request", {
21184
+ this.deps.eventBus?.emit(BusEvent.PERMISSION_REQUEST, {
21013
21185
  sessionId: this.session.id,
21014
21186
  permission: permReq
21015
21187
  });
21016
21188
  const autoDecision = this.checkAutoApprove(permReq);
21017
21189
  if (autoDecision) {
21018
- this.session.emit("permission_request", permReq);
21190
+ this.session.emit(SessionEv.PERMISSION_REQUEST, permReq);
21019
21191
  this.emitAfterResolve(mw, permReq.id, autoDecision, "system", startTime);
21020
21192
  return autoDecision;
21021
21193
  }
21022
21194
  const promise = this.session.permissionGate.setPending(permReq);
21023
- this.session.emit("permission_request", permReq);
21195
+ this.session.emit(SessionEv.PERMISSION_REQUEST, permReq);
21024
21196
  await this.adapter.sendPermissionRequest(this.session.id, permReq);
21025
21197
  const optionId = await promise;
21026
- this.deps.eventBus?.emit("permission:resolved", {
21198
+ this.deps.eventBus?.emit(BusEvent.PERMISSION_RESOLVED, {
21027
21199
  sessionId: this.session.id,
21028
21200
  requestId: permReq.id,
21029
21201
  decision: optionId,
@@ -21055,7 +21227,7 @@ var init_session_bridge = __esm({
21055
21227
  /** Emit permission:afterResolve middleware hook (fire-and-forget) */
21056
21228
  emitAfterResolve(mw, requestId, decision, userId, startTime) {
21057
21229
  if (mw) {
21058
- mw.execute("permission:afterResolve", {
21230
+ mw.execute(Hook.PERMISSION_AFTER_RESOLVE, {
21059
21231
  sessionId: this.session.id,
21060
21232
  requestId,
21061
21233
  decision,
@@ -21563,6 +21735,14 @@ var init_session_store = __esm({
21563
21735
  }
21564
21736
  return void 0;
21565
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
+ }
21566
21746
  list(channelId) {
21567
21747
  const all = [...this.records.values()];
21568
21748
  if (channelId) return all.filter((r) => r.channelId === channelId);
@@ -21642,6 +21822,8 @@ var init_session_store = __esm({
21642
21822
  for (const [id, record] of this.records) {
21643
21823
  if (record.status === "active" || record.status === "initializing")
21644
21824
  continue;
21825
+ if (record.isAssistant === true)
21826
+ continue;
21645
21827
  const raw = record.lastActiveAt;
21646
21828
  if (!raw) continue;
21647
21829
  const lastActive = new Date(raw).getTime();
@@ -21673,6 +21855,7 @@ var init_session_factory = __esm({
21673
21855
  "use strict";
21674
21856
  init_session2();
21675
21857
  init_log();
21858
+ init_events();
21676
21859
  log35 = createChildLogger({ module: "session-factory" });
21677
21860
  SessionFactory = class {
21678
21861
  constructor(agentManager, sessionManager, speechServiceAccessor, eventBus, instanceRoot) {
@@ -21715,7 +21898,7 @@ var init_session_factory = __esm({
21715
21898
  threadId: ""
21716
21899
  // threadId is assigned after session creation
21717
21900
  };
21718
- 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);
21719
21902
  if (!result) throw new Error("Session creation blocked by middleware");
21720
21903
  createParams = {
21721
21904
  ...params,
@@ -21724,6 +21907,7 @@ var init_session_factory = __esm({
21724
21907
  channelId: result.channelId
21725
21908
  };
21726
21909
  }
21910
+ const configAllowedPaths = this.configManager?.get().workspace?.security?.allowedPaths ?? [];
21727
21911
  let agentInstance;
21728
21912
  try {
21729
21913
  if (createParams.resumeAgentSessionId) {
@@ -21731,7 +21915,8 @@ var init_session_factory = __esm({
21731
21915
  agentInstance = await this.agentManager.resume(
21732
21916
  createParams.agentName,
21733
21917
  createParams.workingDirectory,
21734
- createParams.resumeAgentSessionId
21918
+ createParams.resumeAgentSessionId,
21919
+ configAllowedPaths
21735
21920
  );
21736
21921
  } catch (resumeErr) {
21737
21922
  log35.warn(
@@ -21740,13 +21925,15 @@ var init_session_factory = __esm({
21740
21925
  );
21741
21926
  agentInstance = await this.agentManager.spawn(
21742
21927
  createParams.agentName,
21743
- createParams.workingDirectory
21928
+ createParams.workingDirectory,
21929
+ configAllowedPaths
21744
21930
  );
21745
21931
  }
21746
21932
  } else {
21747
21933
  agentInstance = await this.agentManager.spawn(
21748
21934
  createParams.agentName,
21749
- createParams.workingDirectory
21935
+ createParams.workingDirectory,
21936
+ configAllowedPaths
21750
21937
  );
21751
21938
  }
21752
21939
  } catch (err) {
@@ -21778,7 +21965,7 @@ var init_session_factory = __esm({
21778
21965
  message: guidanceLines.join("\n")
21779
21966
  };
21780
21967
  const failedSessionId = createParams.existingSessionId ?? `failed-${Date.now()}`;
21781
- this.eventBus.emit("agent:event", {
21968
+ this.eventBus.emit(BusEvent.AGENT_EVENT, {
21782
21969
  sessionId: failedSessionId,
21783
21970
  event: guidance
21784
21971
  });
@@ -21804,11 +21991,13 @@ var init_session_factory = __esm({
21804
21991
  }
21805
21992
  session.applySpawnResponse(agentInstance.initialSessionResponse, agentInstance.agentCapabilities);
21806
21993
  this.sessionManager.registerSession(session);
21807
- this.eventBus.emit("session:created", {
21808
- sessionId: session.id,
21809
- agent: session.agentName,
21810
- status: session.status
21811
- });
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
+ }
21812
22001
  return session;
21813
22002
  }
21814
22003
  /**
@@ -21826,6 +22015,7 @@ var init_session_factory = __esm({
21826
22015
  if (!this.sessionStore || !this.createFullSession) return null;
21827
22016
  const record = this.sessionStore.get(sessionId);
21828
22017
  if (!record) return null;
22018
+ if (record.isAssistant) return null;
21829
22019
  if (record.status === "error" || record.status === "cancelled") return null;
21830
22020
  const existing = this.resumeLocks.get(sessionId);
21831
22021
  if (existing) return existing;
@@ -21893,6 +22083,7 @@ var init_session_factory = __esm({
21893
22083
  log35.debug({ threadId, channelId }, "No session record found for thread");
21894
22084
  return null;
21895
22085
  }
22086
+ if (record.isAssistant) return null;
21896
22087
  if (record.status === "error" || record.status === "cancelled") {
21897
22088
  log35.warn(
21898
22089
  { threadId, sessionId: record.sessionId, status: record.status },
@@ -22052,9 +22243,9 @@ var init_session_factory = __esm({
22052
22243
  return { session, contextResult };
22053
22244
  }
22054
22245
  wireSideEffects(session, deps) {
22055
- session.on("agent_event", (event) => {
22246
+ session.on(SessionEv.AGENT_EVENT, (event) => {
22056
22247
  if (event.type !== "usage") return;
22057
- deps.eventBus.emit("usage:recorded", {
22248
+ deps.eventBus.emit(BusEvent.USAGE_RECORDED, {
22058
22249
  sessionId: session.id,
22059
22250
  agentName: session.agentName,
22060
22251
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -22063,7 +22254,7 @@ var init_session_factory = __esm({
22063
22254
  cost: event.cost
22064
22255
  });
22065
22256
  });
22066
- session.on("status_change", (_from, to) => {
22257
+ session.on(SessionEv.STATUS_CHANGE, (_from, to) => {
22067
22258
  if ((to === "finished" || to === "cancelled") && deps.tunnelService) {
22068
22259
  deps.tunnelService.stopBySession(session.id).then((stopped) => {
22069
22260
  for (const entry of stopped) {
@@ -22091,6 +22282,7 @@ var init_agent_switch_handler = __esm({
22091
22282
  "use strict";
22092
22283
  init_agent_registry();
22093
22284
  init_log();
22285
+ init_events();
22094
22286
  log36 = createChildLogger({ module: "agent-switch" });
22095
22287
  AgentSwitchHandler = class {
22096
22288
  constructor(deps) {
@@ -22116,7 +22308,7 @@ var init_agent_switch_handler = __esm({
22116
22308
  if (!agentDef) throw new Error(`Agent "${toAgent}" is not installed`);
22117
22309
  const fromAgent = session.agentName;
22118
22310
  const middlewareChain = this.deps.getMiddlewareChain();
22119
- const result = await middlewareChain?.execute("agent:beforeSwitch", {
22311
+ const result = await middlewareChain?.execute(Hook.AGENT_BEFORE_SWITCH, {
22120
22312
  sessionId,
22121
22313
  fromAgent,
22122
22314
  toAgent
@@ -22130,9 +22322,9 @@ var init_agent_switch_handler = __esm({
22130
22322
  type: "system_message",
22131
22323
  message: `Switching from ${fromAgent} to ${toAgent}...`
22132
22324
  };
22133
- session.emit("agent_event", startEvent);
22134
- eventBus.emit("agent:event", { sessionId, event: startEvent });
22135
- 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, {
22136
22328
  sessionId,
22137
22329
  fromAgent,
22138
22330
  toAgent,
@@ -22156,11 +22348,12 @@ var init_agent_switch_handler = __esm({
22156
22348
  }
22157
22349
  const fromAgentSessionId = session.agentSessionId;
22158
22350
  const fileService = this.deps.getService("file-service");
22351
+ const configAllowedPaths = configManager.get().workspace?.security?.allowedPaths ?? [];
22159
22352
  try {
22160
22353
  await session.switchAgent(toAgent, async () => {
22161
22354
  if (canResume) {
22162
22355
  try {
22163
- const instance2 = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId);
22356
+ const instance2 = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId, configAllowedPaths);
22164
22357
  if (fileService) instance2.addAllowedPath(fileService.baseDir);
22165
22358
  resumed = true;
22166
22359
  return instance2;
@@ -22168,7 +22361,7 @@ var init_agent_switch_handler = __esm({
22168
22361
  log36.warn({ sessionId, toAgent }, "Resume failed, falling back to new agent with context injection");
22169
22362
  }
22170
22363
  }
22171
- const instance = await agentManager.spawn(toAgent, session.workingDirectory);
22364
+ const instance = await agentManager.spawn(toAgent, session.workingDirectory, configAllowedPaths);
22172
22365
  if (fileService) instance.addAllowedPath(fileService.baseDir);
22173
22366
  try {
22174
22367
  const contextService = this.deps.getService("context");
@@ -22192,9 +22385,9 @@ var init_agent_switch_handler = __esm({
22192
22385
  type: "system_message",
22193
22386
  message: resumed ? `Switched to ${toAgent} (resumed previous session).` : `Switched to ${toAgent} (new session).`
22194
22387
  };
22195
- session.emit("agent_event", successEvent);
22196
- eventBus.emit("agent:event", { sessionId, event: successEvent });
22197
- 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, {
22198
22391
  sessionId,
22199
22392
  fromAgent,
22200
22393
  toAgent,
@@ -22207,9 +22400,9 @@ var init_agent_switch_handler = __esm({
22207
22400
  type: "system_message",
22208
22401
  message: `Failed to switch to ${toAgent}: ${errorMessage}`
22209
22402
  };
22210
- session.emit("agent_event", failedEvent);
22211
- eventBus.emit("agent:event", { sessionId, event: failedEvent });
22212
- 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, {
22213
22406
  sessionId,
22214
22407
  fromAgent,
22215
22408
  toAgent,
@@ -22258,7 +22451,7 @@ var init_agent_switch_handler = __esm({
22258
22451
  currentPromptCount: 0,
22259
22452
  agentSwitchHistory: session.agentSwitchHistory
22260
22453
  });
22261
- middlewareChain?.execute("agent:afterSwitch", {
22454
+ middlewareChain?.execute(Hook.AGENT_AFTER_SWITCH, {
22262
22455
  sessionId,
22263
22456
  fromAgent,
22264
22457
  toAgent,
@@ -23209,6 +23402,7 @@ var init_lifecycle_manager = __esm({
23209
23402
  init_middleware_chain();
23210
23403
  init_error_tracker();
23211
23404
  init_plugin_context();
23405
+ init_events();
23212
23406
  SETUP_TIMEOUT_MS = 3e4;
23213
23407
  TEARDOWN_TIMEOUT_MS = 1e4;
23214
23408
  LifecycleManager = class {
@@ -23315,7 +23509,7 @@ var init_lifecycle_manager = __esm({
23315
23509
  }
23316
23510
  const registryEntry = this.pluginRegistry?.get(plugin2.name);
23317
23511
  if (registryEntry && registryEntry.enabled === false) {
23318
- this.eventBus?.emit("plugin:disabled", { name: plugin2.name });
23512
+ this.eventBus?.emit(BusEvent.PLUGIN_DISABLED, { name: plugin2.name });
23319
23513
  continue;
23320
23514
  }
23321
23515
  if (registryEntry && plugin2.migrate && registryEntry.version !== plugin2.version && this.settingsManager) {
@@ -23358,7 +23552,7 @@ var init_lifecycle_manager = __esm({
23358
23552
  if (!validation.valid) {
23359
23553
  this._failed.add(plugin2.name);
23360
23554
  this.getPluginLogger(plugin2.name).error(`Settings validation failed: ${validation.errors?.join("; ")}`);
23361
- 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("; ")}` });
23362
23556
  continue;
23363
23557
  }
23364
23558
  }
@@ -23381,13 +23575,13 @@ var init_lifecycle_manager = __esm({
23381
23575
  await withTimeout(plugin2.setup(ctx), SETUP_TIMEOUT_MS, `${plugin2.name}.setup()`);
23382
23576
  this.contexts.set(plugin2.name, ctx);
23383
23577
  this._loaded.add(plugin2.name);
23384
- this.eventBus?.emit("plugin:loaded", { name: plugin2.name, version: plugin2.version });
23578
+ this.eventBus?.emit(BusEvent.PLUGIN_LOADED, { name: plugin2.name, version: plugin2.version });
23385
23579
  } catch (err) {
23386
23580
  this._failed.add(plugin2.name);
23387
23581
  ctx.cleanup();
23388
23582
  console.error(`[lifecycle] Plugin ${plugin2.name} setup() FAILED:`, err);
23389
23583
  this.getPluginLogger(plugin2.name).error(`setup() failed: ${err}`);
23390
- this.eventBus?.emit("plugin:failed", { name: plugin2.name, error: String(err) });
23584
+ this.eventBus?.emit(BusEvent.PLUGIN_FAILED, { name: plugin2.name, error: String(err) });
23391
23585
  }
23392
23586
  }
23393
23587
  }
@@ -23408,7 +23602,7 @@ var init_lifecycle_manager = __esm({
23408
23602
  this._loaded.delete(name);
23409
23603
  this._failed.delete(name);
23410
23604
  this.loadOrder = this.loadOrder.filter((p2) => p2.name !== name);
23411
- this.eventBus?.emit("plugin:unloaded", { name });
23605
+ this.eventBus?.emit(BusEvent.PLUGIN_UNLOADED, { name });
23412
23606
  }
23413
23607
  async shutdown() {
23414
23608
  const reversed = [...this.loadOrder].reverse();
@@ -23425,7 +23619,7 @@ var init_lifecycle_manager = __esm({
23425
23619
  ctx.cleanup();
23426
23620
  this.contexts.delete(plugin2.name);
23427
23621
  }
23428
- this.eventBus?.emit("plugin:unloaded", { name: plugin2.name });
23622
+ this.eventBus?.emit(BusEvent.PLUGIN_UNLOADED, { name: plugin2.name });
23429
23623
  }
23430
23624
  this._loaded.clear();
23431
23625
  this.loadOrder = [];
@@ -23596,21 +23790,25 @@ var init_assistant_manager = __esm({
23596
23790
  this.registry = registry;
23597
23791
  }
23598
23792
  sessions = /* @__PURE__ */ new Map();
23599
- respawning = /* @__PURE__ */ new Set();
23600
23793
  pendingSystemPrompts = /* @__PURE__ */ new Map();
23601
- async spawn(channelId, threadId) {
23794
+ async getOrSpawn(channelId, threadId) {
23795
+ const existing = this.core.sessionStore?.findAssistant(channelId);
23602
23796
  const session = await this.core.createSession({
23603
23797
  channelId,
23604
23798
  agentName: this.core.configManager.get().defaultAgent,
23605
23799
  workingDirectory: this.core.configManager.resolveWorkspace(),
23606
23800
  initialName: "Assistant",
23607
23801
  isAssistant: true,
23608
- threadId
23802
+ threadId,
23803
+ existingSessionId: existing?.sessionId
23609
23804
  });
23610
23805
  this.sessions.set(channelId, session);
23611
23806
  const systemPrompt = this.registry.buildSystemPrompt(channelId);
23612
23807
  this.pendingSystemPrompts.set(channelId, systemPrompt);
23613
- log41.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
+ );
23614
23812
  return session;
23615
23813
  }
23616
23814
  get(channelId) {
@@ -23631,19 +23829,6 @@ var init_assistant_manager = __esm({
23631
23829
  }
23632
23830
  return false;
23633
23831
  }
23634
- async respawn(channelId, threadId) {
23635
- if (this.respawning.has(channelId)) {
23636
- return this.sessions.get(channelId);
23637
- }
23638
- this.respawning.add(channelId);
23639
- try {
23640
- const old = this.sessions.get(channelId);
23641
- if (old) await old.destroy();
23642
- return await this.spawn(channelId, threadId);
23643
- } finally {
23644
- this.respawning.delete(channelId);
23645
- }
23646
- }
23647
23832
  };
23648
23833
  }
23649
23834
  });
@@ -23880,6 +24065,7 @@ var init_core = __esm({
23880
24065
  init_middleware_chain();
23881
24066
  init_error_tracker();
23882
24067
  init_log();
24068
+ init_events();
23883
24069
  log42 = createChildLogger({ module: "core" });
23884
24070
  OpenACPCore = class {
23885
24071
  configManager;
@@ -24114,7 +24300,7 @@ var init_core = __esm({
24114
24300
  );
24115
24301
  if (this.lifecycleManager?.middlewareChain) {
24116
24302
  const result = await this.lifecycleManager.middlewareChain.execute(
24117
- "message:incoming",
24303
+ Hook.MESSAGE_INCOMING,
24118
24304
  message,
24119
24305
  async (msg) => msg
24120
24306
  );
@@ -24166,9 +24352,10 @@ ${text6}`;
24166
24352
  }
24167
24353
  }
24168
24354
  const sourceAdapterId = message.routing?.sourceAdapterId ?? message.channelId;
24355
+ const routing = sourceAdapterId !== message.routing?.sourceAdapterId ? { ...message.routing, sourceAdapterId } : message.routing;
24169
24356
  if (sourceAdapterId && sourceAdapterId !== "sse" && sourceAdapterId !== "api") {
24170
24357
  const turnId = nanoid5(8);
24171
- this.eventBus.emit("message:queued", {
24358
+ this.eventBus.emit(BusEvent.MESSAGE_QUEUED, {
24172
24359
  sessionId: session.id,
24173
24360
  turnId,
24174
24361
  text: text6,
@@ -24177,9 +24364,9 @@ ${text6}`;
24177
24364
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
24178
24365
  queueDepth: session.queueDepth
24179
24366
  });
24180
- await session.enqueuePrompt(text6, message.attachments, message.routing, turnId);
24367
+ await session.enqueuePrompt(text6, message.attachments, routing, turnId);
24181
24368
  } else {
24182
- await session.enqueuePrompt(text6, message.attachments, message.routing);
24369
+ await session.enqueuePrompt(text6, message.attachments, routing);
24183
24370
  }
24184
24371
  }
24185
24372
  // --- Unified Session Creation Pipeline ---
@@ -24223,6 +24410,7 @@ ${text6}`;
24223
24410
  createdAt: session.createdAt.toISOString(),
24224
24411
  lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
24225
24412
  name: session.name,
24413
+ isAssistant: params.isAssistant,
24226
24414
  platform: platform2,
24227
24415
  platforms,
24228
24416
  firstAgent: session.firstAgent,
@@ -24238,7 +24426,7 @@ ${text6}`;
24238
24426
  log42.warn({ err, sessionId: session.id }, "Failed to flush pending skill commands");
24239
24427
  });
24240
24428
  if (params.createThread && session.threadId) {
24241
- this.eventBus.emit("session:threadReady", {
24429
+ this.eventBus.emit(BusEvent.SESSION_THREAD_READY, {
24242
24430
  sessionId: session.id,
24243
24431
  channelId: params.channelId,
24244
24432
  threadId: session.threadId
@@ -24262,6 +24450,36 @@ ${text6}`;
24262
24450
  );
24263
24451
  return allowOption.id;
24264
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
+ });
24265
24483
  }
24266
24484
  this.sessionFactory.wireSideEffects(session, {
24267
24485
  eventBus: this.eventBus,
@@ -24718,18 +24936,6 @@ Prompts: ${session.promptCount}` };
24718
24936
  return { type: "list", title: "\u{1F4CB} Sessions", items };
24719
24937
  }
24720
24938
  });
24721
- registry.register({
24722
- name: "clear",
24723
- description: "Clear session history",
24724
- category: "system",
24725
- handler: async (args2) => {
24726
- if (!core.assistantManager) return { type: "error", message: "Assistant not available" };
24727
- const assistant = core.assistantManager.get(args2.channelId);
24728
- if (!assistant) return { type: "error", message: "No assistant session for this channel." };
24729
- await core.assistantManager.respawn(args2.channelId, assistant.threadId);
24730
- return { type: "text", text: "\u2705 Assistant history cleared." };
24731
- }
24732
- });
24733
24939
  registry.register({
24734
24940
  name: "newchat",
24735
24941
  description: "New chat, same agent & workspace",
@@ -25256,7 +25462,7 @@ function registerCategoryCommand(registry, core, category, commandName) {
25256
25462
  }
25257
25463
  try {
25258
25464
  await session.setConfigOption(configOption.id, { type: "select", value: raw });
25259
- core.eventBus.emit("session:configChanged", { sessionId: session.id });
25465
+ core.eventBus.emit(BusEvent.SESSION_CONFIG_CHANGED, { sessionId: session.id });
25260
25466
  return { type: "text", text: labels.successMsg(match.name, configOption.name) };
25261
25467
  } catch (err) {
25262
25468
  log43.error({ err, commandName, configId: configOption.id }, "setConfigOption failed");
@@ -25318,7 +25524,7 @@ function registerDangerousCommand(registry, core) {
25318
25524
  try {
25319
25525
  const targetValue = wantOn ? bypassValue : nonBypassDefault;
25320
25526
  await session.setConfigOption(modeConfig.id, { type: "select", value: targetValue });
25321
- core.eventBus.emit("session:configChanged", { sessionId: session.id });
25527
+ core.eventBus.emit(BusEvent.SESSION_CONFIG_CHANGED, { sessionId: session.id });
25322
25528
  return {
25323
25529
  type: "text",
25324
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."
@@ -25333,7 +25539,7 @@ function registerDangerousCommand(registry, core) {
25333
25539
  await core.sessionManager.patchRecord(session.id, {
25334
25540
  clientOverrides: { ...session.clientOverrides }
25335
25541
  });
25336
- core.eventBus.emit("session:configChanged", { sessionId: session.id });
25542
+ core.eventBus.emit(BusEvent.SESSION_CONFIG_CHANGED, { sessionId: session.id });
25337
25543
  return {
25338
25544
  type: "text",
25339
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."
@@ -25353,6 +25559,7 @@ var init_config6 = __esm({
25353
25559
  "src/core/commands/config.ts"() {
25354
25560
  "use strict";
25355
25561
  init_log();
25562
+ init_events();
25356
25563
  init_bypass_detection();
25357
25564
  init_bypass_detection();
25358
25565
  log43 = createChildLogger({ module: "commands/config" });
@@ -26632,7 +26839,7 @@ async function runSetup(configManager, opts) {
26632
26839
  const config = {
26633
26840
  instanceName,
26634
26841
  defaultAgent,
26635
- workspace: { ...workspace, security: { allowedPaths: [], envWhitelist: [] } },
26842
+ workspace: { ...workspace, allowExternalWorkspaces: true, security: { allowedPaths: [], envWhitelist: [] } },
26636
26843
  logging: {
26637
26844
  level: "info",
26638
26845
  logDir: path56.join(instanceRoot, "logs"),
@@ -27081,7 +27288,7 @@ async function startServer(opts) {
27081
27288
  serviceRegistry.register("field-registry", fieldRegistry, "core");
27082
27289
  registerSystemCommands(commandRegistry, core);
27083
27290
  try {
27084
- core.eventBus.emit("kernel:booted");
27291
+ core.eventBus.emit(BusEvent.KERNEL_BOOTED);
27085
27292
  core.lifecycleManager.settingsManager = settingsManager;
27086
27293
  core.lifecycleManager.pluginRegistry = pluginRegistry;
27087
27294
  await core.lifecycleManager.boot(corePlugins);
@@ -27194,8 +27401,8 @@ async function startServer(opts) {
27194
27401
  if (tunnelSvc) {
27195
27402
  core.tunnelService = tunnelSvc;
27196
27403
  }
27197
- core.eventBus.emit("system:commands-ready", { commands: commandRegistry.getAll() });
27198
- core.eventBus.emit("system:ready");
27404
+ core.eventBus.emit(BusEvent.SYSTEM_COMMANDS_READY, { commands: commandRegistry.getAll() });
27405
+ core.eventBus.emit(BusEvent.SYSTEM_READY);
27199
27406
  } catch (err) {
27200
27407
  if (spinner4) {
27201
27408
  spinner4.fail("Plugin boot failed");
@@ -27431,6 +27638,7 @@ var init_main = __esm({
27431
27638
  init_commands4();
27432
27639
  init_instance_registry();
27433
27640
  init_plugin_field_registry();
27641
+ init_events();
27434
27642
  RESTART_EXIT_CODE = 75;
27435
27643
  shuttingDown = false;
27436
27644
  isDirectExecution = process.argv[1]?.endsWith("main.js");
@@ -29177,6 +29385,15 @@ async function uninstallPlugin(name, purge, instanceRoot, json = false) {
29177
29385
  init_api_client();
29178
29386
  init_helpers();
29179
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
+ }
29180
29397
  function printApiHelp() {
29181
29398
  console.log(`
29182
29399
  \x1B[1mopenacp api\x1B[0m \u2014 Interact with the running OpenACP daemon
@@ -29470,8 +29687,8 @@ Shows the version of the currently running daemon process.
29470
29687
  });
29471
29688
  const data = await res.json();
29472
29689
  if (!res.ok) {
29473
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29474
- console.error(`Error: ${data.error}`);
29690
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29691
+ console.error(`Error: ${extractApiError(data)}`);
29475
29692
  process.exit(1);
29476
29693
  }
29477
29694
  if (json) jsonSuccess(data);
@@ -29494,8 +29711,8 @@ Shows the version of the currently running daemon process.
29494
29711
  });
29495
29712
  const data = await res.json();
29496
29713
  if (!res.ok) {
29497
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29498
- console.error(`Error: ${data.error}`);
29714
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29715
+ console.error(`Error: ${extractApiError(data)}`);
29499
29716
  process.exit(1);
29500
29717
  }
29501
29718
  if (json) jsonSuccess({ cancelled: true, sessionId });
@@ -29559,8 +29776,8 @@ Shows the version of the currently running daemon process.
29559
29776
  process.exit(1);
29560
29777
  }
29561
29778
  if (!res.ok) {
29562
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29563
- console.error(`Error: ${data.error}`);
29779
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29780
+ console.error(`Error: ${extractApiError(data)}`);
29564
29781
  process.exit(1);
29565
29782
  }
29566
29783
  if (json) jsonSuccess(data);
@@ -29606,8 +29823,8 @@ Shows the version of the currently running daemon process.
29606
29823
  });
29607
29824
  const data = await res.json();
29608
29825
  if (!res.ok) {
29609
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29610
- console.error(`Error: ${data.error}`);
29826
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29827
+ console.error(`Error: ${extractApiError(data)}`);
29611
29828
  process.exit(1);
29612
29829
  }
29613
29830
  if (json) jsonSuccess(data);
@@ -29622,8 +29839,8 @@ Shows the version of the currently running daemon process.
29622
29839
  const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}`);
29623
29840
  const data = await res.json();
29624
29841
  if (!res.ok) {
29625
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29626
- console.error(`Error: ${data.error}`);
29842
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29843
+ console.error(`Error: ${extractApiError(data)}`);
29627
29844
  process.exit(1);
29628
29845
  }
29629
29846
  if (json) jsonSuccess(data);
@@ -29660,8 +29877,8 @@ Shows the version of the currently running daemon process.
29660
29877
  });
29661
29878
  const data = await res.json();
29662
29879
  if (!res.ok) {
29663
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29664
- console.error(`Error: ${data.error}`);
29880
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29881
+ console.error(`Error: ${extractApiError(data)}`);
29665
29882
  process.exit(1);
29666
29883
  }
29667
29884
  if (json) jsonSuccess(data);
@@ -29671,8 +29888,8 @@ Shows the version of the currently running daemon process.
29671
29888
  const res = await call("/api/health");
29672
29889
  const data = await res.json();
29673
29890
  if (!res.ok) {
29674
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29675
- console.error(`Error: ${data.error}`);
29891
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29892
+ console.error(`Error: ${extractApiError(data)}`);
29676
29893
  process.exit(1);
29677
29894
  }
29678
29895
  if (json) jsonSuccess(data);
@@ -29697,8 +29914,8 @@ Shows the version of the currently running daemon process.
29697
29914
  const res = await call("/api/restart", { method: "POST" });
29698
29915
  const data = await res.json();
29699
29916
  if (!res.ok) {
29700
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29701
- console.error(`Error: ${data.error}`);
29917
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29918
+ console.error(`Error: ${extractApiError(data)}`);
29702
29919
  process.exit(1);
29703
29920
  }
29704
29921
  if (json) jsonSuccess({ restarted: true });
@@ -29710,8 +29927,8 @@ Shows the version of the currently running daemon process.
29710
29927
  const res = await call("/api/config");
29711
29928
  const data = await res.json();
29712
29929
  if (!res.ok) {
29713
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29714
- console.error(`Error: ${data.error}`);
29930
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29931
+ console.error(`Error: ${extractApiError(data)}`);
29715
29932
  process.exit(1);
29716
29933
  }
29717
29934
  if (json) jsonSuccess(data);
@@ -29736,8 +29953,8 @@ Shows the version of the currently running daemon process.
29736
29953
  });
29737
29954
  const data = await res.json();
29738
29955
  if (!res.ok) {
29739
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29740
- console.error(`Error: ${data.error}`);
29956
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29957
+ console.error(`Error: ${extractApiError(data)}`);
29741
29958
  process.exit(1);
29742
29959
  }
29743
29960
  if (json) jsonSuccess(data);
@@ -29769,8 +29986,8 @@ Shows the version of the currently running daemon process.
29769
29986
  const res = await call("/api/tunnel");
29770
29987
  const data = await res.json();
29771
29988
  if (!res.ok) {
29772
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29773
- console.error(`Error: ${data.error}`);
29989
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
29990
+ console.error(`Error: ${extractApiError(data)}`);
29774
29991
  process.exit(1);
29775
29992
  }
29776
29993
  if (json) jsonSuccess(data);
@@ -29794,8 +30011,8 @@ Shows the version of the currently running daemon process.
29794
30011
  });
29795
30012
  const data = await res.json();
29796
30013
  if (!res.ok) {
29797
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29798
- console.error(`Error: ${data.error}`);
30014
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30015
+ console.error(`Error: ${extractApiError(data)}`);
29799
30016
  process.exit(1);
29800
30017
  }
29801
30018
  if (json) jsonSuccess({ sent: true });
@@ -29804,8 +30021,8 @@ Shows the version of the currently running daemon process.
29804
30021
  const res = await call("/api/version");
29805
30022
  const data = await res.json();
29806
30023
  if (!res.ok) {
29807
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29808
- console.error(`Error: ${data.error}`);
30024
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30025
+ console.error(`Error: ${extractApiError(data)}`);
29809
30026
  process.exit(1);
29810
30027
  }
29811
30028
  if (json) jsonSuccess(data);
@@ -29822,8 +30039,8 @@ Shows the version of the currently running daemon process.
29822
30039
  const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config`);
29823
30040
  const data = await res.json();
29824
30041
  if (!res.ok) {
29825
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29826
- console.error(`Error: ${data.error}`);
30042
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30043
+ console.error(`Error: ${extractApiError(data)}`);
29827
30044
  process.exit(1);
29828
30045
  }
29829
30046
  if (json) jsonSuccess(data);
@@ -29871,8 +30088,8 @@ Client overrides: ${JSON.stringify(clientOverrides)}`);
29871
30088
  });
29872
30089
  const data = await res.json();
29873
30090
  if (!res.ok) {
29874
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29875
- console.error(`Error: ${data.error}`);
30091
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30092
+ console.error(`Error: ${extractApiError(data)}`);
29876
30093
  process.exit(1);
29877
30094
  }
29878
30095
  if (json) jsonSuccess(data);
@@ -29886,8 +30103,8 @@ Client overrides: ${JSON.stringify(clientOverrides)}`);
29886
30103
  const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config/overrides`);
29887
30104
  const data = await res.json();
29888
30105
  if (!res.ok) {
29889
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29890
- console.error(`Error: ${data.error}`);
30106
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30107
+ console.error(`Error: ${extractApiError(data)}`);
29891
30108
  process.exit(1);
29892
30109
  }
29893
30110
  if (json) jsonSuccess(data);
@@ -29915,8 +30132,8 @@ Client overrides: ${JSON.stringify(clientOverrides)}`);
29915
30132
  });
29916
30133
  const data = await res.json();
29917
30134
  if (!res.ok) {
29918
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29919
- console.error(`Error: ${data.error}`);
30135
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30136
+ console.error(`Error: ${extractApiError(data)}`);
29920
30137
  process.exit(1);
29921
30138
  }
29922
30139
  if (json) jsonSuccess(data);
@@ -29926,8 +30143,8 @@ Client overrides: ${JSON.stringify(clientOverrides)}`);
29926
30143
  const res = await call(`/api/sessions/${encodeURIComponent(sessionId)}/config/overrides`);
29927
30144
  const data = await res.json();
29928
30145
  if (!res.ok) {
29929
- if (json) jsonError(ErrorCodes.API_ERROR, String(data.error ?? "API request failed"));
29930
- console.error(`Error: ${data.error}`);
30146
+ if (json) jsonError(ErrorCodes.API_ERROR, extractApiError(data));
30147
+ console.error(`Error: ${extractApiError(data)}`);
29931
30148
  process.exit(1);
29932
30149
  }
29933
30150
  if (json) jsonSuccess(data);