@openacp/cli 0.6.9 → 2026.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +116 -152
  2. package/dist/cli.d.ts +11 -0
  3. package/dist/cli.js +27740 -415
  4. package/dist/cli.js.map +1 -1
  5. package/dist/data/registry-snapshot.json +1 -1
  6. package/dist/index.d.ts +1944 -463
  7. package/dist/index.js +17365 -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-LNEGLMOE.js +0 -799
  13. package/dist/adapter-LNEGLMOE.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-BDYVCIBH.js +0 -735
  43. package/dist/chunk-BDYVCIBH.js.map +0 -1
  44. package/dist/chunk-BN3X7UXB.js +0 -738
  45. package/dist/chunk-BN3X7UXB.js.map +0 -1
  46. package/dist/chunk-BNLGTZ34.js +0 -122
  47. package/dist/chunk-BNLGTZ34.js.map +0 -1
  48. package/dist/chunk-GAK6PIBW.js +0 -224
  49. package/dist/chunk-GAK6PIBW.js.map +0 -1
  50. package/dist/chunk-H5P2C6H4.js +0 -4740
  51. package/dist/chunk-H5P2C6H4.js.map +0 -1
  52. package/dist/chunk-I7WC6E5S.js +0 -71
  53. package/dist/chunk-I7WC6E5S.js.map +0 -1
  54. package/dist/chunk-J4SJTKIK.js +0 -203
  55. package/dist/chunk-J4SJTKIK.js.map +0 -1
  56. package/dist/chunk-JHYXKVV2.js +0 -183
  57. package/dist/chunk-JHYXKVV2.js.map +0 -1
  58. package/dist/chunk-JKBFUAJK.js +0 -282
  59. package/dist/chunk-JKBFUAJK.js.map +0 -1
  60. package/dist/chunk-JUYDFUSN.js +0 -673
  61. package/dist/chunk-JUYDFUSN.js.map +0 -1
  62. package/dist/chunk-KIRH7TUJ.js +0 -219
  63. package/dist/chunk-KIRH7TUJ.js.map +0 -1
  64. package/dist/chunk-LBIKITQT.js +0 -22
  65. package/dist/chunk-LBIKITQT.js.map +0 -1
  66. package/dist/chunk-LGP2YGRL.js +0 -4880
  67. package/dist/chunk-LGP2YGRL.js.map +0 -1
  68. package/dist/chunk-NAMYZIS5.js +0 -1
  69. package/dist/chunk-NAMYZIS5.js.map +0 -1
  70. package/dist/chunk-NVPG6JCL.js +0 -724
  71. package/dist/chunk-NVPG6JCL.js.map +0 -1
  72. package/dist/chunk-O7CPGUAI.js +0 -298
  73. package/dist/chunk-O7CPGUAI.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-RVLWZLVB.js +0 -13
  93. package/dist/config-editor-RVLWZLVB.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-7IVQKB2H.js +0 -2083
  99. package/dist/discord-7IVQKB2H.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-ZK4MPMBG.js +0 -238
  115. package/dist/main-ZK4MPMBG.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-3GQSYBE4.js +0 -35
  127. package/dist/setup-3GQSYBE4.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,40 @@ 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: "current_mode_update";
144
+ modeId: string;
145
+ } | {
146
+ type: "config_option_update";
147
+ options: ConfigOption[];
148
+ } | {
149
+ type: "model_update";
150
+ modelId: string;
151
+ } | {
152
+ type: "user_message_chunk";
153
+ content: string;
154
+ } | {
155
+ type: "resource_content";
156
+ uri: string;
157
+ name: string;
158
+ text?: string;
159
+ blob?: string;
160
+ mimeType?: string;
161
+ } | {
162
+ type: "resource_link";
163
+ uri: string;
164
+ name: string;
165
+ mimeType?: string;
166
+ title?: string;
167
+ description?: string;
168
+ size?: number;
169
+ } | {
170
+ type: "tts_strip";
108
171
  };
