@openacp/cli 0.6.10 → 2026.41.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.
Files changed (133) hide show
  1. package/README.md +34 -16
  2. package/dist/cli.d.ts +11 -0
  3. package/dist/cli.js +28362 -449
  4. package/dist/cli.js.map +1 -1
  5. package/dist/data/registry-snapshot.json +1 -1
  6. package/dist/index.d.ts +1930 -467
  7. package/dist/index.js +17331 -102
  8. package/dist/index.js.map +1 -1
  9. package/package.json +13 -7
  10. package/dist/action-detect-P7ZE4NEM.js +0 -16
  11. package/dist/action-detect-P7ZE4NEM.js.map +0 -1
  12. package/dist/adapter-ZOANORGM.js +0 -799
  13. package/dist/adapter-ZOANORGM.js.map +0 -1
  14. package/dist/admin-6SYB6XCZ.js +0 -23
  15. package/dist/admin-6SYB6XCZ.js.map +0 -1
  16. package/dist/agent-catalog-FC3HGDEQ.js +0 -11
  17. package/dist/agent-catalog-FC3HGDEQ.js.map +0 -1
  18. package/dist/agent-dependencies-4OWBMZWZ.js +0 -24
  19. package/dist/agent-dependencies-4OWBMZWZ.js.map +0 -1
  20. package/dist/agent-registry-WT4NXPYG.js +0 -9
  21. package/dist/agent-registry-WT4NXPYG.js.map +0 -1
  22. package/dist/agent-store-VZLFPTZU.js +0 -9
  23. package/dist/agent-store-VZLFPTZU.js.map +0 -1
  24. package/dist/agents-QO7DKARJ.js +0 -15
  25. package/dist/agents-QO7DKARJ.js.map +0 -1
  26. package/dist/api-client-CFQT5U7D.js +0 -14
  27. package/dist/api-client-CFQT5U7D.js.map +0 -1
  28. package/dist/autostart-X33OGMX6.js +0 -23
  29. package/dist/autostart-X33OGMX6.js.map +0 -1
  30. package/dist/chunk-2CJ46J3C.js +0 -154
  31. package/dist/chunk-2CJ46J3C.js.map +0 -1
  32. package/dist/chunk-2HMQOC7N.js +0 -134
  33. package/dist/chunk-2HMQOC7N.js.map +0 -1
  34. package/dist/chunk-33RP6K2O.js +0 -435
  35. package/dist/chunk-33RP6K2O.js.map +0 -1
  36. package/dist/chunk-34M4OS5P.js +0 -83
  37. package/dist/chunk-34M4OS5P.js.map +0 -1
  38. package/dist/chunk-4CTX774K.js +0 -265
  39. package/dist/chunk-4CTX774K.js.map +0 -1
  40. package/dist/chunk-7QJS2XBD.js +0 -92
  41. package/dist/chunk-7QJS2XBD.js.map +0 -1
  42. package/dist/chunk-BNLGTZ34.js +0 -122
  43. package/dist/chunk-BNLGTZ34.js.map +0 -1
  44. package/dist/chunk-CS3KCJ5D.js +0 -4788
  45. package/dist/chunk-CS3KCJ5D.js.map +0 -1
  46. package/dist/chunk-GAK6PIBW.js +0 -224
  47. package/dist/chunk-GAK6PIBW.js.map +0 -1
  48. package/dist/chunk-I7WC6E5S.js +0 -71
  49. package/dist/chunk-I7WC6E5S.js.map +0 -1
  50. package/dist/chunk-J4SJTKIK.js +0 -203
  51. package/dist/chunk-J4SJTKIK.js.map +0 -1
  52. package/dist/chunk-JHYXKVV2.js +0 -183
  53. package/dist/chunk-JHYXKVV2.js.map +0 -1
  54. package/dist/chunk-JKBFUAJK.js +0 -282
  55. package/dist/chunk-JKBFUAJK.js.map +0 -1
  56. package/dist/chunk-KIRH7TUJ.js +0 -219
  57. package/dist/chunk-KIRH7TUJ.js.map +0 -1
  58. package/dist/chunk-LBIKITQT.js +0 -22
  59. package/dist/chunk-LBIKITQT.js.map +0 -1
  60. package/dist/chunk-LCRLAV4G.js +0 -1085
  61. package/dist/chunk-LCRLAV4G.js.map +0 -1
  62. package/dist/chunk-LGP2YGRL.js +0 -4880
  63. package/dist/chunk-LGP2YGRL.js.map +0 -1
  64. package/dist/chunk-MKHUZLII.js +0 -738
  65. package/dist/chunk-MKHUZLII.js.map +0 -1
  66. package/dist/chunk-NAMYZIS5.js +0 -1
  67. package/dist/chunk-NAMYZIS5.js.map +0 -1
  68. package/dist/chunk-NVPG6JCL.js +0 -724
  69. package/dist/chunk-NVPG6JCL.js.map +0 -1
  70. package/dist/chunk-O7CPGUAI.js +0 -298
  71. package/dist/chunk-O7CPGUAI.js.map +0 -1
  72. package/dist/chunk-OWP7RZ62.js +0 -697
  73. package/dist/chunk-OWP7RZ62.js.map +0 -1
  74. package/dist/chunk-S64CB6J3.js +0 -98
  75. package/dist/chunk-S64CB6J3.js.map +0 -1
  76. package/dist/chunk-UKT3G5IA.js +0 -484
  77. package/dist/chunk-UKT3G5IA.js.map +0 -1
  78. package/dist/chunk-V5GZQEIY.js +0 -101
  79. package/dist/chunk-V5GZQEIY.js.map +0 -1
  80. package/dist/chunk-VOIJ6OY4.js +0 -63
  81. package/dist/chunk-VOIJ6OY4.js.map +0 -1
  82. package/dist/chunk-VUNV25KB.js +0 -16
  83. package/dist/chunk-VUNV25KB.js.map +0 -1
  84. package/dist/chunk-W3EYKZNQ.js +0 -45
  85. package/dist/chunk-W3EYKZNQ.js.map +0 -1
  86. package/dist/chunk-WTZDAYZX.js +0 -172
  87. package/dist/chunk-WTZDAYZX.js.map +0 -1
  88. package/dist/chunk-XANPHG7W.js +0 -145
  89. package/dist/chunk-XANPHG7W.js.map +0 -1
  90. package/dist/config-6S355X75.js +0 -15
  91. package/dist/config-6S355X75.js.map +0 -1
  92. package/dist/config-editor-QQTZMWGD.js +0 -13
  93. package/dist/config-editor-QQTZMWGD.js.map +0 -1
  94. package/dist/config-registry-AHYI4MYL.js +0 -18
  95. package/dist/config-registry-AHYI4MYL.js.map +0 -1
  96. package/dist/daemon-4CS6HMB5.js +0 -30
  97. package/dist/daemon-4CS6HMB5.js.map +0 -1
  98. package/dist/discord-OMC52Y54.js +0 -2239
  99. package/dist/discord-OMC52Y54.js.map +0 -1
  100. package/dist/dist-UHQK5CXN.js +0 -21151
  101. package/dist/dist-UHQK5CXN.js.map +0 -1
  102. package/dist/doctor-HZZ5BSHB.js +0 -10
  103. package/dist/doctor-HZZ5BSHB.js.map +0 -1
  104. package/dist/doctor-OLYBO3V3.js +0 -15
  105. package/dist/doctor-OLYBO3V3.js.map +0 -1
  106. package/dist/install-cloudflared-Z7VCGOVG.js +0 -33
  107. package/dist/install-cloudflared-Z7VCGOVG.js.map +0 -1
  108. package/dist/install-jq-HUYSQWKR.js +0 -32
  109. package/dist/install-jq-HUYSQWKR.js.map +0 -1
  110. package/dist/integrate-PNEHRY2I.js +0 -373
  111. package/dist/integrate-PNEHRY2I.js.map +0 -1
  112. package/dist/log-NXABYJTT.js +0 -24
  113. package/dist/log-NXABYJTT.js.map +0 -1
  114. package/dist/main-XOZCLFUK.js +0 -238
  115. package/dist/main-XOZCLFUK.js.map +0 -1
  116. package/dist/menu-YY5MKHEK.js +0 -16
  117. package/dist/menu-YY5MKHEK.js.map +0 -1
  118. package/dist/new-session-FEO4J4VU.js +0 -17
  119. package/dist/new-session-FEO4J4VU.js.map +0 -1
  120. package/dist/post-upgrade-CJG5I7M2.js +0 -80
  121. package/dist/post-upgrade-CJG5I7M2.js.map +0 -1
  122. package/dist/session-IUSI7P5S.js +0 -20
  123. package/dist/session-IUSI7P5S.js.map +0 -1
  124. package/dist/settings-RQPAM4KC.js +0 -14
  125. package/dist/settings-RQPAM4KC.js.map +0 -1
  126. package/dist/setup-XHS4OMPM.js +0 -37
  127. package/dist/setup-XHS4OMPM.js.map +0 -1
  128. package/dist/suggest-7D6B542M.js +0 -38
  129. package/dist/suggest-7D6B542M.js.map +0 -1
  130. package/dist/tunnel-service-CJLUH6SZ.js +0 -1174
  131. package/dist/tunnel-service-CJLUH6SZ.js.map +0 -1
  132. package/dist/version-NQZBM5M7.js +0 -16
  133. package/dist/version-NQZBM5M7.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,10 +1,37 @@
1
1
  import pino from 'pino';
2
- import { z } from 'zod';
2
+ import * as zod from 'zod';
3
+ import { z, ZodSchema } from 'zod';
3
4
  import { EventEmitter } from 'node:events';
4
- import { Readable, Writable } from 'node:stream';
5
- import { PromptResponse } from '@agentclientprotocol/sdk';
5
+ import { SetSessionConfigOptionResponse, ListSessionsResponse, LoadSessionResponse, ForkSessionResponse, PromptResponse as PromptResponse$1 } from '@agentclientprotocol/sdk';
6
+ import { FastifyInstance, FastifyPluginAsync, preHandlerHookHandler, FastifyRequest, FastifyReply } from 'fastify';
6
7
  import * as http from 'node:http';
7
8
 
9
+ type OutputMode = "low" | "medium" | "high";
10
+ /** @deprecated Use OutputMode instead */
11
+ type DisplayVerbosity = OutputMode;
12
+ declare const STATUS_ICONS: Record<string, string>;
13
+ declare const KIND_ICONS: Record<string, string>;
14
+ interface ViewerLinks {
15
+ file?: string;
16
+ diff?: string;
17
+ }
18
+ interface ToolCallMeta {
19
+ id: string;
20
+ name: string;
21
+ kind?: string;
22
+ status?: string;
23
+ content?: unknown;
24
+ rawInput?: unknown;
25
+ viewerLinks?: ViewerLinks;
26
+ viewerFilePath?: string;
27
+ displaySummary?: string;
28
+ displayTitle?: string;
29
+ displayKind?: string;
30
+ }
31
+ interface ToolUpdateMeta extends ToolCallMeta {
32
+ status: string;
33
+ }
34
+
8
35
  interface Attachment {
9
36
  type: 'image' | 'audio' | 'file';
10
37
  filePath: string;
@@ -21,7 +48,7 @@ interface IncomingMessage {
21
48
  attachments?: Attachment[];
22
49
  }
23
50
  interface OutgoingMessage {
24
- type: "text" | "thought" | "tool_call" | "tool_update" | "plan" | "usage" | "session_end" | "error" | "attachment" | "system_message";
51
+ type: "text" | "thought" | "tool_call" | "tool_update" | "plan" | "usage" | "session_end" | "error" | "attachment" | "system_message" | "mode_change" | "config_update" | "model_update" | "user_replay" | "resource" | "resource_link";
25
52
  text: string;
26
53
  metadata?: Record<string, unknown>;
27
54
  attachment?: Attachment;
@@ -63,6 +90,7 @@ type AgentEvent = {
63
90
  content?: unknown;
64
91
  locations?: unknown;
65
92
  rawInput?: unknown;
93
+ rawOutput?: unknown;
66
94
  meta?: unknown;
67
95
  } | {
68
96
  type: "tool_update";
@@ -73,6 +101,7 @@ type AgentEvent = {
73
101
  content?: unknown;
74
102
  locations?: unknown;
75
103
  rawInput?: unknown;
104
+ rawOutput?: unknown;
76
105
  meta?: unknown;
77
106
  } | {
78
107
  type: "plan";
@@ -105,6 +134,34 @@ type AgentEvent = {
105
134
  } | {
106
135
  type: "system_message";
107
136
  message: string;
137
+ } | {
138
+ type: "session_info_update";
139
+ title?: string;
140
+ updatedAt?: string;
141
+ _meta?: Record<string, unknown>;
142
+ } | {
143
+ type: "config_option_update";
144
+ options: ConfigOption[];
145
+ } | {
146
+ type: "user_message_chunk";
147
+ content: string;
148
+ } | {
149
+ type: "resource_content";
150
+ uri: string;
151
+ name: string;
152
+ text?: string;
153
+ blob?: string;
154
+ mimeType?: string;
155
+ } | {
156
+ type: "resource_link";
157
+ uri: string;
158
+ name: string;
159
+ mimeType?: string;
160
+ title?: string;
161
+ description?: string;
162
+ size?: number;
163
+ } | {
164
+ type: "tts_strip";
108
165
  };
109
166
  interface PlanEntry {
110
167
  content: string;
@@ -196,6 +253,12 @@ interface InstallResult {
196
253
  setupSteps?: string[];
197
254
  }
198
255
  type SessionStatus = "initializing" | "active" | "cancelled" | "finished" | "error";
256
+ interface AgentSwitchEntry {
257
+ agentName: string;
258
+ agentSessionId: string;
259
+ switchedAt: string;
260
+ promptCount: number;
261
+ }
199
262
  interface SessionRecord<P = Record<string, unknown>> {
200
263
  sessionId: string;
201
264
  agentSessionId: string;
@@ -208,7 +271,22 @@ interface SessionRecord<P = Record<string, unknown>> {
208
271
  lastActiveAt: string;
209
272
  name?: string;
210
273
  dangerousMode?: boolean;
274
+ clientOverrides?: {
275
+ bypassPermissions?: boolean;
276
+ };
277
+ outputMode?: OutputMode;
211
278
  platform: P;
279
+ firstAgent?: string;
280
+ currentPromptCount?: number;
281
+ agentSwitchHistory?: AgentSwitchEntry[];
282
+ acpState?: {
283
+ configOptions?: ConfigOption[];
284
+ agentCapabilities?: AgentCapabilities;
285
+ currentMode?: string;
286
+ availableModes?: SessionMode[];
287
+ currentModel?: string;
288
+ availableModels?: ModelInfo[];
289
+ };
212
290
  }
213
291
  interface TelegramPlatformData {
214
292
  topicId: number;
@@ -226,20 +304,172 @@ interface UsageRecord {
226
304
  };
227
305
  timestamp: string;
228
306
  }
229
- interface UsageSummary {
230
- period: "today" | "week" | "month" | "all";
231
- totalTokens: number;
232
- totalCost: number;
233
- currency: string;
234
- sessionCount: number;
235
- recordCount: number;
307
+ interface UsageRecordEvent {
308
+ sessionId: string;
309
+ agentName: string;
310
+ timestamp: string;
311
+ tokensUsed: number;
312
+ contextSize: number;
313
+ cost?: {
314
+ amount: number;
315
+ currency: string;
316
+ };
236
317
  }
237
- interface DiscordPlatformData {
238
- threadId: string;
239
- skillMsgId?: string;
318
+ interface SessionMode {
319
+ id: string;
320
+ name: string;
321
+ description?: string;
322
+ }
323
+ interface SessionModeState {
324
+ currentModeId: string;
325
+ availableModes: SessionMode[];
326
+ }
327
+ interface ConfigSelectChoice {
328
+ value: string;
329
+ label: string;
330
+ description?: string;
331
+ }
332
+ interface ConfigSelectGroup {
333
+ group: string;
334
+ name: string;
335
+ options: ConfigSelectChoice[];
336
+ }
337
+ type ConfigOption = {
338
+ id: string;
339
+ name: string;
340
+ description?: string;
341
+ category?: string;
342
+ type: "select";
343
+ currentValue: string;
344
+ options: (ConfigSelectChoice | ConfigSelectGroup)[];
345
+ _meta?: Record<string, unknown>;
346
+ } | {
347
+ id: string;
348
+ name: string;
349
+ description?: string;
350
+ category?: string;
351
+ type: "boolean";
352
+ currentValue: boolean;
353
+ _meta?: Record<string, unknown>;
354
+ };
355
+ type SetConfigOptionValue = {
356
+ type: "select";
357
+ value: string;
358
+ } | {
359
+ type: "boolean";
360
+ value: boolean;
361
+ };
362
+ interface ModelInfo {
363
+ id: string;
364
+ name: string;
365
+ description?: string;
366
+ }
367
+ interface SessionModelState {
368
+ currentModelId: string;
369
+ availableModels: ModelInfo[];
240
370
  }
371
+ interface AgentCapabilities {
372
+ name: string;
373
+ title?: string;
374
+ version?: string;
375
+ loadSession?: boolean;
376
+ promptCapabilities?: {
377
+ image?: boolean;
378
+ audio?: boolean;
379
+ embeddedContext?: boolean;
380
+ };
381
+ sessionCapabilities?: {
382
+ list?: boolean;
383
+ fork?: boolean;
384
+ close?: boolean;
385
+ };
386
+ mcp?: {
387
+ http?: boolean;
388
+ sse?: boolean;
389
+ };
390
+ authMethods?: AuthMethod[];
391
+ }
392
+ interface NewSessionResponse {
393
+ sessionId: string;
394
+ modes?: SessionModeState;
395
+ configOptions?: ConfigOption[];
396
+ models?: SessionModelState;
397
+ }
398
+ type AuthMethod = {
399
+ type: "agent";
400
+ } | {
401
+ type: "env_var";
402
+ name: string;
403
+ description?: string;
404
+ } | {
405
+ type: "terminal";
406
+ };
407
+ interface AuthenticateRequest {
408
+ methodId: string;
409
+ }
410
+ type StopReason = "end_turn" | "max_tokens" | "max_turn_requests" | "refusal" | "cancelled";
411
+ interface PromptResponse {
412
+ stopReason: StopReason;
413
+ _meta?: Record<string, unknown>;
414
+ }
415
+ type ContentBlock = {
416
+ type: "text";
417
+ text: string;
418
+ } | {
419
+ type: "image";
420
+ data: string;
421
+ mimeType: string;
422
+ uri?: string;
423
+ } | {
424
+ type: "audio";
425
+ data: string;
426
+ mimeType: string;
427
+ } | {
428
+ type: "resource";
429
+ resource: {
430
+ uri: string;
431
+ text?: string;
432
+ blob?: string;
433
+ mimeType?: string;
434
+ };
435
+ } | {
436
+ type: "resource_link";
437
+ uri: string;
438
+ name: string;
439
+ mimeType?: string;
440
+ title?: string;
441
+ description?: string;
442
+ size?: number;
443
+ };
444
+ interface SessionListItem {
445
+ sessionId: string;
446
+ title?: string;
447
+ createdAt: string;
448
+ updatedAt?: string;
449
+ _meta?: Record<string, unknown>;
450
+ }
451
+ interface SessionListResponse {
452
+ sessions: SessionListItem[];
453
+ nextCursor?: string;
454
+ }
455
+ type McpServerConfig = {
456
+ type?: "stdio";
457
+ name: string;
458
+ command: string;
459
+ args?: string[];
460
+ env?: Record<string, string>;
461
+ } | {
462
+ type: "http";
463
+ name: string;
464
+ url: string;
465
+ headers?: Record<string, string>;
466
+ } | {
467
+ type: "sse";
468
+ name: string;
469
+ url: string;
470
+ headers?: Record<string, string>;
471
+ };
241
472
 
242
- declare const PLUGINS_DIR: string;
243
473
  declare const LoggingSchema: z.ZodDefault<z.ZodObject<{
244
474
  level: z.ZodDefault<z.ZodEnum<["silent", "debug", "info", "warn", "error", "fatal"]>>;
245
475
  logDir: z.ZodDefault<z.ZodString>;
@@ -278,8 +508,8 @@ declare const TunnelSchema: z.ZodDefault<z.ZodObject<{
278
508
  token?: string | undefined;
279
509
  }>>;
280
510
  }, "strip", z.ZodTypeAny, {
281
- options: Record<string, unknown>;
282
511
  enabled: boolean;
512
+ options: Record<string, unknown>;
283
513
  port: number;
284
514
  provider: "cloudflare" | "ngrok" | "bore" | "tailscale";
285
515
  maxUserTunnels: number;
@@ -289,8 +519,8 @@ declare const TunnelSchema: z.ZodDefault<z.ZodObject<{
289
519
  token?: string | undefined;
290
520
  };
291
521
  }, {
292
- options?: Record<string, unknown> | undefined;
293
522
  enabled?: boolean | undefined;
523
+ options?: Record<string, unknown> | undefined;
294
524
  port?: number | undefined;
295
525
  provider?: "cloudflare" | "ngrok" | "bore" | "tailscale" | undefined;
296
526
  maxUserTunnels?: number | undefined;
@@ -322,147 +552,52 @@ declare const UsageSchema: z.ZodDefault<z.ZodObject<{
322
552
  }>>;
323
553
  type UsageConfig = z.infer<typeof UsageSchema>;
324
554
  declare const ConfigSchema: z.ZodObject<{
325
- channels: z.ZodObject<{
326
- slack: z.ZodOptional<z.ZodObject<{
327
- enabled: z.ZodDefault<z.ZodBoolean>;
328
- adapter: z.ZodOptional<z.ZodLiteral<"slack">>;
329
- botToken: z.ZodOptional<z.ZodString>;
330
- appToken: z.ZodOptional<z.ZodString>;
331
- signingSecret: z.ZodOptional<z.ZodString>;
332
- notificationChannelId: z.ZodOptional<z.ZodString>;
333
- allowedUserIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
334
- channelPrefix: z.ZodDefault<z.ZodString>;
335
- autoCreateSession: z.ZodDefault<z.ZodBoolean>;
336
- startupChannelId: z.ZodOptional<z.ZodString>;
337
- }, "strip", z.ZodTypeAny, {
338
- enabled: boolean;
339
- allowedUserIds: string[];
340
- channelPrefix: string;
341
- autoCreateSession: boolean;
342
- adapter?: "slack" | undefined;
343
- botToken?: string | undefined;
344
- appToken?: string | undefined;
345
- signingSecret?: string | undefined;
346
- notificationChannelId?: string | undefined;
347
- startupChannelId?: string | undefined;
348
- }, {
349
- enabled?: boolean | undefined;
350
- adapter?: "slack" | undefined;
351
- botToken?: string | undefined;
352
- appToken?: string | undefined;
353
- signingSecret?: string | undefined;
354
- notificationChannelId?: string | undefined;
355
- allowedUserIds?: string[] | undefined;
356
- channelPrefix?: string | undefined;
357
- autoCreateSession?: boolean | undefined;
358
- startupChannelId?: string | undefined;
359
- }>>;
360
- }, "strip", z.ZodObject<{
555
+ instanceName: z.ZodOptional<z.ZodString>;
556
+ channels: z.ZodObject<{}, "strip", z.ZodObject<{
361
557
  enabled: z.ZodDefault<z.ZodBoolean>;
362
558
  adapter: z.ZodOptional<z.ZodString>;
363
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
559
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
560
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
364
561
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
365
562
  enabled: z.ZodDefault<z.ZodBoolean>;
366
563
  adapter: z.ZodOptional<z.ZodString>;
367
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
564
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
565
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
368
566
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
369
567
  enabled: z.ZodDefault<z.ZodBoolean>;
370
568
  adapter: z.ZodOptional<z.ZodString>;
371
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
372
- }, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{
373
- slack: z.ZodOptional<z.ZodObject<{
374
- enabled: z.ZodDefault<z.ZodBoolean>;
375
- adapter: z.ZodOptional<z.ZodLiteral<"slack">>;
376
- botToken: z.ZodOptional<z.ZodString>;
377
- appToken: z.ZodOptional<z.ZodString>;
378
- signingSecret: z.ZodOptional<z.ZodString>;
379
- notificationChannelId: z.ZodOptional<z.ZodString>;
380
- allowedUserIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
381
- channelPrefix: z.ZodDefault<z.ZodString>;
382
- autoCreateSession: z.ZodDefault<z.ZodBoolean>;
383
- startupChannelId: z.ZodOptional<z.ZodString>;
384
- }, "strip", z.ZodTypeAny, {
385
- enabled: boolean;
386
- allowedUserIds: string[];
387
- channelPrefix: string;
388
- autoCreateSession: boolean;
389
- adapter?: "slack" | undefined;
390
- botToken?: string | undefined;
391
- appToken?: string | undefined;
392
- signingSecret?: string | undefined;
393
- notificationChannelId?: string | undefined;
394
- startupChannelId?: string | undefined;
395
- }, {
396
- enabled?: boolean | undefined;
397
- adapter?: "slack" | undefined;
398
- botToken?: string | undefined;
399
- appToken?: string | undefined;
400
- signingSecret?: string | undefined;
401
- notificationChannelId?: string | undefined;
402
- allowedUserIds?: string[] | undefined;
403
- channelPrefix?: string | undefined;
404
- autoCreateSession?: boolean | undefined;
405
- startupChannelId?: string | undefined;
406
- }>>;
407
- }, z.ZodObject<{
569
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
570
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
571
+ }, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{}, z.ZodObject<{
408
572
  enabled: z.ZodDefault<z.ZodBoolean>;
409
573
  adapter: z.ZodOptional<z.ZodString>;
410
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
574
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
575
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
411
576
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
412
577
  enabled: z.ZodDefault<z.ZodBoolean>;
413
578
  adapter: z.ZodOptional<z.ZodString>;
414
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
579
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
580
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
415
581
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
416
582
  enabled: z.ZodDefault<z.ZodBoolean>;
417
583
  adapter: z.ZodOptional<z.ZodString>;
418
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
419
- }, z.ZodTypeAny, "passthrough">>, "strip">, z.objectInputType<{
420
- slack: z.ZodOptional<z.ZodObject<{
421
- enabled: z.ZodDefault<z.ZodBoolean>;
422
- adapter: z.ZodOptional<z.ZodLiteral<"slack">>;
423
- botToken: z.ZodOptional<z.ZodString>;
424
- appToken: z.ZodOptional<z.ZodString>;
425
- signingSecret: z.ZodOptional<z.ZodString>;
426
- notificationChannelId: z.ZodOptional<z.ZodString>;
427
- allowedUserIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
428
- channelPrefix: z.ZodDefault<z.ZodString>;
429
- autoCreateSession: z.ZodDefault<z.ZodBoolean>;
430
- startupChannelId: z.ZodOptional<z.ZodString>;
431
- }, "strip", z.ZodTypeAny, {
432
- enabled: boolean;
433
- allowedUserIds: string[];
434
- channelPrefix: string;
435
- autoCreateSession: boolean;
436
- adapter?: "slack" | undefined;
437
- botToken?: string | undefined;
438
- appToken?: string | undefined;
439
- signingSecret?: string | undefined;
440
- notificationChannelId?: string | undefined;
441
- startupChannelId?: string | undefined;
442
- }, {
443
- enabled?: boolean | undefined;
444
- adapter?: "slack" | undefined;
445
- botToken?: string | undefined;
446
- appToken?: string | undefined;
447
- signingSecret?: string | undefined;
448
- notificationChannelId?: string | undefined;
449
- allowedUserIds?: string[] | undefined;
450
- channelPrefix?: string | undefined;
451
- autoCreateSession?: boolean | undefined;
452
- startupChannelId?: string | undefined;
453
- }>>;
454
- }, z.ZodObject<{
584
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
585
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
586
+ }, z.ZodTypeAny, "passthrough">>, "strip">, z.objectInputType<{}, z.ZodObject<{
455
587
  enabled: z.ZodDefault<z.ZodBoolean>;
456
588
  adapter: z.ZodOptional<z.ZodString>;
457
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
589
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
590
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
458
591
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
459
592
  enabled: z.ZodDefault<z.ZodBoolean>;
460
593
  adapter: z.ZodOptional<z.ZodString>;
461
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
594
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
595
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
462
596
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
463
597
  enabled: z.ZodDefault<z.ZodBoolean>;
464
598
  adapter: z.ZodOptional<z.ZodString>;
465
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
599
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
600
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
466
601
  }, z.ZodTypeAny, "passthrough">>, "strip">>;
467
602
  agents: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
468
603
  command: z.ZodString;
@@ -557,8 +692,8 @@ declare const ConfigSchema: z.ZodObject<{
557
692
  token?: string | undefined;
558
693
  }>>;
559
694
  }, "strip", z.ZodTypeAny, {
560
- options: Record<string, unknown>;
561
695
  enabled: boolean;
696
+ options: Record<string, unknown>;
562
697
  port: number;
563
698
  provider: "cloudflare" | "ngrok" | "bore" | "tailscale";
564
699
  maxUserTunnels: number;
@@ -568,8 +703,8 @@ declare const ConfigSchema: z.ZodObject<{
568
703
  token?: string | undefined;
569
704
  };
570
705
  }, {
571
- options?: Record<string, unknown> | undefined;
572
706
  enabled?: boolean | undefined;
707
+ options?: Record<string, unknown> | undefined;
573
708
  port?: number | undefined;
574
709
  provider?: "cloudflare" | "ngrok" | "bore" | "tailscale" | undefined;
575
710
  maxUserTunnels?: number | undefined;
@@ -690,6 +825,14 @@ declare const ConfigSchema: z.ZodObject<{
690
825
  }, z.ZodTypeAny, "passthrough">> | undefined;
691
826
  } | undefined;
692
827
  }>>>;
828
+ outputMode: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
829
+ agentSwitch: z.ZodDefault<z.ZodObject<{
830
+ labelHistory: z.ZodDefault<z.ZodBoolean>;
831
+ }, "strip", z.ZodTypeAny, {
832
+ labelHistory: boolean;
833
+ }, {
834
+ labelHistory?: boolean | undefined;
835
+ }>>;
693
836
  }, "strip", z.ZodTypeAny, {
694
837
  api: {
695
838
  port: number;
@@ -702,8 +845,8 @@ declare const ConfigSchema: z.ZodObject<{
702
845
  workingDirectory?: string | undefined;
703
846
  }>;
704
847
  tunnel: {
705
- options: Record<string, unknown>;
706
848
  enabled: boolean;
849
+ options: Record<string, unknown>;
707
850
  port: number;
708
851
  provider: "cloudflare" | "ngrok" | "bore" | "tailscale";
709
852
  maxUserTunnels: number;
@@ -713,24 +856,19 @@ declare const ConfigSchema: z.ZodObject<{
713
856
  token?: string | undefined;
714
857
  };
715
858
  };
716
- channels: {
717
- slack?: {
718
- enabled: boolean;
719
- allowedUserIds: string[];
720
- channelPrefix: string;
721
- autoCreateSession: boolean;
722
- adapter?: "slack" | undefined;
723
- botToken?: string | undefined;
724
- appToken?: string | undefined;
725
- signingSecret?: string | undefined;
726
- notificationChannelId?: string | undefined;
727
- startupChannelId?: string | undefined;
728
- } | undefined;
729
- } & {
859
+ usage: {
860
+ enabled: boolean;
861
+ warningThreshold: number;
862
+ currency: string;
863
+ retentionDays: number;
864
+ monthlyBudget?: number | undefined;
865
+ };
866
+ channels: {} & {
730
867
  [k: string]: z.objectOutputType<{
731
868
  enabled: z.ZodDefault<z.ZodBoolean>;
732
869
  adapter: z.ZodOptional<z.ZodString>;
733
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
870
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
871
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
734
872
  }, z.ZodTypeAny, "passthrough">;
735
873
  };
736
874
  defaultAgent: string;
@@ -754,13 +892,6 @@ declare const ConfigSchema: z.ZodObject<{
754
892
  sessionStore: {
755
893
  ttlDays: number;
756
894
  };
757
- usage: {
758
- enabled: boolean;
759
- warningThreshold: number;
760
- currency: string;
761
- retentionDays: number;
762
- monthlyBudget?: number | undefined;
763
- };
764
895
  integrations: Record<string, {
765
896
  installed: boolean;
766
897
  installedAt?: string | undefined;
@@ -781,25 +912,18 @@ declare const ConfigSchema: z.ZodObject<{
781
912
  }, z.ZodTypeAny, "passthrough">>;
782
913
  };
783
914
  };
915
+ agentSwitch: {
916
+ labelHistory: boolean;
917
+ };
918
+ instanceName?: string | undefined;
919
+ outputMode?: "low" | "medium" | "high" | undefined;
784
920
  }, {
785
- channels: {
786
- slack?: {
787
- enabled?: boolean | undefined;
788
- adapter?: "slack" | undefined;
789
- botToken?: string | undefined;
790
- appToken?: string | undefined;
791
- signingSecret?: string | undefined;
792
- notificationChannelId?: string | undefined;
793
- allowedUserIds?: string[] | undefined;
794
- channelPrefix?: string | undefined;
795
- autoCreateSession?: boolean | undefined;
796
- startupChannelId?: string | undefined;
797
- } | undefined;
798
- } & {
921
+ channels: {} & {
799
922
  [k: string]: z.objectInputType<{
800
923
  enabled: z.ZodDefault<z.ZodBoolean>;
801
924
  adapter: z.ZodOptional<z.ZodString>;
802
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
925
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
926
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
803
927
  }, z.ZodTypeAny, "passthrough">;
804
928
  };
805
929
  defaultAgent: string;
@@ -814,8 +938,8 @@ declare const ConfigSchema: z.ZodObject<{
814
938
  workingDirectory?: string | undefined;
815
939
  }> | undefined;
816
940
  tunnel?: {
817
- options?: Record<string, unknown> | undefined;
818
941
  enabled?: boolean | undefined;
942
+ options?: Record<string, unknown> | undefined;
819
943
  port?: number | undefined;
820
944
  provider?: "cloudflare" | "ngrok" | "bore" | "tailscale" | undefined;
821
945
  maxUserTunnels?: number | undefined;
@@ -825,6 +949,15 @@ declare const ConfigSchema: z.ZodObject<{
825
949
  token?: string | undefined;
826
950
  } | undefined;
827
951
  } | undefined;
952
+ usage?: {
953
+ enabled?: boolean | undefined;
954
+ monthlyBudget?: number | undefined;
955
+ warningThreshold?: number | undefined;
956
+ currency?: string | undefined;
957
+ retentionDays?: number | undefined;
958
+ } | undefined;
959
+ instanceName?: string | undefined;
960
+ outputMode?: "low" | "medium" | "high" | undefined;
828
961
  workspace?: {
829
962
  baseDir?: string | undefined;
830
963
  } | undefined;
@@ -845,13 +978,6 @@ declare const ConfigSchema: z.ZodObject<{
845
978
  sessionStore?: {
846
979
  ttlDays?: number | undefined;
847
980
  } | undefined;
848
- usage?: {
849
- enabled?: boolean | undefined;
850
- monthlyBudget?: number | undefined;
851
- warningThreshold?: number | undefined;
852
- currency?: string | undefined;
853
- retentionDays?: number | undefined;
854
- } | undefined;
855
981
  integrations?: Record<string, {
856
982
  installed: boolean;
857
983
  installedAt?: string | undefined;
@@ -872,13 +998,16 @@ declare const ConfigSchema: z.ZodObject<{
872
998
  }, z.ZodTypeAny, "passthrough">> | undefined;
873
999
  } | undefined;
874
1000
  } | undefined;
1001
+ agentSwitch?: {
1002
+ labelHistory?: boolean | undefined;
1003
+ } | undefined;
875
1004
  }>;
876
1005
  type Config = z.infer<typeof ConfigSchema>;
877
1006
  declare function expandHome(p: string): string;
878
- declare class ConfigManager extends EventEmitter {
1007
+ declare class ConfigManager$1 extends EventEmitter {
879
1008
  private config;
880
1009
  private configPath;
881
- constructor();
1010
+ constructor(configPath?: string);
882
1011
  load(): Promise<void>;
883
1012
  get(): Config;
884
1013
  save(updates: Record<string, unknown>, changePath?: string): Promise<void>;
@@ -890,7 +1019,7 @@ declare class ConfigManager extends EventEmitter {
890
1019
  private deepMerge;
891
1020
  }
892
1021
 
893
- type Logger = pino.Logger;
1022
+ type Logger$1 = pino.Logger;
894
1023
  declare const log: {
895
1024
  info: (...args: unknown[]) => void;
896
1025
  warn: (...args: unknown[]) => void;
@@ -899,14 +1028,14 @@ declare const log: {
899
1028
  fatal: (...args: unknown[]) => void;
900
1029
  child: (bindings: pino.Bindings) => pino.Logger<never, boolean>;
901
1030
  };
902
- declare function initLogger(config: LoggingConfig): Logger;
1031
+ declare function initLogger(config: LoggingConfig): Logger$1;
903
1032
  /** Change log level at runtime. Pino transport targets respect parent level changes automatically. */
904
1033
  declare function setLogLevel(level: string): void;
905
1034
  declare function createChildLogger(context: {
906
1035
  module: string;
907
1036
  [key: string]: unknown;
908
- }): Logger;
909
- declare function createSessionLogger(sessionId: string, parentLogger: Logger): Logger;
1037
+ }): Logger$1;
1038
+ declare function createSessionLogger(sessionId: string, parentLogger: Logger$1): Logger$1;
910
1039
  declare function shutdownLogger(): Promise<void>;
911
1040
  declare function cleanupOldSessionLogs(retentionDays: number): Promise<void>;
912
1041
 
@@ -914,7 +1043,17 @@ interface ChannelConfig {
914
1043
  enabled: boolean;
915
1044
  [key: string]: unknown;
916
1045
  }
1046
+ interface AdapterCapabilities {
1047
+ streaming: boolean;
1048
+ richFormatting: boolean;
1049
+ threads: boolean;
1050
+ reactions: boolean;
1051
+ fileUpload: boolean;
1052
+ voice: boolean;
1053
+ }
917
1054
  interface IChannelAdapter {
1055
+ readonly name: string;
1056
+ readonly capabilities: AdapterCapabilities;
918
1057
  start(): Promise<void>;
919
1058
  stop(): Promise<void>;
920
1059
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
@@ -922,17 +1061,25 @@ interface IChannelAdapter {
922
1061
  sendNotification(notification: NotificationMessage): Promise<void>;
923
1062
  createSessionThread(sessionId: string, name: string): Promise<string>;
924
1063
  renameSessionThread(sessionId: string, newName: string): Promise<void>;
925
- deleteSessionThread(sessionId: string): Promise<void>;
926
- sendSkillCommands(sessionId: string, commands: AgentCommand[]): Promise<void>;
927
- cleanupSkillCommands(sessionId: string): Promise<void>;
1064
+ deleteSessionThread?(sessionId: string): Promise<void>;
1065
+ archiveSessionTopic?(sessionId: string): Promise<void>;
1066
+ stripTTSBlock?(sessionId: string): Promise<void>;
1067
+ sendSkillCommands?(sessionId: string, commands: AgentCommand[]): Promise<void>;
1068
+ cleanupSkillCommands?(sessionId: string): Promise<void>;
1069
+ /** Flush skill commands that were queued before threadId was available */
1070
+ flushPendingSkillCommands?(sessionId: string): Promise<void>;
1071
+ cleanupSessionState?(sessionId: string): Promise<void>;
928
1072
  }
929
1073
  /**
930
1074
  * Base class providing default no-op implementations for optional methods.
931
1075
  * Adapters can extend this or implement IChannelAdapter directly.
1076
+ * @deprecated Use MessagingAdapter or StreamAdapter instead. Kept for backward compat during migration.
932
1077
  */
933
1078
  declare abstract class ChannelAdapter<TCore = unknown> implements IChannelAdapter {
934
1079
  readonly core: TCore;
935
1080
  protected config: ChannelConfig;
1081
+ abstract readonly name: string;
1082
+ readonly capabilities: AdapterCapabilities;
936
1083
  constructor(core: TCore, config: ChannelConfig);
937
1084
  abstract start(): Promise<void>;
938
1085
  abstract stop(): Promise<void>;
@@ -944,18 +1091,12 @@ declare abstract class ChannelAdapter<TCore = unknown> implements IChannelAdapte
944
1091
  deleteSessionThread(_sessionId: string): Promise<void>;
945
1092
  sendSkillCommands(_sessionId: string, _commands: AgentCommand[]): Promise<void>;
946
1093
  cleanupSkillCommands(_sessionId: string): Promise<void>;
1094
+ cleanupSessionState(_sessionId: string): Promise<void>;
947
1095
  archiveSessionTopic(_sessionId: string): Promise<void>;
948
1096
  }
949
1097
 
950
- declare class NotificationManager {
951
- private adapters;
952
- constructor(adapters: Map<string, ChannelAdapter>);
953
- notify(channelId: string, notification: NotificationMessage): Promise<void>;
954
- notifyAll(notification: NotificationMessage): Promise<void>;
955
- }
956
-
957
- declare function nodeToWebWritable(nodeStream: Writable): WritableStream<Uint8Array>;
958
- declare function nodeToWebReadable(nodeStream: Readable): ReadableStream<Uint8Array>;
1098
+ declare function nodeToWebWritable(nodeStream: NodeJS.WritableStream): WritableStream<Uint8Array>;
1099
+ declare function nodeToWebReadable(nodeStream: NodeJS.ReadableStream): ReadableStream<Uint8Array>;
959
1100
 
960
1101
  declare class StderrCapture {
961
1102
  private maxLines;
@@ -1000,6 +1141,57 @@ declare class TypedEmitter<T extends Record<string & keyof T, (...args: any[]) =
1000
1141
  private deliver;
1001
1142
  }
1002
1143
 
1144
+ interface ErrorBudgetConfig {
1145
+ maxErrors: number;
1146
+ windowMs: number;
1147
+ }
1148
+ declare class ErrorTracker {
1149
+ private errors;
1150
+ private disabled;
1151
+ private exempt;
1152
+ private config;
1153
+ onDisabled?: (pluginName: string, reason: string) => void;
1154
+ constructor(config?: Partial<ErrorBudgetConfig>);
1155
+ increment(pluginName: string): void;
1156
+ isDisabled(pluginName: string): boolean;
1157
+ reset(pluginName: string): void;
1158
+ setExempt(pluginName: string): void;
1159
+ }
1160
+
1161
+ declare class MiddlewareChain {
1162
+ private chains;
1163
+ private errorHandler?;
1164
+ private errorTracker?;
1165
+ add(hook: string, pluginName: string, opts: {
1166
+ priority?: number;
1167
+ handler: Function;
1168
+ }): void;
1169
+ execute<T>(hook: string, payload: T, coreHandler: (p: T) => T | Promise<T>): Promise<T | null>;
1170
+ removeAll(pluginName: string): void;
1171
+ setErrorHandler(fn: (pluginName: string, error: Error) => void): void;
1172
+ setErrorTracker(tracker: ErrorTracker): void;
1173
+ }
1174
+
1175
+ type TraceLayer = "acp" | "core" | "telegram";
1176
+ /**
1177
+ * Per-session debug trace logger. Writes JSONL files to <workingDirectory>/.log/.
1178
+ * Only active when OPENACP_DEBUG=true. Zero overhead when disabled.
1179
+ *
1180
+ * Note: Uses appendFileSync for simplicity. This blocks the event loop briefly per write,
1181
+ * which is acceptable for a debug-only tool. The DEBUG_ENABLED guard ensures zero overhead
1182
+ * in production.
1183
+ */
1184
+ declare class DebugTracer {
1185
+ private sessionId;
1186
+ private workingDirectory;
1187
+ private dirCreated;
1188
+ private logDir;
1189
+ constructor(sessionId: string, workingDirectory: string);
1190
+ log(layer: TraceLayer, data: Record<string, unknown>): void;
1191
+ /** No-op cleanup — establishes the pattern for future async implementations */
1192
+ destroy(): void;
1193
+ }
1194
+
1003
1195
  interface AgentInstanceEvents {
1004
1196
  agent_event: (event: AgentEvent) => void;
1005
1197
  }
@@ -1007,21 +1199,38 @@ declare class AgentInstance extends TypedEmitter<AgentInstanceEvents> {
1007
1199
  private connection;
1008
1200
  private child;
1009
1201
  private stderrCapture;
1010
- private terminals;
1202
+ private terminalManager;
1203
+ private static mcpManager;
1204
+ private _destroying;
1011
1205
  sessionId: string;
1012
1206
  agentName: string;
1013
1207
  promptCapabilities?: {
1014
1208
  image?: boolean;
1015
1209
  audio?: boolean;
1016
1210
  };
1211
+ agentCapabilities?: AgentCapabilities;
1212
+ /** Preserved from newSession/resumeSession response for ACP state propagation */
1213
+ initialSessionResponse?: {
1214
+ modes?: unknown;
1215
+ configOptions?: unknown;
1216
+ models?: unknown;
1217
+ };
1218
+ middlewareChain?: MiddlewareChain;
1219
+ debugTracer: DebugTracer | null;
1017
1220
  onPermissionRequest: (request: PermissionRequest) => Promise<string>;
1018
1221
  private constructor();
1019
1222
  private static spawnSubprocess;
1020
1223
  private setupCrashDetection;
1021
- static spawn(agentDef: AgentDefinition, workingDirectory: string): Promise<AgentInstance>;
1022
- static resume(agentDef: AgentDefinition, workingDirectory: string, agentSessionId: string): Promise<AgentInstance>;
1224
+ static spawn(agentDef: AgentDefinition, workingDirectory: string, mcpServers?: McpServerConfig[]): Promise<AgentInstance>;
1225
+ static resume(agentDef: AgentDefinition, workingDirectory: string, agentSessionId: string, mcpServers?: McpServerConfig[]): Promise<AgentInstance>;
1023
1226
  private createClient;
1024
- prompt(text: string, attachments?: Attachment[]): Promise<PromptResponse>;
1227
+ setConfigOption(configId: string, value: SetConfigOptionValue): Promise<SetSessionConfigOptionResponse>;
1228
+ listSessions(cwd?: string, cursor?: string): Promise<ListSessionsResponse>;
1229
+ loadSession(sessionId: string, cwd: string, mcpServers?: McpServerConfig[]): Promise<LoadSessionResponse>;
1230
+ authenticate(methodId: string): Promise<void>;
1231
+ forkSession(sessionId: string, cwd: string, mcpServers?: McpServerConfig[]): Promise<ForkSessionResponse>;
1232
+ closeSession(sessionId: string): Promise<void>;
1233
+ prompt(text: string, attachments?: Attachment[]): Promise<PromptResponse$1>;
1025
1234
  cancel(): Promise<void>;
1026
1235
  destroy(): Promise<void>;
1027
1236
  }
@@ -1043,7 +1252,9 @@ declare class AgentStore {
1043
1252
  declare class AgentCatalog {
1044
1253
  private store;
1045
1254
  private registryAgents;
1046
- constructor(store?: AgentStore);
1255
+ private cachePath;
1256
+ private agentsDir;
1257
+ constructor(store?: AgentStore, cachePath?: string, agentsDir?: string);
1047
1258
  load(): void;
1048
1259
  fetchRegistry(): Promise<void>;
1049
1260
  refreshRegistryIfStale(): Promise<void>;
@@ -1120,11 +1331,11 @@ interface TTSResult {
1120
1331
  audioBuffer: Buffer;
1121
1332
  mimeType: string;
1122
1333
  }
1123
- interface STTProvider {
1334
+ interface STTProvider$1 {
1124
1335
  readonly name: string;
1125
1336
  transcribe(audioBuffer: Buffer, mimeType: string, options?: STTOptions): Promise<STTResult>;
1126
1337
  }
1127
- interface TTSProvider {
1338
+ interface TTSProvider$1 {
1128
1339
  readonly name: string;
1129
1340
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
1130
1341
  }
@@ -1144,21 +1355,31 @@ interface SpeechServiceConfig {
1144
1355
  };
1145
1356
  }
1146
1357
 
1358
+ type ProviderFactory = (config: SpeechServiceConfig) => {
1359
+ stt: Map<string, STTProvider$1>;
1360
+ tts: Map<string, TTSProvider$1>;
1361
+ };
1147
1362
  declare class SpeechService {
1148
1363
  private config;
1149
1364
  private sttProviders;
1150
1365
  private ttsProviders;
1366
+ private providerFactory?;
1151
1367
  constructor(config: SpeechServiceConfig);
1152
- registerSTTProvider(name: string, provider: STTProvider): void;
1153
- registerTTSProvider(name: string, provider: TTSProvider): void;
1368
+ /** Set a factory function that can recreate providers from config (for hot-reload) */
1369
+ setProviderFactory(factory: ProviderFactory): void;
1370
+ registerSTTProvider(name: string, provider: STTProvider$1): void;
1371
+ registerTTSProvider(name: string, provider: TTSProvider$1): void;
1372
+ unregisterTTSProvider(name: string): void;
1154
1373
  isSTTAvailable(): boolean;
1155
1374
  isTTSAvailable(): boolean;
1156
1375
  transcribe(audioBuffer: Buffer, mimeType: string, options?: STTOptions): Promise<STTResult>;
1157
1376
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
1158
1377
  updateConfig(config: SpeechServiceConfig): void;
1378
+ /** Re-create factory-managed providers from config. Preserves externally-registered providers (e.g. from plugins). */
1379
+ refreshProviders(newConfig: SpeechServiceConfig): void;
1159
1380
  }
1160
1381
 
1161
- declare class GroqSTT implements STTProvider {
1382
+ declare class GroqSTT implements STTProvider$1 {
1162
1383
  private apiKey;
1163
1384
  private defaultModel;
1164
1385
  readonly name = "groq";
@@ -1173,6 +1394,7 @@ interface SessionEvents {
1173
1394
  status_change: (from: SessionStatus, to: SessionStatus) => void;
1174
1395
  named: (name: string) => void;
1175
1396
  error: (error: Error) => void;
1397
+ prompt_count_changed: (count: number) => void;
1176
1398
  }
1177
1399
  declare class Session extends TypedEmitter<SessionEvents> {
1178
1400
  id: string;
@@ -1186,10 +1408,17 @@ declare class Session extends TypedEmitter<SessionEvents> {
1186
1408
  name?: string;
1187
1409
  createdAt: Date;
1188
1410
  voiceMode: "off" | "next" | "on";
1189
- dangerousMode: boolean;
1411
+ configOptions: ConfigOption[];
1412
+ clientOverrides: {
1413
+ bypassPermissions?: boolean;
1414
+ };
1415
+ agentCapabilities?: AgentCapabilities;
1190
1416
  archiving: boolean;
1191
1417
  promptCount: number;
1192
- log: Logger;
1418
+ firstAgent: string;
1419
+ agentSwitchHistory: AgentSwitchEntry[];
1420
+ log: Logger$1;
1421
+ middlewareChain?: MiddlewareChain;
1193
1422
  readonly permissionGate: PermissionGate;
1194
1423
  private readonly queue;
1195
1424
  private speechService?;
@@ -1222,12 +1451,27 @@ declare class Session extends TypedEmitter<SessionEvents> {
1222
1451
  private maybeTranscribeAudio;
1223
1452
  private processTTSResponse;
1224
1453
  private autoName;
1225
- generateSummary(timeoutMs?: number): Promise<string>;
1226
1454
  /** Fire-and-forget warm-up: primes model cache while user types their first message */
1227
1455
  warmup(): Promise<void>;
1228
1456
  private runWarmup;
1457
+ setInitialConfigOptions(options: ConfigOption[]): void;
1458
+ setAgentCapabilities(caps: AgentCapabilities | undefined): void;
1459
+ getConfigOption(id: string): ConfigOption | undefined;
1460
+ getConfigByCategory(category: string): ConfigOption | undefined;
1461
+ getConfigValue(id: string): string | undefined;
1462
+ /** Set session name explicitly and emit 'named' event */
1463
+ setName(name: string): void;
1464
+ updateConfigOptions(options: ConfigOption[]): Promise<void>;
1465
+ /** Snapshot of current ACP state for persistence */
1466
+ toAcpStateSnapshot(): NonNullable<SessionRecord["acpState"]>;
1467
+ /** Check if the agent supports a specific session capability */
1468
+ supportsCapability(cap: 'list' | 'fork' | 'close' | 'loadSession'): boolean;
1229
1469
  /** Cancel the current prompt and clear the queue. Stays in active state. */
1230
1470
  abortPrompt(): Promise<void>;
1471
+ /** Search backward through agentSwitchHistory for the last entry matching agentName */
1472
+ findLastSwitchEntry(agentName: string): AgentSwitchEntry | undefined;
1473
+ /** Switch the agent instance in-place, preserving session identity */
1474
+ switchAgent(agentName: string, createAgent: () => Promise<AgentInstance>): Promise<void>;
1231
1475
  destroy(): Promise<void>;
1232
1476
  }
1233
1477
 
@@ -1249,90 +1493,455 @@ declare class PromptQueue {
1249
1493
  get isProcessing(): boolean;
1250
1494
  }
1251
1495
 
1252
- interface TunnelEntry {
1253
- port: number;
1254
- type: 'system' | 'user';
1255
- provider: string;
1256
- label?: string;
1257
- publicUrl?: string;
1258
- sessionId?: string;
1259
- status: 'stopped' | 'starting' | 'active' | 'failed';
1260
- createdAt: string;
1261
- }
1262
-
1263
- interface ViewerEntry {
1264
- id: string;
1265
- type: 'file' | 'diff';
1266
- filePath?: string;
1267
- content: string;
1268
- oldContent?: string;
1269
- language?: string;
1270
- sessionId: string;
1271
- workingDirectory: string;
1272
- createdAt: number;
1273
- expiresAt: number;
1496
+ type PluginPermission = 'events:read' | 'events:emit' | 'services:register' | 'services:use' | 'middleware:register' | 'commands:register' | 'storage:read' | 'storage:write' | 'kernel:access';
1497
+ interface OpenACPPlugin {
1498
+ /** Unique identifier, e.g., '@openacp/security' */
1499
+ name: string;
1500
+ /** Semver version */
1501
+ version: string;
1502
+ /** Human-readable description */
1503
+ description?: string;
1504
+ /** Required plugin dependencies — loaded before this plugin's setup() */
1505
+ pluginDependencies?: Record<string, string>;
1506
+ /** Optional dependencies — used if available, gracefully degrade if not */
1507
+ optionalPluginDependencies?: Record<string, string>;
1508
+ /** Override a built-in plugin (replaces it entirely) */
1509
+ overrides?: string;
1510
+ /** Required permissions — PluginContext enforces these */
1511
+ permissions?: PluginPermission[];
1512
+ /** Called during startup in dependency order */
1513
+ setup(ctx: PluginContext): Promise<void>;
1514
+ /** Called during shutdown in reverse order. 10s timeout. */
1515
+ teardown?(): Promise<void>;
1516
+ install?(ctx: InstallContext): Promise<void>;
1517
+ uninstall?(ctx: InstallContext, opts: {
1518
+ purge: boolean;
1519
+ }): Promise<void>;
1520
+ configure?(ctx: InstallContext): Promise<void>;
1521
+ migrate?(ctx: MigrateContext, oldSettings: unknown, oldVersion: string): Promise<unknown>;
1522
+ settingsSchema?: zod.ZodSchema;
1523
+ essential?: boolean;
1524
+ /** Settings keys that can be copied when creating a new instance from this one */
1525
+ inheritableKeys?: string[];
1526
+ }
1527
+ interface PluginStorage {
1528
+ get<T>(key: string): Promise<T | undefined>;
1529
+ set<T>(key: string, value: T): Promise<void>;
1530
+ delete(key: string): Promise<void>;
1531
+ list(): Promise<string[]>;
1532
+ getDataDir(): string;
1533
+ }
1534
+ interface SettingsAPI {
1535
+ get<T = unknown>(key: string): Promise<T | undefined>;
1536
+ set<T = unknown>(key: string, value: T): Promise<void>;
1537
+ getAll(): Promise<Record<string, unknown>>;
1538
+ setAll(settings: Record<string, unknown>): Promise<void>;
1539
+ delete(key: string): Promise<void>;
1540
+ clear(): Promise<void>;
1541
+ has(key: string): Promise<boolean>;
1542
+ }
1543
+ interface TerminalIO {
1544
+ text(opts: {
1545
+ message: string;
1546
+ placeholder?: string;
1547
+ defaultValue?: string;
1548
+ validate?: (value: string) => string | undefined;
1549
+ }): Promise<string>;
1550
+ select<T>(opts: {
1551
+ message: string;
1552
+ options: {
1553
+ value: T;
1554
+ label: string;
1555
+ hint?: string;
1556
+ }[];
1557
+ }): Promise<T>;
1558
+ confirm(opts: {
1559
+ message: string;
1560
+ initialValue?: boolean;
1561
+ }): Promise<boolean>;
1562
+ password(opts: {
1563
+ message: string;
1564
+ validate?: (value: string) => string | undefined;
1565
+ }): Promise<string>;
1566
+ multiselect<T>(opts: {
1567
+ message: string;
1568
+ options: {
1569
+ value: T;
1570
+ label: string;
1571
+ hint?: string;
1572
+ }[];
1573
+ required?: boolean;
1574
+ }): Promise<T[]>;
1575
+ log: {
1576
+ info(message: string): void;
1577
+ success(message: string): void;
1578
+ warning(message: string): void;
1579
+ error(message: string): void;
1580
+ step(message: string): void;
1581
+ };
1582
+ spinner(): {
1583
+ start(message: string): void;
1584
+ stop(message?: string): void;
1585
+ fail(message?: string): void;
1586
+ };
1587
+ note(message: string, title?: string): void;
1588
+ cancel(message?: string): void;
1589
+ }
1590
+ interface InstallContext {
1591
+ pluginName: string;
1592
+ terminal: TerminalIO;
1593
+ settings: SettingsAPI;
1594
+ legacyConfig?: Record<string, unknown>;
1595
+ dataDir: string;
1596
+ log: Logger;
1597
+ /** Root of the OpenACP instance directory (e.g. ~/.openacp) */
1598
+ instanceRoot?: string;
1274
1599
  }
1275
- declare class ViewerStore {
1276
- private entries;
1277
- private cleanupTimer;
1278
- private ttlMs;
1279
- constructor(ttlMinutes?: number);
1280
- storeFile(sessionId: string, filePath: string, content: string, workingDirectory: string): string | null;
1281
- storeDiff(sessionId: string, filePath: string, oldContent: string, newContent: string, workingDirectory: string): string | null;
1282
- get(id: string): ViewerEntry | undefined;
1283
- private cleanup;
1284
- private isPathAllowed;
1285
- private detectLanguage;
1286
- destroy(): void;
1600
+ interface MigrateContext {
1601
+ pluginName: string;
1602
+ settings: SettingsAPI;
1603
+ log: Logger;
1287
1604
  }
1288
-
1289
- declare class TunnelService {
1290
- private registry;
1291
- private store;
1292
- private server;
1293
- private config;
1294
- private systemPort;
1295
- constructor(config: TunnelConfig);
1605
+ type CommandResponse = {
1606
+ type: 'text';
1607
+ text: string;
1608
+ } | {
1609
+ type: 'menu';
1610
+ title: string;
1611
+ options: MenuOption[];
1612
+ } | {
1613
+ type: 'list';
1614
+ title: string;
1615
+ items: ListItem[];
1616
+ } | {
1617
+ type: 'confirm';
1618
+ question: string;
1619
+ onYes: string;
1620
+ onNo: string;
1621
+ } | {
1622
+ type: 'error';
1623
+ message: string;
1624
+ } | {
1625
+ type: 'silent';
1626
+ };
1627
+ interface MenuOption {
1628
+ label: string;
1629
+ command: string;
1630
+ hint?: string;
1631
+ }
1632
+ interface ListItem {
1633
+ label: string;
1634
+ detail?: string;
1635
+ }
1636
+ interface CommandArgs {
1637
+ /** Raw argument string after command name */
1638
+ raw: string;
1639
+ /** Parsed key/value options (e.g., --flag value) */
1640
+ options?: Record<string, string>;
1641
+ /** Session ID where command was invoked (null if from notification/system topic) */
1642
+ sessionId: string | null;
1643
+ /** Channel ID ('telegram', 'discord', 'slack') */
1644
+ channelId: string;
1645
+ /** User ID who invoked the command */
1646
+ userId: string;
1647
+ /** Reply helper — sends message to the topic where command was invoked */
1648
+ reply(content: string | CommandResponse | OutgoingMessage): Promise<void>;
1649
+ /** Direct access to OpenACPCore instance. Available when 'kernel:access' permission is granted. */
1650
+ coreAccess?: CoreAccess;
1651
+ }
1652
+ interface CommandDef {
1653
+ /** Command name without slash, e.g., 'context' for /context */
1654
+ name: string;
1655
+ /** Short description shown in command list */
1656
+ description: string;
1657
+ /** Usage pattern, e.g., '<session-number>' */
1658
+ usage?: string;
1659
+ /** Whether this is a built-in system command or registered by a plugin */
1660
+ category: 'system' | 'plugin';
1661
+ /** Plugin that registered this command (set automatically by plugin manager) */
1662
+ pluginName?: string;
1663
+ /** Handler function */
1664
+ handler(args: CommandArgs): Promise<CommandResponse | void>;
1665
+ }
1666
+ interface SessionManager$1 {
1667
+ [key: string]: unknown;
1668
+ }
1669
+ interface ConfigManager {
1670
+ [key: string]: unknown;
1671
+ }
1672
+ interface EventBus$1 {
1673
+ [key: string]: unknown;
1674
+ }
1675
+ /**
1676
+ * Typed view of the OpenACPCore instance exposed to plugins via ctx.core.
1677
+ * Plugins that need kernel:access should cast ctx.core to this interface
1678
+ * instead of using `as any`. Only includes fields plugins actually need.
1679
+ */
1680
+ interface CoreAccess {
1681
+ configManager: ConfigManager;
1682
+ sessionManager: SessionManager$1;
1683
+ adapters: Map<string, IChannelAdapter>;
1684
+ }
1685
+ interface Logger {
1686
+ trace(msg: string, ...args: unknown[]): void;
1687
+ debug(msg: string, ...args: unknown[]): void;
1688
+ info(msg: string, ...args: unknown[]): void;
1689
+ warn(msg: string, ...args: unknown[]): void;
1690
+ error(msg: string, ...args: unknown[]): void;
1691
+ fatal(msg: string, ...args: unknown[]): void;
1692
+ child(bindings: Record<string, unknown>): Logger;
1693
+ }
1694
+ interface PluginContext {
1695
+ pluginName: string;
1696
+ pluginConfig: Record<string, unknown>;
1697
+ /** Subscribe to events. Auto-cleaned on teardown. Requires 'events:read'. */
1698
+ on(event: string, handler: (...args: unknown[]) => void): void;
1699
+ off(event: string, handler: (...args: unknown[]) => void): void;
1700
+ /** Emit custom events. Event names MUST be prefixed with plugin name. Requires 'events:emit'. */
1701
+ emit(event: string, payload: unknown): void;
1702
+ /** Register middleware. Requires 'middleware:register'. */
1703
+ registerMiddleware<H extends MiddlewareHook>(hook: H, opts: MiddlewareOptions<MiddlewarePayloadMap[H]>): void;
1704
+ /** Provide a service. Requires 'services:register'. */
1705
+ registerService<T>(name: string, implementation: T): void;
1706
+ /** Consume a service. Requires 'services:use'. */
1707
+ getService<T>(name: string): T | undefined;
1708
+ /** Register slash command. Requires 'commands:register'. */
1709
+ registerCommand(def: CommandDef): void;
1710
+ /** Plugin-scoped storage. Requires 'storage:read' and/or 'storage:write'. */
1711
+ storage: PluginStorage;
1712
+ /** Plugin-scoped logger. Always available (no permission needed). */
1713
+ log: Logger;
1714
+ /**
1715
+ * Send message to a session. Requires 'services:use'.
1716
+ *
1717
+ * Routing: sessionId → lookup session → find adapter for session's channelId
1718
+ * → [HOOK: message:outgoing] → adapter.sendMessage()
1719
+ */
1720
+ sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
1721
+ sessions: SessionManager$1;
1722
+ config: ConfigManager;
1723
+ eventBus: EventBus$1;
1724
+ /** Direct access to OpenACPCore instance. Requires 'kernel:access'. */
1725
+ core: unknown;
1726
+ /**
1727
+ * Root directory for this OpenACP instance (default: ~/.openacp).
1728
+ * Plugins should derive file paths from this instead of hardcoding ~/.openacp.
1729
+ */
1730
+ instanceRoot: string;
1731
+ }
1732
+ interface MiddlewarePayloadMap {
1733
+ 'message:incoming': {
1734
+ channelId: string;
1735
+ threadId: string;
1736
+ userId: string;
1737
+ text: string;
1738
+ attachments?: Attachment[];
1739
+ };
1740
+ 'message:outgoing': {
1741
+ sessionId: string;
1742
+ message: OutgoingMessage;
1743
+ };
1744
+ 'agent:beforePrompt': {
1745
+ sessionId: string;
1746
+ text: string;
1747
+ attachments?: Attachment[];
1748
+ };
1749
+ 'agent:beforeEvent': {
1750
+ sessionId: string;
1751
+ event: AgentEvent;
1752
+ };
1753
+ 'agent:afterEvent': {
1754
+ sessionId: string;
1755
+ event: AgentEvent;
1756
+ outgoingMessage: OutgoingMessage;
1757
+ };
1758
+ 'turn:start': {
1759
+ sessionId: string;
1760
+ promptText: string;
1761
+ promptNumber: number;
1762
+ };
1763
+ 'turn:end': {
1764
+ sessionId: string;
1765
+ stopReason: StopReason;
1766
+ durationMs: number;
1767
+ };
1768
+ 'fs:beforeRead': {
1769
+ sessionId: string;
1770
+ path: string;
1771
+ line?: number;
1772
+ limit?: number;
1773
+ };
1774
+ 'fs:beforeWrite': {
1775
+ sessionId: string;
1776
+ path: string;
1777
+ content: string;
1778
+ };
1779
+ 'terminal:beforeCreate': {
1780
+ sessionId: string;
1781
+ command: string;
1782
+ args?: string[];
1783
+ env?: Record<string, string>;
1784
+ cwd?: string;
1785
+ };
1786
+ 'terminal:afterExit': {
1787
+ sessionId: string;
1788
+ terminalId: string;
1789
+ command: string;
1790
+ exitCode: number;
1791
+ durationMs: number;
1792
+ };
1793
+ 'permission:beforeRequest': {
1794
+ sessionId: string;
1795
+ request: PermissionRequest;
1796
+ autoResolve?: string;
1797
+ };
1798
+ 'permission:afterResolve': {
1799
+ sessionId: string;
1800
+ requestId: string;
1801
+ decision: string;
1802
+ userId: string;
1803
+ durationMs: number;
1804
+ };
1805
+ 'session:beforeCreate': {
1806
+ agentName: string;
1807
+ workingDir: string;
1808
+ userId: string;
1809
+ channelId: string;
1810
+ threadId: string;
1811
+ };
1812
+ 'session:afterDestroy': {
1813
+ sessionId: string;
1814
+ reason: string;
1815
+ durationMs: number;
1816
+ promptCount: number;
1817
+ };
1818
+ 'config:beforeChange': {
1819
+ sessionId: string;
1820
+ configId: string;
1821
+ oldValue: unknown;
1822
+ newValue: unknown;
1823
+ };
1824
+ 'agent:beforeCancel': {
1825
+ sessionId: string;
1826
+ reason?: string;
1827
+ };
1828
+ 'agent:beforeSwitch': {
1829
+ sessionId: string;
1830
+ fromAgent: string;
1831
+ toAgent: string;
1832
+ };
1833
+ 'agent:afterSwitch': {
1834
+ sessionId: string;
1835
+ fromAgent: string;
1836
+ toAgent: string;
1837
+ resumed: boolean;
1838
+ };
1839
+ }
1840
+ type MiddlewareHook = keyof MiddlewarePayloadMap;
1841
+ type MiddlewareFn<T> = (payload: T, next: () => Promise<T>) => Promise<T | null>;
1842
+ interface MiddlewareOptions<T> {
1843
+ /** Override execution order within same dependency level. Lower = earlier. */
1844
+ priority?: number;
1845
+ /** The middleware handler */
1846
+ handler: MiddlewareFn<T>;
1847
+ }
1848
+ interface SecurityService {
1849
+ checkAccess(userId: string): Promise<{
1850
+ allowed: boolean;
1851
+ reason?: string;
1852
+ }>;
1853
+ checkSessionLimit(userId: string): Promise<{
1854
+ allowed: boolean;
1855
+ reason?: string;
1856
+ }>;
1857
+ getUserRole(userId: string): Promise<'admin' | 'user' | 'blocked'>;
1858
+ }
1859
+ interface FileServiceInterface {
1860
+ saveFile(sessionId: string, fileName: string, data: Buffer, mimeType: string): Promise<Attachment>;
1861
+ resolveFile(filePath: string): Promise<Attachment | null>;
1862
+ readTextFileWithRange(path: string, opts?: {
1863
+ line?: number;
1864
+ limit?: number;
1865
+ }): Promise<string>;
1866
+ extensionFromMime(mimeType: string): string;
1867
+ convertOggToWav(oggData: Buffer): Promise<Buffer>;
1868
+ }
1869
+ interface NotificationService {
1870
+ notify(channelId: string, notification: NotificationMessage): Promise<void>;
1871
+ notifyAll(notification: NotificationMessage): Promise<void>;
1872
+ }
1873
+ interface UsageService {
1874
+ trackUsage(record: UsageRecord): Promise<void>;
1875
+ checkBudget(sessionId: string): Promise<{
1876
+ ok: boolean;
1877
+ percent: number;
1878
+ warning?: string;
1879
+ }>;
1880
+ }
1881
+ interface TTSProvider {
1882
+ synthesize(text: string, opts?: {
1883
+ language?: string;
1884
+ voice?: string;
1885
+ }): Promise<Buffer>;
1886
+ }
1887
+ interface STTProvider {
1888
+ transcribe(audio: Buffer, opts?: {
1889
+ language?: string;
1890
+ }): Promise<string>;
1891
+ }
1892
+ interface SpeechServiceInterface {
1893
+ textToSpeech(text: string, opts?: {
1894
+ language?: string;
1895
+ voice?: string;
1896
+ }): Promise<Buffer>;
1897
+ speechToText(audio: Buffer, opts?: {
1898
+ language?: string;
1899
+ }): Promise<string>;
1900
+ registerTTSProvider(name: string, provider: TTSProvider): void;
1901
+ registerSTTProvider(name: string, provider: STTProvider): void;
1902
+ }
1903
+ interface ContextProvider$1 {
1904
+ provide(sessionId: string, opts?: {
1905
+ maxTokens?: number;
1906
+ }): Promise<string>;
1907
+ }
1908
+ interface ContextService {
1909
+ buildContext(sessionId: string, opts?: {
1910
+ maxTokens?: number;
1911
+ }): Promise<string>;
1912
+ registerProvider(provider: ContextProvider$1): void;
1913
+ }
1914
+ interface ViewerStoreInterface {
1915
+ storeFile(sessionId: string, filePath: string, content: string, workingDirectory: string): string | null;
1916
+ storeDiff(sessionId: string, filePath: string, oldContent: string, newContent: string, workingDirectory: string): string | null;
1917
+ storeOutput(sessionId: string, label: string, output: string): string | null;
1918
+ }
1919
+ interface TunnelServiceInterface {
1920
+ getPublicUrl(): string;
1296
1921
  start(): Promise<string>;
1297
1922
  stop(): Promise<void>;
1298
- addTunnel(port: number, opts?: {
1299
- label?: string;
1300
- sessionId?: string;
1301
- }): Promise<TunnelEntry>;
1302
- stopTunnel(port: number): Promise<void>;
1303
- stopAllUser(): Promise<void>;
1304
- stopBySession(sessionId: string): Promise<TunnelEntry[]>;
1305
- listTunnels(): TunnelEntry[];
1306
- getTunnel(port: number): TunnelEntry | null;
1307
- getPublicUrl(): string;
1308
- getStore(): ViewerStore;
1923
+ getStore(): ViewerStoreInterface;
1309
1924
  fileUrl(entryId: string): string;
1310
1925
  diffUrl(entryId: string): string;
1926
+ outputUrl(entryId: string): string;
1311
1927
  }
1312
1928
 
1313
1929
  declare class MessageTransformer {
1314
- private tunnelService?;
1315
- constructor(tunnelService?: TunnelService | undefined);
1930
+ tunnelService?: TunnelServiceInterface;
1931
+ /** Cache rawInput from tool_call so it's available in tool_update (which often lacks it) */
1932
+ private toolRawInputCache;
1933
+ /** Cache viewer links generated from intermediate updates so completion events carry them */
1934
+ private toolViewerCache;
1935
+ constructor(tunnelService?: TunnelServiceInterface);
1316
1936
  transform(event: AgentEvent, sessionContext?: {
1317
1937
  id: string;
1318
1938
  workingDirectory: string;
1319
1939
  }): OutgoingMessage;
1940
+ /** Check if rawInput is a non-empty object (not null, not {}) */
1941
+ private isNonEmptyInput;
1320
1942
  private enrichWithViewerLinks;
1321
1943
  }
1322
1944
 
1323
- declare class FileService {
1324
- private baseDir;
1325
- constructor(baseDir: string);
1326
- saveFile(sessionId: string, fileName: string, data: Buffer, mimeType: string): Promise<Attachment>;
1327
- resolveFile(filePath: string): Promise<Attachment | null>;
1328
- /**
1329
- * Convert OGG Opus audio to WAV format.
1330
- * Telegram voice messages use OGG Opus which many AI agents can't read.
1331
- */
1332
- convertOggToWav(oggData: Buffer): Promise<Buffer>;
1333
- static extensionFromMime(mimeType: string): string;
1334
- }
1335
-
1336
1945
  interface SessionStore {
1337
1946
  save(record: SessionRecord): Promise<void>;
1338
1947
  get(sessionId: string): SessionRecord | undefined;
@@ -1352,7 +1961,9 @@ interface EventBusEvents {
1352
1961
  sessionId: string;
1353
1962
  status?: SessionStatus;
1354
1963
  name?: string;
1355
- dangerousMode?: boolean;
1964
+ clientOverrides?: {
1965
+ bypassPermissions?: boolean;
1966
+ };
1356
1967
  }) => void;
1357
1968
  "session:deleted": (data: {
1358
1969
  sessionId: string;
@@ -1365,6 +1976,57 @@ interface EventBusEvents {
1365
1976
  sessionId: string;
1366
1977
  permission: PermissionRequest;
1367
1978
  }) => void;
1979
+ "permission:resolved": (data: {
1980
+ sessionId: string;
1981
+ requestId: string;
1982
+ decision: string;
1983
+ }) => void;
1984
+ "kernel:booted": () => void;
1985
+ "system:ready": () => void;
1986
+ "system:shutdown": () => void;
1987
+ "system:commands-ready": (data: {
1988
+ commands: Array<{
1989
+ name: string;
1990
+ description: string;
1991
+ }>;
1992
+ }) => void;
1993
+ "plugin:loaded": (data: {
1994
+ name: string;
1995
+ version: string;
1996
+ }) => void;
1997
+ "plugin:failed": (data: {
1998
+ name: string;
1999
+ error: string;
2000
+ }) => void;
2001
+ "plugin:disabled": (data: {
2002
+ name: string;
2003
+ reason: string;
2004
+ }) => void;
2005
+ "plugin:unloaded": (data: {
2006
+ name: string;
2007
+ }) => void;
2008
+ "session:ended": (data: {
2009
+ sessionId: string;
2010
+ reason: string;
2011
+ }) => void;
2012
+ "session:named": (data: {
2013
+ sessionId: string;
2014
+ name: string;
2015
+ }) => void;
2016
+ "agent:prompt": (data: {
2017
+ sessionId: string;
2018
+ text: string;
2019
+ attachments?: unknown[];
2020
+ }) => void;
2021
+ "usage:recorded": (data: UsageRecordEvent) => void;
2022
+ "session:agentSwitch": (data: {
2023
+ sessionId: string;
2024
+ fromAgent: string;
2025
+ toAgent: string;
2026
+ status: "starting" | "succeeded" | "failed";
2027
+ resumed?: boolean;
2028
+ error?: string;
2029
+ }) => void;
1368
2030
  }
1369
2031
  declare class EventBus extends TypedEmitter<EventBusEvents> {
1370
2032
  }
@@ -1373,6 +2035,7 @@ declare class SessionManager {
1373
2035
  private sessions;
1374
2036
  private store;
1375
2037
  private eventBus?;
2038
+ middlewareChain?: MiddlewareChain;
1376
2039
  setEventBus(eventBus: EventBus): void;
1377
2040
  constructor(store?: SessionStore | null);
1378
2041
  createSession(channelId: string, agentName: string, workingDirectory: string, agentManager: AgentManager): Promise<Session>;
@@ -1390,19 +2053,23 @@ declare class SessionManager {
1390
2053
  statuses?: string[];
1391
2054
  }): SessionRecord[];
1392
2055
  removeRecord(sessionId: string): Promise<void>;
2056
+ /**
2057
+ * Graceful shutdown: persist session state without killing agent subprocesses.
2058
+ * Agent processes will exit naturally when the parent process terminates.
2059
+ */
2060
+ shutdownAll(): Promise<void>;
2061
+ /**
2062
+ * Forcefully destroy all sessions (kill agent subprocesses).
2063
+ * Use only when sessions must be fully torn down (e.g. archive).
2064
+ */
1393
2065
  destroyAll(): Promise<void>;
1394
2066
  }
1395
2067
 
1396
- declare class SecurityGuard {
1397
- private configManager;
1398
- private sessionManager;
1399
- constructor(configManager: ConfigManager, sessionManager: SessionManager);
1400
- checkAccess(message: IncomingMessage): {
1401
- allowed: true;
1402
- } | {
1403
- allowed: false;
1404
- reason: string;
1405
- };
2068
+ declare class NotificationManager {
2069
+ private adapters;
2070
+ constructor(adapters: Map<string, IChannelAdapter>);
2071
+ notify(channelId: string, notification: NotificationMessage): Promise<void>;
2072
+ notifyAll(notification: NotificationMessage): Promise<void>;
1406
2073
  }
1407
2074
 
1408
2075
  interface BridgeDeps {
@@ -1410,7 +2077,8 @@ interface BridgeDeps {
1410
2077
  notificationManager: NotificationManager;
1411
2078
  sessionManager: SessionManager;
1412
2079
  eventBus?: EventBus;
1413
- fileService?: FileService;
2080
+ fileService?: FileServiceInterface;
2081
+ middlewareChain?: MiddlewareChain;
1414
2082
  }
1415
2083
  declare class SessionBridge {
1416
2084
  private session;
@@ -1421,78 +2089,86 @@ declare class SessionBridge {
1421
2089
  private sessionEventHandler?;
1422
2090
  private statusChangeHandler?;
1423
2091
  private namedHandler?;
1424
- constructor(session: Session, adapter: ChannelAdapter, deps: BridgeDeps);
2092
+ private promptCountHandler?;
2093
+ constructor(session: Session, adapter: IChannelAdapter, deps: BridgeDeps);
2094
+ private get tracer();
2095
+ /** Send message to adapter, optionally running through message:outgoing middleware */
2096
+ private sendMessage;
1425
2097
  connect(): void;
1426
2098
  disconnect(): void;
1427
2099
  private wireAgentToSession;
1428
2100
  private wireSessionToAdapter;
2101
+ private handleAgentEvent;
2102
+ /** Persist current ACP state (configOptions, agentCapabilities) to session store as cache */
2103
+ private persistAcpState;
1429
2104
  private wirePermissions;
1430
2105
  private wireLifecycle;
1431
2106
  }
1432
2107
 
1433
- declare class UsageStore {
1434
- private filePath;
1435
- private retentionDays;
1436
- private records;
1437
- private debounceTimer;
1438
- private cleanupInterval;
1439
- private flushHandler;
1440
- constructor(filePath: string, retentionDays: number);
1441
- append(record: UsageRecord): void;
1442
- query(period: "today" | "week" | "month" | "all"): UsageSummary;
1443
- getMonthlyTotal(): {
1444
- totalCost: number;
1445
- currency: string;
1446
- };
1447
- cleanup(): void;
1448
- flushSync(): void;
1449
- destroy(): void;
1450
- private load;
1451
- private getCutoff;
1452
- private scheduleDiskWrite;
1453
- }
1454
-
1455
- declare class UsageBudget {
1456
- private store;
1457
- private config;
1458
- private now;
1459
- private lastNotifiedStatus;
1460
- private lastNotifiedMonth;
1461
- constructor(store: UsageStore, config: UsageConfig, now?: () => Date);
1462
- check(): {
1463
- status: "ok" | "warning" | "exceeded";
1464
- message?: string;
1465
- };
1466
- getStatus(): {
1467
- status: "ok" | "warning" | "exceeded";
1468
- used: number;
1469
- budget: number;
1470
- percent: number;
1471
- };
2108
+ interface TunnelEntry {
2109
+ port: number;
2110
+ type: 'system' | 'user';
2111
+ provider: string;
2112
+ label?: string;
2113
+ publicUrl?: string;
2114
+ sessionId?: string;
2115
+ status: 'stopped' | 'starting' | 'active' | 'failed';
2116
+ retryCount: number;
2117
+ createdAt: string;
1472
2118
  }
1473
2119
 
1474
- interface SessionCreateParams {
1475
- channelId: string;
1476
- agentName: string;
2120
+ interface ViewerEntry {
2121
+ id: string;
2122
+ type: 'file' | 'diff' | 'output';
2123
+ filePath?: string;
2124
+ content: string;
2125
+ oldContent?: string;
2126
+ language?: string;
2127
+ sessionId: string;
1477
2128
  workingDirectory: string;
1478
- resumeAgentSessionId?: string;
1479
- existingSessionId?: string;
1480
- initialName?: string;
2129
+ createdAt: number;
2130
+ expiresAt: number;
1481
2131
  }
1482
- interface SideEffectDeps {
1483
- usageStore?: UsageStore | null;
1484
- usageBudget?: UsageBudget | null;
1485
- notificationManager: NotificationManager;
1486
- tunnelService?: TunnelService;
2132
+ declare class ViewerStore {
2133
+ private entries;
2134
+ private cleanupTimer;
2135
+ private ttlMs;
2136
+ constructor(ttlMinutes?: number);
2137
+ storeFile(sessionId: string, filePath: string, content: string, workingDirectory: string): string | null;
2138
+ storeDiff(sessionId: string, filePath: string, oldContent: string, newContent: string, workingDirectory: string): string | null;
2139
+ storeOutput(sessionId: string, label: string, output: string): string | null;
2140
+ get(id: string): ViewerEntry | undefined;
2141
+ private cleanup;
2142
+ private isPathAllowed;
2143
+ private detectLanguage;
2144
+ destroy(): void;
1487
2145
  }
1488
- declare class SessionFactory {
1489
- private agentManager;
1490
- private sessionManager;
1491
- private speechService;
1492
- private eventBus;
1493
- constructor(agentManager: AgentManager, sessionManager: SessionManager, speechService: SpeechService, eventBus: EventBus);
1494
- create(params: SessionCreateParams): Promise<Session>;
1495
- wireSideEffects(session: Session, deps: SideEffectDeps): void;
2146
+
2147
+ declare class TunnelService {
2148
+ private registry;
2149
+ private store;
2150
+ private server;
2151
+ private config;
2152
+ private systemPort;
2153
+ private startError;
2154
+ constructor(config: TunnelConfig, registryPath?: string, binDir?: string);
2155
+ start(): Promise<string>;
2156
+ stop(): Promise<void>;
2157
+ addTunnel(port: number, opts?: {
2158
+ label?: string;
2159
+ sessionId?: string;
2160
+ }): Promise<TunnelEntry>;
2161
+ stopTunnel(port: number): Promise<void>;
2162
+ stopAllUser(): Promise<void>;
2163
+ stopBySession(sessionId: string): Promise<TunnelEntry[]>;
2164
+ listTunnels(): TunnelEntry[];
2165
+ getTunnel(port: number): TunnelEntry | null;
2166
+ getPublicUrl(): string;
2167
+ getStartError(): string | undefined;
2168
+ getStore(): ViewerStore;
2169
+ fileUrl(entryId: string): string;
2170
+ diffUrl(entryId: string): string;
2171
+ outputUrl(entryId: string): string;
1496
2172
  }
1497
2173
 
1498
2174
  interface ContextProvider {
@@ -1509,6 +2185,8 @@ interface ContextQuery {
1509
2185
  interface ContextOptions {
1510
2186
  maxTokens?: number;
1511
2187
  limit?: number;
2188
+ /** When true, insert `## [agentName]` headers at agent boundaries in merged history */
2189
+ labelAgent?: boolean;
1512
2190
  }
1513
2191
  interface SessionInfo {
1514
2192
  checkpointId: string;
@@ -1543,47 +2221,246 @@ interface ContextResult {
1543
2221
  declare class ContextManager {
1544
2222
  private providers;
1545
2223
  private cache;
1546
- constructor();
2224
+ constructor(cachePath?: string);
1547
2225
  register(provider: ContextProvider): void;
1548
2226
  getProvider(repoPath: string): Promise<ContextProvider | null>;
1549
2227
  listSessions(query: ContextQuery): Promise<SessionListResult | null>;
1550
2228
  buildContext(query: ContextQuery, options?: ContextOptions): Promise<ContextResult | null>;
1551
2229
  }
1552
2230
 
1553
- declare class OpenACPCore {
1554
- configManager: ConfigManager;
1555
- agentCatalog: AgentCatalog;
1556
- agentManager: AgentManager;
1557
- sessionManager: SessionManager;
2231
+ interface SessionCreateParams {
2232
+ channelId: string;
2233
+ agentName: string;
2234
+ workingDirectory: string;
2235
+ resumeAgentSessionId?: string;
2236
+ existingSessionId?: string;
2237
+ initialName?: string;
2238
+ }
2239
+ interface SideEffectDeps {
2240
+ eventBus: EventBus;
1558
2241
  notificationManager: NotificationManager;
2242
+ tunnelService?: TunnelService;
2243
+ }
2244
+ declare class SessionFactory {
2245
+ private agentManager;
2246
+ private sessionManager;
2247
+ private speechServiceAccessor;
2248
+ private eventBus;
2249
+ private instanceRoot?;
2250
+ middlewareChain?: MiddlewareChain;
2251
+ private resumeLocks;
2252
+ /** Injected by Core after construction — needed for lazy resume error feedback */
2253
+ adapters?: Map<string, IChannelAdapter>;
2254
+ /** Injected by Core after construction — needed for lazy resume store lookup */
2255
+ sessionStore?: SessionStore | null;
2256
+ /** Injected by Core — creates full session with thread + bridge + persist */
2257
+ createFullSession?: (params: SessionCreateParams & {
2258
+ threadId?: string;
2259
+ createThread?: boolean;
2260
+ }) => Promise<Session>;
2261
+ /** Injected by Core — needed for resolving default agent and workspace */
2262
+ configManager?: ConfigManager$1;
2263
+ /** Injected by Core — needed for resolving agent definitions */
2264
+ agentCatalog?: AgentCatalog;
2265
+ /** Injected by Core — needed for context-aware session creation */
2266
+ getContextManager?: () => ContextManager | undefined;
2267
+ constructor(agentManager: AgentManager, sessionManager: SessionManager, speechServiceAccessor: SpeechService | (() => SpeechService), eventBus: EventBus, instanceRoot?: string | undefined);
2268
+ private get speechService();
2269
+ create(params: SessionCreateParams): Promise<Session>;
2270
+ /**
2271
+ * Get active session by thread, or attempt lazy resume from store.
2272
+ * Used by adapter command handlers and handleMessage().
2273
+ */
2274
+ getOrResume(channelId: string, threadId: string): Promise<Session | null>;
2275
+ private lazyResume;
2276
+ handleNewSession(channelId: string, agentName?: string, workspacePath?: string, options?: {
2277
+ createThread?: boolean;
2278
+ }): Promise<Session>;
2279
+ /** NOTE: handleNewChat is currently dead code — never called outside core.ts itself.
2280
+ * Moving it anyway for completeness; can be removed in a future cleanup. */
2281
+ handleNewChat(channelId: string, currentThreadId: string): Promise<Session | null>;
2282
+ createSessionWithContext(params: {
2283
+ channelId: string;
2284
+ agentName: string;
2285
+ workingDirectory: string;
2286
+ contextQuery: ContextQuery;
2287
+ contextOptions?: ContextOptions;
2288
+ createThread?: boolean;
2289
+ }): Promise<{
2290
+ session: Session;
2291
+ contextResult: ContextResult | null;
2292
+ }>;
2293
+ wireSideEffects(session: Session, deps: SideEffectDeps): void;
2294
+ }
2295
+
2296
+ declare class SecurityGuard {
2297
+ private configManager;
2298
+ private sessionManager;
2299
+ constructor(configManager: ConfigManager$1, sessionManager: SessionManager);
2300
+ checkAccess(message: IncomingMessage): {
2301
+ allowed: true;
2302
+ } | {
2303
+ allowed: false;
2304
+ reason: string;
2305
+ };
2306
+ }
2307
+
2308
+ declare class ServiceRegistry {
2309
+ private services;
2310
+ register<T>(name: string, implementation: T, pluginName: string): void;
2311
+ registerOverride<T>(name: string, implementation: T, pluginName: string): void;
2312
+ get<T>(name: string): T | undefined;
2313
+ has(name: string): boolean;
2314
+ list(): Array<{
2315
+ name: string;
2316
+ pluginName: string;
2317
+ }>;
2318
+ unregister(name: string): void;
2319
+ unregisterByPlugin(pluginName: string): void;
2320
+ }
2321
+
2322
+ interface ValidationResult {
2323
+ valid: boolean;
2324
+ errors?: string[];
2325
+ }
2326
+ declare class SettingsManager {
2327
+ private basePath;
2328
+ constructor(basePath: string);
2329
+ getBasePath(): string;
2330
+ createAPI(pluginName: string): SettingsAPI;
2331
+ loadSettings(pluginName: string): Promise<Record<string, unknown>>;
2332
+ validateSettings(_pluginName: string, settings: unknown, schema?: ZodSchema): ValidationResult;
2333
+ getSettingsPath(pluginName: string): string;
2334
+ getPluginSettings(pluginName: string): Promise<Record<string, unknown>>;
2335
+ updatePluginSettings(pluginName: string, updates: Record<string, unknown>): Promise<void>;
2336
+ }
2337
+
2338
+ interface PluginEntry {
2339
+ version: string;
2340
+ installedAt: string;
2341
+ updatedAt: string;
2342
+ source: 'builtin' | 'npm' | 'local';
2343
+ enabled: boolean;
2344
+ settingsPath: string;
2345
+ description?: string;
2346
+ }
2347
+ type RegisterInput = Omit<PluginEntry, 'installedAt' | 'updatedAt'>;
2348
+ declare class PluginRegistry {
2349
+ private registryPath;
2350
+ private data;
2351
+ constructor(registryPath: string);
2352
+ list(): Map<string, PluginEntry>;
2353
+ get(name: string): PluginEntry | undefined;
2354
+ register(name: string, entry: RegisterInput): void;
2355
+ remove(name: string): void;
2356
+ setEnabled(name: string, enabled: boolean): void;
2357
+ updateVersion(name: string, version: string): void;
2358
+ listEnabled(): Map<string, PluginEntry>;
2359
+ listBySource(source: PluginEntry['source']): Map<string, PluginEntry>;
2360
+ load(): Promise<void>;
2361
+ save(): Promise<void>;
2362
+ }
2363
+
2364
+ interface LifecycleManagerOpts {
2365
+ serviceRegistry?: ServiceRegistry;
2366
+ middlewareChain?: MiddlewareChain;
2367
+ errorTracker?: ErrorTracker;
2368
+ eventBus?: EventBus$1 & {
2369
+ on(event: string, handler: (...args: unknown[]) => void): void;
2370
+ off(event: string, handler: (...args: unknown[]) => void): void;
2371
+ emit(event: string, payload: unknown): void;
2372
+ };
2373
+ storagePath?: string;
2374
+ sessions?: unknown;
2375
+ config?: unknown;
2376
+ core?: unknown;
2377
+ log?: Logger;
2378
+ settingsManager?: SettingsManager;
2379
+ pluginRegistry?: PluginRegistry;
2380
+ /** Root directory for this OpenACP instance (default: ~/.openacp) */
2381
+ instanceRoot?: string;
2382
+ }
2383
+ declare class LifecycleManager {
2384
+ readonly serviceRegistry: ServiceRegistry;
2385
+ readonly middlewareChain: MiddlewareChain;
2386
+ readonly errorTracker: ErrorTracker;
2387
+ private eventBus;
2388
+ private storagePath;
2389
+ private sessions;
2390
+ private config;
2391
+ private core;
2392
+ private log;
2393
+ settingsManager: SettingsManager | undefined;
2394
+ private pluginRegistry;
2395
+ private instanceRoot;
2396
+ private contexts;
2397
+ private loadOrder;
2398
+ private _loaded;
2399
+ private _failed;
2400
+ get loadedPlugins(): string[];
2401
+ get failedPlugins(): string[];
2402
+ get registry(): PluginRegistry | undefined;
2403
+ constructor(opts?: LifecycleManagerOpts);
2404
+ private getPluginLogger;
2405
+ boot(plugins: OpenACPPlugin[]): Promise<void>;
2406
+ unloadPlugin(name: string): Promise<void>;
2407
+ shutdown(): Promise<void>;
2408
+ }
2409
+
2410
+ interface InstanceContext {
2411
+ id: string;
2412
+ root: string;
2413
+ isGlobal: boolean;
2414
+ paths: {
2415
+ config: string;
2416
+ sessions: string;
2417
+ agents: string;
2418
+ registryCache: string;
2419
+ plugins: string;
2420
+ pluginsData: string;
2421
+ pluginRegistry: string;
2422
+ logs: string;
2423
+ pid: string;
2424
+ running: string;
2425
+ apiPort: string;
2426
+ apiSecret: string;
2427
+ bin: string;
2428
+ cache: string;
2429
+ tunnels: string;
2430
+ agentsDir: string;
2431
+ };
2432
+ }
2433
+
2434
+ declare class OpenACPCore {
2435
+ configManager: ConfigManager$1;
2436
+ agentCatalog: AgentCatalog;
2437
+ agentManager: AgentManager;
2438
+ sessionManager: SessionManager;
1559
2439
  messageTransformer: MessageTransformer;
1560
- fileService: FileService;
1561
- readonly speechService: SpeechService;
1562
- securityGuard: SecurityGuard;
1563
- adapters: Map<string, ChannelAdapter>;
2440
+ adapters: Map<string, IChannelAdapter>;
2441
+ /** sessionId → SessionBridge — tracks active bridges for disconnect/reconnect during agent switch */
2442
+ private bridges;
1564
2443
  /** Set by main.ts — triggers graceful shutdown with restart exit code */
1565
2444
  requestRestart: (() => Promise<void>) | null;
1566
2445
  private _tunnelService?;
1567
2446
  private sessionStore;
1568
- private resumeLocks;
1569
2447
  eventBus: EventBus;
1570
2448
  sessionFactory: SessionFactory;
1571
- readonly usageStore: UsageStore | null;
1572
- readonly usageBudget: UsageBudget | null;
1573
- readonly contextManager: ContextManager;
1574
- constructor(configManager: ConfigManager);
2449
+ readonly lifecycleManager: LifecycleManager;
2450
+ private agentSwitchHandler;
2451
+ readonly instanceContext?: InstanceContext;
2452
+ private getService;
2453
+ get securityGuard(): SecurityGuard;
2454
+ get notificationManager(): NotificationManager;
2455
+ get fileService(): FileServiceInterface;
2456
+ get speechService(): SpeechService;
2457
+ get contextManager(): ContextManager;
2458
+ constructor(configManager: ConfigManager$1, ctx?: InstanceContext);
1575
2459
  get tunnelService(): TunnelService | undefined;
1576
2460
  set tunnelService(service: TunnelService | undefined);
1577
- registerAdapter(name: string, adapter: ChannelAdapter): void;
2461
+ registerAdapter(name: string, adapter: IChannelAdapter): void;
1578
2462
  start(): Promise<void>;
1579
2463
  stop(): Promise<void>;
1580
- summarizeSession(sessionId: string): Promise<{
1581
- ok: true;
1582
- summary: string;
1583
- } | {
1584
- ok: false;
1585
- error: string;
1586
- }>;
1587
2464
  archiveSession(sessionId: string): Promise<{
1588
2465
  ok: true;
1589
2466
  } | {
@@ -1599,6 +2476,7 @@ declare class OpenACPCore {
1599
2476
  existingSessionId?: string;
1600
2477
  createThread?: boolean;
1601
2478
  initialName?: string;
2479
+ threadId?: string;
1602
2480
  }): Promise<Session>;
1603
2481
  handleNewSession(channelId: string, agentName?: string, workspacePath?: string, options?: {
1604
2482
  createThread?: boolean;
@@ -1625,40 +2503,137 @@ declare class OpenACPCore {
1625
2503
  session: Session;
1626
2504
  contextResult: ContextResult | null;
1627
2505
  }>;
1628
- /**
1629
- * Get active session by thread, or attempt lazy resume from store.
1630
- * Used by adapter command handlers that need a session but don't go through handleMessage().
1631
- */
2506
+ switchSessionAgent(sessionId: string, toAgent: string): Promise<{
2507
+ resumed: boolean;
2508
+ }>;
1632
2509
  getOrResumeSession(channelId: string, threadId: string): Promise<Session | null>;
1633
- private lazyResume;
1634
2510
  /** Create a SessionBridge for the given session and adapter */
1635
- createBridge(session: Session, adapter: ChannelAdapter): SessionBridge;
2511
+ createBridge(session: Session, adapter: IChannelAdapter): SessionBridge;
2512
+ }
2513
+
2514
+ interface RegisteredCommand extends CommandDef {
2515
+ /** Scope extracted from pluginName, e.g. '@openacp/speech' → 'speech' */
2516
+ scope?: string;
2517
+ }
2518
+ /**
2519
+ * Central command registry with namespace resolution and adapter-specific overrides.
2520
+ *
2521
+ * Namespace rules:
2522
+ * - System commands always own the short name.
2523
+ * - Among plugins, the first to register wins the short name.
2524
+ * - Every plugin command also gets a qualified name: `scope:name`.
2525
+ * - Adapter plugins (@openacp/telegram, @openacp/discord)
2526
+ * registering a command that already exists → stored as an override
2527
+ * keyed by `channelId:commandName`, used when channelId matches.
2528
+ */
2529
+ declare class CommandRegistry {
2530
+ /** Main registry: short names + qualified names → RegisteredCommand */
2531
+ private commands;
2532
+ /** Adapter-specific overrides: `channelId:commandName` → RegisteredCommand */
2533
+ private overrides;
2534
+ private static ADAPTER_SCOPES;
2535
+ /**
2536
+ * Register a command definition.
2537
+ * @param def - Command definition
2538
+ * @param pluginName - Plugin that owns the command (set automatically by PluginContext)
2539
+ */
2540
+ register(def: CommandDef, pluginName?: string): void;
2541
+ /** Retrieve a command by name (short or qualified). */
2542
+ get(name: string): RegisteredCommand | undefined;
2543
+ /** Remove a command by name (short or qualified). Also removes its qualified name entry. */
2544
+ unregister(name: string): void;
2545
+ /** Remove all commands registered by a given plugin. */
2546
+ unregisterByPlugin(pluginName: string): void;
2547
+ /** Return all unique commands (deduplicated — each command appears once). */
2548
+ getAll(): RegisteredCommand[];
2549
+ /** Filter commands by category. */
2550
+ getByCategory(category: 'system' | 'plugin'): RegisteredCommand[];
2551
+ /**
2552
+ * Parse and execute a command string.
2553
+ * @param commandString - Full command string, e.g. "/greet hello world"
2554
+ * @param baseArgs - Base arguments (channelId, userId, etc.)
2555
+ * @returns CommandResponse
2556
+ */
2557
+ execute(commandString: string, baseArgs: CommandArgs): Promise<CommandResponse>;
2558
+ /** Extract scope from plugin name: '@openacp/speech' → 'speech', 'my-plugin' → 'my-plugin' */
2559
+ static extractScope(pluginName: string): string;
1636
2560
  }
1637
2561
 
1638
- interface AdapterFactory {
2562
+ interface CheckResult {
2563
+ status: "pass" | "warn" | "fail";
2564
+ message: string;
2565
+ fixable?: boolean;
2566
+ fixRisk?: "safe" | "risky";
2567
+ fix?: () => Promise<FixResult>;
2568
+ }
2569
+ interface FixResult {
2570
+ success: boolean;
2571
+ message: string;
2572
+ }
2573
+ interface DoctorReport {
2574
+ categories: CategoryResult[];
2575
+ summary: {
2576
+ passed: number;
2577
+ warnings: number;
2578
+ failed: number;
2579
+ fixed: number;
2580
+ };
2581
+ pendingFixes: PendingFix[];
2582
+ }
2583
+ interface CategoryResult {
1639
2584
  name: string;
1640
- createAdapter(core: OpenACPCore, config: ChannelConfig): ChannelAdapter;
2585
+ results: CheckResult[];
2586
+ }
2587
+ interface PendingFix {
2588
+ category: string;
2589
+ message: string;
2590
+ fix: () => Promise<FixResult>;
2591
+ }
2592
+
2593
+ declare class DoctorEngine {
2594
+ private dryRun;
2595
+ private dataDir;
2596
+ constructor(options?: {
2597
+ dryRun?: boolean;
2598
+ dataDir?: string;
2599
+ });
2600
+ runAll(): Promise<DoctorReport>;
2601
+ private buildContext;
2602
+ }
2603
+
2604
+ interface ConfigFieldDef {
2605
+ path: string;
2606
+ displayName: string;
2607
+ group: string;
2608
+ type: "toggle" | "select" | "number" | "string";
2609
+ options?: string[] | ((config: Config) => string[]);
2610
+ scope: "safe" | "sensitive";
2611
+ hotReload: boolean;
1641
2612
  }
1642
- declare function installPlugin(packageName: string): void;
1643
- declare function uninstallPlugin(packageName: string): void;
1644
- declare function listPlugins(): Record<string, string>;
1645
- declare function loadAdapterFactory(packageName: string): Promise<AdapterFactory | null>;
2613
+ declare const CONFIG_REGISTRY: ConfigFieldDef[];
2614
+ declare function getFieldDef(path: string): ConfigFieldDef | undefined;
2615
+ declare function getSafeFields(): ConfigFieldDef[];
2616
+ declare function isHotReloadable(path: string): boolean;
2617
+ declare function resolveOptions(def: ConfigFieldDef, config: Config): string[] | undefined;
2618
+ declare function getConfigValue(config: Config, path: string): unknown;
1646
2619
 
2620
+ declare function runConfigEditor(configManager: ConfigManager$1, mode?: 'file' | 'api', apiPort?: number): Promise<void>;
2621
+
2622
+ declare function getPidPath(root?: string): string;
1647
2623
  declare function getStatus(pidPath?: string): {
1648
2624
  running: boolean;
1649
2625
  pid?: number;
1650
2626
  };
1651
- declare function startDaemon(pidPath?: string, logDir?: string): {
2627
+ declare function startDaemon(pidPath?: string, logDir?: string, instanceRoot?: string): {
1652
2628
  pid: number;
1653
2629
  } | {
1654
2630
  error: string;
1655
2631
  };
1656
- declare function stopDaemon(pidPath?: string): Promise<{
2632
+ declare function stopDaemon(pidPath?: string, instanceRoot?: string): Promise<{
1657
2633
  stopped: boolean;
1658
2634
  pid?: number;
1659
2635
  error?: string;
1660
2636
  }>;
1661
- declare function getPidPath(): string;
1662
2637
 
1663
2638
  declare function isAutoStartSupported(): boolean;
1664
2639
  declare function installAutoStart(logDir: string): {
@@ -1671,7 +2646,142 @@ declare function uninstallAutoStart(): {
1671
2646
  };
1672
2647
  declare function isAutoStartInstalled(): boolean;
1673
2648
 
1674
- declare function runConfigEditor(configManager: ConfigManager, mode?: 'file' | 'api', apiPort?: number): Promise<void>;
2649
+ declare class FileService {
2650
+ private baseDir;
2651
+ constructor(baseDir: string);
2652
+ /**
2653
+ * Remove session file directories older than maxAgeDays.
2654
+ * Called on startup to prevent unbounded disk growth.
2655
+ */
2656
+ cleanupOldFiles(maxAgeDays: number): Promise<number>;
2657
+ saveFile(sessionId: string, fileName: string, data: Buffer, mimeType: string): Promise<Attachment>;
2658
+ resolveFile(filePath: string): Promise<Attachment | null>;
2659
+ /**
2660
+ * Convert OGG Opus audio to WAV format.
2661
+ * Telegram voice messages use OGG Opus which many AI agents can't read.
2662
+ */
2663
+ convertOggToWav(oggData: Buffer): Promise<Buffer>;
2664
+ /** Instance method — delegates to static for FileServiceInterface compliance */
2665
+ readTextFileWithRange(filePath: string, options?: {
2666
+ line?: number;
2667
+ limit?: number;
2668
+ }): Promise<string>;
2669
+ static readTextFileWithRange(filePath: string, options?: {
2670
+ line?: number;
2671
+ limit?: number;
2672
+ }): Promise<string>;
2673
+ /** Instance method — delegates to static for FileServiceInterface compliance */
2674
+ extensionFromMime(mimeType: string): string;
2675
+ static extensionFromMime(mimeType: string): string;
2676
+ }
2677
+
2678
+ interface ApiConfig {
2679
+ port: number;
2680
+ host: string;
2681
+ }
2682
+
2683
+ interface StoredToken {
2684
+ id: string;
2685
+ name: string;
2686
+ role: string;
2687
+ scopes?: string[];
2688
+ createdAt: string;
2689
+ refreshDeadline: string;
2690
+ lastUsedAt?: string;
2691
+ revoked: boolean;
2692
+ }
2693
+ interface CreateTokenOpts {
2694
+ role: string;
2695
+ name: string;
2696
+ expire: string;
2697
+ scopes?: string[];
2698
+ }
2699
+
2700
+ declare class TokenStore {
2701
+ private filePath;
2702
+ private tokens;
2703
+ private savePromise;
2704
+ private savePending;
2705
+ constructor(filePath: string);
2706
+ load(): Promise<void>;
2707
+ save(): Promise<void>;
2708
+ private scheduleSave;
2709
+ create(opts: CreateTokenOpts): StoredToken;
2710
+ get(id: string): StoredToken | undefined;
2711
+ revoke(id: string): void;
2712
+ list(): StoredToken[];
2713
+ private lastUsedSaveTimer;
2714
+ updateLastUsed(id: string): void;
2715
+ /** Wait for any in-flight save to complete */
2716
+ flush(): Promise<void>;
2717
+ destroy(): void;
2718
+ cleanup(): void;
2719
+ }
2720
+
2721
+ interface ApiServerOptions {
2722
+ port: number;
2723
+ host: string;
2724
+ getSecret: () => string;
2725
+ getJwtSecret: () => string;
2726
+ tokenStore: TokenStore;
2727
+ logger?: boolean;
2728
+ }
2729
+ interface ApiServerInstance {
2730
+ app: FastifyInstance;
2731
+ start(): Promise<{
2732
+ port: number;
2733
+ host: string;
2734
+ }>;
2735
+ stop(): Promise<void>;
2736
+ registerPlugin(prefix: string, plugin: FastifyPluginAsync, opts?: {
2737
+ auth?: boolean;
2738
+ }): void;
2739
+ }
2740
+ declare function createApiServer(options: ApiServerOptions): Promise<ApiServerInstance>;
2741
+
2742
+ interface ApiServerService {
2743
+ registerPlugin(prefix: string, plugin: FastifyPluginAsync, opts?: {
2744
+ auth?: boolean;
2745
+ }): void;
2746
+ authPreHandler: preHandlerHookHandler;
2747
+ requireScopes(...scopes: string[]): preHandlerHookHandler;
2748
+ requireRole(role: string): preHandlerHookHandler;
2749
+ getPort(): number;
2750
+ getBaseUrl(): string;
2751
+ getTunnelUrl(): string | null;
2752
+ }
2753
+ declare function createApiServerService(server: ApiServerInstance, getPort: () => number, getBaseUrl: () => string, getTunnelUrl: () => string | null, authPreHandler: preHandlerHookHandler): ApiServerService;
2754
+
2755
+ interface SessionStats {
2756
+ active: number;
2757
+ total: number;
2758
+ }
2759
+ declare class SSEManager {
2760
+ private eventBus;
2761
+ private getSessionStats;
2762
+ private startedAt;
2763
+ private sseConnections;
2764
+ private sseCleanupHandlers;
2765
+ private healthInterval?;
2766
+ private boundHandlers;
2767
+ constructor(eventBus: EventBus | undefined, getSessionStats: () => SessionStats, startedAt: number);
2768
+ setup(): void;
2769
+ handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void;
2770
+ broadcast(event: string, data: unknown): void;
2771
+ /**
2772
+ * Returns a Fastify route handler that hijacks the response
2773
+ * and delegates to the raw http SSE handler.
2774
+ */
2775
+ createFastifyHandler(): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
2776
+ stop(): void;
2777
+ }
2778
+
2779
+ declare class StaticServer {
2780
+ private uiDir;
2781
+ constructor(uiDir?: string);
2782
+ isAvailable(): boolean;
2783
+ serve(req: http.IncomingMessage, res: http.ServerResponse): boolean;
2784
+ }
1675
2785
 
1676
2786
  interface TopicInfo {
1677
2787
  sessionId: string;
@@ -1718,89 +2828,393 @@ declare class TopicManager {
1718
2828
  private isSystemTopic;
1719
2829
  }
1720
2830
 
1721
- interface ApiConfig {
1722
- port: number;
1723
- host: string;
2831
+ declare class EntireProvider implements ContextProvider {
2832
+ readonly name = "entire";
2833
+ isAvailable(repoPath: string): Promise<boolean>;
2834
+ listSessions(query: ContextQuery): Promise<SessionListResult>;
2835
+ buildContext(query: ContextQuery, options?: ContextOptions): Promise<ContextResult>;
2836
+ private buildSessionMarkdowns;
2837
+ private resolveSessions;
2838
+ private buildTitle;
1724
2839
  }
1725
- declare class ApiServer {
1726
- private core;
2840
+
2841
+ interface RenderedMessage<TComponents = unknown> {
2842
+ body: string;
2843
+ format: "html" | "markdown" | "plain" | "structured";
2844
+ attachments?: RenderedAttachment[];
2845
+ components?: TComponents;
2846
+ }
2847
+ interface RenderedPermission<TComponents = unknown> extends RenderedMessage<TComponents> {
2848
+ actions: RenderedAction[];
2849
+ }
2850
+ interface RenderedAction {
2851
+ id: string;
2852
+ label: string;
2853
+ isAllow?: boolean;
2854
+ }
2855
+ interface RenderedAttachment {
2856
+ type: "file" | "image" | "audio";
2857
+ data: Buffer | string;
2858
+ mimeType?: string;
2859
+ filename?: string;
2860
+ }
2861
+ interface IRenderer {
2862
+ renderText(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2863
+ renderToolCall(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2864
+ renderToolUpdate(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2865
+ renderPlan(content: OutgoingMessage): RenderedMessage;
2866
+ renderUsage(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2867
+ renderPermission(request: PermissionRequest): RenderedPermission;
2868
+ renderError(content: OutgoingMessage): RenderedMessage;
2869
+ renderNotification(notification: NotificationMessage): RenderedMessage;
2870
+ renderThought?(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2871
+ renderAttachment?(content: OutgoingMessage): RenderedMessage;
2872
+ renderSessionEnd?(content: OutgoingMessage): RenderedMessage;
2873
+ renderSystemMessage?(content: OutgoingMessage): RenderedMessage;
2874
+ renderModeChange?(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2875
+ renderConfigUpdate?(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2876
+ renderModelUpdate?(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2877
+ renderResource?(content: OutgoingMessage): RenderedMessage;
2878
+ renderResourceLink?(content: OutgoingMessage): RenderedMessage;
2879
+ }
2880
+ /**
2881
+ * BaseRenderer — plain text defaults. Extend for platform-specific rendering.
2882
+ */
2883
+ declare class BaseRenderer implements IRenderer {
2884
+ renderText(content: OutgoingMessage): RenderedMessage;
2885
+ renderToolCall(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2886
+ renderToolUpdate(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2887
+ renderPlan(content: OutgoingMessage): RenderedMessage;
2888
+ renderUsage(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2889
+ renderPermission(request: PermissionRequest): RenderedPermission;
2890
+ renderError(content: OutgoingMessage): RenderedMessage;
2891
+ renderNotification(notification: NotificationMessage): RenderedMessage;
2892
+ renderSystemMessage(content: OutgoingMessage): RenderedMessage;
2893
+ renderModeChange(content: OutgoingMessage): RenderedMessage;
2894
+ renderConfigUpdate(): RenderedMessage;
2895
+ renderModelUpdate(content: OutgoingMessage): RenderedMessage;
2896
+ renderResource(content: OutgoingMessage): RenderedMessage;
2897
+ renderResourceLink(content: OutgoingMessage): RenderedMessage;
2898
+ }
2899
+
2900
+ interface AdapterContext {
2901
+ configManager: {
2902
+ get(): Record<string, unknown>;
2903
+ };
2904
+ fileService?: unknown;
2905
+ }
2906
+ interface MessagingAdapterConfig extends ChannelConfig {
2907
+ maxMessageLength: number;
2908
+ flushInterval?: number;
2909
+ sendInterval?: number;
2910
+ thinkingRefreshInterval?: number;
2911
+ thinkingDuration?: number;
2912
+ displayVerbosity?: DisplayVerbosity;
2913
+ }
2914
+ declare abstract class MessagingAdapter implements IChannelAdapter {
2915
+ protected context: AdapterContext;
2916
+ protected adapterConfig: MessagingAdapterConfig;
2917
+ abstract readonly name: string;
2918
+ abstract readonly renderer: IRenderer;
2919
+ abstract readonly capabilities: AdapterCapabilities;
2920
+ constructor(context: AdapterContext, adapterConfig: MessagingAdapterConfig);
2921
+ sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
2922
+ protected dispatchMessage(sessionId: string, content: OutgoingMessage, verbosity: DisplayVerbosity): Promise<void>;
2923
+ protected handleText(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2924
+ protected handleThought(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2925
+ protected handleToolCall(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2926
+ protected handleToolUpdate(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2927
+ protected handlePlan(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2928
+ protected handleUsage(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2929
+ protected handleError(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2930
+ protected handleAttachment(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2931
+ protected handleSystem(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2932
+ protected handleSessionEnd(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2933
+ protected handleModeChange(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2934
+ protected handleConfigUpdate(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2935
+ protected handleModelUpdate(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2936
+ protected handleUserReplay(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2937
+ protected handleResource(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2938
+ protected handleResourceLink(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2939
+ protected getVerbosity(): DisplayVerbosity;
2940
+ protected shouldDisplay(content: OutgoingMessage, verbosity: DisplayVerbosity): boolean;
2941
+ abstract start(): Promise<void>;
2942
+ abstract stop(): Promise<void>;
2943
+ abstract createSessionThread(sessionId: string, name: string): Promise<string>;
2944
+ abstract renameSessionThread(sessionId: string, newName: string): Promise<void>;
2945
+ abstract sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
2946
+ abstract sendNotification(notification: NotificationMessage): Promise<void>;
2947
+ }
2948
+
2949
+ interface StreamEvent {
2950
+ type: string;
2951
+ sessionId?: string;
2952
+ payload: unknown;
2953
+ timestamp: number;
2954
+ }
2955
+ declare abstract class StreamAdapter implements IChannelAdapter {
2956
+ abstract readonly name: string;
2957
+ capabilities: AdapterCapabilities;
2958
+ constructor(config?: Partial<AdapterCapabilities>);
2959
+ sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
2960
+ sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
2961
+ sendNotification(notification: NotificationMessage): Promise<void>;
2962
+ createSessionThread(_sessionId: string, _name: string): Promise<string>;
2963
+ renameSessionThread(sessionId: string, name: string): Promise<void>;
2964
+ protected abstract emit(sessionId: string, event: StreamEvent): Promise<void>;
2965
+ protected abstract broadcast(event: StreamEvent): Promise<void>;
2966
+ abstract start(): Promise<void>;
2967
+ abstract stop(): Promise<void>;
2968
+ }
2969
+
2970
+ type QueueItemType = 'text' | 'other';
2971
+ interface SendQueueConfig {
2972
+ minInterval: number;
2973
+ categoryIntervals?: Record<string, number>;
2974
+ onRateLimited?: () => void;
2975
+ onError?: (error: Error) => void;
2976
+ }
2977
+ interface EnqueueOptions {
2978
+ type?: QueueItemType;
2979
+ key?: string;
2980
+ category?: string;
2981
+ }
2982
+ declare class SendQueue {
1727
2983
  private config;
1728
- private topicManager?;
1729
- private server;
1730
- private actualPort;
1731
- private portFilePath;
1732
- private startedAt;
1733
- private secret;
1734
- private secretFilePath;
1735
- private sseManager;
1736
- private staticServer;
1737
- private router;
1738
- constructor(core: OpenACPCore, config: ApiConfig, portFilePath?: string, topicManager?: TopicManager | undefined, secretFilePath?: string, uiDir?: string);
1739
- start(): Promise<void>;
1740
- stop(): Promise<void>;
1741
- getPort(): number;
1742
- getSecret(): string;
1743
- private writePortFile;
1744
- private removePortFile;
1745
- private loadOrCreateSecret;
1746
- private authenticate;
1747
- private handleRequest;
1748
- private sendJson;
1749
- private readBody;
2984
+ private items;
2985
+ private processing;
2986
+ private lastExec;
2987
+ private lastCategoryExec;
2988
+ constructor(config: SendQueueConfig);
2989
+ get pending(): number;
2990
+ enqueue<T>(fn: () => Promise<T>, opts?: EnqueueOptions): Promise<T | undefined>;
2991
+ onRateLimited(): void;
2992
+ clear(): void;
2993
+ private scheduleProcess;
2994
+ private getInterval;
2995
+ private processNext;
1750
2996
  }
1751
2997
 
1752
- interface SessionStats {
1753
- active: number;
1754
- total: number;
2998
+ interface DraftConfig {
2999
+ flushInterval: number;
3000
+ maxLength: number;
3001
+ onFlush: (sessionId: string, text: string, isEdit: boolean) => Promise<string | undefined>;
3002
+ onError?: (sessionId: string, error: Error) => void;
3003
+ }
3004
+ declare class Draft {
3005
+ private sessionId;
3006
+ private config;
3007
+ private buffer;
3008
+ private _messageId?;
3009
+ private firstFlushPending;
3010
+ private flushTimer?;
3011
+ private flushPromise;
3012
+ constructor(sessionId: string, config: DraftConfig);
3013
+ get isEmpty(): boolean;
3014
+ get messageId(): string | undefined;
3015
+ append(text: string): void;
3016
+ finalize(): Promise<string | undefined>;
3017
+ destroy(): void;
3018
+ private scheduleFlush;
3019
+ private flush;
1755
3020
  }
1756
- declare class SSEManager {
1757
- private eventBus;
1758
- private getSessionStats;
1759
- private startedAt;
1760
- private sseConnections;
1761
- private sseCleanupHandlers;
1762
- private healthInterval?;
1763
- private boundHandlers;
1764
- constructor(eventBus: EventBus | undefined, getSessionStats: () => SessionStats, startedAt: number);
1765
- setup(): void;
1766
- handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void;
1767
- broadcast(event: string, data: unknown): void;
1768
- stop(): void;
3021
+ declare class DraftManager {
3022
+ private config;
3023
+ private drafts;
3024
+ constructor(config: DraftConfig);
3025
+ getOrCreate(sessionId: string): Draft;
3026
+ finalize(sessionId: string): Promise<void>;
3027
+ finalizeAll(): Promise<void>;
3028
+ destroy(sessionId: string): void;
3029
+ destroyAll(): void;
1769
3030
  }
1770
3031
 
1771
- declare class StaticServer {
1772
- private uiDir;
1773
- constructor(uiDir?: string);
1774
- isAvailable(): boolean;
1775
- serve(req: http.IncomingMessage, res: http.ServerResponse): boolean;
3032
+ interface TrackedToolCall extends ToolCallMeta {
3033
+ messageId: string;
3034
+ }
3035
+ declare class ToolCallTracker {
3036
+ private sessions;
3037
+ track(sessionId: string, meta: ToolCallMeta, messageId: string): void;
3038
+ update(sessionId: string, toolId: string, status: string, patch?: Partial<Pick<ToolCallMeta, 'viewerLinks' | 'viewerFilePath' | 'name' | 'kind'>>): TrackedToolCall | null;
3039
+ getActive(sessionId: string): TrackedToolCall[];
3040
+ clear(sessionId: string): void;
3041
+ clearAll(): void;
1776
3042
  }
1777
3043
 
1778
- interface ConfigFieldDef {
1779
- path: string;
1780
- displayName: string;
1781
- group: string;
1782
- type: "toggle" | "select" | "number" | "string";
1783
- options?: string[] | ((config: Config) => string[]);
1784
- scope: "safe" | "sensitive";
1785
- hotReload: boolean;
3044
+ interface ActivityConfig {
3045
+ thinkingRefreshInterval: number;
3046
+ maxThinkingDuration: number;
3047
+ }
3048
+ interface ActivityCallbacks {
3049
+ sendThinkingIndicator(): Promise<void>;
3050
+ updateThinkingIndicator(): Promise<void>;
3051
+ removeThinkingIndicator(): Promise<void>;
3052
+ }
3053
+ declare class ActivityTracker {
3054
+ private config;
3055
+ private sessions;
3056
+ constructor(config: ActivityConfig);
3057
+ onThinkingStart(sessionId: string, callbacks: ActivityCallbacks): void;
3058
+ onTextStart(sessionId: string): void;
3059
+ onSessionEnd(sessionId: string): void;
3060
+ destroy(): void;
3061
+ private cleanup;
3062
+ private startRefresh;
3063
+ private stopRefresh;
1786
3064
  }
1787
- declare const CONFIG_REGISTRY: ConfigFieldDef[];
1788
- declare function getFieldDef(path: string): ConfigFieldDef | undefined;
1789
- declare function getSafeFields(): ConfigFieldDef[];
1790
- declare function isHotReloadable(path: string): boolean;
1791
- declare function resolveOptions(def: ConfigFieldDef, config: Config): string[] | undefined;
1792
- declare function getConfigValue(config: Config, path: string): unknown;
1793
3065
 
1794
- declare class EntireProvider implements ContextProvider {
1795
- readonly name = "entire";
1796
- isAvailable(repoPath: string): Promise<boolean>;
1797
- listSessions(query: ContextQuery): Promise<SessionListResult>;
1798
- buildContext(query: ContextQuery, options?: ContextOptions): Promise<ContextResult>;
1799
- private buildSessionMarkdowns;
1800
- private resolveSessions;
1801
- private buildTitle;
3066
+ interface ToolEntry {
3067
+ id: string;
3068
+ name: string;
3069
+ kind: string;
3070
+ rawInput: unknown;
3071
+ content: string | null;
3072
+ status: string;
3073
+ viewerLinks?: ViewerLinks;
3074
+ diffStats?: {
3075
+ added: number;
3076
+ removed: number;
3077
+ };
3078
+ displaySummary?: string;
3079
+ displayTitle?: string;
3080
+ displayKind?: string;
3081
+ isNoise: boolean;
3082
+ }
3083
+ declare class ToolStateMap {
3084
+ private entries;
3085
+ private pendingUpdates;
3086
+ /**
3087
+ * Creates or updates an entry from a tool_call event.
3088
+ * If a pending update exists for this id, applies it immediately.
3089
+ */
3090
+ upsert(meta: ToolCallMeta, kind: string, rawInput: unknown): ToolEntry;
3091
+ /**
3092
+ * Updates an existing entry from a tool_call_update event.
3093
+ * If the entry doesn't exist yet (out-of-order delivery), buffers the update.
3094
+ */
3095
+ merge(id: string, status: string, rawInput?: unknown, content?: string | null, viewerLinks?: ViewerLinks, diffStats?: {
3096
+ added: number;
3097
+ removed: number;
3098
+ }): ToolEntry | undefined;
3099
+ private _applyUpdate;
3100
+ get(id: string): ToolEntry | undefined;
3101
+ clear(): void;
3102
+ }
3103
+ declare class ThoughtBuffer {
3104
+ private chunks;
3105
+ private sealed;
3106
+ append(chunk: string): void;
3107
+ seal(): string;
3108
+ getText(): string;
3109
+ isSealed(): boolean;
3110
+ reset(): void;
1802
3111
  }
1803
3112
 
3113
+ interface ToolDisplaySpec {
3114
+ id: string;
3115
+ kind: string;
3116
+ icon: string;
3117
+ title: string;
3118
+ description: string | null;
3119
+ command: string | null;
3120
+ inputContent: string | null;
3121
+ outputSummary: string | null;
3122
+ outputContent: string | null;
3123
+ diffStats: {
3124
+ added: number;
3125
+ removed: number;
3126
+ } | null;
3127
+ viewerLinks?: ViewerLinks;
3128
+ outputViewerLink?: string;
3129
+ outputFallbackContent?: string;
3130
+ status: string;
3131
+ isNoise: boolean;
3132
+ isHidden: boolean;
3133
+ }
3134
+ interface ThoughtDisplaySpec {
3135
+ indicator: string;
3136
+ content: string | null;
3137
+ }
3138
+ declare class DisplaySpecBuilder {
3139
+ private tunnelService?;
3140
+ constructor(tunnelService?: TunnelServiceInterface | undefined);
3141
+ buildToolSpec(entry: ToolEntry, mode: OutputMode, sessionContext?: {
3142
+ id: string;
3143
+ workingDirectory: string;
3144
+ }): ToolDisplaySpec;
3145
+ buildThoughtSpec(content: string, mode: OutputMode): ThoughtDisplaySpec;
3146
+ }
3147
+
3148
+ interface UsageData {
3149
+ tokensUsed?: number;
3150
+ contextSize?: number;
3151
+ cost?: number;
3152
+ }
3153
+ interface ToolCardSnapshot {
3154
+ specs: ToolDisplaySpec[];
3155
+ planEntries?: PlanEntry[];
3156
+ usage?: UsageData;
3157
+ totalVisible: number;
3158
+ completedVisible: number;
3159
+ allComplete: boolean;
3160
+ }
3161
+ interface ToolCardStateConfig {
3162
+ onFlush: (snapshot: ToolCardSnapshot) => void;
3163
+ }
3164
+ declare class ToolCardState {
3165
+ private specs;
3166
+ private planEntries?;
3167
+ private usage?;
3168
+ private finalized;
3169
+ private isFirstFlush;
3170
+ private debounceTimer?;
3171
+ private onFlush;
3172
+ constructor(config: ToolCardStateConfig);
3173
+ updateFromSpec(spec: ToolDisplaySpec): void;
3174
+ updatePlan(entries: PlanEntry[]): void;
3175
+ appendUsage(usage: UsageData): void;
3176
+ finalize(): void;
3177
+ destroy(): void;
3178
+ hasContent(): boolean;
3179
+ private snapshot;
3180
+ private flush;
3181
+ private scheduleFlush;
3182
+ private clearDebounce;
3183
+ }
3184
+
3185
+ declare function progressBar(ratio: number, length?: number): string;
3186
+ declare function formatTokens(n: number): string;
3187
+ declare function stripCodeFences(text: string): string;
3188
+ declare function truncateContent(text: string, maxLen: number): string;
3189
+ declare function splitMessage(text: string, maxLength: number): string[];
3190
+
3191
+ declare function extractContentText(content: unknown, depth?: number): string;
3192
+ declare function formatToolSummary(name: string, rawInput: unknown, displaySummary?: string): string;
3193
+ declare function formatToolTitle(name: string, rawInput: unknown, displayTitle?: string): string;
3194
+ declare function resolveToolIcon(tool: {
3195
+ status?: string;
3196
+ displayKind?: string;
3197
+ kind?: string;
3198
+ }): string;
3199
+
3200
+ interface ConfigManagerLike {
3201
+ get(): Record<string, unknown>;
3202
+ }
3203
+ interface SessionManagerLike {
3204
+ getSessionRecord(id: string): {
3205
+ outputMode?: OutputMode;
3206
+ } | undefined;
3207
+ }
3208
+ declare class OutputModeResolver {
3209
+ resolve(configManager: ConfigManagerLike, adapterName: string, sessionId?: string, sessionManager?: SessionManagerLike): OutputMode;
3210
+ }
3211
+
3212
+ /**
3213
+ * OpenACP Product Guide — comprehensive reference for the AI assistant.
3214
+ * The assistant reads this at runtime to answer user questions about features.
3215
+ */
3216
+ declare const PRODUCT_GUIDE = "\n# OpenACP \u2014 Product Guide\n\nOpenACP lets you chat with AI coding agents (like Claude Code) through messaging platforms (Telegram, Discord).\nYou type messages in your chat platform, the agent reads/writes/runs code in your project folder, and results stream back in real time.\n\n---\n\n## Quick Start\n\n1. Start OpenACP: `openacp` (or `openacp start` for background daemon)\n2. Open your messaging platform (Telegram group or Discord server) \u2014 you'll see the Assistant topic/thread\n3. Tap/click \uD83C\uDD95 New Session or type /new\n4. Pick an agent and a project folder\n5. Chat in the session topic/thread \u2014 the agent works on your code\n\n---\n\n## Core Concepts\n\n### Sessions\nA session = one conversation with one AI agent working in one project folder.\nEach session gets its own topic (Telegram) or forum thread (Discord). Chat there to give instructions to the agent.\n\n### Agents\nAn agent is an AI coding tool (e.g., Claude Code, Gemini, Cursor, Codex, etc.).\nOpenACP supports 28+ agents from the official ACP Registry (agentclientprotocol.com).\nYou can install multiple agents and choose which one to use per session.\nThe default agent is used when you don't specify one.\n\n### Agent Management\n- Browse agents: `/agents` in your chat platform or `openacp agents` in CLI\n- Install: tap the install button in /agents, or `openacp agents install <name>`\n- Uninstall: `openacp agents uninstall <name>`\n- Setup/login: `openacp agents run <name> -- <args>` (e.g., `openacp agents run gemini -- auth login`)\n- Details: `openacp agents info <name>` shows version, dependencies, and setup steps\n\nSome agents need additional setup before they can be used:\n- Claude: requires `claude login`\n- Gemini: requires `openacp agents run gemini -- auth login`\n- Codex: requires setting `OPENAI_API_KEY` environment variable\n- GitHub Copilot: requires `openacp agents run copilot -- auth login`\n\nAgents are installed in three ways depending on the agent:\n- **npx** \u2014 Node.js agents, downloaded automatically on first use\n- **uvx** \u2014 Python agents, downloaded automatically on first use\n- **binary** \u2014 Platform-specific binaries, downloaded to `~/.openacp/agents/`\n\n### Project Folder (Workspace)\nThe directory where the agent reads, writes, and runs code.\nWhen creating a session, you choose which folder the agent works in.\nYou can type a full path like `~/code/my-project` or just a name like `my-project` (it becomes `<base-dir>/my-project`).\n\n### System Topics\n- **Assistant** \u2014 Always-on helper that can answer questions, create sessions, check status, troubleshoot\n- **Notifications** \u2014 System alerts (permission requests, session errors, completions)\n\n---\n\n## Creating Sessions\n\n### From menu\nTap \uD83C\uDD95 New Session \u2192 choose agent (if multiple) \u2192 choose project folder \u2192 confirm\n\n### From command\n- `/new` \u2014 Interactive flow (asks agent + folder)\n- `/new claude ~/code/my-project` \u2014 Create directly with specific agent and folder\n\n### From Assistant topic\nJust ask: \"Create a session for my-project with claude\" \u2014 the assistant handles it\n\n### Quick new chat\n`/newchat` in a session topic \u2014 creates new session with same agent and folder as current one\n\n---\n\n## Working with Sessions\n\n### Chat\nType messages in the session topic. The agent responds with code changes, explanations, tool outputs.\n\n### What you see while the agent works\n- **\uD83D\uDCAD Thinking indicator** \u2014 Shows when the agent is reasoning, with elapsed time\n- **Text responses** \u2014 Streamed in real time, updated every few seconds\n- **Tool calls** \u2014 When the agent runs commands or edits files, you see tool name, input, status, and output\n- **\uD83D\uDCCB Plan card** \u2014 Visual task progress with completed/in-progress/pending items and progress bar\n- **\"View File\" / \"View Diff\" buttons** \u2014 Opens in browser with Monaco editor (requires tunnel)\n\n### Session lifecycle\n1. **Creating** \u2014 Topic created, agent spawning\n2. **Warming up** \u2014 Agent primes its cache (happens automatically, invisible to you)\n3. **Active** \u2014 Ready for your messages\n4. **Auto-naming** \u2014 After your first message, the session gets a descriptive name (agent summarizes in ~5 words). The topic title updates automatically.\n5. **Finished/Error** \u2014 Session completed or hit an error\n\n### Agent skills\nSome agents provide slash commands (e.g., /compact, /review). Available skills are pinned in the session topic.\n\n### Permission requests\nWhen the agent wants to run a command, it asks for permission.\nYou see buttons: \u2705 Allow, \u274C Reject (and sometimes \"Always Allow\").\nA notification also appears in the Notifications topic with a link to the request.\n\n### Dangerous mode\nAuto-approves ALL permission requests \u2014 the agent runs any command without asking.\n- Enable: `/enable_dangerous` or tap the \u2620\uFE0F button in the session\n- Disable: `/disable_dangerous` or tap the \uD83D\uDD10 button\n- \u26A0\uFE0F Use with caution \u2014 the agent can execute anything\n\n### Session timeout\nIdle sessions are automatically cancelled after a configurable timeout (default: 60 minutes).\nConfigure via `security.sessionTimeoutMinutes` in config.\n\n---\n\n## Session Transfer (Handoff)\n\n### Chat \u2192 Terminal\n1. Type `/handoff` in a session topic/thread\n2. You get a command like `claude --resume <SESSION_ID>`\n3. Copy and run it in your terminal \u2014 the session continues there with full conversation history\n\n### Terminal \u2192 Chat\n1. First time: run `openacp integrate claude` to install the handoff skill (one-time setup)\n2. In Claude Code, use the /openacp:handoff slash command\n3. The session appears as a new topic/thread and you can continue chatting there\n\n### How it works\n- The agent session ID is shared between platforms\n- Conversation history is preserved \u2014 pick up where you left off\n- The agent that supports resume (e.g., Claude with `--resume`) handles the actual transfer\n\n---\n\n## Managing Sessions\n\n### Status\n- `/status` \u2014 Shows active sessions count and details\n- Ask the Assistant: \"What sessions are running?\"\n\n### List all sessions\n- `/sessions` \u2014 Shows all sessions with status (active, finished, error)\n\n### Cancel\n- `/cancel` in a session topic \u2014 cancels that session\n- Ask the Assistant: \"Cancel the stuck session\"\n\n### Cleanup\n- From `/sessions` \u2192 tap cleanup buttons (finished, errors, all)\n- Ask the Assistant: \"Clean up old sessions\"\n\n---\n\n## Assistant Topic\n\nThe Assistant is an always-on AI helper in its own topic. It can:\n- Answer questions about OpenACP\n- Create sessions for you\n- Check status and health\n- Cancel sessions\n- Clean up old sessions\n- Troubleshoot issues\n- Manage configuration\n\nJust chat naturally: \"How do I create a session?\", \"What's the status?\", \"Something is stuck\"\n\n### Clear history\n`/clear` in the Assistant topic \u2014 resets the conversation\n\n---\n\n## System Commands\n\n| Command | Where | What it does |\n|---------|-------|-------------|\n| `/new [agent] [path]` | Anywhere | Create new session |\n| `/newchat` | Session topic | New session, same agent + folder |\n| `/cancel` | Session topic | Cancel current session |\n| `/status` | Anywhere | Show status |\n| `/sessions` | Anywhere | List all sessions |\n| `/agents` | Anywhere | Browse & install agents from ACP Registry |\n| `/install <name>` | Anywhere | Install an agent |\n| `/enable_dangerous` | Session topic | Auto-approve all permissions |\n| `/disable_dangerous` | Session topic | Restore permission prompts |\n| `/handoff` | Session topic | Transfer session to terminal |\n| `/clear` | Assistant topic | Clear assistant history |\n| `/menu` | Anywhere | Show action menu |\n| `/help` | Anywhere | Show help |\n| `/restart` | Anywhere | Restart OpenACP |\n| `/update` | Anywhere | Update to latest version |\n| `/integrate` | Anywhere | Manage agent integrations |\n\n---\n\n## Menu Buttons\n\n| Button | Action |\n|--------|--------|\n| \uD83C\uDD95 New Session | Create new session (interactive) |\n| \uD83D\uDCCB Sessions | List all sessions with cleanup options |\n| \uD83D\uDCCA Status | Show active/total session count |\n| \uD83E\uDD16 Agents | List available agents |\n| \uD83D\uDD17 Integrate | Manage agent integrations |\n| \u2753 Help | Show help text |\n| \uD83D\uDD04 Restart | Restart OpenACP |\n| \u2B06\uFE0F Update | Check and install updates |\n\n---\n\n## CLI Commands\n\n### Server\n- `openacp` \u2014 Start (uses configured mode: foreground or daemon)\n- `openacp start` \u2014 Start as background daemon\n- `openacp stop` \u2014 Stop daemon\n- `openacp status` \u2014 Show daemon status\n- `openacp logs` \u2014 Tail daemon logs\n- `openacp --foreground` \u2014 Force foreground mode (useful for debugging or containers)\n\n### Auto-start (run on boot)\n- macOS: installs a LaunchAgent in `~/Library/LaunchAgents/`\n- Linux: installs a systemd user service in `~/.config/systemd/user/`\n- Enabled automatically when you start the daemon. Remove with `openacp stop`.\n\n### Configuration\n- `openacp config` \u2014 Interactive config editor\n- `openacp reset` \u2014 Delete all data and start fresh\n\n### Agent Management (CLI)\n- `openacp agents` \u2014 List all agents (installed + available from ACP Registry)\n- `openacp agents install <name>` \u2014 Install an agent\n- `openacp agents uninstall <name>` \u2014 Remove an agent\n- `openacp agents info <name>` \u2014 Show details, dependencies, and setup guide\n- `openacp agents run <name> [-- args]` \u2014 Run agent CLI directly (for login, config, etc.)\n- `openacp agents refresh` \u2014 Force-refresh registry cache\n\n### Plugins\n- `openacp install <package>` \u2014 Install adapter plugin\n- `openacp uninstall <package>` \u2014 Remove adapter plugin\n- `openacp plugins` \u2014 List installed plugins\n\n### Integration\n- `openacp integrate <agent>` \u2014 Install agent integration (e.g., Claude handoff skill)\n- `openacp integrate <agent> --uninstall` \u2014 Remove integration\n\n### API (requires running daemon)\n`openacp api <command>` \u2014 Interact with running daemon:\n\n| Command | Description |\n|---------|-------------|\n| `status` | List active sessions |\n| `session <id>` | Session details |\n| `new <agent> <path>` | Create session |\n| `send <id> \"text\"` | Send prompt |\n| `cancel <id>` | Cancel session |\n| `dangerous <id> on/off` | Toggle dangerous mode |\n| `topics [--status x,y]` | List topics |\n| `delete-topic <id> [--force]` | Delete topic |\n| `cleanup [--status x,y]` | Cleanup old topics |\n| `agents` | List agents |\n| `health` | System health |\n| `config` | Show config |\n| `config set <key> <value>` | Update config |\n| `adapters` | List adapters |\n| `tunnel` | Tunnel status |\n| `notify \"message\"` | Send notification |\n| `version` | Daemon version |\n| `restart` | Restart daemon |\n\n---\n\n## File Viewer (Tunnel)\n\nWhen tunnel is enabled, file edits and diffs get \"View\" buttons that open in your browser:\n- **Monaco Editor** \u2014 Full VS Code editor with syntax highlighting\n- **Diff viewer** \u2014 Side-by-side or inline comparison\n- **Line highlighting** \u2014 Click lines to highlight\n- Dark/light theme toggle\n\n### Setup\nEnable in config: set `tunnel.enabled` to `true`.\nProviders: Cloudflare (default, free), ngrok, bore, Tailscale Funnel.\n\n### Port Tunneling\n\nExpose any local port (dev servers, APIs, etc.) to the internet:\n\n**CLI commands** (agent can call these directly):\n- `openacp tunnel add <port> --label <name>` \u2014 Create tunnel to a local port\n- `openacp tunnel list` \u2014 List active tunnels\n- `openacp tunnel stop <port>` \u2014 Stop a tunnel\n- `openacp tunnel stop-all` \u2014 Stop all user tunnels\n\n**Telegram commands**:\n- `/tunnel <port> [label]` \u2014 Create tunnel\n- `/tunnels` \u2014 List active tunnels\n- `/tunnel stop <port>` \u2014 Stop tunnel\n\nExample: after starting a dev server on port 3000, run `openacp tunnel add 3000 --label my-app` to get a public URL.\n\n---\n\n## Configuration\n\nConfig file: `~/.openacp/config.json`\n\n### Channels\n- **channels.telegram.botToken** \u2014 Your Telegram bot token\n- **channels.telegram.chatId** \u2014 Your Telegram supergroup ID\n- **channels.discord.botToken** \u2014 Your Discord bot token\n- **channels.discord.guildId** \u2014 Your Discord server (guild) ID\n\n### Agents\n- **defaultAgent** \u2014 Which agent to use by default\n- Agents are managed via `/agents` (Telegram) or `openacp agents` (CLI)\n- Installed agents are stored in `~/.openacp/agents.json`\n- Agent list is fetched from the ACP Registry CDN and cached locally (24h)\n\n### Workspace\n- **workspace.baseDir** \u2014 Base directory for project folders (default: `~/openacp-workspace`)\n\n### Security\n- **security.allowedUserIds** \u2014 Restrict who can use the bot (empty = everyone)\n- **security.maxConcurrentSessions** \u2014 Max parallel sessions (default: 5)\n- **security.sessionTimeoutMinutes** \u2014 Auto-cancel idle sessions (default: 60)\n\n### Tunnel / File Viewer\n- **tunnel.enabled** \u2014 Enable file viewer tunnel\n- **tunnel.provider** \u2014 Tunnel provider: cloudflare (default, free), ngrok, bore, tailscale\n- **tunnel.port** \u2014 Local port for tunnel server (default: 3100)\n- **tunnel.auth.enabled** \u2014 Enable authentication for tunnel URLs\n- **tunnel.auth.token** \u2014 Auth token for tunnel access\n- **tunnel.storeTtlMinutes** \u2014 How long viewer links stay cached (default: 60)\n\n### Logging\n- **logging.level** \u2014 Log level: silent, debug, info, warn, error, fatal (default: info)\n- **logging.logDir** \u2014 Log directory (default: `~/.openacp/logs`)\n- **logging.maxFileSize** \u2014 Max log file size before rotation\n- **logging.maxFiles** \u2014 Max number of rotated log files\n- **logging.sessionLogRetentionDays** \u2014 Auto-delete old session logs (default: 30)\n\n### Data Retention\n- **sessionStore.ttlDays** \u2014 How long session records persist (default: 30). Old records are cleaned up automatically.\n\n### Environment variables\nOverride config with env vars:\n- `OPENACP_TELEGRAM_BOT_TOKEN`\n- `OPENACP_TELEGRAM_CHAT_ID`\n- `OPENACP_DISCORD_BOT_TOKEN`\n- `OPENACP_DISCORD_GUILD_ID`\n- `OPENACP_DEFAULT_AGENT`\n- `OPENACP_RUN_MODE` \u2014 foreground or daemon\n- `OPENACP_API_PORT` \u2014 API server port (default: 21420)\n- `OPENACP_TUNNEL_ENABLED`\n- `OPENACP_TUNNEL_PORT`\n- `OPENACP_TUNNEL_PROVIDER`\n- `OPENACP_LOG_LEVEL`\n- `OPENACP_LOG_DIR`\n- `OPENACP_DEBUG` \u2014 Sets log level to debug\n\n---\n\n## Troubleshooting\n\n### Session stuck / not responding\n- Check status: ask Assistant \"Is anything stuck?\"\n- Cancel and create new: `/cancel` then `/new`\n- Check system health: Assistant can run health check\n\n### Agent not found\n- Check available agents: `/agents` or `openacp agents`\n- Install missing agent: `openacp agents install <name>`\n- Some agents need login first: `openacp agents info <name>` to see setup steps\n- Run agent CLI for setup: `openacp agents run <name> -- <args>`\n\n### Permission request not showing\n- Check Notifications topic for the alert\n- Try `/enable_dangerous` to auto-approve (if you trust the agent)\n\n### Session disappeared after restart\n- Sessions persist across restarts\n- Send a message in the old topic \u2014 it auto-resumes\n- If topic was deleted, the session record may still exist in status\n\n### Bot not responding at all\n- Check daemon: `openacp status`\n- Check logs: `openacp logs`\n- Restart: `openacp start` or `/restart`\n\n### Messages going to wrong topic\n- Each session is bound to a specific topic/thread\n- If you see messages appearing in the Assistant topic instead of the session topic, try creating a new session\n\n### Viewing logs\n- Session-specific logs: `~/.openacp/logs/sessions/`\n- System logs: `openacp logs` to tail live\n- Set `OPENACP_DEBUG=true` for verbose output\n\n---\n\n## Data & Storage\n\nAll data is stored in `~/.openacp/`:\n- `config.json` \u2014 Configuration\n- `agents.json` \u2014 Installed agents (managed by AgentCatalog)\n- `registry-cache.json` \u2014 Cached ACP Registry data (refreshes every 24h)\n- `agents/` \u2014 Downloaded binary agents\n- `sessions/` \u2014 Session records and state\n- `topics/` \u2014 Topic-to-session mappings\n- `logs/` \u2014 System and session logs\n- `plugins/` \u2014 Installed adapter plugins\n- `openacp.pid` \u2014 Daemon PID file\n\nSession records auto-cleanup: 30 days (configurable via `sessionStore.ttlDays`).\nSession logs auto-cleanup: 30 days (configurable via `logging.sessionLogRetentionDays`).\n";
3217
+
1804
3218
  interface TelegramChannelConfig extends ChannelConfig {
1805
3219
  botToken: string;
1806
3220
  chatId: number;
@@ -1808,39 +3222,88 @@ interface TelegramChannelConfig extends ChannelConfig {
1808
3222
  assistantTopicId: number | null;
1809
3223
  }
1810
3224
 
1811
- declare class TelegramAdapter extends ChannelAdapter<OpenACPCore> {
3225
+ declare class TelegramAdapter extends MessagingAdapter {
3226
+ readonly name = "telegram";
3227
+ readonly renderer: IRenderer;
3228
+ readonly capabilities: AdapterCapabilities;
3229
+ private core;
1812
3230
  private bot;
1813
3231
  private telegramConfig;
3232
+ private saveTopicIds?;
1814
3233
  private permissionHandler;
1815
3234
  private assistantSession;
1816
3235
  private assistantInitializing;
1817
3236
  private notificationTopicId;
1818
3237
  private assistantTopicId;
1819
3238
  private sendQueue;
1820
- private toolTracker;
3239
+ private _sessionThreadIds;
3240
+ private outputModeResolver;
1821
3241
  private draftManager;
1822
3242
  private skillManager;
1823
3243
  private fileService;
1824
3244
  private sessionTrackers;
1825
- private get verbosity();
3245
+ private callbackCache;
3246
+ private callbackCounter;
3247
+ /** Pending skill commands queued when session.threadId was not yet set */
3248
+ private _pendingSkillCommands;
3249
+ private getThreadId;
1826
3250
  private getOrCreateTracker;
1827
- constructor(core: OpenACPCore, config: TelegramChannelConfig);
3251
+ constructor(core: OpenACPCore, config: TelegramChannelConfig, saveTopicIds?: (updates: {
3252
+ notificationTopicId?: number;
3253
+ assistantTopicId?: number;
3254
+ }) => Promise<void>);
1828
3255
  start(): Promise<void>;
3256
+ /**
3257
+ * Retry an async operation with exponential backoff.
3258
+ * Used for Telegram API calls that may fail due to transient network issues.
3259
+ */
3260
+ private retryWithBackoff;
3261
+ /**
3262
+ * Register Telegram commands in the background with retries.
3263
+ * Non-critical — bot works fine without autocomplete commands.
3264
+ */
3265
+ private registerCommandsWithRetry;
1829
3266
  stop(): Promise<void>;
3267
+ private renderCommandResponse;
3268
+ private toCallbackData;
3269
+ private fromCallbackData;
1830
3270
  private setupRoutes;
1831
- private messageHandlers;
3271
+ /**
3272
+ * Per-session serial dispatch queues.
3273
+ * SessionBridge fires sendMessage() as fire-and-forget, so multiple events
3274
+ * (tool_call, tool_update, text) can arrive concurrently. Without serialization,
3275
+ * fast handlers (tool_update) overtake slow ones (tool_call with draftManager.finalize),
3276
+ * causing out-of-order processing where a tool's completion update is processed before
3277
+ * its creation event. This queue ensures events are processed in the order they arrive.
3278
+ */
3279
+ private _dispatchQueues;
3280
+ private getTracer;
1832
3281
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
3282
+ protected handleThought(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
3283
+ protected handleText(sessionId: string, content: OutgoingMessage): Promise<void>;
3284
+ protected handleToolCall(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
3285
+ protected handleToolUpdate(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
3286
+ protected handlePlan(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
3287
+ protected handleUsage(sessionId: string, content: OutgoingMessage, verbosity: DisplayVerbosity): Promise<void>;
3288
+ protected handleAttachment(sessionId: string, content: OutgoingMessage): Promise<void>;
3289
+ protected handleSessionEnd(sessionId: string, _content: OutgoingMessage): Promise<void>;
3290
+ protected handleError(sessionId: string, content: OutgoingMessage): Promise<void>;
3291
+ protected handleSystem(sessionId: string, content: OutgoingMessage): Promise<void>;
1833
3292
  sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
1834
3293
  sendNotification(notification: NotificationMessage): Promise<void>;
1835
3294
  createSessionThread(sessionId: string, name: string): Promise<string>;
1836
3295
  renameSessionThread(sessionId: string, newName: string): Promise<void>;
1837
3296
  deleteSessionThread(sessionId: string): Promise<void>;
1838
3297
  sendSkillCommands(sessionId: string, commands: AgentCommand[]): Promise<void>;
3298
+ /** Flush any skill commands that were queued before threadId was available */
3299
+ flushPendingSkillCommands(sessionId: string): Promise<void>;
1839
3300
  private resolveSessionId;
1840
3301
  private downloadTelegramFile;
1841
3302
  private handleIncomingMedia;
1842
3303
  cleanupSkillCommands(sessionId: string): Promise<void>;
3304
+ cleanupSessionState(sessionId: string): Promise<void>;
3305
+ stripTTSBlock(sessionId: string): Promise<void>;
1843
3306
  archiveSessionTopic(sessionId: string): Promise<void>;
1844
3307
  }
1845
3308
 
1846
- export { type AdapterFactory, AgentCatalog, type AgentCommand, type AgentDefinition, type AgentDistribution, type AgentEvent, AgentInstance, type AgentListItem, AgentManager, AgentStore, type ApiConfig, ApiServer, type Attachment, type AvailabilityResult, type BridgeDeps, CONFIG_REGISTRY, ChannelAdapter, type ChannelConfig, type CleanupResult, type Config, type ConfigFieldDef, ConfigManager, ContextManager, type ContextOptions, type ContextProvider, type ContextQuery, type ContextResult, type SessionInfo as ContextSessionInfo, type DeleteTopicResult, type DiscordPlatformData, EntireProvider, EventBus, type EventBusEvents, FileService, GroqSTT, type IChannelAdapter, type IncomingMessage, type InstallProgress, type InstallResult, type InstalledAgent, type Logger, type LoggingConfig, MessageTransformer, NotificationManager, type NotificationMessage, OpenACPCore, type OutgoingMessage, PLUGINS_DIR, PermissionGate, type PermissionOption, type PermissionRequest, type PlanEntry, PromptQueue, type RegistryAgent, type RegistryBinaryTarget, type RegistryDistribution, SSEManager, type STTOptions, type STTProvider, type STTResult, SecurityGuard, Session, SessionBridge, type SessionCreateParams, type SessionEvents, SessionFactory, type SessionListResult, SessionManager, type SessionRecord, type SessionStatus, type SideEffectDeps, type SpeechProviderConfig, SpeechService, type SpeechServiceConfig, StaticServer, StderrCapture, type TTSOptions, type TTSProvider, type TTSResult, TelegramAdapter, type TelegramPlatformData, type TopicInfo, TopicManager, TypedEmitter, UsageBudget, type UsageConfig, type UsageRecord, UsageStore, type UsageSummary, cleanupOldSessionLogs, createChildLogger, createSessionLogger, expandHome, getConfigValue, getFieldDef, getPidPath, getSafeFields, getStatus, initLogger, installAutoStart, installPlugin, isAutoStartInstalled, isAutoStartSupported, isHotReloadable, listPlugins, loadAdapterFactory, log, nodeToWebReadable, nodeToWebWritable, resolveOptions, runConfigEditor, setLogLevel, shutdownLogger, startDaemon, stopDaemon, uninstallAutoStart, uninstallPlugin };
3309
+ export { ActivityTracker, type AdapterCapabilities, type AgentCapabilities, AgentCatalog, type AgentCommand, type AgentDefinition, type AgentDistribution, type AgentEvent, AgentInstance, type AgentListItem, AgentManager, AgentStore, type AgentSwitchEntry, type ApiConfig, type ApiServerInstance, type ApiServerOptions, type ApiServerService, type Attachment, type AuthMethod, type AuthenticateRequest, type AvailabilityResult, BaseRenderer, type BridgeDeps, CONFIG_REGISTRY, ChannelAdapter, type ChannelConfig, type CleanupResult, type CommandArgs, type CommandDef, CommandRegistry, type CommandResponse, type Config, type ConfigFieldDef, ConfigManager$1 as ConfigManager, type ConfigOption, type ConfigSelectChoice, type ConfigSelectGroup, type ContentBlock, ContextManager, type ContextOptions, type ContextProvider, type ContextQuery, type ContextResult, type ContextService, type SessionInfo as ContextSessionInfo, type DeleteTopicResult, DisplaySpecBuilder, type DisplayVerbosity, DoctorEngine, type DoctorReport, DraftManager, EntireProvider, EventBus, type EventBusEvents, FileService, type FileServiceInterface, GroqSTT, type IChannelAdapter, type IRenderer, type IncomingMessage, type InstallContext, type InstallProgress, type InstallResult, type InstalledAgent, KIND_ICONS, type ListItem, type Logger$1 as Logger, type LoggingConfig, type McpServerConfig, type MenuOption, MessageTransformer, MessagingAdapter, type MessagingAdapterConfig, type MigrateContext, type ModelInfo, type NewSessionResponse, NotificationManager, type NotificationMessage, type NotificationService, OpenACPCore, type OpenACPPlugin, type OutgoingMessage, type OutputMode, OutputModeResolver, PRODUCT_GUIDE, type PendingFix, PermissionGate, type PermissionOption, type PermissionRequest, type PlanEntry, type PluginContext, type PluginPermission, type PluginStorage, PromptQueue, type PromptResponse, type RegistryAgent, type RegistryBinaryTarget, type RegistryDistribution, type RenderedMessage, SSEManager, STATUS_ICONS, type STTOptions, type STTProvider$1 as STTProvider, type STTResult, SecurityGuard, type SecurityService, SendQueue, Session, SessionBridge, type SessionCreateParams, type SessionEvents, SessionFactory, type SessionListItem, type SessionListResponse, type SessionListResult, SessionManager, type SessionMode, type SessionModeState, type SessionModelState, type SessionRecord, type SessionStatus, type SetConfigOptionValue, type SettingsAPI, type SideEffectDeps, type SpeechProviderConfig, SpeechService, type SpeechServiceConfig, type SpeechServiceInterface, StaticServer, StderrCapture, type StopReason, StreamAdapter, type TTSOptions, type TTSProvider$1 as TTSProvider, type TTSResult, TelegramAdapter, type TelegramPlatformData, type TerminalIO, ThoughtBuffer, type ThoughtDisplaySpec, type ToolCallMeta, ToolCallTracker, type ToolCardSnapshot, ToolCardState, type ToolCardStateConfig, type ToolDisplaySpec, type ToolEntry, ToolStateMap, type ToolUpdateMeta, type TopicInfo, TopicManager, type TunnelServiceInterface, TypedEmitter, type UsageConfig, type UsageRecord, type UsageRecordEvent, type UsageService, type ViewerLinks, cleanupOldSessionLogs, createApiServer, createApiServerService, createChildLogger, createSessionLogger, expandHome, extractContentText, formatTokens, formatToolSummary, formatToolTitle, getConfigValue, getFieldDef, getPidPath, getSafeFields, getStatus, initLogger, installAutoStart, isAutoStartInstalled, isAutoStartSupported, isHotReloadable, log, nodeToWebReadable, nodeToWebWritable, progressBar, resolveOptions, resolveToolIcon, runConfigEditor, setLogLevel, shutdownLogger, splitMessage, startDaemon, stopDaemon, stripCodeFences, truncateContent, uninstallAutoStart };