109
172
  interface PlanEntry {
110
173
  content: string;
@@ -196,6 +259,12 @@ interface InstallResult {
196
259
  setupSteps?: string[];
197
260
  }
198
261
  type SessionStatus = "initializing" | "active" | "cancelled" | "finished" | "error";
262
+ interface AgentSwitchEntry {
263
+ agentName: string;
264
+ agentSessionId: string;
265
+ switchedAt: string;
266
+ promptCount: number;
267
+ }
199
268
  interface SessionRecord<P = Record<string, unknown>> {
200
269
  sessionId: string;
201
270
  agentSessionId: string;
@@ -208,7 +277,19 @@ interface SessionRecord<P = Record<string, unknown>> {
208
277
  lastActiveAt: string;
209
278
  name?: string;
210
279
  dangerousMode?: boolean;
280
+ outputMode?: OutputMode;
211
281
  platform: P;
282
+ firstAgent?: string;
283
+ currentPromptCount?: number;
284
+ agentSwitchHistory?: AgentSwitchEntry[];
285
+ acpState?: {
286
+ currentMode?: string;
287
+ availableModes?: SessionMode[];
288
+ configOptions?: ConfigOption[];
289
+ currentModel?: string;
290
+ availableModels?: ModelInfo[];
291
+ agentCapabilities?: AgentCapabilities;
292
+ };
212
293
  }
213
294
  interface TelegramPlatformData {
214
295
  topicId: number;
@@ -226,20 +307,172 @@ interface UsageRecord {
226
307
  };
227
308
  timestamp: string;
228
309
  }
229
- interface UsageSummary {
230
- period: "today" | "week" | "month" | "all";
231
- totalTokens: number;
232
- totalCost: number;
233
- currency: string;
234
- sessionCount: number;
235
- recordCount: number;
310
+ interface UsageRecordEvent {
311
+ sessionId: string;
312
+ agentName: string;
313
+ timestamp: string;
314
+ tokensUsed: number;
315
+ contextSize: number;
316
+ cost?: {
317
+ amount: number;
318
+ currency: string;
319
+ };
236
320
  }
237
- interface DiscordPlatformData {
238
- threadId: string;
239
- skillMsgId?: string;
321
+ interface SessionMode {
322
+ id: string;
323
+ name: string;
324
+ description?: string;
325
+ }
326
+ interface SessionModeState {
327
+ currentModeId: string;
328
+ availableModes: SessionMode[];
329
+ }
330
+ interface ConfigSelectChoice {
331
+ value: string;
332
+ label: string;
333
+ description?: string;
334
+ }
335
+ interface ConfigSelectGroup {
336
+ group: string;
337
+ name: string;
338
+ options: ConfigSelectChoice[];
339
+ }
340
+ type ConfigOption = {
341
+ id: string;
342
+ name: string;
343
+ description?: string;
344
+ category?: string;
345
+ type: "select";
346
+ currentValue: string;
347
+ options: (ConfigSelectChoice | ConfigSelectGroup)[];
348
+ _meta?: Record<string, unknown>;
349
+ } | {
350
+ id: string;
351
+ name: string;
352
+ description?: string;
353
+ category?: string;
354
+ type: "boolean";
355
+ currentValue: boolean;
356
+ _meta?: Record<string, unknown>;
357
+ };
358
+ type SetConfigOptionValue = {
359
+ type: "select";
360
+ value: string;
361
+ } | {
362
+ type: "boolean";
363
+ value: boolean;
364
+ };
365
+ interface ModelInfo {
366
+ id: string;
367
+ name: string;
368
+ description?: string;
240
369
  }
370
+ interface SessionModelState {
371
+ currentModelId: string;
372
+ availableModels: ModelInfo[];
373
+ }
374
+ interface AgentCapabilities {
375
+ name: string;
376
+ title?: string;
377
+ version?: string;
378
+ loadSession?: boolean;
379
+ promptCapabilities?: {
380
+ image?: boolean;
381
+ audio?: boolean;
382
+ embeddedContext?: boolean;
383
+ };
384
+ sessionCapabilities?: {
385
+ list?: boolean;
386
+ fork?: boolean;
387
+ close?: boolean;
388
+ };
389
+ mcp?: {
390
+ http?: boolean;
391
+ sse?: boolean;
392
+ };
393
+ authMethods?: AuthMethod[];
394
+ }
395
+ interface NewSessionResponse {
396
+ sessionId: string;
397
+ modes?: SessionModeState;
398
+ configOptions?: ConfigOption[];
399
+ models?: SessionModelState;
400
+ }
401
+ type AuthMethod = {
402
+ type: "agent";
403
+ } | {
404
+ type: "env_var";
405
+ name: string;
406
+ description?: string;
407
+ } | {
408
+ type: "terminal";
409
+ };
410
+ interface AuthenticateRequest {
411
+ methodId: string;
412
+ }
413
+ type StopReason = "end_turn" | "max_tokens" | "max_turn_requests" | "refusal" | "cancelled";
414
+ interface PromptResponse {
415
+ stopReason: StopReason;
416
+ _meta?: Record<string, unknown>;
417
+ }
418
+ type ContentBlock = {
419
+ type: "text";
420
+ text: string;
421
+ } | {
422
+ type: "image";
423
+ data: string;
424
+ mimeType: string;
425
+ uri?: string;
426
+ } | {
427
+ type: "audio";
428
+ data: string;
429
+ mimeType: string;
430
+ } | {
431
+ type: "resource";
432
+ resource: {
433
+ uri: string;
434
+ text?: string;
435
+ blob?: string;
436
+ mimeType?: string;
437
+ };
438
+ } | {
439
+ type: "resource_link";
440
+ uri: string;
441
+ name: string;
442
+ mimeType?: string;
443
+ title?: string;
444
+ description?: string;
445
+ size?: number;
446
+ };
447
+ interface SessionListItem {
448
+ sessionId: string;
449
+ title?: string;
450
+ createdAt: string;
451
+ updatedAt?: string;
452
+ _meta?: Record<string, unknown>;
453
+ }
454
+ interface SessionListResponse {
455
+ sessions: SessionListItem[];
456
+ nextCursor?: string;
457
+ }
458
+ type McpServerConfig = {
459
+ type?: "stdio";
460
+ name: string;
461
+ command: string;
462
+ args?: string[];
463
+ env?: Record<string, string>;
464
+ } | {
465
+ type: "http";
466
+ name: string;
467
+ url: string;
468
+ headers?: Record<string, string>;
469
+ } | {
470
+ type: "sse";
471
+ name: string;
472
+ url: string;
473
+ headers?: Record<string, string>;
474
+ };
241
475
 
242
- declare const PLUGINS_DIR: string;
243
476
  declare const LoggingSchema: z.ZodDefault<z.ZodObject<{
244
477
  level: z.ZodDefault<z.ZodEnum<["silent", "debug", "info", "warn", "error", "fatal"]>>;
245
478
  logDir: z.ZodDefault<z.ZodString>;
@@ -278,8 +511,8 @@ declare const TunnelSchema: z.ZodDefault<z.ZodObject<{
278
511
  token?: string | undefined;
279
512
  }>>;
280
513
  }, "strip", z.ZodTypeAny, {
281
- options: Record<string, unknown>;
282
514
  enabled: boolean;
515
+ options: Record<string, unknown>;
283
516
  port: number;
284
517
  provider: "cloudflare" | "ngrok" | "bore" | "tailscale";
285
518
  maxUserTunnels: number;
@@ -289,8 +522,8 @@ declare const TunnelSchema: z.ZodDefault<z.ZodObject<{
289
522
  token?: string | undefined;
290
523
  };
291
524
  }, {
292
- options?: Record<string, unknown> | undefined;
293
525
  enabled?: boolean | undefined;
526
+ options?: Record<string, unknown> | undefined;
294
527
  port?: number | undefined;
295
528
  provider?: "cloudflare" | "ngrok" | "bore" | "tailscale" | undefined;
296
529
  maxUserTunnels?: number | undefined;
@@ -322,147 +555,52 @@ declare const UsageSchema: z.ZodDefault<z.ZodObject<{
322
555
  }>>;
323
556
  type UsageConfig = z.infer<typeof UsageSchema>;
324
557
  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<{
558
+ instanceName: z.ZodOptional<z.ZodString>;
559
+ channels: z.ZodObject<{}, "strip", z.ZodObject<{
361
560
  enabled: z.ZodDefault<z.ZodBoolean>;
362
561
  adapter: z.ZodOptional<z.ZodString>;
363
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
562
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
563
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
364
564
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
365
565
  enabled: z.ZodDefault<z.ZodBoolean>;
366
566
  adapter: z.ZodOptional<z.ZodString>;
367
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
567
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
568
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
368
569
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
369
570
  enabled: z.ZodDefault<z.ZodBoolean>;
370
571
  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<{
572
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
573
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
574
+ }, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{}, z.ZodObject<{
408
575
  enabled: z.ZodDefault<z.ZodBoolean>;
409
576
  adapter: z.ZodOptional<z.ZodString>;
410
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
577
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
578
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
411
579
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
412
580
  enabled: z.ZodDefault<z.ZodBoolean>;
413
581
  adapter: z.ZodOptional<z.ZodString>;
414
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
582
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
583
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
415
584
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
416
585
  enabled: z.ZodDefault<z.ZodBoolean>;
417
586
  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<{
587
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
588
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
589
+ }, z.ZodTypeAny, "passthrough">>, "strip">, z.objectInputType<{}, z.ZodObject<{
455
590
  enabled: z.ZodDefault<z.ZodBoolean>;
456
591
  adapter: z.ZodOptional<z.ZodString>;
457
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
592
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
593
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
458
594
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
459
595
  enabled: z.ZodDefault<z.ZodBoolean>;
460
596
  adapter: z.ZodOptional<z.ZodString>;
461
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
597
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
598
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
462
599
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
463
600
  enabled: z.ZodDefault<z.ZodBoolean>;
464
601
  adapter: z.ZodOptional<z.ZodString>;
465
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
602
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
603
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
466
604
  }, z.ZodTypeAny, "passthrough">>, "strip">>;
467
605
  agents: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
468
606
  command: z.ZodString;
@@ -557,8 +695,8 @@ declare const ConfigSchema: z.ZodObject<{
557
695
  token?: string | undefined;
558
696
  }>>;
559
697
  }, "strip", z.ZodTypeAny, {
560
- options: Record<string, unknown>;
561
698
  enabled: boolean;
699
+ options: Record<string, unknown>;
562
700
  port: number;
563
701
  provider: "cloudflare" | "ngrok" | "bore" | "tailscale";
564
702
  maxUserTunnels: number;
@@ -568,8 +706,8 @@ declare const ConfigSchema: z.ZodObject<{
568
706
  token?: string | undefined;
569
707
  };
570
708
  }, {
571
- options?: Record<string, unknown> | undefined;
572
709
  enabled?: boolean | undefined;
710
+ options?: Record<string, unknown> | undefined;
573
711
  port?: number | undefined;
574
712
  provider?: "cloudflare" | "ngrok" | "bore" | "tailscale" | undefined;
575
713
  maxUserTunnels?: number | undefined;
@@ -690,6 +828,14 @@ declare const ConfigSchema: z.ZodObject<{
690
828
  }, z.ZodTypeAny, "passthrough">> | undefined;
691
829
  } | undefined;
692
830
  }>>>;
831
+ outputMode: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
832
+ agentSwitch: z.ZodDefault<z.ZodObject<{
833
+ labelHistory: z.ZodDefault<z.ZodBoolean>;
834
+ }, "strip", z.ZodTypeAny, {
835
+ labelHistory: boolean;
836
+ }, {
837
+ labelHistory?: boolean | undefined;
838
+ }>>;
693
839
  }, "strip", z.ZodTypeAny, {
694
840
  api: {
695
841
  port: number;
@@ -702,8 +848,8 @@ declare const ConfigSchema: z.ZodObject<{
702
848
  workingDirectory?: string | undefined;
703
849
  }>;
704
850
  tunnel: {
705
- options: Record<string, unknown>;
706
851
  enabled: boolean;
852
+ options: Record<string, unknown>;
707
853
  port: number;
708
854
  provider: "cloudflare" | "ngrok" | "bore" | "tailscale";
709
855
  maxUserTunnels: number;
@@ -713,24 +859,19 @@ declare const ConfigSchema: z.ZodObject<{
713
859
  token?: string | undefined;
714
860
  };
715
861
  };
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
- } & {
862
+ usage: {
863
+ enabled: boolean;
864
+ warningThreshold: number;
865
+ currency: string;
866
+ retentionDays: number;
867
+ monthlyBudget?: number | undefined;
868
+ };
869
+ channels: {} & {
730
870
  [k: string]: z.objectOutputType<{
731
871
  enabled: z.ZodDefault<z.ZodBoolean>;
732
872
  adapter: z.ZodOptional<z.ZodString>;
733
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
873
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
874
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
734
875
  }, z.ZodTypeAny, "passthrough">;
735
876
  };
736
877
  defaultAgent: string;
@@ -754,13 +895,6 @@ declare const ConfigSchema: z.ZodObject<{
754
895
  sessionStore: {
755
896
  ttlDays: number;
756
897
  };
757
- usage: {
758
- enabled: boolean;
759
- warningThreshold: number;
760
- currency: string;
761
- retentionDays: number;
762
- monthlyBudget?: number | undefined;
763
- };
764
898
  integrations: Record<string, {
765
899
  installed: boolean;
766
900
  installedAt?: string | undefined;
@@ -781,25 +915,18 @@ declare const ConfigSchema: z.ZodObject<{
781
915
  }, z.ZodTypeAny, "passthrough">>;
782
916
  };
783
917
  };
918
+ agentSwitch: {
919
+ labelHistory: boolean;
920
+ };
921
+ instanceName?: string | undefined;
922
+ outputMode?: "low" | "medium" | "high" | undefined;
784
923
  }, {
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
- } & {
924
+ channels: {} & {
799
925
  [k: string]: z.objectInputType<{
800
926
  enabled: z.ZodDefault<z.ZodBoolean>;
801
927
  adapter: z.ZodOptional<z.ZodString>;
802
- displayVerbosity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["low", "medium", "high"]>>>;
928
+ displayVerbosity: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
929
+ outputMode: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
803
930
  }, z.ZodTypeAny, "passthrough">;
804
931
  };
805
932
  defaultAgent: string;
@@ -814,8 +941,8 @@ declare const ConfigSchema: z.ZodObject<{
814
941
  workingDirectory?: string | undefined;
815
942
  }> | undefined;
816
943
  tunnel?: {
817
- options?: Record<string, unknown> | undefined;
818
944
  enabled?: boolean | undefined;
945
+ options?: Record<string, unknown> | undefined;
819
946
  port?: number | undefined;
820
947
  provider?: "cloudflare" | "ngrok" | "bore" | "tailscale" | undefined;
821
948
  maxUserTunnels?: number | undefined;
@@ -825,6 +952,15 @@ declare const ConfigSchema: z.ZodObject<{
825
952
  token?: string | undefined;
826
953
  } | undefined;
827
954
  } | undefined;
955
+ usage?: {
956
+ enabled?: boolean | undefined;
957
+ monthlyBudget?: number | undefined;
958
+ warningThreshold?: number | undefined;
959
+ currency?: string | undefined;
960
+ retentionDays?: number | undefined;
961
+ } | undefined;
962
+ instanceName?: string | undefined;
963
+ outputMode?: "low" | "medium" | "high" | undefined;
828
964
  workspace?: {
829
965
  baseDir?: string | undefined;
830
966
  } | undefined;
@@ -845,13 +981,6 @@ declare const ConfigSchema: z.ZodObject<{
845
981
  sessionStore?: {
846
982
  ttlDays?: number | undefined;
847
983
  } | 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
984
  integrations?: Record<string, {
856
985
  installed: boolean;
857
986
  installedAt?: string | undefined;
@@ -872,13 +1001,16 @@ declare const ConfigSchema: z.ZodObject<{
872
1001
  }, z.ZodTypeAny, "passthrough">> | undefined;
873
1002
  } | undefined;
874
1003
  } | undefined;
1004
+ agentSwitch?: {
1005
+ labelHistory?: boolean | undefined;
1006
+ } | undefined;
875
1007
  }>;
876
1008
  type Config = z.infer<typeof ConfigSchema>;
877
1009
  declare function expandHome(p: string): string;
878
- declare class ConfigManager extends EventEmitter {
1010
+ declare class ConfigManager$1 extends EventEmitter {
879
1011
  private config;
880
1012
  private configPath;
881
- constructor();
1013
+ constructor(configPath?: string);
882
1014
  load(): Promise<void>;
883
1015
  get(): Config;
884
1016
  save(updates: Record<string, unknown>, changePath?: string): Promise<void>;
@@ -890,7 +1022,7 @@ declare class ConfigManager extends EventEmitter {
890
1022
  private deepMerge;
891
1023
  }
892
1024
 
893
- type Logger = pino.Logger;
1025
+ type Logger$1 = pino.Logger;
894
1026
  declare const log: {
895
1027
  info: (...args: unknown[]) => void;
896
1028
  warn: (...args: unknown[]) => void;
@@ -899,14 +1031,14 @@ declare const log: {
899
1031
  fatal: (...args: unknown[]) => void;
900
1032
  child: (bindings: pino.Bindings) => pino.Logger<never, boolean>;
901
1033
  };
902
- declare function initLogger(config: LoggingConfig): Logger;
1034
+ declare function initLogger(config: LoggingConfig): Logger$1;
903
1035
  /** Change log level at runtime. Pino transport targets respect parent level changes automatically. */
904
1036
  declare function setLogLevel(level: string): void;
905
1037
  declare function createChildLogger(context: {
906
1038
  module: string;
907
1039
  [key: string]: unknown;
908
- }): Logger;
909
- declare function createSessionLogger(sessionId: string, parentLogger: Logger): Logger;
1040
+ }): Logger$1;
1041
+ declare function createSessionLogger(sessionId: string, parentLogger: Logger$1): Logger$1;
910
1042
  declare function shutdownLogger(): Promise<void>;
911
1043
  declare function cleanupOldSessionLogs(retentionDays: number): Promise<void>;
912
1044
 
@@ -914,7 +1046,17 @@ interface ChannelConfig {
914
1046
  enabled: boolean;
915
1047
  [key: string]: unknown;
916
1048
  }
1049
+ interface AdapterCapabilities {
1050
+ streaming: boolean;
1051
+ richFormatting: boolean;
1052
+ threads: boolean;
1053
+ reactions: boolean;
1054
+ fileUpload: boolean;
1055
+ voice: boolean;
1056
+ }
917
1057
  interface IChannelAdapter {
1058
+ readonly name: string;
1059
+ readonly capabilities: AdapterCapabilities;
918
1060
  start(): Promise<void>;
919
1061
  stop(): Promise<void>;
920
1062
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
@@ -922,17 +1064,25 @@ interface IChannelAdapter {
922
1064
  sendNotification(notification: NotificationMessage): Promise<void>;
923
1065
  createSessionThread(sessionId: string, name: string): Promise<string>;
924
1066
  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>;
1067
+ deleteSessionThread?(sessionId: string): Promise<void>;
1068
+ archiveSessionTopic?(sessionId: string): Promise<void>;
1069
+ stripTTSBlock?(sessionId: string): Promise<void>;
1070
+ sendSkillCommands?(sessionId: string, commands: AgentCommand[]): Promise<void>;
1071
+ cleanupSkillCommands?(sessionId: string): Promise<void>;
1072
+ /** Flush skill commands that were queued before threadId was available */
1073
+ flushPendingSkillCommands?(sessionId: string): Promise<void>;
1074
+ cleanupSessionState?(sessionId: string): Promise<void>;
928
1075
  }
929
1076
  /**
930
1077
  * Base class providing default no-op implementations for optional methods.
931
1078
  * Adapters can extend this or implement IChannelAdapter directly.
1079
+ * @deprecated Use MessagingAdapter or StreamAdapter instead. Kept for backward compat during migration.
932
1080
  */
933
1081
  declare abstract class ChannelAdapter<TCore = unknown> implements IChannelAdapter {
934
1082
  readonly core: TCore;
935
1083
  protected config: ChannelConfig;
1084
+ abstract readonly name: string;
1085
+ readonly capabilities: AdapterCapabilities;
936
1086
  constructor(core: TCore, config: ChannelConfig);
937
1087
  abstract start(): Promise<void>;
938
1088
  abstract stop(): Promise<void>;
@@ -944,18 +1094,12 @@ declare abstract class ChannelAdapter<TCore = unknown> implements IChannelAdapte
944
1094
  deleteSessionThread(_sessionId: string): Promise<void>;
945
1095
  sendSkillCommands(_sessionId: string, _commands: AgentCommand[]): Promise<void>;
946
1096
  cleanupSkillCommands(_sessionId: string): Promise<void>;
1097
+ cleanupSessionState(_sessionId: string): Promise<void>;
947
1098
  archiveSessionTopic(_sessionId: string): Promise<void>;
948
1099
  }
949
1100
 
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>;
1101
+ declare function nodeToWebWritable(nodeStream: NodeJS.WritableStream): WritableStream<Uint8Array>;
1102
+ declare function nodeToWebReadable(nodeStream: NodeJS.ReadableStream): ReadableStream<Uint8Array>;
959
1103
 
960
1104
  declare class StderrCapture {
961
1105
  private maxLines;
@@ -1000,6 +1144,57 @@ declare class TypedEmitter<T extends Record<string & keyof T, (...args: any[]) =
1000
1144
  private deliver;
1001
1145
  }
1002
1146
 
1147
+ interface ErrorBudgetConfig {
1148
+ maxErrors: number;
1149
+ windowMs: number;
1150
+ }
1151
+ declare class ErrorTracker {
1152
+ private errors;
1153
+ private disabled;
1154
+ private exempt;
1155
+ private config;
1156
+ onDisabled?: (pluginName: string, reason: string) => void;
1157
+ constructor(config?: Partial<ErrorBudgetConfig>);
1158
+ increment(pluginName: string): void;
1159
+ isDisabled(pluginName: string): boolean;
1160
+ reset(pluginName: string): void;
1161
+ setExempt(pluginName: string): void;
1162
+ }
1163
+
1164
+ declare class MiddlewareChain {
1165
+ private chains;
1166
+ private errorHandler?;
1167
+ private errorTracker?;
1168
+ add(hook: string, pluginName: string, opts: {
1169
+ priority?: number;
1170
+ handler: Function;
1171
+ }): void;
1172
+ execute<T>(hook: string, payload: T, coreHandler: (p: T) => T | Promise<T>): Promise<T | null>;
1173
+ removeAll(pluginName: string): void;
1174
+ setErrorHandler(fn: (pluginName: string, error: Error) => void): void;
1175
+ setErrorTracker(tracker: ErrorTracker): void;
1176
+ }
1177
+
1178
+ type TraceLayer = "acp" | "core" | "telegram";
1179
+ /**
1180
+ * Per-session debug trace logger. Writes JSONL files to <workingDirectory>/.log/.
1181
+ * Only active when OPENACP_DEBUG=true. Zero overhead when disabled.
1182
+ *
1183
+ * Note: Uses appendFileSync for simplicity. This blocks the event loop briefly per write,
1184
+ * which is acceptable for a debug-only tool. The DEBUG_ENABLED guard ensures zero overhead
1185
+ * in production.
1186
+ */
1187
+ declare class DebugTracer {
1188
+ private sessionId;
1189
+ private workingDirectory;
1190
+ private dirCreated;
1191
+ private logDir;
1192
+ constructor(sessionId: string, workingDirectory: string);
1193
+ log(layer: TraceLayer, data: Record<string, unknown>): void;
1194
+ /** No-op cleanup — establishes the pattern for future async implementations */
1195
+ destroy(): void;
1196
+ }
1197
+
1003
1198
  interface AgentInstanceEvents {
1004
1199
  agent_event: (event: AgentEvent) => void;
1005
1200
  }
@@ -1007,21 +1202,40 @@ declare class AgentInstance extends TypedEmitter<AgentInstanceEvents> {
1007
1202
  private connection;
1008
1203
  private child;
1009
1204
  private stderrCapture;
1010
- private terminals;
1205
+ private terminalManager;
1206
+ private static mcpManager;
1207
+ private _destroying;
1011
1208
  sessionId: string;
1012
1209
  agentName: string;
1013
1210
  promptCapabilities?: {
1014
1211
  image?: boolean;
1015
1212
  audio?: boolean;
1016
1213
  };
1214
+ agentCapabilities?: AgentCapabilities;
1215
+ /** Preserved from newSession/resumeSession response for ACP state propagation */
1216
+ initialSessionResponse?: {
1217
+ modes?: unknown;
1218
+ configOptions?: unknown;
1219
+ models?: unknown;
1220
+ };
1221
+ middlewareChain?: MiddlewareChain;
1222
+ debugTracer: DebugTracer | null;
1017
1223
  onPermissionRequest: (request: PermissionRequest) => Promise<string>;
1018
1224
  private constructor();
1019
1225
  private static spawnSubprocess;
1020
1226
  private setupCrashDetection;
1021
- static spawn(agentDef: AgentDefinition, workingDirectory: string): Promise<AgentInstance>;
1022
- static resume(agentDef: AgentDefinition, workingDirectory: string, agentSessionId: string): Promise<AgentInstance>;
1227
+ static spawn(agentDef: AgentDefinition, workingDirectory: string, mcpServers?: McpServerConfig[]): Promise<AgentInstance>;
1228
+ static resume(agentDef: AgentDefinition, workingDirectory: string, agentSessionId: string, mcpServers?: McpServerConfig[]): Promise<AgentInstance>;
1023
1229
  private createClient;
1024
- prompt(text: string, attachments?: Attachment[]): Promise<PromptResponse>;
1230
+ setMode(modeId: string): Promise<void>;
1231
+ setConfigOption(configId: string, value: SetConfigOptionValue): Promise<SetSessionConfigOptionResponse>;
1232
+ setModel(modelId: string): Promise<void>;
1233
+ listSessions(cwd?: string, cursor?: string): Promise<ListSessionsResponse>;
1234
+ loadSession(sessionId: string, cwd: string, mcpServers?: McpServerConfig[]): Promise<LoadSessionResponse>;
1235
+ authenticate(methodId: string): Promise<void>;
1236
+ forkSession(sessionId: string, cwd: string, mcpServers?: McpServerConfig[]): Promise<ForkSessionResponse>;
1237
+ closeSession(sessionId: string): Promise<void>;
1238
+ prompt(text: string, attachments?: Attachment[]): Promise<PromptResponse$1>;
1025
1239
  cancel(): Promise<void>;
1026
1240
  destroy(): Promise<void>;
1027
1241
  }
@@ -1043,7 +1257,9 @@ declare class AgentStore {
1043
1257
  declare class AgentCatalog {
1044
1258
  private store;
1045
1259
  private registryAgents;
1046
- constructor(store?: AgentStore);
1260
+ private cachePath;
1261
+ private agentsDir;
1262
+ constructor(store?: AgentStore, cachePath?: string, agentsDir?: string);
1047
1263
  load(): void;
1048
1264
  fetchRegistry(): Promise<void>;
1049
1265
  refreshRegistryIfStale(): Promise<void>;
@@ -1120,11 +1336,11 @@ interface TTSResult {
1120
1336
  audioBuffer: Buffer;
1121
1337
  mimeType: string;
1122
1338
  }
1123
- interface STTProvider {
1339
+ interface STTProvider$1 {
1124
1340
  readonly name: string;
1125
1341
  transcribe(audioBuffer: Buffer, mimeType: string, options?: STTOptions): Promise<STTResult>;
1126
1342
  }
1127
- interface TTSProvider {
1343
+ interface TTSProvider$1 {
1128
1344
  readonly name: string;
1129
1345
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
1130
1346
  }
@@ -1144,21 +1360,31 @@ interface SpeechServiceConfig {
1144
1360
  };
1145
1361
  }
1146
1362
 
1363
+ type ProviderFactory = (config: SpeechServiceConfig) => {
1364
+ stt: Map<string, STTProvider$1>;
1365
+ tts: Map<string, TTSProvider$1>;
1366
+ };
1147
1367
  declare class SpeechService {
1148
1368
  private config;
1149
1369
  private sttProviders;
1150
1370
  private ttsProviders;
1371
+ private providerFactory?;
1151
1372
  constructor(config: SpeechServiceConfig);
1152
- registerSTTProvider(name: string, provider: STTProvider): void;
1153
- registerTTSProvider(name: string, provider: TTSProvider): void;
1373
+ /** Set a factory function that can recreate providers from config (for hot-reload) */
1374
+ setProviderFactory(factory: ProviderFactory): void;
1375
+ registerSTTProvider(name: string, provider: STTProvider$1): void;
1376
+ registerTTSProvider(name: string, provider: TTSProvider$1): void;
1377
+ unregisterTTSProvider(name: string): void;
1154
1378
  isSTTAvailable(): boolean;
1155
1379
  isTTSAvailable(): boolean;
1156
1380
  transcribe(audioBuffer: Buffer, mimeType: string, options?: STTOptions): Promise<STTResult>;
1157
1381
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
1158
1382
  updateConfig(config: SpeechServiceConfig): void;
1383
+ /** Re-create factory-managed providers from config. Preserves externally-registered providers (e.g. from plugins). */
1384
+ refreshProviders(newConfig: SpeechServiceConfig): void;
1159
1385
  }
1160
1386
 
1161
- declare class GroqSTT implements STTProvider {
1387
+ declare class GroqSTT implements STTProvider$1 {
1162
1388
  private apiKey;
1163
1389
  private defaultModel;
1164
1390
  readonly name = "groq";
@@ -1173,6 +1399,7 @@ interface SessionEvents {
1173
1399
  status_change: (from: SessionStatus, to: SessionStatus) => void;
1174
1400
  named: (name: string) => void;
1175
1401
  error: (error: Error) => void;
1402
+ prompt_count_changed: (count: number) => void;
1176
1403
  }
1177
1404
  declare class Session extends TypedEmitter<SessionEvents> {
1178
1405
  id: string;
@@ -1187,9 +1414,18 @@ declare class Session extends TypedEmitter<SessionEvents> {
1187
1414
  createdAt: Date;
1188
1415
  voiceMode: "off" | "next" | "on";
1189
1416
  dangerousMode: boolean;
1417
+ currentMode?: string;
1418
+ availableModes: SessionMode[];
1419
+ configOptions: ConfigOption[];
1420
+ currentModel?: string;
1421
+ availableModels: ModelInfo[];
1422
+ agentCapabilities?: AgentCapabilities;
1190
1423
  archiving: boolean;
1191
1424
  promptCount: number;
1192
- log: Logger;
1425
+ firstAgent: string;
1426
+ agentSwitchHistory: AgentSwitchEntry[];
1427
+ log: Logger$1;
1428
+ middlewareChain?: MiddlewareChain;
1193
1429
  readonly permissionGate: PermissionGate;
1194
1430
  private readonly queue;
1195
1431
  private speechService?;
@@ -1222,12 +1458,30 @@ declare class Session extends TypedEmitter<SessionEvents> {
1222
1458
  private maybeTranscribeAudio;
1223
1459
  private processTTSResponse;
1224
1460
  private autoName;
1225
- generateSummary(timeoutMs?: number): Promise<string>;
1226
1461
  /** Fire-and-forget warm-up: primes model cache while user types their first message */
1227
1462
  warmup(): Promise<void>;
1228
1463
  private runWarmup;
1464
+ setInitialAcpState(state: {
1465
+ modes?: SessionModeState | null;
1466
+ configOptions?: ConfigOption[] | null;
1467
+ models?: SessionModelState | null;
1468
+ agentCapabilities?: AgentCapabilities | null;
1469
+ }): void;
1470
+ /** Set session name explicitly and emit 'named' event */
1471
+ setName(name: string): void;
1472
+ updateMode(modeId: string): Promise<void>;
1473
+ updateConfigOptions(options: ConfigOption[]): Promise<void>;
1474
+ updateModel(modelId: string): Promise<void>;
1475
+ /** Snapshot of current ACP state for persistence */
1476
+ toAcpStateSnapshot(): NonNullable<SessionRecord["acpState"]>;
1477
+ /** Check if the agent supports a specific session capability */
1478
+ supportsCapability(cap: 'list' | 'fork' | 'close' | 'loadSession'): boolean;
1229
1479
  /** Cancel the current prompt and clear the queue. Stays in active state. */
1230
1480
  abortPrompt(): Promise<void>;
1481
+ /** Search backward through agentSwitchHistory for the last entry matching agentName */
1482
+ findLastSwitchEntry(agentName: string): AgentSwitchEntry | undefined;
1483
+ /** Switch the agent instance in-place, preserving session identity */
1484
+ switchAgent(agentName: string, createAgent: () => Promise<AgentInstance>): Promise<void>;
1231
1485
  destroy(): Promise<void>;
1232
1486
  }
1233
1487
 
@@ -1249,90 +1503,465 @@ declare class PromptQueue {
1249
1503
  get isProcessing(): boolean;
1250
1504
  }
1251
1505
 
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;
1506
+ type PluginPermission = 'events:read' | 'events:emit' | 'services:register' | 'services:use' | 'middleware:register' | 'commands:register' | 'storage:read' | 'storage:write' | 'kernel:access';
1507
+ interface OpenACPPlugin {
1508
+ /** Unique identifier, e.g., '@openacp/security' */
1509
+ name: string;
1510
+ /** Semver version */
1511
+ version: string;
1512
+ /** Human-readable description */
1513
+ description?: string;
1514
+ /** Required plugin dependencies — loaded before this plugin's setup() */
1515
+ pluginDependencies?: Record<string, string>;
1516
+ /** Optional dependencies — used if available, gracefully degrade if not */
1517
+ optionalPluginDependencies?: Record<string, string>;
1518
+ /** Override a built-in plugin (replaces it entirely) */
1519
+ overrides?: string;
1520
+ /** Required permissions — PluginContext enforces these */
1521
+ permissions?: PluginPermission[];
1522
+ /** Called during startup in dependency order */
1523
+ setup(ctx: PluginContext): Promise<void>;
1524
+ /** Called during shutdown in reverse order. 10s timeout. */
1525
+ teardown?(): Promise<void>;
1526
+ install?(ctx: InstallContext): Promise<void>;
1527
+ uninstall?(ctx: InstallContext, opts: {
1528
+ purge: boolean;
1529
+ }): Promise<void>;
1530
+ configure?(ctx: InstallContext): Promise<void>;
1531
+ migrate?(ctx: MigrateContext, oldSettings: unknown, oldVersion: string): Promise<unknown>;
1532
+ settingsSchema?: zod.ZodSchema;
1533
+ essential?: boolean;
1534
+ /** Settings keys that can be copied when creating a new instance from this one */
1535
+ inheritableKeys?: string[];
1536
+ }
1537
+ interface PluginStorage {
1538
+ get<T>(key: string): Promise<T | undefined>;
1539
+ set<T>(key: string, value: T): Promise<void>;
1540
+ delete(key: string): Promise<void>;
1541
+ list(): Promise<string[]>;
1542
+ getDataDir(): string;
1543
+ }
1544
+ interface SettingsAPI {
1545
+ get<T = unknown>(key: string): Promise<T | undefined>;
1546
+ set<T = unknown>(key: string, value: T): Promise<void>;
1547
+ getAll(): Promise<Record<string, unknown>>;
1548
+ setAll(settings: Record<string, unknown>): Promise<void>;
1549
+ delete(key: string): Promise<void>;
1550
+ clear(): Promise<void>;
1551
+ has(key: string): Promise<boolean>;
1552
+ }
1553
+ interface TerminalIO {
1554
+ text(opts: {
1555
+ message: string;
1556
+ placeholder?: string;
1557
+ defaultValue?: string;
1558
+ validate?: (value: string) => string | undefined;
1559
+ }): Promise<string>;
1560
+ select<T>(opts: {
1561
+ message: string;
1562
+ options: {
1563
+ value: T;
1564
+ label: string;
1565
+ hint?: string;
1566
+ }[];
1567
+ }): Promise<T>;
1568
+ confirm(opts: {
1569
+ message: string;
1570
+ initialValue?: boolean;
1571
+ }): Promise<boolean>;
1572
+ password(opts: {
1573
+ message: string;
1574
+ validate?: (value: string) => string | undefined;
1575
+ }): Promise<string>;
1576
+ multiselect<T>(opts: {
1577
+ message: string;
1578
+ options: {
1579
+ value: T;
1580
+ label: string;
1581
+ hint?: string;
1582
+ }[];
1583
+ required?: boolean;
1584
+ }): Promise<T[]>;
1585
+ log: {
1586
+ info(message: string): void;
1587
+ success(message: string): void;
1588
+ warning(message: string): void;
1589
+ error(message: string): void;
1590
+ step(message: string): void;
1591
+ };
1592
+ spinner(): {
1593
+ start(message: string): void;
1594
+ stop(message?: string): void;
1595
+ fail(message?: string): void;
1596
+ };
1597
+ note(message: string, title?: string): void;
1598
+ cancel(message?: string): void;
1599
+ }
1600
+ interface InstallContext {
1601
+ pluginName: string;
1602
+ terminal: TerminalIO;
1603
+ settings: SettingsAPI;
1604
+ legacyConfig?: Record<string, unknown>;
1605
+ dataDir: string;
1606
+ log: Logger;
1607
+ /** Root of the OpenACP instance directory (e.g. ~/.openacp) */
1608
+ instanceRoot?: string;
1274
1609
  }
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;
1610
+ interface MigrateContext {
1611
+ pluginName: string;
1612
+ settings: SettingsAPI;
1613
+ log: Logger;
1287
1614
  }
1288
-
1289
- declare class TunnelService {
1290
- private registry;
1291
- private store;
1292
- private server;
1293
- private config;
1294
- private systemPort;
1295
- constructor(config: TunnelConfig);
1615
+ type CommandResponse = {
1616
+ type: 'text';
1617
+ text: string;
1618
+ } | {
1619
+ type: 'menu';
1620
+ title: string;
1621
+ options: MenuOption[];
1622
+ } | {
1623
+ type: 'list';
1624
+ title: string;
1625
+ items: ListItem[];
1626
+ } | {
1627
+ type: 'confirm';
1628
+ question: string;
1629
+ onYes: string;
1630
+ onNo: string;
1631
+ } | {
1632
+ type: 'error';
1633
+ message: string;
1634
+ } | {
1635
+ type: 'silent';
1636
+ };
1637
+ interface MenuOption {
1638
+ label: string;
1639
+ command: string;
1640
+ hint?: string;
1641
+ }
1642
+ interface ListItem {
1643
+ label: string;
1644
+ detail?: string;
1645
+ }
1646
+ interface CommandArgs {
1647
+ /** Raw argument string after command name */
1648
+ raw: string;
1649
+ /** Parsed key/value options (e.g., --flag value) */
1650
+ options?: Record<string, string>;
1651
+ /** Session ID where command was invoked (null if from notification/system topic) */
1652
+ sessionId: string | null;
1653
+ /** Channel ID ('telegram', 'discord', 'slack') */
1654
+ channelId: string;
1655
+ /** User ID who invoked the command */
1656
+ userId: string;
1657
+ /** Reply helper — sends message to the topic where command was invoked */
1658
+ reply(content: string | CommandResponse | OutgoingMessage): Promise<void>;
1659
+ /** Direct access to OpenACPCore instance. Available when 'kernel:access' permission is granted. */
1660
+ coreAccess?: CoreAccess;
1661
+ }
1662
+ interface CommandDef {
1663
+ /** Command name without slash, e.g., 'context' for /context */
1664
+ name: string;
1665
+ /** Short description shown in command list */
1666
+ description: string;
1667
+ /** Usage pattern, e.g., '<session-number>' */
1668
+ usage?: string;
1669
+ /** Whether this is a built-in system command or registered by a plugin */
1670
+ category: 'system' | 'plugin';
1671
+ /** Plugin that registered this command (set automatically by plugin manager) */
1672
+ pluginName?: string;
1673
+ /** Handler function */
1674
+ handler(args: CommandArgs): Promise<CommandResponse | void>;
1675
+ }
1676
+ interface SessionManager$1 {
1677
+ [key: string]: unknown;
1678
+ }
1679
+ interface ConfigManager {
1680
+ [key: string]: unknown;
1681
+ }
1682
+ interface EventBus$1 {
1683
+ [key: string]: unknown;
1684
+ }
1685
+ /**
1686
+ * Typed view of the OpenACPCore instance exposed to plugins via ctx.core.
1687
+ * Plugins that need kernel:access should cast ctx.core to this interface
1688
+ * instead of using `as any`. Only includes fields plugins actually need.
1689
+ */
1690
+ interface CoreAccess {
1691
+ configManager: ConfigManager;
1692
+ sessionManager: SessionManager$1;
1693
+ adapters: Map<string, IChannelAdapter>;
1694
+ }
1695
+ interface Logger {
1696
+ trace(msg: string, ...args: unknown[]): void;
1697
+ debug(msg: string, ...args: unknown[]): void;
1698
+ info(msg: string, ...args: unknown[]): void;
1699
+ warn(msg: string, ...args: unknown[]): void;
1700
+ error(msg: string, ...args: unknown[]): void;
1701
+ fatal(msg: string, ...args: unknown[]): void;
1702
+ child(bindings: Record<string, unknown>): Logger;
1703
+ }
1704
+ interface PluginContext {
1705
+ pluginName: string;
1706
+ pluginConfig: Record<string, unknown>;
1707
+ /** Subscribe to events. Auto-cleaned on teardown. Requires 'events:read'. */
1708
+ on(event: string, handler: (...args: unknown[]) => void): void;
1709
+ off(event: string, handler: (...args: unknown[]) => void): void;
1710
+ /** Emit custom events. Event names MUST be prefixed with plugin name. Requires 'events:emit'. */
1711
+ emit(event: string, payload: unknown): void;
1712
+ /** Register middleware. Requires 'middleware:register'. */
1713
+ registerMiddleware<H extends MiddlewareHook>(hook: H, opts: MiddlewareOptions<MiddlewarePayloadMap[H]>): void;
1714
+ /** Provide a service. Requires 'services:register'. */
1715
+ registerService<T>(name: string, implementation: T): void;
1716
+ /** Consume a service. Requires 'services:use'. */
1717
+ getService<T>(name: string): T | undefined;
1718
+ /** Register slash command. Requires 'commands:register'. */
1719
+ registerCommand(def: CommandDef): void;
1720
+ /** Plugin-scoped storage. Requires 'storage:read' and/or 'storage:write'. */
1721
+ storage: PluginStorage;
1722
+ /** Plugin-scoped logger. Always available (no permission needed). */
1723
+ log: Logger;
1724
+ /**
1725
+ * Send message to a session. Requires 'services:use'.
1726
+ *
1727
+ * Routing: sessionId → lookup session → find adapter for session's channelId
1728
+ * → [HOOK: message:outgoing] → adapter.sendMessage()
1729
+ */
1730
+ sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
1731
+ sessions: SessionManager$1;
1732
+ config: ConfigManager;
1733
+ eventBus: EventBus$1;
1734
+ /** Direct access to OpenACPCore instance. Requires 'kernel:access'. */
1735
+ core: unknown;
1736
+ /**
1737
+ * Root directory for this OpenACP instance (default: ~/.openacp).
1738
+ * Plugins should derive file paths from this instead of hardcoding ~/.openacp.
1739
+ */
1740
+ instanceRoot: string;
1741
+ }
1742
+ interface MiddlewarePayloadMap {
1743
+ 'message:incoming': {
1744
+ channelId: string;
1745
+ threadId: string;
1746
+ userId: string;
1747
+ text: string;
1748
+ attachments?: Attachment[];
1749
+ };
1750
+ 'message:outgoing': {
1751
+ sessionId: string;
1752
+ message: OutgoingMessage;
1753
+ };
1754
+ 'agent:beforePrompt': {
1755
+ sessionId: string;
1756
+ text: string;
1757
+ attachments?: Attachment[];
1758
+ };
1759
+ 'agent:beforeEvent': {
1760
+ sessionId: string;
1761
+ event: AgentEvent;
1762
+ };
1763
+ 'agent:afterEvent': {
1764
+ sessionId: string;
1765
+ event: AgentEvent;
1766
+ outgoingMessage: OutgoingMessage;
1767
+ };
1768
+ 'turn:start': {
1769
+ sessionId: string;
1770
+ promptText: string;
1771
+ promptNumber: number;
1772
+ };
1773
+ 'turn:end': {
1774
+ sessionId: string;
1775
+ stopReason: StopReason;
1776
+ durationMs: number;
1777
+ };
1778
+ 'fs:beforeRead': {
1779
+ sessionId: string;
1780
+ path: string;
1781
+ line?: number;
1782
+ limit?: number;
1783
+ };
1784
+ 'fs:beforeWrite': {
1785
+ sessionId: string;
1786
+ path: string;
1787
+ content: string;
1788
+ };
1789
+ 'terminal:beforeCreate': {
1790
+ sessionId: string;
1791
+ command: string;
1792
+ args?: string[];
1793
+ env?: Record<string, string>;
1794
+ cwd?: string;
1795
+ };
1796
+ 'terminal:afterExit': {
1797
+ sessionId: string;
1798
+ terminalId: string;
1799
+ command: string;
1800
+ exitCode: number;
1801
+ durationMs: number;
1802
+ };
1803
+ 'permission:beforeRequest': {
1804
+ sessionId: string;
1805
+ request: PermissionRequest;
1806
+ autoResolve?: string;
1807
+ };
1808
+ 'permission:afterResolve': {
1809
+ sessionId: string;
1810
+ requestId: string;
1811
+ decision: string;
1812
+ userId: string;
1813
+ durationMs: number;
1814
+ };
1815
+ 'session:beforeCreate': {
1816
+ agentName: string;
1817
+ workingDir: string;
1818
+ userId: string;
1819
+ channelId: string;
1820
+ threadId: string;
1821
+ };
1822
+ 'session:afterDestroy': {
1823
+ sessionId: string;
1824
+ reason: string;
1825
+ durationMs: number;
1826
+ promptCount: number;
1827
+ };
1828
+ 'mode:beforeChange': {
1829
+ sessionId: string;
1830
+ fromMode: string | undefined;
1831
+ toMode: string;
1832
+ };
1833
+ 'config:beforeChange': {
1834
+ sessionId: string;
1835
+ configId: string;
1836
+ oldValue: unknown;
1837
+ newValue: unknown;
1838
+ };
1839
+ 'model:beforeChange': {
1840
+ sessionId: string;
1841
+ fromModel: string | undefined;
1842
+ toModel: string;
1843
+ };
1844
+ 'agent:beforeCancel': {
1845
+ sessionId: string;
1846
+ reason?: string;
1847
+ };
1848
+ 'agent:beforeSwitch': {
1849
+ sessionId: string;
1850
+ fromAgent: string;
1851
+ toAgent: string;
1852
+ };
1853
+ 'agent:afterSwitch': {
1854
+ sessionId: string;
1855
+ fromAgent: string;
1856
+ toAgent: string;
1857
+ resumed: boolean;
1858
+ };
1859
+ }
1860
+ type MiddlewareHook = keyof MiddlewarePayloadMap;
1861
+ type MiddlewareFn<T> = (payload: T, next: () => Promise<T>) => Promise<T | null>;
1862
+ interface MiddlewareOptions<T> {
1863
+ /** Override execution order within same dependency level. Lower = earlier. */
1864
+ priority?: number;
1865
+ /** The middleware handler */
1866
+ handler: MiddlewareFn<T>;
1867
+ }
1868
+ interface SecurityService {
1869
+ checkAccess(userId: string): Promise<{
1870
+ allowed: boolean;
1871
+ reason?: string;
1872
+ }>;
1873
+ checkSessionLimit(userId: string): Promise<{
1874
+ allowed: boolean;
1875
+ reason?: string;
1876
+ }>;
1877
+ getUserRole(userId: string): Promise<'admin' | 'user' | 'blocked'>;
1878
+ }
1879
+ interface FileServiceInterface {
1880
+ saveFile(sessionId: string, fileName: string, data: Buffer, mimeType: string): Promise<Attachment>;
1881
+ resolveFile(filePath: string): Promise<Attachment | null>;
1882
+ readTextFileWithRange(path: string, opts?: {
1883
+ line?: number;
1884
+ limit?: number;
1885
+ }): Promise<string>;
1886
+ extensionFromMime(mimeType: string): string;
1887
+ convertOggToWav(oggData: Buffer): Promise<Buffer>;
1888
+ }
1889
+ interface NotificationService {
1890
+ notify(channelId: string, notification: NotificationMessage): Promise<void>;
1891
+ notifyAll(notification: NotificationMessage): Promise<void>;
1892
+ }
1893
+ interface UsageService {
1894
+ trackUsage(record: UsageRecord): Promise<void>;
1895
+ checkBudget(sessionId: string): Promise<{
1896
+ ok: boolean;
1897
+ percent: number;
1898
+ warning?: string;
1899
+ }>;
1900
+ }
1901
+ interface TTSProvider {
1902
+ synthesize(text: string, opts?: {
1903
+ language?: string;
1904
+ voice?: string;
1905
+ }): Promise<Buffer>;
1906
+ }
1907
+ interface STTProvider {
1908
+ transcribe(audio: Buffer, opts?: {
1909
+ language?: string;
1910
+ }): Promise<string>;
1911
+ }
1912
+ interface SpeechServiceInterface {
1913
+ textToSpeech(text: string, opts?: {
1914
+ language?: string;
1915
+ voice?: string;
1916
+ }): Promise<Buffer>;
1917
+ speechToText(audio: Buffer, opts?: {
1918
+ language?: string;
1919
+ }): Promise<string>;
1920
+ registerTTSProvider(name: string, provider: TTSProvider): void;
1921
+ registerSTTProvider(name: string, provider: STTProvider): void;
1922
+ }
1923
+ interface ContextProvider$1 {
1924
+ provide(sessionId: string, opts?: {
1925
+ maxTokens?: number;
1926
+ }): Promise<string>;
1927
+ }
1928
+ interface ContextService {
1929
+ buildContext(sessionId: string, opts?: {
1930
+ maxTokens?: number;
1931
+ }): Promise<string>;
1932
+ registerProvider(provider: ContextProvider$1): void;
1933
+ }
1934
+ interface ViewerStoreInterface {
1935
+ storeFile(sessionId: string, filePath: string, content: string, workingDirectory: string): string | null;
1936
+ storeDiff(sessionId: string, filePath: string, oldContent: string, newContent: string, workingDirectory: string): string | null;
1937
+ storeOutput(sessionId: string, label: string, output: string): string | null;
1938
+ }
1939
+ interface TunnelServiceInterface {
1940
+ getPublicUrl(): string;
1296
1941
  start(): Promise<string>;
1297
1942
  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;
1943
+ getStore(): ViewerStoreInterface;
1309
1944
  fileUrl(entryId: string): string;
1310
1945
  diffUrl(entryId: string): string;
1946
+ outputUrl(entryId: string): string;
1311
1947
  }
1312
1948
 
1313
1949
  declare class MessageTransformer {
1314
- private tunnelService?;
1315
- constructor(tunnelService?: TunnelService | undefined);
1950
+ tunnelService?: TunnelServiceInterface;
1951
+ /** Cache rawInput from tool_call so it's available in tool_update (which often lacks it) */
1952
+ private toolRawInputCache;
1953
+ /** Cache viewer links generated from intermediate updates so completion events carry them */
1954
+ private toolViewerCache;
1955
+ constructor(tunnelService?: TunnelServiceInterface);
1316
1956
  transform(event: AgentEvent, sessionContext?: {
1317
1957
  id: string;
1318
1958
  workingDirectory: string;
1319
1959
  }): OutgoingMessage;
1960
+ /** Check if rawInput is a non-empty object (not null, not {}) */
1961
+ private isNonEmptyInput;
1320
1962
  private enrichWithViewerLinks;
1321
1963
  }
1322
1964
 
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
1965
  interface SessionStore {
1337
1966
  save(record: SessionRecord): Promise<void>;
1338
1967
  get(sessionId: string): SessionRecord | undefined;
@@ -1365,6 +1994,57 @@ interface EventBusEvents {
1365
1994
  sessionId: string;
1366
1995
  permission: PermissionRequest;
1367
1996
  }) => void;
1997
+ "permission:resolved": (data: {
1998
+ sessionId: string;
1999
+ requestId: string;
2000
+ decision: string;
2001
+ }) => void;
2002
+ "kernel:booted": () => void;
2003
+ "system:ready": () => void;
2004
+ "system:shutdown": () => void;
2005
+ "system:commands-ready": (data: {
2006
+ commands: Array<{
2007
+ name: string;
2008
+ description: string;
2009
+ }>;
2010
+ }) => void;
2011
+ "plugin:loaded": (data: {
2012
+ name: string;
2013
+ version: string;
2014
+ }) => void;
2015
+ "plugin:failed": (data: {
2016
+ name: string;
2017
+ error: string;
2018
+ }) => void;
2019
+ "plugin:disabled": (data: {
2020
+ name: string;
2021
+ reason: string;
2022
+ }) => void;
2023
+ "plugin:unloaded": (data: {
2024
+ name: string;
2025
+ }) => void;
2026
+ "session:ended": (data: {
2027
+ sessionId: string;
2028
+ reason: string;
2029
+ }) => void;
2030
+ "session:named": (data: {
2031
+ sessionId: string;
2032
+ name: string;
2033
+ }) => void;
2034
+ "agent:prompt": (data: {
2035
+ sessionId: string;
2036
+ text: string;
2037
+ attachments?: unknown[];
2038
+ }) => void;
2039
+ "usage:recorded": (data: UsageRecordEvent) => void;
2040
+ "session:agentSwitch": (data: {
2041
+ sessionId: string;
2042
+ fromAgent: string;
2043
+ toAgent: string;
2044
+ status: "starting" | "succeeded" | "failed";
2045
+ resumed?: boolean;
2046
+ error?: string;
2047
+ }) => void;
1368
2048
  }
1369
2049
  declare class EventBus extends TypedEmitter<EventBusEvents> {
1370
2050
  }
@@ -1373,6 +2053,7 @@ declare class SessionManager {
1373
2053
  private sessions;
1374
2054
  private store;
1375
2055
  private eventBus?;
2056
+ middlewareChain?: MiddlewareChain;
1376
2057
  setEventBus(eventBus: EventBus): void;
1377
2058
  constructor(store?: SessionStore | null);
1378
2059
  createSession(channelId: string, agentName: string, workingDirectory: string, agentManager: AgentManager): Promise<Session>;
@@ -1390,19 +2071,23 @@ declare class SessionManager {
1390
2071
  statuses?: string[];
1391
2072
  }): SessionRecord[];
1392
2073
  removeRecord(sessionId: string): Promise<void>;
2074
+ /**
2075
+ * Graceful shutdown: persist session state without killing agent subprocesses.
2076
+ * Agent processes will exit naturally when the parent process terminates.
2077
+ */
2078
+ shutdownAll(): Promise<void>;
2079
+ /**
2080
+ * Forcefully destroy all sessions (kill agent subprocesses).
2081
+ * Use only when sessions must be fully torn down (e.g. archive).
2082
+ */
1393
2083
  destroyAll(): Promise<void>;
1394
2084
  }
1395
2085
 
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
- };
2086
+ declare class NotificationManager {
2087
+ private adapters;
2088
+ constructor(adapters: Map<string, IChannelAdapter>);
2089
+ notify(channelId: string, notification: NotificationMessage): Promise<void>;
2090
+ notifyAll(notification: NotificationMessage): Promise<void>;
1406
2091
  }
1407
2092
 
1408
2093
  interface BridgeDeps {
@@ -1410,7 +2095,8 @@ interface BridgeDeps {
1410
2095
  notificationManager: NotificationManager;
1411
2096
  sessionManager: SessionManager;
1412
2097
  eventBus?: EventBus;
1413
- fileService?: FileService;
2098
+ fileService?: FileServiceInterface;
2099
+ middlewareChain?: MiddlewareChain;
1414
2100
  }
1415
2101
  declare class SessionBridge {
1416
2102
  private session;
@@ -1421,78 +2107,86 @@ declare class SessionBridge {
1421
2107
  private sessionEventHandler?;
1422
2108
  private statusChangeHandler?;
1423
2109
  private namedHandler?;
1424
- constructor(session: Session, adapter: ChannelAdapter, deps: BridgeDeps);
2110
+ private promptCountHandler?;
2111
+ constructor(session: Session, adapter: IChannelAdapter, deps: BridgeDeps);
2112
+ private get tracer();
2113
+ /** Send message to adapter, optionally running through message:outgoing middleware */
2114
+ private sendMessage;
1425
2115
  connect(): void;
1426
2116
  disconnect(): void;
1427
2117
  private wireAgentToSession;
1428
2118
  private wireSessionToAdapter;
2119
+ private handleAgentEvent;
2120
+ /** Persist current ACP state (mode, config, model) to session store as cache */
2121
+ private persistAcpState;
1429
2122
  private wirePermissions;
1430
2123
  private wireLifecycle;
1431
2124
  }
1432
2125
 
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
- };
2126
+ interface TunnelEntry {
2127
+ port: number;
2128
+ type: 'system' | 'user';
2129
+ provider: string;
2130
+ label?: string;
2131
+ publicUrl?: string;
2132
+ sessionId?: string;
2133
+ status: 'stopped' | 'starting' | 'active' | 'failed';
2134
+ retryCount: number;
2135
+ createdAt: string;
1472
2136
  }
1473
2137
 
1474
- interface SessionCreateParams {
1475
- channelId: string;
1476
- agentName: string;
2138
+ interface ViewerEntry {
2139
+ id: string;
2140
+ type: 'file' | 'diff' | 'output';
2141
+ filePath?: string;
2142
+ content: string;
2143
+ oldContent?: string;
2144
+ language?: string;
2145
+ sessionId: string;
1477
2146
  workingDirectory: string;
1478
- resumeAgentSessionId?: string;
1479
- existingSessionId?: string;
1480
- initialName?: string;
2147
+ createdAt: number;
2148
+ expiresAt: number;
1481
2149
  }
1482
- interface SideEffectDeps {
1483
- usageStore?: UsageStore | null;
1484
- usageBudget?: UsageBudget | null;
1485
- notificationManager: NotificationManager;
1486
- tunnelService?: TunnelService;
2150
+ declare class ViewerStore {
2151
+ private entries;
2152
+ private cleanupTimer;
2153
+ private ttlMs;
2154
+ constructor(ttlMinutes?: number);
2155
+ storeFile(sessionId: string, filePath: string, content: string, workingDirectory: string): string | null;
2156
+ storeDiff(sessionId: string, filePath: string, oldContent: string, newContent: string, workingDirectory: string): string | null;
2157
+ storeOutput(sessionId: string, label: string, output: string): string | null;
2158
+ get(id: string): ViewerEntry | undefined;
2159
+ private cleanup;
2160
+ private isPathAllowed;
2161
+ private detectLanguage;
2162
+ destroy(): void;
1487
2163
  }
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;
2164
+
2165
+ declare class TunnelService {
2166
+ private registry;
2167
+ private store;
2168
+ private server;
2169
+ private config;
2170
+ private systemPort;
2171
+ private startError;
2172
+ constructor(config: TunnelConfig, registryPath?: string, binDir?: string);
2173
+ start(): Promise<string>;
2174
+ stop(): Promise<void>;
2175
+ addTunnel(port: number, opts?: {
2176
+ label?: string;
2177
+ sessionId?: string;
2178
+ }): Promise<TunnelEntry>;
2179
+ stopTunnel(port: number): Promise<void>;
2180
+ stopAllUser(): Promise<void>;
2181
+ stopBySession(sessionId: string): Promise<TunnelEntry[]>;
2182
+ listTunnels(): TunnelEntry[];
2183
+ getTunnel(port: number): TunnelEntry | null;
2184
+ getPublicUrl(): string;
2185
+ getStartError(): string | undefined;
2186
+ getStore(): ViewerStore;
2187
+ fileUrl(entryId: string): string;
2188
+ diffUrl(entryId: string): string;
2189
+ outputUrl(entryId: string): string;
1496
2190
  }
1497
2191
 
1498
2192
  interface ContextProvider {
@@ -1509,6 +2203,8 @@ interface ContextQuery {
1509
2203
  interface ContextOptions {
1510
2204
  maxTokens?: number;
1511
2205
  limit?: number;
2206
+ /** When true, insert `## [agentName]` headers at agent boundaries in merged history */
2207
+ labelAgent?: boolean;
1512
2208
  }
1513
2209
  interface SessionInfo {
1514
2210
  checkpointId: string;
@@ -1543,47 +2239,246 @@ interface ContextResult {
1543
2239
  declare class ContextManager {
1544
2240
  private providers;
1545
2241
  private cache;
1546
- constructor();
2242
+ constructor(cachePath?: string);
1547
2243
  register(provider: ContextProvider): void;
1548
2244
  getProvider(repoPath: string): Promise<ContextProvider | null>;
1549
2245
  listSessions(query: ContextQuery): Promise<SessionListResult | null>;
1550
2246
  buildContext(query: ContextQuery, options?: ContextOptions): Promise<ContextResult | null>;
1551
2247
  }
1552
2248
 
1553
- declare class OpenACPCore {
1554
- configManager: ConfigManager;
2249
+ interface SessionCreateParams {
2250
+ channelId: string;
2251
+ agentName: string;
2252
+ workingDirectory: string;
2253
+ resumeAgentSessionId?: string;
2254
+ existingSessionId?: string;
2255
+ initialName?: string;
2256
+ }
2257
+ interface SideEffectDeps {
2258
+ eventBus: EventBus;
2259
+ notificationManager: NotificationManager;
2260
+ tunnelService?: TunnelService;
2261
+ }
2262
+ declare class SessionFactory {
2263
+ private agentManager;
2264
+ private sessionManager;
2265
+ private speechServiceAccessor;
2266
+ private eventBus;
2267
+ private instanceRoot?;
2268
+ middlewareChain?: MiddlewareChain;
2269
+ private resumeLocks;
2270
+ /** Injected by Core after construction — needed for lazy resume error feedback */
2271
+ adapters?: Map<string, IChannelAdapter>;
2272
+ /** Injected by Core after construction — needed for lazy resume store lookup */
2273
+ sessionStore?: SessionStore | null;
2274
+ /** Injected by Core — creates full session with thread + bridge + persist */
2275
+ createFullSession?: (params: SessionCreateParams & {
2276
+ threadId?: string;
2277
+ createThread?: boolean;
2278
+ }) => Promise<Session>;
2279
+ /** Injected by Core — needed for resolving default agent and workspace */
2280
+ configManager?: ConfigManager$1;
2281
+ /** Injected by Core — needed for resolving agent definitions */
2282
+ agentCatalog?: AgentCatalog;
2283
+ /** Injected by Core — needed for context-aware session creation */
2284
+ getContextManager?: () => ContextManager | undefined;
2285
+ constructor(agentManager: AgentManager, sessionManager: SessionManager, speechServiceAccessor: SpeechService | (() => SpeechService), eventBus: EventBus, instanceRoot?: string | undefined);
2286
+ private get speechService();
2287
+ create(params: SessionCreateParams): Promise<Session>;
2288
+ /**
2289
+ * Get active session by thread, or attempt lazy resume from store.
2290
+ * Used by adapter command handlers and handleMessage().
2291
+ */
2292
+ getOrResume(channelId: string, threadId: string): Promise<Session | null>;
2293
+ private lazyResume;
2294
+ handleNewSession(channelId: string, agentName?: string, workspacePath?: string, options?: {
2295
+ createThread?: boolean;
2296
+ }): Promise<Session>;
2297
+ /** NOTE: handleNewChat is currently dead code — never called outside core.ts itself.
2298
+ * Moving it anyway for completeness; can be removed in a future cleanup. */
2299
+ handleNewChat(channelId: string, currentThreadId: string): Promise<Session | null>;
2300
+ createSessionWithContext(params: {
2301
+ channelId: string;
2302
+ agentName: string;
2303
+ workingDirectory: string;
2304
+ contextQuery: ContextQuery;
2305
+ contextOptions?: ContextOptions;
2306
+ createThread?: boolean;
2307
+ }): Promise<{
2308
+ session: Session;
2309
+ contextResult: ContextResult | null;
2310
+ }>;
2311
+ wireSideEffects(session: Session, deps: SideEffectDeps): void;
2312
+ }
2313
+
2314
+ declare class SecurityGuard {
2315
+ private configManager;
2316
+ private sessionManager;
2317
+ constructor(configManager: ConfigManager$1, sessionManager: SessionManager);
2318
+ checkAccess(message: IncomingMessage): {
2319
+ allowed: true;
2320
+ } | {
2321
+ allowed: false;
2322
+ reason: string;
2323
+ };
2324
+ }
2325
+
2326
+ declare class ServiceRegistry {
2327
+ private services;
2328
+ register<T>(name: string, implementation: T, pluginName: string): void;
2329
+ registerOverride<T>(name: string, implementation: T, pluginName: string): void;
2330
+ get<T>(name: string): T | undefined;
2331
+ has(name: string): boolean;
2332
+ list(): Array<{
2333
+ name: string;
2334
+ pluginName: string;
2335
+ }>;
2336
+ unregister(name: string): void;
2337
+ unregisterByPlugin(pluginName: string): void;
2338
+ }
2339
+
2340
+ interface ValidationResult {
2341
+ valid: boolean;
2342
+ errors?: string[];
2343
+ }
2344
+ declare class SettingsManager {
2345
+ private basePath;
2346
+ constructor(basePath: string);
2347
+ getBasePath(): string;
2348
+ createAPI(pluginName: string): SettingsAPI;
2349
+ loadSettings(pluginName: string): Promise<Record<string, unknown>>;
2350
+ validateSettings(_pluginName: string, settings: unknown, schema?: ZodSchema): ValidationResult;
2351
+ getSettingsPath(pluginName: string): string;
2352
+ getPluginSettings(pluginName: string): Promise<Record<string, unknown>>;
2353
+ updatePluginSettings(pluginName: string, updates: Record<string, unknown>): Promise<void>;
2354
+ }
2355
+
2356
+ interface PluginEntry {
2357
+ version: string;
2358
+ installedAt: string;
2359
+ updatedAt: string;
2360
+ source: 'builtin' | 'npm' | 'local';
2361
+ enabled: boolean;
2362
+ settingsPath: string;
2363
+ description?: string;
2364
+ }
2365
+ type RegisterInput = Omit<PluginEntry, 'installedAt' | 'updatedAt'>;
2366
+ declare class PluginRegistry {
2367
+ private registryPath;
2368
+ private data;
2369
+ constructor(registryPath: string);
2370
+ list(): Map<string, PluginEntry>;
2371
+ get(name: string): PluginEntry | undefined;
2372
+ register(name: string, entry: RegisterInput): void;
2373
+ remove(name: string): void;
2374
+ setEnabled(name: string, enabled: boolean): void;
2375
+ updateVersion(name: string, version: string): void;
2376
+ listEnabled(): Map<string, PluginEntry>;
2377
+ listBySource(source: PluginEntry['source']): Map<string, PluginEntry>;
2378
+ load(): Promise<void>;
2379
+ save(): Promise<void>;
2380
+ }
2381
+
2382
+ interface LifecycleManagerOpts {
2383
+ serviceRegistry?: ServiceRegistry;
2384
+ middlewareChain?: MiddlewareChain;
2385
+ errorTracker?: ErrorTracker;
2386
+ eventBus?: EventBus$1 & {
2387
+ on(event: string, handler: (...args: unknown[]) => void): void;
2388
+ off(event: string, handler: (...args: unknown[]) => void): void;
2389
+ emit(event: string, payload: unknown): void;
2390
+ };
2391
+ storagePath?: string;
2392
+ sessions?: unknown;
2393
+ config?: unknown;
2394
+ core?: unknown;
2395
+ log?: Logger;
2396
+ settingsManager?: SettingsManager;
2397
+ pluginRegistry?: PluginRegistry;
2398
+ /** Root directory for this OpenACP instance (default: ~/.openacp) */
2399
+ instanceRoot?: string;
2400
+ }
2401
+ declare class LifecycleManager {
2402
+ readonly serviceRegistry: ServiceRegistry;
2403
+ readonly middlewareChain: MiddlewareChain;
2404
+ readonly errorTracker: ErrorTracker;
2405
+ private eventBus;
2406
+ private storagePath;
2407
+ private sessions;
2408
+ private config;
2409
+ private core;
2410
+ private log;
2411
+ settingsManager: SettingsManager | undefined;
2412
+ private pluginRegistry;
2413
+ private instanceRoot;
2414
+ private contexts;
2415
+ private loadOrder;
2416
+ private _loaded;
2417
+ private _failed;
2418
+ get loadedPlugins(): string[];
2419
+ get failedPlugins(): string[];
2420
+ get registry(): PluginRegistry | undefined;
2421
+ constructor(opts?: LifecycleManagerOpts);
2422
+ private getPluginLogger;
2423
+ boot(plugins: OpenACPPlugin[]): Promise<void>;
2424
+ unloadPlugin(name: string): Promise<void>;
2425
+ shutdown(): Promise<void>;
2426
+ }
2427
+
2428
+ interface InstanceContext {
2429
+ id: string;
2430
+ root: string;
2431
+ isGlobal: boolean;
2432
+ paths: {
2433
+ config: string;
2434
+ sessions: string;
2435
+ agents: string;
2436
+ registryCache: string;
2437
+ plugins: string;
2438
+ pluginsData: string;
2439
+ pluginRegistry: string;
2440
+ logs: string;
2441
+ pid: string;
2442
+ running: string;
2443
+ apiPort: string;
2444
+ apiSecret: string;
2445
+ bin: string;
2446
+ cache: string;
2447
+ tunnels: string;
2448
+ agentsDir: string;
2449
+ };
2450
+ }
2451
+
2452
+ declare class OpenACPCore {
2453
+ configManager: ConfigManager$1;
1555
2454
  agentCatalog: AgentCatalog;
1556
2455
  agentManager: AgentManager;
1557
2456
  sessionManager: SessionManager;
1558
- notificationManager: NotificationManager;
1559
2457
  messageTransformer: MessageTransformer;
1560
- fileService: FileService;
1561
- readonly speechService: SpeechService;
1562
- securityGuard: SecurityGuard;
1563
- adapters: Map<string, ChannelAdapter>;
2458
+ adapters: Map<string, IChannelAdapter>;
2459
+ /** sessionId → SessionBridge — tracks active bridges for disconnect/reconnect during agent switch */
2460
+ private bridges;
1564
2461
  /** Set by main.ts — triggers graceful shutdown with restart exit code */
1565
2462
  requestRestart: (() => Promise<void>) | null;
1566
2463
  private _tunnelService?;
1567
2464
  private sessionStore;
1568
- private resumeLocks;
1569
2465
  eventBus: EventBus;
1570
2466
  sessionFactory: SessionFactory;
1571
- readonly usageStore: UsageStore | null;
1572
- readonly usageBudget: UsageBudget | null;
1573
- readonly contextManager: ContextManager;
1574
- constructor(configManager: ConfigManager);
2467
+ readonly lifecycleManager: LifecycleManager;
2468
+ private agentSwitchHandler;
2469
+ readonly instanceContext?: InstanceContext;
2470
+ private getService;
2471
+ get securityGuard(): SecurityGuard;
2472
+ get notificationManager(): NotificationManager;
2473
+ get fileService(): FileServiceInterface;
2474
+ get speechService(): SpeechService;
2475
+ get contextManager(): ContextManager;
2476
+ constructor(configManager: ConfigManager$1, ctx?: InstanceContext);
1575
2477
  get tunnelService(): TunnelService | undefined;
1576
2478
  set tunnelService(service: TunnelService | undefined);
1577
- registerAdapter(name: string, adapter: ChannelAdapter): void;
2479
+ registerAdapter(name: string, adapter: IChannelAdapter): void;
1578
2480
  start(): Promise<void>;
1579
2481
  stop(): Promise<void>;
1580
- summarizeSession(sessionId: string): Promise<{
1581
- ok: true;
1582
- summary: string;
1583
- } | {
1584
- ok: false;
1585
- error: string;
1586
- }>;
1587
2482
  archiveSession(sessionId: string): Promise<{
1588
2483
  ok: true;
1589
2484
  } | {
@@ -1599,6 +2494,7 @@ declare class OpenACPCore {
1599
2494
  existingSessionId?: string;
1600
2495
  createThread?: boolean;
1601
2496
  initialName?: string;
2497
+ threadId?: string;
1602
2498
  }): Promise<Session>;
1603
2499
  handleNewSession(channelId: string, agentName?: string, workspacePath?: string, options?: {
1604
2500
  createThread?: boolean;
@@ -1625,40 +2521,137 @@ declare class OpenACPCore {
1625
2521
  session: Session;
1626
2522
  contextResult: ContextResult | null;
1627
2523
  }>;
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
- */
2524
+ switchSessionAgent(sessionId: string, toAgent: string): Promise<{
2525
+ resumed: boolean;
2526
+ }>;
1632
2527
  getOrResumeSession(channelId: string, threadId: string): Promise<Session | null>;
1633
- private lazyResume;
1634
2528
  /** Create a SessionBridge for the given session and adapter */
1635
- createBridge(session: Session, adapter: ChannelAdapter): SessionBridge;
2529
+ createBridge(session: Session, adapter: IChannelAdapter): SessionBridge;
2530
+ }
2531
+
2532
+ interface RegisteredCommand extends CommandDef {
2533
+ /** Scope extracted from pluginName, e.g. '@openacp/speech' → 'speech' */
2534
+ scope?: string;
2535
+ }
2536
+ /**
2537
+ * Central command registry with namespace resolution and adapter-specific overrides.
2538
+ *
2539
+ * Namespace rules:
2540
+ * - System commands always own the short name.
2541
+ * - Among plugins, the first to register wins the short name.
2542
+ * - Every plugin command also gets a qualified name: `scope:name`.
2543
+ * - Adapter plugins (@openacp/telegram, @openacp/discord)
2544
+ * registering a command that already exists → stored as an override
2545
+ * keyed by `channelId:commandName`, used when channelId matches.
2546
+ */
2547
+ declare class CommandRegistry {
2548
+ /** Main registry: short names + qualified names → RegisteredCommand */
2549
+ private commands;
2550
+ /** Adapter-specific overrides: `channelId:commandName` → RegisteredCommand */
2551
+ private overrides;
2552
+ private static ADAPTER_SCOPES;
2553
+ /**
2554
+ * Register a command definition.
2555
+ * @param def - Command definition
2556
+ * @param pluginName - Plugin that owns the command (set automatically by PluginContext)
2557
+ */
2558
+ register(def: CommandDef, pluginName?: string): void;
2559
+ /** Retrieve a command by name (short or qualified). */
2560
+ get(name: string): RegisteredCommand | undefined;
2561
+ /** Remove a command by name (short or qualified). Also removes its qualified name entry. */
2562
+ unregister(name: string): void;
2563
+ /** Remove all commands registered by a given plugin. */
2564
+ unregisterByPlugin(pluginName: string): void;
2565
+ /** Return all unique commands (deduplicated — each command appears once). */
2566
+ getAll(): RegisteredCommand[];
2567
+ /** Filter commands by category. */
2568
+ getByCategory(category: 'system' | 'plugin'): RegisteredCommand[];
2569
+ /**
2570
+ * Parse and execute a command string.
2571
+ * @param commandString - Full command string, e.g. "/greet hello world"
2572
+ * @param baseArgs - Base arguments (channelId, userId, etc.)
2573
+ * @returns CommandResponse
2574
+ */
2575
+ execute(commandString: string, baseArgs: CommandArgs): Promise<CommandResponse>;
2576
+ /** Extract scope from plugin name: '@openacp/speech' → 'speech', 'my-plugin' → 'my-plugin' */
2577
+ static extractScope(pluginName: string): string;
1636
2578
  }
1637
2579
 
1638
- interface AdapterFactory {
2580
+ interface CheckResult {
2581
+ status: "pass" | "warn" | "fail";
2582
+ message: string;
2583
+ fixable?: boolean;
2584
+ fixRisk?: "safe" | "risky";
2585
+ fix?: () => Promise<FixResult>;
2586
+ }
2587
+ interface FixResult {
2588
+ success: boolean;
2589
+ message: string;
2590
+ }
2591
+ interface DoctorReport {
2592
+ categories: CategoryResult[];
2593
+ summary: {
2594
+ passed: number;
2595
+ warnings: number;
2596
+ failed: number;
2597
+ fixed: number;
2598
+ };
2599
+ pendingFixes: PendingFix[];
2600
+ }
2601
+ interface CategoryResult {
1639
2602
  name: string;
1640
- createAdapter(core: OpenACPCore, config: ChannelConfig): ChannelAdapter;
2603
+ results: CheckResult[];
2604
+ }
2605
+ interface PendingFix {
2606
+ category: string;
2607
+ message: string;
2608
+ fix: () => Promise<FixResult>;
1641
2609
  }
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>;
1646
2610
 
2611
+ declare class DoctorEngine {
2612
+ private dryRun;
2613
+ private dataDir;
2614
+ constructor(options?: {
2615
+ dryRun?: boolean;
2616
+ dataDir?: string;
2617
+ });
2618
+ runAll(): Promise<DoctorReport>;
2619
+ private buildContext;
2620
+ }
2621
+
2622
+ interface ConfigFieldDef {
2623
+ path: string;
2624
+ displayName: string;
2625
+ group: string;
2626
+ type: "toggle" | "select" | "number" | "string";
2627
+ options?: string[] | ((config: Config) => string[]);
2628
+ scope: "safe" | "sensitive";
2629
+ hotReload: boolean;
2630
+ }
2631
+ declare const CONFIG_REGISTRY: ConfigFieldDef[];
2632
+ declare function getFieldDef(path: string): ConfigFieldDef | undefined;
2633
+ declare function getSafeFields(): ConfigFieldDef[];
2634
+ declare function isHotReloadable(path: string): boolean;
2635
+ declare function resolveOptions(def: ConfigFieldDef, config: Config): string[] | undefined;
2636
+ declare function getConfigValue(config: Config, path: string): unknown;
2637
+
2638
+ declare function runConfigEditor(configManager: ConfigManager$1, mode?: 'file' | 'api', apiPort?: number): Promise<void>;
2639
+
2640
+ declare function getPidPath(root?: string): string;
1647
2641
  declare function getStatus(pidPath?: string): {
1648
2642
  running: boolean;
1649
2643
  pid?: number;
1650
2644
  };
1651
- declare function startDaemon(pidPath?: string, logDir?: string): {
2645
+ declare function startDaemon(pidPath?: string, logDir?: string, instanceRoot?: string): {
1652
2646
  pid: number;
1653
2647
  } | {
1654
2648
  error: string;
1655
2649
  };
1656
- declare function stopDaemon(pidPath?: string): Promise<{
2650
+ declare function stopDaemon(pidPath?: string, instanceRoot?: string): Promise<{
1657
2651
  stopped: boolean;
1658
2652
  pid?: number;
1659
2653
  error?: string;
1660
2654
  }>;
1661
- declare function getPidPath(): string;
1662
2655
 
1663
2656
  declare function isAutoStartSupported(): boolean;
1664
2657
  declare function installAutoStart(logDir: string): {
@@ -1671,7 +2664,142 @@ declare function uninstallAutoStart(): {
1671
2664
  };
1672
2665
  declare function isAutoStartInstalled(): boolean;
1673
2666
 
1674
- declare function runConfigEditor(configManager: ConfigManager, mode?: 'file' | 'api', apiPort?: number): Promise<void>;
2667
+ declare class FileService {
2668
+ private baseDir;
2669
+ constructor(baseDir: string);
2670
+ /**
2671
+ * Remove session file directories older than maxAgeDays.
2672
+ * Called on startup to prevent unbounded disk growth.
2673
+ */
2674
+ cleanupOldFiles(maxAgeDays: number): Promise<number>;
2675
+ saveFile(sessionId: string, fileName: string, data: Buffer, mimeType: string): Promise<Attachment>;
2676
+ resolveFile(filePath: string): Promise<Attachment | null>;
2677
+ /**
2678
+ * Convert OGG Opus audio to WAV format.
2679
+ * Telegram voice messages use OGG Opus which many AI agents can't read.
2680
+ */
2681
+ convertOggToWav(oggData: Buffer): Promise<Buffer>;
2682
+ /** Instance method — delegates to static for FileServiceInterface compliance */
2683
+ readTextFileWithRange(filePath: string, options?: {
2684
+ line?: number;
2685
+ limit?: number;
2686
+ }): Promise<string>;
2687
+ static readTextFileWithRange(filePath: string, options?: {
2688
+ line?: number;
2689
+ limit?: number;
2690
+ }): Promise<string>;
2691
+ /** Instance method — delegates to static for FileServiceInterface compliance */
2692
+ extensionFromMime(mimeType: string): string;
2693
+ static extensionFromMime(mimeType: string): string;
2694
+ }
2695
+
2696
+ interface ApiConfig {
2697
+ port: number;
2698
+ host: string;
2699
+ }
2700
+
2701
+ interface StoredToken {
2702
+ id: string;
2703
+ name: string;
2704
+ role: string;
2705
+ scopes?: string[];
2706
+ createdAt: string;
2707
+ refreshDeadline: string;
2708
+ lastUsedAt?: string;
2709
+ revoked: boolean;
2710
+ }
2711
+ interface CreateTokenOpts {
2712
+ role: string;
2713
+ name: string;
2714
+ expire: string;
2715
+ scopes?: string[];
2716
+ }
2717
+
2718
+ declare class TokenStore {
2719
+ private filePath;
2720
+ private tokens;
2721
+ private savePromise;
2722
+ private savePending;
2723
+ constructor(filePath: string);
2724
+ load(): Promise<void>;
2725
+ save(): Promise<void>;
2726
+ private scheduleSave;
2727
+ create(opts: CreateTokenOpts): StoredToken;
2728
+ get(id: string): StoredToken | undefined;
2729
+ revoke(id: string): void;
2730
+ list(): StoredToken[];
2731
+ private lastUsedSaveTimer;
2732
+ updateLastUsed(id: string): void;
2733
+ /** Wait for any in-flight save to complete */
2734
+ flush(): Promise<void>;
2735
+ destroy(): void;
2736
+ cleanup(): void;
2737
+ }
2738
+
2739
+ interface ApiServerOptions {
2740
+ port: number;
2741
+ host: string;
2742
+ getSecret: () => string;
2743
+ getJwtSecret: () => string;
2744
+ tokenStore: TokenStore;
2745
+ logger?: boolean;
2746
+ }
2747
+ interface ApiServerInstance {
2748
+ app: FastifyInstance;
2749
+ start(): Promise<{
2750
+ port: number;
2751
+ host: string;
2752
+ }>;
2753
+ stop(): Promise<void>;
2754
+ registerPlugin(prefix: string, plugin: FastifyPluginAsync, opts?: {
2755
+ auth?: boolean;
2756
+ }): void;
2757
+ }
2758
+ declare function createApiServer(options: ApiServerOptions): Promise<ApiServerInstance>;
2759
+
2760
+ interface ApiServerService {
2761
+ registerPlugin(prefix: string, plugin: FastifyPluginAsync, opts?: {
2762
+ auth?: boolean;
2763
+ }): void;
2764
+ authPreHandler: preHandlerHookHandler;
2765
+ requireScopes(...scopes: string[]): preHandlerHookHandler;
2766
+ requireRole(role: string): preHandlerHookHandler;
2767
+ getPort(): number;
2768
+ getBaseUrl(): string;
2769
+ getTunnelUrl(): string | null;
2770
+ }
2771
+ declare function createApiServerService(server: ApiServerInstance, getPort: () => number, getBaseUrl: () => string, getTunnelUrl: () => string | null, authPreHandler: preHandlerHookHandler): ApiServerService;
2772
+
2773
+ interface SessionStats {
2774
+ active: number;
2775
+ total: number;
2776
+ }
2777
+ declare class SSEManager {
2778
+ private eventBus;
2779
+ private getSessionStats;
2780
+ private startedAt;
2781
+ private sseConnections;
2782
+ private sseCleanupHandlers;
2783
+ private healthInterval?;
2784
+ private boundHandlers;
2785
+ constructor(eventBus: EventBus | undefined, getSessionStats: () => SessionStats, startedAt: number);
2786
+ setup(): void;
2787
+ handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void;
2788
+ broadcast(event: string, data: unknown): void;
2789
+ /**
2790
+ * Returns a Fastify route handler that hijacks the response
2791
+ * and delegates to the raw http SSE handler.
2792
+ */
2793
+ createFastifyHandler(): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
2794
+ stop(): void;
2795
+ }
2796
+
2797
+ declare class StaticServer {
2798
+ private uiDir;
2799
+ constructor(uiDir?: string);
2800
+ isAvailable(): boolean;
2801
+ serve(req: http.IncomingMessage, res: http.ServerResponse): boolean;
2802
+ }
1675
2803
 
1676
2804
  interface TopicInfo {
1677
2805
  sessionId: string;
@@ -1718,89 +2846,393 @@ declare class TopicManager {
1718
2846
  private isSystemTopic;
1719
2847
  }
1720
2848
 
1721
- interface ApiConfig {
1722
- port: number;
1723
- host: string;
2849
+ declare class EntireProvider implements ContextProvider {
2850
+ readonly name = "entire";
2851
+ isAvailable(repoPath: string): Promise<boolean>;
2852
+ listSessions(query: ContextQuery): Promise<SessionListResult>;
2853
+ buildContext(query: ContextQuery, options?: ContextOptions): Promise<ContextResult>;
2854
+ private buildSessionMarkdowns;
2855
+ private resolveSessions;
2856
+ private buildTitle;
1724
2857
  }
1725
- declare class ApiServer {
1726
- private core;
2858
+
2859
+ interface RenderedMessage<TComponents = unknown> {
2860
+ body: string;
2861
+ format: "html" | "markdown" | "plain" | "structured";
2862
+ attachments?: RenderedAttachment[];
2863
+ components?: TComponents;
2864
+ }
2865
+ interface RenderedPermission<TComponents = unknown> extends RenderedMessage<TComponents> {
2866
+ actions: RenderedAction[];
2867
+ }
2868
+ interface RenderedAction {
2869
+ id: string;
2870
+ label: string;
2871
+ isAllow?: boolean;
2872
+ }
2873
+ interface RenderedAttachment {
2874
+ type: "file" | "image" | "audio";
2875
+ data: Buffer | string;
2876
+ mimeType?: string;
2877
+ filename?: string;
2878
+ }
2879
+ interface IRenderer {
2880
+ renderText(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2881
+ renderToolCall(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2882
+ renderToolUpdate(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2883
+ renderPlan(content: OutgoingMessage): RenderedMessage;
2884
+ renderUsage(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2885
+ renderPermission(request: PermissionRequest): RenderedPermission;
2886
+ renderError(content: OutgoingMessage): RenderedMessage;
2887
+ renderNotification(notification: NotificationMessage): RenderedMessage;
2888
+ renderThought?(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2889
+ renderAttachment?(content: OutgoingMessage): RenderedMessage;
2890
+ renderSessionEnd?(content: OutgoingMessage): RenderedMessage;
2891
+ renderSystemMessage?(content: OutgoingMessage): RenderedMessage;
2892
+ renderModeChange?(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2893
+ renderConfigUpdate?(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2894
+ renderModelUpdate?(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2895
+ renderResource?(content: OutgoingMessage): RenderedMessage;
2896
+ renderResourceLink?(content: OutgoingMessage): RenderedMessage;
2897
+ }
2898
+ /**
2899
+ * BaseRenderer — plain text defaults. Extend for platform-specific rendering.
2900
+ */
2901
+ declare class BaseRenderer implements IRenderer {
2902
+ renderText(content: OutgoingMessage): RenderedMessage;
2903
+ renderToolCall(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2904
+ renderToolUpdate(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2905
+ renderPlan(content: OutgoingMessage): RenderedMessage;
2906
+ renderUsage(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2907
+ renderPermission(request: PermissionRequest): RenderedPermission;
2908
+ renderError(content: OutgoingMessage): RenderedMessage;
2909
+ renderNotification(notification: NotificationMessage): RenderedMessage;
2910
+ renderSystemMessage(content: OutgoingMessage): RenderedMessage;
2911
+ renderModeChange(content: OutgoingMessage): RenderedMessage;
2912
+ renderConfigUpdate(): RenderedMessage;
2913
+ renderModelUpdate(content: OutgoingMessage): RenderedMessage;
2914
+ renderResource(content: OutgoingMessage): RenderedMessage;
2915
+ renderResourceLink(content: OutgoingMessage): RenderedMessage;
2916
+ }
2917
+
2918
+ interface AdapterContext {
2919
+ configManager: {
2920
+ get(): Record<string, unknown>;
2921
+ };
2922
+ fileService?: unknown;
2923
+ }
2924
+ interface MessagingAdapterConfig extends ChannelConfig {
2925
+ maxMessageLength: number;
2926
+ flushInterval?: number;
2927
+ sendInterval?: number;
2928
+ thinkingRefreshInterval?: number;
2929
+ thinkingDuration?: number;
2930
+ displayVerbosity?: DisplayVerbosity;
2931
+ }
2932
+ declare abstract class MessagingAdapter implements IChannelAdapter {
2933
+ protected context: AdapterContext;
2934
+ protected adapterConfig: MessagingAdapterConfig;
2935
+ abstract readonly name: string;
2936
+ abstract readonly renderer: IRenderer;
2937
+ abstract readonly capabilities: AdapterCapabilities;
2938
+ constructor(context: AdapterContext, adapterConfig: MessagingAdapterConfig);
2939
+ sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
2940
+ protected dispatchMessage(sessionId: string, content: OutgoingMessage, verbosity: DisplayVerbosity): Promise<void>;
2941
+ protected handleText(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2942
+ protected handleThought(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2943
+ protected handleToolCall(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2944
+ protected handleToolUpdate(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2945
+ protected handlePlan(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2946
+ protected handleUsage(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2947
+ protected handleError(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2948
+ protected handleAttachment(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2949
+ protected handleSystem(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2950
+ protected handleSessionEnd(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2951
+ protected handleModeChange(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2952
+ protected handleConfigUpdate(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2953
+ protected handleModelUpdate(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2954
+ protected handleUserReplay(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2955
+ protected handleResource(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2956
+ protected handleResourceLink(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2957
+ protected getVerbosity(): DisplayVerbosity;
2958
+ protected shouldDisplay(content: OutgoingMessage, verbosity: DisplayVerbosity): boolean;
2959
+ abstract start(): Promise<void>;
2960
+ abstract stop(): Promise<void>;
2961
+ abstract createSessionThread(sessionId: string, name: string): Promise<string>;
2962
+ abstract renameSessionThread(sessionId: string, newName: string): Promise<void>;
2963
+ abstract sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
2964
+ abstract sendNotification(notification: NotificationMessage): Promise<void>;
2965
+ }
2966
+
2967
+ interface StreamEvent {
2968
+ type: string;
2969
+ sessionId?: string;
2970
+ payload: unknown;
2971
+ timestamp: number;
2972
+ }
2973
+ declare abstract class StreamAdapter implements IChannelAdapter {
2974
+ abstract readonly name: string;
2975
+ capabilities: AdapterCapabilities;
2976
+ constructor(config?: Partial<AdapterCapabilities>);
2977
+ sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
2978
+ sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
2979
+ sendNotification(notification: NotificationMessage): Promise<void>;
2980
+ createSessionThread(_sessionId: string, _name: string): Promise<string>;
2981
+ renameSessionThread(sessionId: string, name: string): Promise<void>;
2982
+ protected abstract emit(sessionId: string, event: StreamEvent): Promise<void>;
2983
+ protected abstract broadcast(event: StreamEvent): Promise<void>;
2984
+ abstract start(): Promise<void>;
2985
+ abstract stop(): Promise<void>;
2986
+ }
2987
+
2988
+ type QueueItemType = 'text' | 'other';
2989
+ interface SendQueueConfig {
2990
+ minInterval: number;
2991
+ categoryIntervals?: Record<string, number>;
2992
+ onRateLimited?: () => void;
2993
+ onError?: (error: Error) => void;
2994
+ }
2995
+ interface EnqueueOptions {
2996
+ type?: QueueItemType;
2997
+ key?: string;
2998
+ category?: string;
2999
+ }
3000
+ declare class SendQueue {
1727
3001
  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;
3002
+ private items;
3003
+ private processing;
3004
+ private lastExec;
3005
+ private lastCategoryExec;
3006
+ constructor(config: SendQueueConfig);
3007
+ get pending(): number;
3008
+ enqueue<T>(fn: () => Promise<T>, opts?: EnqueueOptions): Promise<T | undefined>;
3009
+ onRateLimited(): void;
3010
+ clear(): void;
3011
+ private scheduleProcess;
3012
+ private getInterval;
3013
+ private processNext;
1750
3014
  }
1751
3015
 
1752
- interface SessionStats {
1753
- active: number;
1754
- total: number;
3016
+ interface DraftConfig {
3017
+ flushInterval: number;
3018
+ maxLength: number;
3019
+ onFlush: (sessionId: string, text: string, isEdit: boolean) => Promise<string | undefined>;
3020
+ onError?: (sessionId: string, error: Error) => void;
3021
+ }
3022
+ declare class Draft {
3023
+ private sessionId;
3024
+ private config;
3025
+ private buffer;
3026
+ private _messageId?;
3027
+ private firstFlushPending;
3028
+ private flushTimer?;
3029
+ private flushPromise;
3030
+ constructor(sessionId: string, config: DraftConfig);
3031
+ get isEmpty(): boolean;
3032
+ get messageId(): string | undefined;
3033
+ append(text: string): void;
3034
+ finalize(): Promise<string | undefined>;
3035
+ destroy(): void;
3036
+ private scheduleFlush;
3037
+ private flush;
1755
3038
  }
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;
3039
+ declare class DraftManager {
3040
+ private config;
3041
+ private drafts;
3042
+ constructor(config: DraftConfig);
3043
+ getOrCreate(sessionId: string): Draft;
3044
+ finalize(sessionId: string): Promise<void>;
3045
+ finalizeAll(): Promise<void>;
3046
+ destroy(sessionId: string): void;
3047
+ destroyAll(): void;
1769
3048
  }
1770
3049
 
1771
- declare class StaticServer {
1772
- private uiDir;
1773
- constructor(uiDir?: string);
1774
- isAvailable(): boolean;
1775
- serve(req: http.IncomingMessage, res: http.ServerResponse): boolean;
3050
+ interface TrackedToolCall extends ToolCallMeta {
3051
+ messageId: string;
3052
+ }
3053
+ declare class ToolCallTracker {
3054
+ private sessions;
3055
+ track(sessionId: string, meta: ToolCallMeta, messageId: string): void;
3056
+ update(sessionId: string, toolId: string, status: string, patch?: Partial<Pick<ToolCallMeta, 'viewerLinks' | 'viewerFilePath' | 'name' | 'kind'>>): TrackedToolCall | null;
3057
+ getActive(sessionId: string): TrackedToolCall[];
3058
+ clear(sessionId: string): void;
3059
+ clearAll(): void;
1776
3060
  }
1777
3061
 
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;
3062
+ interface ActivityConfig {
3063
+ thinkingRefreshInterval: number;
3064
+ maxThinkingDuration: number;
3065
+ }
3066
+ interface ActivityCallbacks {
3067
+ sendThinkingIndicator(): Promise<void>;
3068
+ updateThinkingIndicator(): Promise<void>;
3069
+ removeThinkingIndicator(): Promise<void>;
3070
+ }
3071
+ declare class ActivityTracker {
3072
+ private config;
3073
+ private sessions;
3074
+ constructor(config: ActivityConfig);
3075
+ onThinkingStart(sessionId: string, callbacks: ActivityCallbacks): void;
3076
+ onTextStart(sessionId: string): void;
3077
+ onSessionEnd(sessionId: string): void;
3078
+ destroy(): void;
3079
+ private cleanup;
3080
+ private startRefresh;
3081
+ private stopRefresh;
1786
3082
  }
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
3083
 
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;
3084
+ interface ToolEntry {
3085
+ id: string;
3086
+ name: string;
3087
+ kind: string;
3088
+ rawInput: unknown;
3089
+ content: string | null;
3090
+ status: string;
3091
+ viewerLinks?: ViewerLinks;
3092
+ diffStats?: {
3093
+ added: number;
3094
+ removed: number;
3095
+ };
3096
+ displaySummary?: string;
3097
+ displayTitle?: string;
3098
+ displayKind?: string;
3099
+ isNoise: boolean;
3100
+ }
3101
+ declare class ToolStateMap {
3102
+ private entries;
3103
+ private pendingUpdates;
3104
+ /**
3105
+ * Creates or updates an entry from a tool_call event.
3106
+ * If a pending update exists for this id, applies it immediately.
3107
+ */
3108
+ upsert(meta: ToolCallMeta, kind: string, rawInput: unknown): ToolEntry;
3109
+ /**
3110
+ * Updates an existing entry from a tool_call_update event.
3111
+ * If the entry doesn't exist yet (out-of-order delivery), buffers the update.
3112
+ */
3113
+ merge(id: string, status: string, rawInput?: unknown, content?: string | null, viewerLinks?: ViewerLinks, diffStats?: {
3114
+ added: number;
3115
+ removed: number;
3116
+ }): ToolEntry | undefined;
3117
+ private _applyUpdate;
3118
+ get(id: string): ToolEntry | undefined;
3119
+ clear(): void;
3120
+ }
3121
+ declare class ThoughtBuffer {
3122
+ private chunks;
3123
+ private sealed;
3124
+ append(chunk: string): void;
3125
+ seal(): string;
3126
+ getText(): string;
3127
+ isSealed(): boolean;
3128
+ reset(): void;
1802
3129
  }
1803
3130
 
3131
+ interface ToolDisplaySpec {
3132
+ id: string;
3133
+ kind: string;
3134
+ icon: string;
3135
+ title: string;
3136
+ description: string | null;
3137
+ command: string | null;
3138
+ inputContent: string | null;
3139
+ outputSummary: string | null;
3140
+ outputContent: string | null;
3141
+ diffStats: {
3142
+ added: number;
3143
+ removed: number;
3144
+ } | null;
3145
+ viewerLinks?: ViewerLinks;
3146
+ outputViewerLink?: string;
3147
+ outputFallbackContent?: string;
3148
+ status: string;
3149
+ isNoise: boolean;
3150
+ isHidden: boolean;
3151
+ }
3152
+ interface ThoughtDisplaySpec {
3153
+ indicator: string;
3154
+ content: string | null;
3155
+ }
3156
+ declare class DisplaySpecBuilder {
3157
+ private tunnelService?;
3158
+ constructor(tunnelService?: TunnelServiceInterface | undefined);
3159
+ buildToolSpec(entry: ToolEntry, mode: OutputMode, sessionContext?: {
3160
+ id: string;
3161
+ workingDirectory: string;
3162
+ }): ToolDisplaySpec;
3163
+ buildThoughtSpec(content: string, mode: OutputMode): ThoughtDisplaySpec;
3164
+ }
3165
+
3166
+ interface UsageData {
3167
+ tokensUsed?: number;
3168
+ contextSize?: number;
3169
+ cost?: number;
3170
+ }
3171
+ interface ToolCardSnapshot {
3172
+ specs: ToolDisplaySpec[];
3173
+ planEntries?: PlanEntry[];
3174
+ usage?: UsageData;
3175
+ totalVisible: number;
3176
+ completedVisible: number;
3177
+ allComplete: boolean;
3178
+ }
3179
+ interface ToolCardStateConfig {
3180
+ onFlush: (snapshot: ToolCardSnapshot) => void;
3181
+ }
3182
+ declare class ToolCardState {
3183
+ private specs;
3184
+ private planEntries?;
3185
+ private usage?;
3186
+ private finalized;
3187
+ private isFirstFlush;
3188
+ private debounceTimer?;
3189
+ private onFlush;
3190
+ constructor(config: ToolCardStateConfig);
3191
+ updateFromSpec(spec: ToolDisplaySpec): void;
3192
+ updatePlan(entries: PlanEntry[]): void;
3193
+ appendUsage(usage: UsageData): void;
3194
+ finalize(): void;
3195
+ destroy(): void;
3196
+ hasContent(): boolean;
3197
+ private snapshot;
3198
+ private flush;
3199
+ private scheduleFlush;
3200
+ private clearDebounce;
3201
+ }
3202
+
3203
+ declare function progressBar(ratio: number, length?: number): string;
3204
+ declare function formatTokens(n: number): string;
3205
+ declare function stripCodeFences(text: string): string;
3206
+ declare function truncateContent(text: string, maxLen: number): string;
3207
+ declare function splitMessage(text: string, maxLength: number): string[];
3208
+
3209
+ declare function extractContentText(content: unknown, depth?: number): string;
3210
+ declare function formatToolSummary(name: string, rawInput: unknown, displaySummary?: string): string;
3211
+ declare function formatToolTitle(name: string, rawInput: unknown, displayTitle?: string): string;
3212
+ declare function resolveToolIcon(tool: {
3213
+ status?: string;
3214
+ displayKind?: string;
3215
+ kind?: string;
3216
+ }): string;
3217
+
3218
+ interface ConfigManagerLike {
3219
+ get(): Record<string, unknown>;
3220
+ }
3221
+ interface SessionManagerLike {
3222
+ getSessionRecord(id: string): {
3223
+ outputMode?: OutputMode;
3224
+ } | undefined;
3225
+ }
3226
+ declare class OutputModeResolver {
3227
+ resolve(configManager: ConfigManagerLike, adapterName: string, sessionId?: string, sessionManager?: SessionManagerLike): OutputMode;
3228
+ }
3229
+
3230
+ /**
3231
+ * OpenACP Product Guide — comprehensive reference for the AI assistant.
3232
+ * The assistant reads this at runtime to answer user questions about features.
3233
+ */
3234
+ 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";
3235
+
1804
3236
  interface TelegramChannelConfig extends ChannelConfig {
1805
3237
  botToken: string;
1806
3238
  chatId: number;
@@ -1808,39 +3240,88 @@ interface TelegramChannelConfig extends ChannelConfig {
1808
3240
  assistantTopicId: number | null;
1809
3241
  }
1810
3242
 
1811
- declare class TelegramAdapter extends ChannelAdapter<OpenACPCore> {
3243
+ declare class TelegramAdapter extends MessagingAdapter {
3244
+ readonly name = "telegram";
3245
+ readonly renderer: IRenderer;
3246
+ readonly capabilities: AdapterCapabilities;
3247
+ private core;
1812
3248
  private bot;
1813
3249
  private telegramConfig;
3250
+ private saveTopicIds?;
1814
3251
  private permissionHandler;
1815
3252
  private assistantSession;
1816
3253
  private assistantInitializing;
1817
3254
  private notificationTopicId;
1818
3255
  private assistantTopicId;
1819
3256
  private sendQueue;
1820
- private toolTracker;
3257
+ private _sessionThreadIds;
3258
+ private outputModeResolver;
1821
3259
  private draftManager;
1822
3260
  private skillManager;
1823
3261
  private fileService;
1824
3262
  private sessionTrackers;
1825
- private get verbosity();
3263
+ private callbackCache;
3264
+ private callbackCounter;
3265
+ /** Pending skill commands queued when session.threadId was not yet set */
3266
+ private _pendingSkillCommands;
3267
+ private getThreadId;
1826
3268
  private getOrCreateTracker;
1827
- constructor(core: OpenACPCore, config: TelegramChannelConfig);
3269
+ constructor(core: OpenACPCore, config: TelegramChannelConfig, saveTopicIds?: (updates: {
3270
+ notificationTopicId?: number;
3271
+ assistantTopicId?: number;
3272
+ }) => Promise<void>);
1828
3273
  start(): Promise<void>;
3274
+ /**
3275
+ * Retry an async operation with exponential backoff.
3276
+ * Used for Telegram API calls that may fail due to transient network issues.
3277
+ */
3278
+ private retryWithBackoff;
3279
+ /**
3280
+ * Register Telegram commands in the background with retries.
3281
+ * Non-critical — bot works fine without autocomplete commands.
3282
+ */
3283
+ private registerCommandsWithRetry;
1829
3284
  stop(): Promise<void>;
3285
+ private renderCommandResponse;
3286
+ private toCallbackData;
3287
+ private fromCallbackData;
1830
3288
  private setupRoutes;
1831
- private messageHandlers;
3289
+ /**
3290
+ * Per-session serial dispatch queues.
3291
+ * SessionBridge fires sendMessage() as fire-and-forget, so multiple events
3292
+ * (tool_call, tool_update, text) can arrive concurrently. Without serialization,
3293
+ * fast handlers (tool_update) overtake slow ones (tool_call with draftManager.finalize),
3294
+ * causing out-of-order processing where a tool's completion update is processed before
3295
+ * its creation event. This queue ensures events are processed in the order they arrive.
3296
+ */
3297
+ private _dispatchQueues;
3298
+ private getTracer;
1832
3299
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
3300
+ protected handleThought(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
3301
+ protected handleText(sessionId: string, content: OutgoingMessage): Promise<void>;
3302
+ protected handleToolCall(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
3303
+ protected handleToolUpdate(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
3304
+ protected handlePlan(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
3305
+ protected handleUsage(sessionId: string, content: OutgoingMessage, verbosity: DisplayVerbosity): Promise<void>;
3306
+ protected handleAttachment(sessionId: string, content: OutgoingMessage): Promise<void>;
3307
+ protected handleSessionEnd(sessionId: string, _content: OutgoingMessage): Promise<void>;
3308
+ protected handleError(sessionId: string, content: OutgoingMessage): Promise<void>;
3309
+ protected handleSystem(sessionId: string, content: OutgoingMessage): Promise<void>;
1833
3310
  sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
1834
3311
  sendNotification(notification: NotificationMessage): Promise<void>;
1835
3312
  createSessionThread(sessionId: string, name: string): Promise<string>;
1836
3313
  renameSessionThread(sessionId: string, newName: string): Promise<void>;
1837
3314
  deleteSessionThread(sessionId: string): Promise<void>;
1838
3315
  sendSkillCommands(sessionId: string, commands: AgentCommand[]): Promise<void>;
3316
+ /** Flush any skill commands that were queued before threadId was available */
3317
+ flushPendingSkillCommands(sessionId: string): Promise<void>;
1839
3318
  private resolveSessionId;
1840
3319
  private downloadTelegramFile;
1841
3320
  private handleIncomingMedia;
1842
3321
  cleanupSkillCommands(sessionId: string): Promise<void>;
3322
+ cleanupSessionState(sessionId: string): Promise<void>;
3323
+ stripTTSBlock(sessionId: string): Promise<void>;
1843
3324
  archiveSessionTopic(sessionId: string): Promise<void>;
1844
3325
  }
1845
3326
 
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 };
3327
+ 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 };