@openacp/cli 2026.408.4 → 2026.410.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as Attachment, O as OutgoingMessage, I as IChannelAdapter, N as NotificationMessage, a as AgentEvent, S as StopReason, P as PermissionRequest, U as UsageRecord, b as AgentCapabilities, c as AgentDefinition, M as McpServerConfig, d as SetConfigOptionValue, e as InstalledAgent, R as RegistryAgent, f as AgentListItem, g as AvailabilityResult, h as InstallProgress, i as InstallResult, j as SessionStatus, T as TurnContext, C as ConfigOption, k as AgentSwitchEntry, l as AgentCommand, m as TurnRouting, n as SessionRecord, o as UsageRecordEvent, p as IncomingMessage, D as DisplayVerbosity, q as ChannelConfig, r as AdapterCapabilities, s as ToolCallMeta, V as ViewerLinks, t as OutputMode, u as PlanEntry } from './channel-CKXNnTy4.js';
2
- export { v as AgentDistribution, w as AuthMethod, x as AuthenticateRequest, y as ChannelAdapter, z as ConfigSelectChoice, B as ConfigSelectGroup, E as ContentBlock, K as KIND_ICONS, F as ModelInfo, G as NewSessionResponse, H as PermissionOption, J as PromptResponse, L as RegistryBinaryTarget, Q as RegistryDistribution, W as STATUS_ICONS, X as SessionListItem, Y as SessionListResponse, Z as SessionMode, _ as SessionModeState, $ as SessionModelState, a0 as TelegramPlatformData, a1 as ToolUpdateMeta, a2 as createTurnContext, a3 as getEffectiveTarget, a4 as isSystemEvent } from './channel-CKXNnTy4.js';
1
+ import { A as Attachment, O as OutgoingMessage, I as IChannelAdapter, N as NotificationMessage, a as AgentEvent, S as StopReason, P as PermissionRequest, U as UsageRecord, b as AgentCapabilities, c as AgentDefinition, M as McpServerConfig, d as SetConfigOptionValue, e as InstalledAgent, R as RegistryAgent, f as AgentListItem, g as AvailabilityResult, h as InstallProgress, i as InstallResult, j as SessionStatus, T as TurnContext, C as ConfigOption, k as AgentSwitchEntry, l as AgentCommand, m as TurnRouting, n as SessionRecord, o as UsageRecordEvent, p as IncomingMessage, D as DisplayVerbosity, q as ChannelConfig, r as AdapterCapabilities, s as ToolCallMeta, V as ViewerLinks, t as OutputMode, u as PlanEntry } from './channel-CFMUPzvH.js';
2
+ export { v as AgentDistribution, w as AuthMethod, x as AuthenticateRequest, y as ChannelAdapter, z as ConfigSelectChoice, B as ConfigSelectGroup, E as ContentBlock, K as KIND_ICONS, F as ModelInfo, G as NewSessionResponse, H as PermissionOption, J as PromptResponse, L as RegistryBinaryTarget, Q as RegistryDistribution, W as STATUS_ICONS, X as SessionListItem, Y as SessionListResponse, Z as SessionMode, _ as SessionModeState, $ as SessionModelState, a0 as TelegramPlatformData, a1 as ToolUpdateMeta, a2 as createTurnContext, a3 as getEffectiveTarget, a4 as isSystemEvent } from './channel-CFMUPzvH.js';
3
3
  import pino from 'pino';
4
4
  import * as zod from 'zod';
5
5
  import { ZodSchema, z } from 'zod';
@@ -8,28 +8,91 @@ import { SetSessionConfigOptionResponse, ListSessionsResponse, LoadSessionRespon
8
8
  import { FastifyInstance, FastifyPluginAsync, preHandlerHookHandler, FastifyRequest, FastifyReply } from 'fastify';
9
9
  import * as http from 'node:http';
10
10
 
11
+ /** A CLI command the assistant can run, shown as a code block in the system prompt. */
11
12
  interface AssistantCommand {
12
13
  command: string;
13
14
  description: string;
14
15
  }
16
+ /**
17
+ * A section that injects live system state into the assistant's system prompt.
18
+ *
19
+ * Each section provides a `buildContext()` callback that is called at prompt
20
+ * composition time. Sections are sorted by priority (lower = earlier in prompt)
21
+ * so the most important context appears first.
22
+ */
15
23
  interface AssistantSection {
16
24
  id: string;
17
25
  title: string;
26
+ /** Lower priority = appears earlier in the system prompt. */
18
27
  priority: number;
28
+ /** Returns the section's context string, or null to skip this section entirely. */
19
29
  buildContext: () => string | null;
20
30
  commands?: AssistantCommand[];
21
31
  }
32
+ /**
33
+ * Registry that collects assistant prompt sections and composes them into
34
+ * a single system prompt.
35
+ *
36
+ * Core modules and plugins register sections (e.g. sessions, agents, config)
37
+ * that each contribute a fragment of live system state. At prompt build time,
38
+ * sections are sorted by priority, their `buildContext()` is called, and the
39
+ * results are assembled with the preamble and CLI guidelines.
40
+ */
22
41
  declare class AssistantRegistry {
23
42
  private sections;
24
43
  private _instanceRoot;
25
- /** Set the instance root path used in assistant guidelines */
44
+ /** Set the instance root path used in assistant guidelines. */
26
45
  setInstanceRoot(root: string): void;
46
+ /** Register a prompt section. Overwrites any existing section with the same id. */
27
47
  register(section: AssistantSection): void;
48
+ /** Remove a previously registered section by id. */
28
49
  unregister(id: string): void;
50
+ /**
51
+ * Compose the full system prompt from all registered sections.
52
+ *
53
+ * Sections are sorted by priority (ascending), each contributing a titled
54
+ * markdown block. If a section's `buildContext()` throws, it is skipped
55
+ * gracefully so one broken section doesn't break the entire prompt.
56
+ *
57
+ * If `channelId` is provided, a "Current Channel" block is injected at the
58
+ * top of the prompt so the assistant can adapt its behavior to the platform.
59
+ */
29
60
  buildSystemPrompt(channelId?: string): string;
30
61
  }
31
62
 
32
- type PluginPermission = 'events:read' | 'events:emit' | 'services:register' | 'services:use' | 'middleware:register' | 'commands:register' | 'storage:read' | 'storage:write' | 'kernel:access';
63
+ /**
64
+ * Permission tokens that gate access to PluginContext capabilities.
65
+ * Declared in a plugin's `permissions` array; enforced at runtime by PluginContext.
66
+ */
67
+ type PluginPermission =
68
+ /** Subscribe to EventBus events */
69
+ 'events:read'
70
+ /** Emit custom events on the EventBus */
71
+ | 'events:emit'
72
+ /** Register services in the ServiceRegistry */
73
+ | 'services:register'
74
+ /** Look up and consume services from the ServiceRegistry */
75
+ | 'services:use'
76
+ /** Register middleware handlers on hook points */
77
+ | 'middleware:register'
78
+ /** Register slash commands, menu items, assistant sections, and editable fields */
79
+ | 'commands:register'
80
+ /** Read from plugin-scoped storage */
81
+ | 'storage:read'
82
+ /** Write to plugin-scoped storage */
83
+ | 'storage:write'
84
+ /** Direct access to OpenACPCore internals (sessions, config, eventBus) */
85
+ | 'kernel:access';
86
+ /**
87
+ * The runtime plugin instance — the object a plugin module default-exports.
88
+ *
89
+ * This is distinct from `PluginEntry` (the registry's persisted metadata about
90
+ * an installed plugin). `OpenACPPlugin` defines behavior (setup/teardown hooks),
91
+ * while `PluginEntry` tracks install state (version, source, enabled flag).
92
+ *
93
+ * Lifecycle: LifecycleManager topo-sorts plugins by `pluginDependencies`,
94
+ * then calls `setup()` on each in order, passing a scoped `PluginContext`.
95
+ */
33
96
  interface OpenACPPlugin {
34
97
  /** Unique identifier, e.g., '@openacp/security' */
35
98
  name: string;
@@ -43,30 +106,48 @@ interface OpenACPPlugin {
43
106
  optionalPluginDependencies?: Record<string, string>;
44
107
  /** Override a built-in plugin (replaces it entirely) */
45
108
  overrides?: string;
46
- /** Required permissions — PluginContext enforces these */
109
+ /** Required permissions — PluginContext enforces these at runtime */
47
110
  permissions?: PluginPermission[];
48
- /** Called during startup in dependency order */
111
+ /** Called during startup in dependency order. 30s timeout. */
49
112
  setup(ctx: PluginContext): Promise<void>;
50
- /** Called during shutdown in reverse order. 10s timeout. */
113
+ /** Called during shutdown in reverse dependency order. 10s timeout. */
51
114
  teardown?(): Promise<void>;
115
+ /** Called once when the plugin is first installed via CLI */
52
116
  install?(ctx: InstallContext): Promise<void>;
117
+ /** Called when the plugin is removed via CLI. `purge` deletes data too. */
53
118
  uninstall?(ctx: InstallContext, opts: {
54
119
  purge: boolean;
55
120
  }): Promise<void>;
121
+ /** Interactive configuration via CLI (post-install) */
56
122
  configure?(ctx: InstallContext): Promise<void>;
123
+ /**
124
+ * Called at boot when the registry's stored version differs from the plugin's
125
+ * current version. Returns new settings to persist, or void to keep existing.
126
+ */
57
127
  migrate?(ctx: MigrateContext, oldSettings: unknown, oldVersion: string): Promise<unknown>;
128
+ /** Zod schema to validate settings before setup(). Validation failure skips the plugin. */
58
129
  settingsSchema?: zod.ZodSchema;
130
+ /** If true, the plugin is critical to core operation (informational flag) */
59
131
  essential?: boolean;
60
132
  /** Settings keys that can be copied when creating a new instance from this one */
61
133
  inheritableKeys?: string[];
62
134
  }
135
+ /**
136
+ * Per-plugin key-value storage backed by a JSON file on disk.
137
+ * Each plugin gets an isolated namespace at `~/.openacp/plugins/<name>/kv.json`.
138
+ */
63
139
  interface PluginStorage {
64
140
  get<T>(key: string): Promise<T | undefined>;
65
141
  set<T>(key: string, value: T): Promise<void>;
66
142
  delete(key: string): Promise<void>;
67
143
  list(): Promise<string[]>;
144
+ /** Returns the plugin's dedicated data directory, creating it if needed */
68
145
  getDataDir(): string;
69
146
  }
147
+ /**
148
+ * Typed API for reading and writing a plugin's settings.json file.
149
+ * Backed by SettingsManager; available in InstallContext and MigrateContext.
150
+ */
70
151
  interface SettingsAPI {
71
152
  get<T = unknown>(key: string): Promise<T | undefined>;
72
153
  set<T = unknown>(key: string, value: T): Promise<void>;
@@ -90,6 +171,11 @@ interface FieldDef {
90
171
  /** Valid values for "select" type */
91
172
  options?: string[];
92
173
  }
174
+ /**
175
+ * Interactive CLI primitives for plugin install/configure flows.
176
+ * Wraps @clack/prompts — only available during CLI operations (install, configure),
177
+ * NOT during normal runtime. Plugins use this in their `install()` and `configure()` hooks.
178
+ */
93
179
  interface TerminalIO {
94
180
  text(opts: {
95
181
  message: string;
@@ -137,6 +223,11 @@ interface TerminalIO {
137
223
  note(message: string, title?: string): void;
138
224
  cancel(message?: string): void;
139
225
  }
226
+ /**
227
+ * Context provided to plugin install/uninstall/configure hooks.
228
+ * Unlike PluginContext (runtime), InstallContext provides terminal I/O
229
+ * for interactive setup but no access to services, middleware, or events.
230
+ */
140
231
  interface InstallContext {
141
232
  pluginName: string;
142
233
  terminal: TerminalIO;
@@ -146,11 +237,19 @@ interface InstallContext {
146
237
  /** Root of the OpenACP instance directory (e.g. ~/.openacp) */
147
238
  instanceRoot?: string;
148
239
  }
240
+ /**
241
+ * Context provided to the `migrate()` hook at boot time when the plugin's
242
+ * current version differs from the version stored in the registry.
243
+ */
149
244
  interface MigrateContext {
150
245
  pluginName: string;
151
246
  settings: SettingsAPI;
152
247
  log: Logger$1;
153
248
  }
249
+ /**
250
+ * Possible response shapes from a command handler.
251
+ * Adapters render each type per-platform (e.g., Telegram inline keyboards for menus).
252
+ */
154
253
  type CommandResponse = {
155
254
  type: 'text';
156
255
  text: string;
@@ -170,9 +269,13 @@ type CommandResponse = {
170
269
  } | {
171
270
  type: 'error';
172
271
  message: string;
173
- } | {
272
+ }
273
+ /** Command handled successfully but produces no visible output */
274
+ | {
174
275
  type: 'silent';
175
- } | {
276
+ }
277
+ /** Command delegates further processing to another system (e.g., agent prompt) */
278
+ | {
176
279
  type: 'delegated';
177
280
  };
178
281
  interface MenuItem {
@@ -250,6 +353,7 @@ interface CoreAccess {
250
353
  sessionManager: SessionManager$1;
251
354
  adapters: Map<string, IChannelAdapter>;
252
355
  }
356
+ /** Pino-compatible logger interface used throughout the plugin system. */
253
357
  interface Logger$1 {
254
358
  trace(msg: string, ...args: unknown[]): void;
255
359
  debug(msg: string, ...args: unknown[]): void;
@@ -257,13 +361,32 @@ interface Logger$1 {
257
361
  warn(msg: string, ...args: unknown[]): void;
258
362
  error(msg: string, ...args: unknown[]): void;
259
363
  fatal(msg: string, ...args: unknown[]): void;
364
+ /** Create a child logger with additional context bindings (e.g., `{ plugin: name }`) */
260
365
  child(bindings: Record<string, unknown>): Logger$1;
261
366
  }
367
+ /**
368
+ * Scoped API surface given to each plugin during setup().
369
+ *
370
+ * Each plugin receives its own PluginContext instance with permission-gated
371
+ * access. This provides isolation: storage is namespaced per-plugin, logs are
372
+ * prefixed with the plugin name, and only declared permissions are allowed.
373
+ *
374
+ * Tier 1 (Events) — subscribe/emit on the shared EventBus.
375
+ * Tier 2 (Actions) — register services, middleware, commands.
376
+ * Tier 3 (Kernel) — direct access to core internals (requires kernel:access).
377
+ */
262
378
  interface PluginContext {
263
379
  pluginName: string;
264
380
  pluginConfig: Record<string, unknown>;
265
381
  /** Subscribe to events. Auto-cleaned on teardown. Requires 'events:read'. */
266
382
  on(event: string, handler: (...args: unknown[]) => void): void;
383
+ /**
384
+ * Unsubscribes a previously registered event handler.
385
+ *
386
+ * Called automatically for all listeners registered via `on()` during plugin teardown.
387
+ * Use manually only when conditional unsubscription is needed before teardown.
388
+ * Requires 'events:read' permission.
389
+ */
267
390
  off(event: string, handler: (...args: unknown[]) => void): void;
268
391
  /** Emit custom events. Event names MUST be prefixed with plugin name. Requires 'events:emit'. */
269
392
  emit(event: string, payload: unknown): void;
@@ -300,8 +423,11 @@ interface PluginContext {
300
423
  * → [HOOK: message:outgoing] → adapter.sendMessage()
301
424
  */
302
425
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
426
+ /** Direct access to SessionManager. Requires 'kernel:access'. */
303
427
  sessions: SessionManager$1;
428
+ /** Direct access to ConfigManager. Requires 'kernel:access'. */
304
429
  config: ConfigManager$1;
430
+ /** Direct access to EventBus. Requires 'kernel:access'. */
305
431
  eventBus: EventBus$1;
306
432
  /** Direct access to OpenACPCore instance. Requires 'kernel:access'. */
307
433
  core: unknown;
@@ -311,6 +437,14 @@ interface PluginContext {
311
437
  */
312
438
  instanceRoot: string;
313
439
  }
440
+ /**
441
+ * Maps each middleware hook name to its payload type.
442
+ *
443
+ * Middleware handlers receive the payload, can modify it, and call `next()` to pass
444
+ * it down the chain. Returning `null` short-circuits the chain (blocks the operation).
445
+ * There are 19 hook points covering message flow, agent lifecycle, file system,
446
+ * terminal, permissions, sessions, config changes, and agent switching.
447
+ */
314
448
  interface MiddlewarePayloadMap {
315
449
  'message:incoming': {
316
450
  channelId: string;
@@ -420,7 +554,14 @@ interface MiddlewarePayloadMap {
420
554
  resumed: boolean;
421
555
  };
422
556
  }
557
+ /** Union of all valid middleware hook names */
423
558
  type MiddlewareHook = keyof MiddlewarePayloadMap;
559
+ /**
560
+ * Middleware handler signature. Receives the current payload and a `next` function.
561
+ * - Call `next()` to continue the chain (optionally with a modified payload).
562
+ * - Return the (possibly modified) payload to pass it upstream.
563
+ * - Return `null` to short-circuit — the operation is blocked entirely.
564
+ */
424
565
  type MiddlewareFn<T> = (payload: T, next: () => Promise<T>) => Promise<T | null>;
425
566
  interface MiddlewareOptions<T> {
426
567
  /** Override execution order within same dependency level. Lower = earlier. */
@@ -509,22 +650,38 @@ interface TunnelServiceInterface {
509
650
  outputUrl(entryId: string): string;
510
651
  }
511
652
 
653
+ /** Result of validating plugin settings against a Zod schema. */
512
654
  interface ValidationResult {
513
655
  valid: boolean;
514
656
  errors?: string[];
515
657
  }
658
+ /**
659
+ * Manages per-plugin settings files.
660
+ *
661
+ * Each plugin's settings are stored at `<basePath>/<pluginName>/settings.json`.
662
+ * The basePath is typically `~/.openacp/plugins/`.
663
+ * Settings are distinct from plugin storage (kv.json) — settings are user-facing
664
+ * configuration, while storage is internal plugin state.
665
+ */
516
666
  declare class SettingsManager {
517
667
  private basePath;
518
668
  constructor(basePath: string);
669
+ /** Returns the base path for all plugin settings directories. */
519
670
  getBasePath(): string;
671
+ /** Create a SettingsAPI instance scoped to a specific plugin. */
520
672
  createAPI(pluginName: string): SettingsAPI;
673
+ /** Load a plugin's settings from disk. Returns empty object if file doesn't exist. */
521
674
  loadSettings(pluginName: string): Promise<Record<string, unknown>>;
675
+ /** Validate settings against a Zod schema. Returns valid if no schema is provided. */
522
676
  validateSettings(_pluginName: string, settings: unknown, schema?: ZodSchema): ValidationResult;
677
+ /** Resolve the absolute path to a plugin's settings.json file. */
523
678
  getSettingsPath(pluginName: string): string;
524
679
  getPluginSettings(pluginName: string): Promise<Record<string, unknown>>;
680
+ /** Merge updates into existing settings (shallow merge). */
525
681
  updatePluginSettings(pluginName: string, updates: Record<string, unknown>): Promise<void>;
526
682
  }
527
683
 
684
+ /** Log rotation and verbosity settings. */
528
685
  declare const LoggingSchema: z.ZodDefault<z.ZodObject<{
529
686
  level: z.ZodDefault<z.ZodEnum<["silent", "debug", "info", "warn", "error", "fatal"]>>;
530
687
  logDir: z.ZodDefault<z.ZodString>;
@@ -544,12 +701,26 @@ declare const LoggingSchema: z.ZodDefault<z.ZodObject<{
544
701
  maxFiles?: number | undefined;
545
702
  sessionLogRetentionDays?: number | undefined;
546
703
  }>>;
704
+ /** Runtime logging configuration. Controls per-module log levels and output destinations. */
547
705
  type LoggingConfig = z.infer<typeof LoggingSchema>;
706
+ /**
707
+ * Zod schema for the global OpenACP config file (`~/.openacp/config.json`).
708
+ *
709
+ * Every field uses `.default()` or `.optional()` so that config files from older
710
+ * versions — which lack newly added fields — still pass validation without error.
711
+ * This is critical for backward compatibility: users should never have to manually
712
+ * edit their config after upgrading.
713
+ *
714
+ * Plugin-specific settings live separately in per-plugin settings files
715
+ * (`~/.openacp/plugins/<name>/settings.json`), not here. This schema only
716
+ * covers global, cross-cutting concerns.
717
+ */
548
718
  declare const ConfigSchema: z.ZodObject<{
719
+ /** Instance UUID, written once at creation time. */
720
+ id: z.ZodOptional<z.ZodString>;
549
721
  instanceName: z.ZodOptional<z.ZodString>;
550
722
  defaultAgent: z.ZodString;
551
723
  workspace: z.ZodDefault<z.ZodObject<{
552
- baseDir: z.ZodDefault<z.ZodString>;
553
724
  allowExternalWorkspaces: z.ZodDefault<z.ZodBoolean>;
554
725
  security: z.ZodDefault<z.ZodObject<{
555
726
  allowedPaths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
@@ -562,14 +733,12 @@ declare const ConfigSchema: z.ZodObject<{
562
733
  envWhitelist?: string[] | undefined;
563
734
  }>>;
564
735
  }, "strip", z.ZodTypeAny, {
565
- baseDir: string;
566
736
  allowExternalWorkspaces: boolean;
567
737
  security: {
568
738
  allowedPaths: string[];
569
739
  envWhitelist: string[];
570
740
  };
571
741
  }, {
572
- baseDir?: string | undefined;
573
742
  allowExternalWorkspaces?: boolean | undefined;
574
743
  security?: {
575
744
  allowedPaths?: string[] | undefined;
@@ -625,7 +794,6 @@ declare const ConfigSchema: z.ZodObject<{
625
794
  }, "strip", z.ZodTypeAny, {
626
795
  defaultAgent: string;
627
796
  workspace: {
628
- baseDir: string;
629
797
  allowExternalWorkspaces: boolean;
630
798
  security: {
631
799
  allowedPaths: string[];
@@ -652,12 +820,13 @@ declare const ConfigSchema: z.ZodObject<{
652
820
  labelHistory: boolean;
653
821
  };
654
822
  instanceName?: string | undefined;
823
+ id?: string | undefined;
655
824
  outputMode?: "low" | "medium" | "high" | undefined;
656
825
  }, {
657
826
  defaultAgent: string;
658
827
  instanceName?: string | undefined;
828
+ id?: string | undefined;
659
829
  workspace?: {
660
- baseDir?: string | undefined;
661
830
  allowExternalWorkspaces?: boolean | undefined;
662
831
  security?: {
663
832
  allowedPaths?: string[] | undefined;
@@ -685,27 +854,72 @@ declare const ConfigSchema: z.ZodObject<{
685
854
  labelHistory?: boolean | undefined;
686
855
  } | undefined;
687
856
  }>;
857
+ /** Validated config object used throughout the codebase. Always obtained via `ConfigManager.get()` to ensure it's up-to-date. */
688
858
  type Config = z.infer<typeof ConfigSchema>;
859
+ /** Expands a leading `~` to the user's home directory. Returns the path unchanged if no `~` prefix. */
689
860
  declare function expandHome(p: string): string;
861
+ /**
862
+ * Manages loading, validating, and persisting the global config file.
863
+ *
864
+ * The load cycle is: read JSON -> apply migrations -> apply env overrides -> validate with Zod.
865
+ * Emits `config:changed` events when individual fields are updated, enabling
866
+ * hot-reload for fields marked as `hotReload` in the config registry.
867
+ */
690
868
  declare class ConfigManager extends EventEmitter {
691
869
  private config;
692
870
  private configPath;
693
871
  constructor(configPath?: string);
872
+ /**
873
+ * Loads config from disk through the full validation pipeline:
874
+ * 1. Create default config if missing (first run)
875
+ * 2. Apply migrations for older config formats
876
+ * 3. Apply environment variable overrides
877
+ * 4. Validate against Zod schema — exits on failure
878
+ */
694
879
  load(): Promise<void>;
880
+ /** Returns a deep clone of the current config to prevent external mutation. */
695
881
  get(): Config;
882
+ /**
883
+ * Merges partial updates into the config file using atomic write (write tmp + rename).
884
+ *
885
+ * Validates the merged result before writing. If `changePath` is provided,
886
+ * emits a `config:changed` event with old and new values for that path,
887
+ * enabling hot-reload without restart.
888
+ */
696
889
  save(updates: Record<string, unknown>, changePath?: string): Promise<void>;
697
890
  /**
698
- * Set a single config value by dot-path (e.g. "logging.level").
699
- * Builds the nested update object, validates, and saves.
700
- * Throws if the path contains blocked keys or the value fails Zod validation.
891
+ * Convenience wrapper for updating a single deeply-nested config field
892
+ * without constructing the full update object manually.
893
+ *
894
+ * Accepts a dot-path (e.g. "logging.level") and builds the nested
895
+ * update object internally before delegating to `save()`.
896
+ * Throws if the path contains prototype-pollution keys.
701
897
  */
702
898
  setPath(dotPath: string, value: unknown): Promise<void>;
899
+ /**
900
+ * Resolves a workspace path from user input.
901
+ *
902
+ * Supports three forms: no input (returns base dir), absolute/tilde paths
903
+ * (validated against allowExternalWorkspaces), and named workspaces
904
+ * (alphanumeric subdirectories under the base).
905
+ */
703
906
  resolveWorkspace(input?: string): string;
907
+ /** Checks whether the config file exists on disk. Wraps synchronous `fs.existsSync` behind an async interface for consistency with the rest of the ConfigManager API. */
704
908
  exists(): Promise<boolean>;
909
+ /** Returns the resolved path to the config JSON file. */
705
910
  getConfigPath(): string;
911
+ /** Writes a complete config object to disk, creating the directory if needed. Used during initial setup. */
706
912
  writeNew(config: Config): Promise<void>;
913
+ /**
914
+ * Applies `OPENACP_*` environment variables as overrides to per-plugin settings.
915
+ *
916
+ * This lets users configure plugin values (bot tokens, ports, etc.) via env vars
917
+ * without editing settings files — useful for Docker, CI, and headless setups.
918
+ */
707
919
  applyEnvToPluginSettings(settingsManager: SettingsManager): Promise<void>;
920
+ /** Applies env var overrides to the raw config object before Zod validation. */
708
921
  private applyEnvOverrides;
922
+ /** Recursively merges source into target, skipping prototype-pollution keys. */
709
923
  private deepMerge;
710
924
  }
711
925
 
@@ -718,30 +932,94 @@ declare const log: {
718
932
  fatal: (...args: unknown[]) => void;
719
933
  child: (bindings: pino.Bindings) => pino.Logger<never, boolean>;
720
934
  };
935
+ /**
936
+ * Initialize the root logger with file + console output.
937
+ *
938
+ * Sets up a multi-target pino transport: pino-pretty to stderr (for humans)
939
+ * and pino-roll to a rotating log file (for persistence). Also creates
940
+ * the sessions/ subdirectory for per-session log files.
941
+ *
942
+ * Safe to call multiple times — subsequent calls are no-ops.
943
+ */
721
944
  declare function initLogger(config: LoggingConfig): Logger;
722
945
  /** Change log level at runtime. Pino transport targets respect parent level changes automatically. */
723
946
  declare function setLogLevel(level: string): void;
947
+ /**
948
+ * Create a child logger scoped to a module (e.g., "core", "session", "telegram").
949
+ *
950
+ * Returns a Proxy that always delegates to the current rootLogger — this
951
+ * ensures child loggers created at module-level (before initLogger runs)
952
+ * pick up the initialized logger with proper transports once it's ready.
953
+ */
724
954
  declare function createChildLogger(context: {
725
955
  module: string;
726
956
  [key: string]: unknown;
727
957
  }): Logger;
958
+ /**
959
+ * Create a per-session logger that writes to both the combined log and
960
+ * a session-specific file (`<logDir>/sessions/<sessionId>.log`).
961
+ *
962
+ * This dual-write pattern allows operators to view all logs in one place
963
+ * (combined log) while also being able to inspect a single session's
964
+ * activity in isolation (session file).
965
+ *
966
+ * Falls back to a simple child logger if the session log file can't be created.
967
+ */
728
968
  declare function createSessionLogger(sessionId: string, parentLogger: Logger): Logger;
969
+ /**
970
+ * Flush and close the root logger transport.
971
+ *
972
+ * Resets all state so the logger can be re-initialized (e.g., after restart).
973
+ * Waits up to 3 seconds for the transport to close gracefully.
974
+ */
729
975
  declare function shutdownLogger(): Promise<void>;
976
+ /**
977
+ * Delete session log files older than the given retention period.
978
+ *
979
+ * Called during startup to prevent unbounded disk usage from accumulated
980
+ * per-session log files.
981
+ */
730
982
  declare function cleanupOldSessionLogs(retentionDays: number): Promise<void>;
731
983
 
984
+ /**
985
+ * Wrap a Node.js WritableStream as a Web API WritableStream.
986
+ *
987
+ * Handles backpressure by waiting for the 'drain' event when the Node
988
+ * stream's internal buffer is full.
989
+ */
732
990
  declare function nodeToWebWritable(nodeStream: NodeJS.WritableStream): WritableStream<Uint8Array>;
991
+ /**
992
+ * Wrap a Node.js ReadableStream as a Web API ReadableStream.
993
+ *
994
+ * Converts Node Buffer chunks to Uint8Array for Web Streams compatibility.
995
+ */
733
996
  declare function nodeToWebReadable(nodeStream: NodeJS.ReadableStream): ReadableStream<Uint8Array>;
734
997
 
998
+ /**
999
+ * Rolling buffer that captures the last N lines of an agent subprocess's stderr.
1000
+ *
1001
+ * Agent stdout carries the ACP protocol (JSON-RPC); stderr is used for
1002
+ * debug/diagnostic output. This capture provides context when the agent
1003
+ * crashes or errors — the last lines of stderr are included in error
1004
+ * messages shown to the user.
1005
+ */
735
1006
  declare class StderrCapture {
736
1007
  private maxLines;
737
1008
  private lines;
738
1009
  constructor(maxLines?: number);
1010
+ /** Append a chunk of stderr output, splitting on newlines and trimming to maxLines. */
739
1011
  append(chunk: string): void;
1012
+ /** Return all captured lines joined as a single string. */
740
1013
  getLastLines(): string;
741
1014
  }
742
1015
 
743
1016
  /**
744
- * A minimal, generic typed event emitter.
1017
+ * Type-safe event emitter where the event map is enforced at compile time.
1018
+ *
1019
+ * Unlike Node's EventEmitter, event names and listener signatures are
1020
+ * validated by TypeScript — no stringly-typed events. Supports pause/resume
1021
+ * with buffering, used by Session and EventBus to defer events during
1022
+ * initialization or agent switches.
745
1023
  *
746
1024
  * Usage:
747
1025
  * interface MyEvents {
@@ -757,8 +1035,16 @@ declare class TypedEmitter<T extends Record<string & keyof T, (...args: any[]) =
757
1035
  private listeners;
758
1036
  private paused;
759
1037
  private buffer;
1038
+ /** Register a listener for the given event. Returns `this` for chaining. */
760
1039
  on<K extends keyof T>(event: K, listener: T[K]): this;
1040
+ /** Remove a specific listener for the given event. */
761
1041
  off<K extends keyof T>(event: K, listener: T[K]): this;
1042
+ /**
1043
+ * Emit an event to all registered listeners.
1044
+ *
1045
+ * When paused, events are buffered (up to MAX_BUFFER_SIZE) unless
1046
+ * the passthrough filter allows them through immediately.
1047
+ */
762
1048
  emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): void;
763
1049
  /**
764
1050
  * Pause event delivery. Events emitted while paused are buffered.
@@ -772,49 +1058,99 @@ declare class TypedEmitter<T extends Record<string & keyof T, (...args: any[]) =
772
1058
  clearBuffer(): void;
773
1059
  get isPaused(): boolean;
774
1060
  get bufferSize(): number;
1061
+ /** Remove all listeners for a specific event, or all events if none specified. */
775
1062
  removeAllListeners(event?: keyof T): void;
1063
+ /** Deliver an event to listeners, isolating errors so one broken listener doesn't break others. */
776
1064
  private deliver;
777
1065
  }
778
1066
 
1067
+ /** Configuration for the sliding-window error budget. */
779
1068
  interface ErrorBudgetConfig {
1069
+ /** Maximum errors allowed within the window before disabling the plugin. Default: 10. */
780
1070
  maxErrors: number;
1071
+ /** Sliding window duration in milliseconds. Default: 3600000 (1 hour). */
781
1072
  windowMs: number;
782
1073
  }
1074
+ /**
1075
+ * Circuit breaker for misbehaving plugins.
1076
+ *
1077
+ * Tracks errors per plugin within a sliding time window. When a plugin exceeds
1078
+ * its error budget (default: 10 errors in 1 hour), it is auto-disabled —
1079
+ * its middleware handlers are skipped by MiddlewareChain. This prevents a
1080
+ * single broken plugin from degrading the entire system.
1081
+ *
1082
+ * Essential plugins can be marked exempt via `setExempt()`.
1083
+ */
783
1084
  declare class ErrorTracker {
784
1085
  private errors;
785
1086
  private disabled;
786
1087
  private exempt;
787
1088
  private config;
1089
+ /** Callback fired when a plugin is auto-disabled due to error budget exhaustion. */
788
1090
  onDisabled?: (pluginName: string, reason: string) => void;
789
1091
  constructor(config?: Partial<ErrorBudgetConfig>);
1092
+ /**
1093
+ * Record an error for a plugin. If the error budget is exceeded,
1094
+ * the plugin is disabled and the `onDisabled` callback fires.
1095
+ */
790
1096
  increment(pluginName: string): void;
1097
+ /** Check if a plugin has been disabled due to errors. */
791
1098
  isDisabled(pluginName: string): boolean;
1099
+ /** Re-enable a plugin and clear its error history. */
792
1100
  reset(pluginName: string): void;
1101
+ /** Mark a plugin as exempt from circuit-breaking (e.g., essential plugins). */
793
1102
  setExempt(pluginName: string): void;
794
1103
  }
795
1104
 
1105
+ /**
1106
+ * Manages ordered middleware chains for each hook point.
1107
+ *
1108
+ * Execution model:
1109
+ * - Handlers run in priority order (lower number = earlier). Default priority: 100.
1110
+ * - Each handler receives the current payload and a `next()` function.
1111
+ * - Calling `next()` passes control to the next handler in the chain.
1112
+ * - Returning `null` short-circuits: the operation is blocked and no further handlers run.
1113
+ * - If a handler throws or times out, it is skipped and the error is tracked.
1114
+ * After enough errors (see ErrorTracker), the plugin's middleware is auto-disabled.
1115
+ */
796
1116
  declare class MiddlewareChain {
797
1117
  private chains;
798
1118
  private errorHandler?;
799
1119
  private errorTracker?;
1120
+ /** Register a middleware handler for a hook. Handlers are kept sorted by priority. */
800
1121
  add(hook: string, pluginName: string, opts: {
801
1122
  priority?: number;
802
1123
  handler: Function;
803
1124
  }): void;
1125
+ /**
1126
+ * Execute the middleware chain for a hook, ending with the core handler.
1127
+ *
1128
+ * The chain is built recursively: each handler calls `next()` to invoke the
1129
+ * next handler, with the core handler at the end. If no middleware is registered,
1130
+ * the core handler runs directly.
1131
+ *
1132
+ * @returns The final payload, or `null` if any handler short-circuited.
1133
+ */
804
1134
  execute<T>(hook: string, payload: T, coreHandler: (p: T) => T | Promise<T>): Promise<T | null>;
1135
+ /** Remove all middleware handlers registered by a specific plugin. */
805
1136
  removeAll(pluginName: string): void;
1137
+ /** Set a callback for middleware errors (e.g., logging). */
806
1138
  setErrorHandler(fn: (pluginName: string, error: Error) => void): void;
1139
+ /** Attach an ErrorTracker for circuit-breaking misbehaving plugins. */
807
1140
  setErrorTracker(tracker: ErrorTracker): void;
808
1141
  }
809
1142
 
810
1143
  type TraceLayer = "acp" | "core" | "telegram";
811
1144
  /**
812
- * Per-session debug trace logger. Writes JSONL files to <workingDirectory>/.log/.
813
- * Only active when OPENACP_DEBUG=true. Zero overhead when disabled.
1145
+ * Per-session debug trace logger that writes JSONL files to `<workingDirectory>/.log/`.
1146
+ *
1147
+ * Only active when `OPENACP_DEBUG=true` is set in the environment.
1148
+ * Each trace layer (acp, core, telegram) gets its own file, making it easy
1149
+ * to inspect protocol-level, core-level, or adapter-level events separately.
814
1150
  *
815
- * Note: Uses appendFileSync for simplicity. This blocks the event loop briefly per write,
816
- * which is acceptable for a debug-only tool. The DEBUG_ENABLED guard ensures zero overhead
817
- * in production.
1151
+ * Uses appendFileSync for simplicity this blocks the event loop briefly per write,
1152
+ * which is acceptable for a debug-only tool. The DEBUG_ENABLED guard ensures zero
1153
+ * overhead in production.
818
1154
  */
819
1155
  declare class DebugTracer {
820
1156
  private sessionId;
@@ -822,21 +1158,45 @@ declare class DebugTracer {
822
1158
  private dirCreated;
823
1159
  private logDir;
824
1160
  constructor(sessionId: string, workingDirectory: string);
1161
+ /**
1162
+ * Write a timestamped JSONL entry to the trace file for the given layer.
1163
+ *
1164
+ * Handles circular references gracefully and silently swallows errors —
1165
+ * debug logging must never crash the application.
1166
+ */
825
1167
  log(layer: TraceLayer, data: Record<string, unknown>): void;
826
1168
  /** No-op cleanup — establishes the pattern for future async implementations */
827
1169
  destroy(): void;
828
1170
  }
829
1171
 
1172
+ /** Events emitted by AgentInstance — consumed by Session to relay to adapters. */
830
1173
  interface AgentInstanceEvents {
831
1174
  agent_event: (event: AgentEvent) => void;
832
1175
  }
1176
+ /**
1177
+ * Manages an ACP agent subprocess and implements the ACP Client interface.
1178
+ *
1179
+ * Each AgentInstance owns exactly one child process. It handles:
1180
+ * - Subprocess spawning with filtered environment and path guarding
1181
+ * - ACP protocol handshake (initialize → newSession/loadSession)
1182
+ * - Translating ACP session updates into internal AgentEvent types
1183
+ * - File I/O and terminal operations requested by the agent
1184
+ * - Permission request proxying (agent → Session → adapter → user → agent)
1185
+ * - Graceful shutdown (SIGTERM with SIGKILL fallback)
1186
+ *
1187
+ * Session wraps this class to add prompt queuing and lifecycle management.
1188
+ */
833
1189
  declare class AgentInstance extends TypedEmitter<AgentInstanceEvents> {
834
1190
  private connection;
835
1191
  private child;
836
1192
  private stderrCapture;
1193
+ /** Manages terminal subprocesses that agents can spawn for shell commands. */
837
1194
  private terminalManager;
1195
+ /** Shared across all instances — resolves MCP server configs for ACP sessions. */
838
1196
  private static mcpManager;
1197
+ /** Guards against emitting crash events during intentional shutdown. */
839
1198
  private _destroying;
1199
+ /** Restricts agent file I/O to the workspace directory and explicitly allowed paths. */
840
1200
  private pathGuard;
841
1201
  sessionId: string;
842
1202
  agentName: string;
@@ -853,30 +1213,132 @@ declare class AgentInstance extends TypedEmitter<AgentInstanceEvents> {
853
1213
  };
854
1214
  middlewareChain?: MiddlewareChain;
855
1215
  debugTracer: DebugTracer | null;
856
- /** Allow external callers (e.g. SessionFactory) to whitelist additional read paths */
1216
+ /**
1217
+ * Whitelist an additional filesystem path for agent read access.
1218
+ *
1219
+ * Called by SessionFactory to allow agents to read files outside the
1220
+ * workspace (e.g., the file-service upload directory for attachments).
1221
+ */
857
1222
  addAllowedPath(p: string): void;
858
1223
  onPermissionRequest: (request: PermissionRequest) => Promise<string>;
859
1224
  private constructor();
1225
+ /**
1226
+ * Spawn the agent child process and complete the ACP protocol handshake.
1227
+ *
1228
+ * Steps:
1229
+ * 1. Resolve the agent command to a directly executable path
1230
+ * 2. Create a PathGuard scoped to the working directory
1231
+ * 3. Spawn the subprocess with a filtered environment (security: only whitelisted
1232
+ * env vars are passed to prevent leaking secrets like API keys)
1233
+ * 4. Wire stdin/stdout through debug-tracing Transform streams
1234
+ * 5. Convert Node streams → Web streams for the ACP SDK
1235
+ * 6. Perform the ACP `initialize` handshake and negotiate capabilities
1236
+ *
1237
+ * Does NOT create a session — callers must follow up with newSession or loadSession.
1238
+ */
860
1239
  private static spawnSubprocess;
1240
+ /**
1241
+ * Monitor the subprocess for unexpected exits and emit error events.
1242
+ *
1243
+ * Distinguishes intentional shutdown (SIGTERM/SIGINT during destroy) from
1244
+ * crashes (non-zero exit code or unexpected signal). Crash events include
1245
+ * captured stderr output for diagnostic context.
1246
+ */
861
1247
  private setupCrashDetection;
1248
+ /**
1249
+ * Spawn a new agent subprocess and create a fresh ACP session.
1250
+ *
1251
+ * This is the primary entry point for starting an agent. It spawns the
1252
+ * subprocess, completes the ACP handshake, and calls `newSession` to
1253
+ * initialize the agent's working context (cwd, MCP servers).
1254
+ *
1255
+ * @param agentDef - Agent definition (command, args, env) from the catalog
1256
+ * @param workingDirectory - Workspace root the agent operates in
1257
+ * @param mcpServers - Optional MCP server configs to extend agent capabilities
1258
+ * @param allowedPaths - Extra filesystem paths the agent may access
1259
+ */
862
1260
  static spawn(agentDef: AgentDefinition, workingDirectory: string, mcpServers?: McpServerConfig[], allowedPaths?: string[]): Promise<AgentInstance>;
1261
+ /**
1262
+ * Spawn a new subprocess and restore an existing agent session.
1263
+ *
1264
+ * Tries loadSession first (preferred, stable API), falls back to the
1265
+ * unstable resumeSession, and finally falls back to creating a brand-new
1266
+ * session if resume fails entirely (e.g., agent lost its state).
1267
+ *
1268
+ * @param agentSessionId - The agent-side session ID to restore
1269
+ */
863
1270
  static resume(agentDef: AgentDefinition, workingDirectory: string, agentSessionId: string, mcpServers?: McpServerConfig[], allowedPaths?: string[]): Promise<AgentInstance>;
1271
+ /**
1272
+ * Build the ACP Client callback object.
1273
+ *
1274
+ * The ACP SDK invokes these callbacks when the agent sends notifications
1275
+ * or requests. Each callback maps an ACP protocol message to either:
1276
+ * - An internal AgentEvent (emitted for Session/adapters to consume)
1277
+ * - A filesystem or terminal operation (executed on the agent's behalf)
1278
+ * - A permission request (proxied to the user via the adapter)
1279
+ */
864
1280
  private createClient;
1281
+ /**
1282
+ * Update a session config option (mode, model, etc.) on the agent.
1283
+ *
1284
+ * Falls back to legacy `setSessionMode`/`unstable_setSessionModel` methods
1285
+ * for agents that haven't adopted the unified `session/set_config_option`
1286
+ * ACP method (detected via JSON-RPC -32601 "Method Not Found" error).
1287
+ */
865
1288
  setConfigOption(configId: string, value: SetConfigOptionValue): Promise<SetSessionConfigOptionResponse>;
1289
+ /** List the agent's known sessions, optionally filtered by working directory. */
866
1290
  listSessions(cwd?: string, cursor?: string): Promise<ListSessionsResponse>;
1291
+ /** Load an existing agent session by ID into this subprocess. */
867
1292
  loadSession(sessionId: string, cwd: string, mcpServers?: McpServerConfig[]): Promise<LoadSessionResponse>;
1293
+ /** Trigger agent-managed authentication (e.g., OAuth flow). */
868
1294
  authenticate(methodId: string): Promise<void>;
1295
+ /** Fork an existing session, creating a new branch with shared history. */
869
1296
  forkSession(sessionId: string, cwd: string, mcpServers?: McpServerConfig[]): Promise<ForkSessionResponse>;
1297
+ /** Close a session on the agent side (cleanup agent-internal state). */
870
1298
  closeSession(sessionId: string): Promise<void>;
1299
+ /**
1300
+ * Send a user prompt to the agent and wait for the complete response.
1301
+ *
1302
+ * Builds ACP content blocks from the text and any attachments (images
1303
+ * are base64-encoded if the agent supports them, otherwise appended as
1304
+ * file paths). The promise resolves when the agent finishes responding;
1305
+ * streaming events arrive via the `agent_event` emitter during execution.
1306
+ *
1307
+ * Attachments that exceed size limits or use unsupported formats are
1308
+ * skipped with a note appended to the prompt text.
1309
+ *
1310
+ * Call `cancel()` to interrupt a running prompt; the agent will stop and
1311
+ * the promise resolves with partial results.
1312
+ */
871
1313
  prompt(text: string, attachments?: Attachment[]): Promise<PromptResponse>;
1314
+ /** Cancel the currently running prompt. The agent should stop and return partial results. */
872
1315
  cancel(): Promise<void>;
1316
+ /**
1317
+ * Gracefully shut down the agent subprocess.
1318
+ *
1319
+ * Sends SIGTERM first, giving the agent up to 10 seconds to clean up.
1320
+ * If the process hasn't exited by then, SIGKILL forces termination.
1321
+ * The timer is unref'd so it doesn't keep the Node process alive
1322
+ * during shutdown.
1323
+ */
873
1324
  destroy(): Promise<void>;
874
1325
  }
875
1326
 
1327
+ /**
1328
+ * Persistent storage for installed agent definitions.
1329
+ *
1330
+ * Agents are stored in `agents.json` (typically `~/.openacp/agents.json`).
1331
+ * The file is validated with Zod on load; corrupted or invalid data is
1332
+ * discarded gracefully with a warning. Writes use atomic rename to
1333
+ * prevent partial writes from corrupting the file.
1334
+ */
1335
+
1336
+ /** JSON-backed store for installed agent definitions (`agents.json`). */
876
1337
  declare class AgentStore {
877
1338
  private data;
878
1339
  readonly filePath: string;
879
- constructor(filePath?: string);
1340
+ constructor(filePath: string);
1341
+ /** Load and validate the store from disk. Starts fresh if file is missing or invalid. */
880
1342
  load(): void;
881
1343
  exists(): boolean;
882
1344
  getInstalled(): Record<string, InstalledAgent>;
@@ -884,27 +1346,69 @@ declare class AgentStore {
884
1346
  addAgent(key: string, agent: InstalledAgent): void;
885
1347
  removeAgent(key: string): void;
886
1348
  hasAgent(key: string): boolean;
1349
+ /**
1350
+ * Persist the store to disk using atomic write (write to .tmp, then rename).
1351
+ * File permissions are restricted to owner-only (0o600) since the store
1352
+ * may contain agent binary paths and environment variables.
1353
+ */
887
1354
  private save;
888
1355
  }
889
1356
 
1357
+ /**
1358
+ * Central catalog of available and installed agents.
1359
+ *
1360
+ * Combines two data sources:
1361
+ * 1. **Registry** — the remote ACP agent registry (CDN-hosted JSON), cached
1362
+ * locally with a 24-hour TTL and a bundled snapshot as fallback.
1363
+ * 2. **Store** — locally installed agents persisted in `agents.json`.
1364
+ *
1365
+ * Provides discovery (list all agents), installation, uninstallation,
1366
+ * and resolution (name → AgentDefinition for spawning).
1367
+ */
890
1368
  declare class AgentCatalog {
891
1369
  private store;
892
- private globalStore;
1370
+ /** Agents available in the remote registry (cached in memory after load). */
893
1371
  private registryAgents;
894
1372
  private cachePath;
1373
+ /** Directory where binary agent archives are extracted to. */
895
1374
  private agentsDir;
896
- constructor(store?: AgentStore, cachePath?: string, agentsDir?: string);
1375
+ constructor(store: AgentStore, cachePath: string, agentsDir?: string);
1376
+ /**
1377
+ * Load installed agents from disk and hydrate the registry from cache/snapshot.
1378
+ *
1379
+ * Also enriches installed agents with registry metadata — fixes agents that
1380
+ * were migrated from older config formats with incomplete data.
1381
+ */
897
1382
  load(): void;
1383
+ /** Fetch the latest agent registry from the CDN and update the local cache. */
898
1384
  fetchRegistry(): Promise<void>;
1385
+ /** Re-fetch registry only if the local cache has expired (24-hour TTL). */
899
1386
  refreshRegistryIfStale(): Promise<void>;
900
1387
  getRegistryAgents(): RegistryAgent[];
901
1388
  getRegistryAgent(registryId: string): RegistryAgent | undefined;
1389
+ /** Find a registry agent by registry ID or by its short alias (e.g., "claude"). */
902
1390
  findRegistryAgent(keyOrId: string): RegistryAgent | undefined;
903
1391
  getInstalled(): InstalledAgent[];
904
1392
  getInstalledEntries(): Record<string, InstalledAgent>;
905
1393
  getInstalledAgent(key: string): InstalledAgent | undefined;
1394
+ /**
1395
+ * Build the unified list of all agents (installed + registry-only).
1396
+ *
1397
+ * Installed agents appear first with their live availability status.
1398
+ * Registry agents that aren't installed yet show whether a distribution
1399
+ * exists for the current platform. Missing external dependencies
1400
+ * (e.g., claude CLI) are surfaced as `missingDeps` for UI display
1401
+ * but do NOT block installation.
1402
+ */
906
1403
  getAvailable(): AgentListItem[];
1404
+ /** Check if an agent can be installed on this system (platform + dependencies). */
907
1405
  checkAvailability(keyOrId: string): AvailabilityResult;
1406
+ /**
1407
+ * Install an agent from the registry.
1408
+ *
1409
+ * Resolves the distribution (npx/uvx/binary), downloads binary archives
1410
+ * if needed, and persists the agent definition in the store.
1411
+ */
908
1412
  install(keyOrId: string, progress?: InstallProgress, force?: boolean): Promise<InstallResult>;
909
1413
  /**
910
1414
  * Register an agent directly into the catalog store without going through
@@ -912,10 +1416,12 @@ declare class AgentCatalog {
912
1416
  * when their CLI dependency is not yet installed.
913
1417
  */
914
1418
  registerFallbackAgent(key: string, data: InstalledAgent): void;
1419
+ /** Remove an installed agent and delete its binary directory if applicable. */
915
1420
  uninstall(key: string): Promise<{
916
1421
  ok: boolean;
917
1422
  error?: string;
918
1423
  }>;
1424
+ /** Convert an installed agent's short key to an AgentDefinition for spawning. */
919
1425
  resolve(key: string): AgentDefinition | undefined;
920
1426
  /**
921
1427
  * Enrich installed agents (especially migrated ones) with registry data.
@@ -927,17 +1433,52 @@ declare class AgentCatalog {
927
1433
  private loadRegistryFromCacheOrSnapshot;
928
1434
  }
929
1435
 
1436
+ /**
1437
+ * High-level facade for spawning and resuming agent instances.
1438
+ *
1439
+ * Resolves agent names to definitions via AgentCatalog, then delegates
1440
+ * to AgentInstance for subprocess management. Used by SessionFactory
1441
+ * to create the agent backing a session.
1442
+ *
1443
+ * Agent switching (swapping the agent mid-session) is coordinated at the
1444
+ * Session layer — AgentManager only handles individual spawn/resume calls.
1445
+ */
930
1446
  declare class AgentManager {
931
1447
  private catalog;
932
1448
  constructor(catalog: AgentCatalog);
1449
+ /** Return definitions for all installed agents. */
933
1450
  getAvailableAgents(): AgentDefinition[];
1451
+ /** Look up a single agent definition by its short name (e.g., "claude", "gemini"). */
934
1452
  getAgent(name: string): AgentDefinition | undefined;
1453
+ /**
1454
+ * Spawn a new agent subprocess with a fresh session.
1455
+ *
1456
+ * @throws If the agent is not installed — includes install instructions in the error message.
1457
+ */
935
1458
  spawn(agentName: string, workingDirectory: string, allowedPaths?: string[]): Promise<AgentInstance>;
1459
+ /**
1460
+ * Spawn a subprocess and resume an existing agent session.
1461
+ *
1462
+ * Falls back to a new session if the agent cannot restore the given session ID.
1463
+ */
936
1464
  resume(agentName: string, workingDirectory: string, agentSessionId: string, allowedPaths?: string[]): Promise<AgentInstance>;
937
1465
  }
938
1466
 
939
1467
  /**
940
- * Encapsulates pending permission state with a typed Promise API.
1468
+ * Blocks the prompt pipeline until the user approves or denies a permission request.
1469
+ *
1470
+ * When an agent requests permission (e.g., to run a shell command), AgentInstance
1471
+ * calls its `onPermissionRequest` callback. SessionBridge handles this by calling
1472
+ * `setPending()`, which returns a promise that blocks the ACP prompt/response cycle
1473
+ * until `resolve()` or `reject()` is called. If the user doesn't respond within
1474
+ * the timeout, the request is automatically rejected.
1475
+ *
1476
+ * Only one permission request can be pending at a time — setting a new one
1477
+ * supersedes (rejects) the previous.
1478
+ *
1479
+ * When `bypassPermissions` is enabled on the session, SessionBridge short-circuits
1480
+ * this gate entirely: `setPending()` is never called, and permissions are auto-approved
1481
+ * upstream before the request reaches this class.
941
1482
  */
942
1483
  declare class PermissionGate {
943
1484
  private request?;
@@ -947,8 +1488,14 @@ declare class PermissionGate {
947
1488
  private timeoutTimer?;
948
1489
  private timeoutMs;
949
1490
  constructor(timeoutMs?: number);
1491
+ /**
1492
+ * Register a new permission request and return a promise that resolves with the
1493
+ * chosen option ID when the user responds, or rejects on timeout / supersession.
1494
+ */
950
1495
  setPending(request: PermissionRequest): Promise<string>;
1496
+ /** Approve the pending request with the given option ID. No-op if already settled. */
951
1497
  resolve(optionId: string): void;
1498
+ /** Deny the pending request. No-op if already settled. */
952
1499
  reject(reason?: string): void;
953
1500
  get isPending(): boolean;
954
1501
  get currentRequest(): PermissionRequest | undefined;
@@ -958,37 +1505,57 @@ declare class PermissionGate {
958
1505
  private cleanup;
959
1506
  }
960
1507
 
1508
+ /** Options passed to an STT provider for a single transcription request. */
961
1509
  interface STTOptions {
1510
+ /** BCP-47 language code hint (e.g. `"en"`, `"vi"`). Improves accuracy when known. */
962
1511
  language?: string;
1512
+ /** Override the default model for this request. */
963
1513
  model?: string;
964
1514
  }
1515
+ /** Result returned by an STT provider after transcription. */
965
1516
  interface STTResult {
966
1517
  text: string;
1518
+ /** Detected or confirmed language (BCP-47). */
967
1519
  language?: string;
1520
+ /** Audio duration in seconds. */
968
1521
  duration?: number;
969
1522
  }
1523
+ /** Options passed to a TTS provider for a single synthesis request. */
970
1524
  interface TTSOptions {
971
1525
  language?: string;
1526
+ /** Voice identifier (provider-specific, e.g. `"en-US-AriaNeural"` for Edge TTS). */
972
1527
  voice?: string;
973
1528
  model?: string;
974
1529
  }
1530
+ /** Audio data produced by a TTS provider. */
975
1531
  interface TTSResult {
976
1532
  audioBuffer: Buffer;
1533
+ /** MIME type of the audio (e.g. `"audio/mp3"`, `"audio/wav"`). */
977
1534
  mimeType: string;
978
1535
  }
1536
+ /** Contract for a speech-to-text provider. */
979
1537
  interface STTProvider {
980
1538
  readonly name: string;
981
1539
  transcribe(audioBuffer: Buffer, mimeType: string, options?: STTOptions): Promise<STTResult>;
982
1540
  }
1541
+ /** Contract for a text-to-speech provider. */
983
1542
  interface TTSProvider {
984
1543
  readonly name: string;
985
1544
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
986
1545
  }
1546
+ /** Provider-level configuration stored in plugin settings (API key, model override, etc.). */
987
1547
  interface SpeechProviderConfig {
988
1548
  apiKey?: string;
989
1549
  model?: string;
990
1550
  [key: string]: unknown;
991
1551
  }
1552
+ /**
1553
+ * Top-level configuration for SpeechService.
1554
+ *
1555
+ * `stt.provider` and `tts.provider` name the active provider.
1556
+ * `null` disables the respective capability.
1557
+ * `providers` holds per-provider credentials and options.
1558
+ */
992
1559
  interface SpeechServiceConfig {
993
1560
  stt: {
994
1561
  provider: string | null;
@@ -1000,10 +1567,28 @@ interface SpeechServiceConfig {
1000
1567
  };
1001
1568
  }
1002
1569
 
1570
+ /**
1571
+ * A factory that recreates provider instances from a new config snapshot.
1572
+ * Used for hot-reload: when settings change, the plugin calls `refreshProviders`
1573
+ * which invokes this factory to build fresh provider objects.
1574
+ *
1575
+ * Returns separate Maps for STT and TTS so that externally-registered providers
1576
+ * (e.g. from `@openacp/msedge-tts-plugin`) are not discarded — only factory-owned
1577
+ * providers are overwritten.
1578
+ */
1003
1579
  type ProviderFactory = (config: SpeechServiceConfig) => {
1004
1580
  stt: Map<string, STTProvider>;
1005
1581
  tts: Map<string, TTSProvider>;
1006
1582
  };
1583
+ /**
1584
+ * Central service for speech-to-text and text-to-speech operations.
1585
+ *
1586
+ * Providers are registered at setup time and may also be registered by external
1587
+ * plugins (e.g. `@openacp/msedge-tts-plugin` registers a TTS provider after boot).
1588
+ * The service itself is registered under the `"speech"` key in the ServiceRegistry
1589
+ * and accessed by `session.ts` to synthesize audio after agent responses when
1590
+ * `voiceMode` is active.
1591
+ */
1007
1592
  declare class SpeechService {
1008
1593
  private config;
1009
1594
  private sttProviders;
@@ -1012,26 +1597,69 @@ declare class SpeechService {
1012
1597
  constructor(config: SpeechServiceConfig);
1013
1598
  /** Set a factory function that can recreate providers from config (for hot-reload) */
1014
1599
  setProviderFactory(factory: ProviderFactory): void;
1600
+ /** Register an STT provider by name. Overwrites any existing provider with the same name. */
1015
1601
  registerSTTProvider(name: string, provider: STTProvider): void;
1602
+ /** Register a TTS provider by name. Called by external TTS plugins (e.g. msedge-tts-plugin). */
1016
1603
  registerTTSProvider(name: string, provider: TTSProvider): void;
1604
+ /** Remove a TTS provider — called by external plugins on teardown. */
1017
1605
  unregisterTTSProvider(name: string): void;
1606
+ /** Returns true if an STT provider is configured and has credentials. */
1018
1607
  isSTTAvailable(): boolean;
1608
+ /**
1609
+ * Returns true if a TTS provider is configured and an implementation is registered.
1610
+ *
1611
+ * Config alone is not enough — the TTS provider plugin must have registered
1612
+ * its implementation via `registerTTSProvider` before this returns true.
1613
+ */
1019
1614
  isTTSAvailable(): boolean;
1615
+ /**
1616
+ * Transcribes audio using the configured STT provider.
1617
+ *
1618
+ * @throws if no STT provider is configured or if the named provider is not registered.
1619
+ */
1020
1620
  transcribe(audioBuffer: Buffer, mimeType: string, options?: STTOptions): Promise<STTResult>;
1621
+ /**
1622
+ * Synthesizes speech using the configured TTS provider.
1623
+ *
1624
+ * @throws if no TTS provider is configured or if the named provider is not registered.
1625
+ */
1021
1626
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
1627
+ /** Replace the active config without rebuilding providers. Use `refreshProviders` to also rebuild. */
1022
1628
  updateConfig(config: SpeechServiceConfig): void;
1023
- /** Re-create factory-managed providers from config. Preserves externally-registered providers (e.g. from plugins). */
1629
+ /**
1630
+ * Reloads TTS and STT providers from a new config snapshot.
1631
+ *
1632
+ * Called after config changes or plugin hot-reload. Factory-managed providers are
1633
+ * rebuilt via the registered `ProviderFactory`; externally-registered providers
1634
+ * (e.g. from `@openacp/msedge-tts-plugin`) are preserved rather than discarded.
1635
+ */
1024
1636
  refreshProviders(newConfig: SpeechServiceConfig): void;
1025
1637
  }
1026
1638
 
1639
+ /**
1640
+ * Speech-to-text provider backed by Groq's hosted Whisper API.
1641
+ *
1642
+ * Groq requires the audio to be submitted as a multipart form upload. The file
1643
+ * must have a valid extension matching its MIME type — Groq uses the extension
1644
+ * to determine the codec, so a mismatch causes a transcription error.
1645
+ *
1646
+ * Free tier limit: 28,800 seconds of audio per day. Max file size: 25 MB.
1647
+ */
1027
1648
  declare class GroqSTT implements STTProvider {
1028
1649
  private apiKey;
1029
1650
  private defaultModel;
1030
1651
  readonly name = "groq";
1031
1652
  constructor(apiKey: string, defaultModel?: string);
1653
+ /**
1654
+ * Transcribes audio using the Groq Whisper API.
1655
+ *
1656
+ * `verbose_json` response format is requested so the API returns language
1657
+ * detection and duration metadata alongside the transcript text.
1658
+ */
1032
1659
  transcribe(audioBuffer: Buffer, mimeType: string, options?: STTOptions): Promise<STTResult>;
1033
1660
  }
1034
1661
 
1662
+ /** Events emitted by a Session instance — SessionBridge subscribes to relay them to adapters. */
1035
1663
  interface SessionEvents {
1036
1664
  agent_event: (event: AgentEvent) => void;
1037
1665
  permission_request: (request: PermissionRequest) => void;
@@ -1042,6 +1670,17 @@ interface SessionEvents {
1042
1670
  prompt_count_changed: (count: number) => void;
1043
1671
  turn_started: (ctx: TurnContext) => void;
1044
1672
  }
1673
+ /**
1674
+ * Manages a single conversation between a user and an AI agent.
1675
+ *
1676
+ * Wraps an AgentInstance with serial prompt queuing (via PromptQueue), permission
1677
+ * gating (via PermissionGate), TTS/STT integration, auto-naming, and a state
1678
+ * machine tracking the session lifecycle. SessionBridge subscribes to this
1679
+ * emitter to forward agent output to channel adapters.
1680
+ *
1681
+ * A session can be attached to multiple adapters simultaneously (e.g., Telegram
1682
+ * and SSE). The `threadIds` map tracks which thread each adapter uses.
1683
+ */
1045
1684
  declare class Session extends TypedEmitter<SessionEvents> {
1046
1685
  id: string;
1047
1686
  channelId: string;
@@ -1052,6 +1691,8 @@ declare class Session extends TypedEmitter<SessionEvents> {
1052
1691
  workingDirectory: string;
1053
1692
  private _agentInstance;
1054
1693
  get agentInstance(): AgentInstance;
1694
+ /** Setting agentInstance wires the agent→session event relay and commands buffer.
1695
+ * This happens both at construction and on agent switch (switchAgent). */
1055
1696
  set agentInstance(agent: AgentInstance);
1056
1697
  agentSessionId: string;
1057
1698
  private _status;
@@ -1108,17 +1749,32 @@ declare class Session extends TypedEmitter<SessionEvents> {
1108
1749
  fail(reason: string): void;
1109
1750
  /** Transition to finished — from active only. Emits session_end for backward compat. */
1110
1751
  finish(reason?: string): void;
1111
- /** Transition to cancelled — from active only (terminal session cancel) */
1752
+ /** Transition to cancelled — from active or error (terminal session cancel) */
1112
1753
  markCancelled(): void;
1113
1754
  private transition;
1114
1755
  /** Number of prompts waiting in queue */
1115
1756
  get queueDepth(): number;
1757
+ /** Whether a prompt is currently being processed by the agent */
1116
1758
  get promptRunning(): boolean;
1759
+ /** Store context markdown to be prepended to the next prompt (used for session resume with history). */
1117
1760
  setContext(markdown: string): void;
1761
+ /** Set TTS mode: "off" = disabled, "next" = one-shot (auto-resets after prompt), "on" = persistent. */
1118
1762
  setVoiceMode(mode: "off" | "next" | "on"): void;
1763
+ /**
1764
+ * Enqueue a user prompt for serial processing.
1765
+ *
1766
+ * Runs the prompt through agent:beforePrompt middleware (which can modify or block),
1767
+ * then adds it to the PromptQueue. Returns a turnId that callers can use to correlate
1768
+ * queued/processing events before the prompt actually runs.
1769
+ */
1119
1770
  enqueuePrompt(text: string, attachments?: Attachment[], routing?: TurnRouting, externalTurnId?: string): Promise<string>;
1120
1771
  private processPrompt;
1772
+ /**
1773
+ * Transcribe audio attachments to text if the agent doesn't support audio natively.
1774
+ * Audio attachments are removed and their transcriptions are appended to the prompt text.
1775
+ */
1121
1776
  private maybeTranscribeAudio;
1777
+ /** Extract [TTS] block from agent response, synthesize speech, and emit audio_content event. */
1122
1778
  private processTTSResponse;
1123
1779
  private autoName;
1124
1780
  setInitialConfigOptions(options: ConfigOption[]): void;
@@ -1150,11 +1806,17 @@ declare class Session extends TypedEmitter<SessionEvents> {
1150
1806
  findLastSwitchEntry(agentName: string): AgentSwitchEntry | undefined;
1151
1807
  /** Switch the agent instance in-place, preserving session identity */
1152
1808
  switchAgent(agentName: string, createAgent: () => Promise<AgentInstance>): Promise<void>;
1809
+ /** Tear down the session: reject pending permissions, clear queue, destroy agent subprocess. */
1153
1810
  destroy(): Promise<void>;
1154
1811
  }
1155
1812
 
1156
1813
  /**
1157
1814
  * Serial prompt queue — ensures prompts are processed one at a time.
1815
+ *
1816
+ * Agents are stateful (each prompt builds on prior context), so concurrent
1817
+ * prompts would corrupt the conversation. This queue guarantees that only
1818
+ * one prompt is processed at a time; additional prompts are buffered and
1819
+ * drained sequentially after the current one completes.
1158
1820
  */
1159
1821
  declare class PromptQueue {
1160
1822
  private processor;
@@ -1165,14 +1827,37 @@ declare class PromptQueue {
1165
1827
  /** Set when abort is triggered; drainNext waits for the current processor to settle before starting the next item. */
1166
1828
  private processorSettled;
1167
1829
  constructor(processor: (text: string, attachments?: Attachment[], routing?: TurnRouting, turnId?: string) => Promise<void>, onError?: ((err: unknown) => void) | undefined);
1830
+ /**
1831
+ * Add a prompt to the queue. If no prompt is currently processing, it runs
1832
+ * immediately. Otherwise, it's buffered and the returned promise resolves
1833
+ * only after the prompt finishes processing.
1834
+ */
1168
1835
  enqueue(text: string, attachments?: Attachment[], routing?: TurnRouting, turnId?: string): Promise<void>;
1836
+ /** Run a single prompt through the processor, then drain the next queued item. */
1169
1837
  private process;
1838
+ /** Dequeue and process the next pending prompt, if any. Called after each prompt completes. */
1170
1839
  private drainNext;
1840
+ /**
1841
+ * Abort the in-flight prompt and discard all queued prompts.
1842
+ * Pending promises are resolved (not rejected) so callers don't see unhandled rejections.
1843
+ */
1171
1844
  clear(): void;
1172
1845
  get pending(): number;
1173
1846
  get isProcessing(): boolean;
1174
1847
  }
1175
1848
 
1849
+ /**
1850
+ * Transforms AgentEvents into OutgoingMessages suitable for adapter delivery.
1851
+ *
1852
+ * Handles two key concerns beyond simple type mapping:
1853
+ * 1. **Tool input caching** — `tool_call` events carry `rawInput`, but subsequent
1854
+ * `tool_update` events for the same tool often omit it. The transformer caches
1855
+ * rawInput so updates can inherit it for viewer link generation.
1856
+ * 2. **Viewer link enrichment** — when a tunnel service is available, tool events
1857
+ * for file reads/edits are enriched with public URLs to the code viewer and diff viewer.
1858
+ * Intermediate updates (with raw content) are preferred over completion events
1859
+ * (which have formatted content with line numbers).
1860
+ */
1176
1861
  declare class MessageTransformer {
1177
1862
  tunnelService?: TunnelServiceInterface;
1178
1863
  /** Cache rawInput from tool_call so it's available in tool_update (which often lacks it) */
@@ -1180,6 +1865,12 @@ declare class MessageTransformer {
1180
1865
  /** Cache viewer links generated from intermediate updates so completion events carry them */
1181
1866
  private toolViewerCache;
1182
1867
  constructor(tunnelService?: TunnelServiceInterface);
1868
+ /**
1869
+ * Convert an agent event to an outgoing message for adapter delivery.
1870
+ *
1871
+ * For tool events, enriches the metadata with diff stats and viewer links
1872
+ * when a tunnel service is available.
1873
+ */
1183
1874
  transform(event: AgentEvent, sessionContext?: {
1184
1875
  id: string;
1185
1876
  workingDirectory: string;
@@ -1193,6 +1884,7 @@ declare class MessageTransformer {
1193
1884
  private enrichWithViewerLinks;
1194
1885
  }
1195
1886
 
1887
+ /** Persistence interface for session records. Implementations handle serialization format and storage. */
1196
1888
  interface SessionStore {
1197
1889
  save(record: SessionRecord): Promise<void>;
1198
1890
  /** Immediately flush pending writes to disk (no debounce). */
@@ -1205,6 +1897,13 @@ interface SessionStore {
1205
1897
  remove(sessionId: string): Promise<void>;
1206
1898
  }
1207
1899
 
1900
+ /**
1901
+ * Event map for the global EventBus.
1902
+ *
1903
+ * Defines all cross-cutting events that flow between core, plugins, and adapters.
1904
+ * Plugins subscribe via `eventBus.on(...)` without needing direct references
1905
+ * to the components that emit these events.
1906
+ */
1208
1907
  interface EventBusEvents {
1209
1908
  "session:created": (data: {
1210
1909
  sessionId: string;
@@ -1307,9 +2006,18 @@ interface EventBusEvents {
1307
2006
  error?: string;
1308
2007
  }) => void;
1309
2008
  }
2009
+ /**
2010
+ * Global event bus for cross-cutting communication.
2011
+ *
2012
+ * Decouples plugins from direct session/core references — plugins and adapters
2013
+ * subscribe to bus events without knowing which component emits them. The core
2014
+ * and sessions emit events here; plugins consume them for features like usage
2015
+ * tracking, notifications, and UI updates.
2016
+ */
1310
2017
  declare class EventBus extends TypedEmitter<EventBusEvents> {
1311
2018
  }
1312
2019
 
2020
+ /** Flattened view of a session for API consumers — merges live state with stored record. */
1313
2021
  interface SessionSummary {
1314
2022
  id: string;
1315
2023
  agent: string;
@@ -1326,30 +2034,63 @@ interface SessionSummary {
1326
2034
  capabilities: AgentCapabilities | null;
1327
2035
  isLive: boolean;
1328
2036
  }
2037
+ /**
2038
+ * Registry for live Session instances. Provides lookup by session ID, channel+thread,
2039
+ * or agent session ID. Coordinates session lifecycle: creation, cancellation, persistence,
2040
+ * and graceful shutdown.
2041
+ *
2042
+ * Live sessions are kept in an in-memory Map. The optional SessionStore handles
2043
+ * disk persistence — the manager delegates save/patch/remove operations to the store.
2044
+ */
1329
2045
  declare class SessionManager {
1330
2046
  private sessions;
1331
2047
  private store;
1332
2048
  private eventBus?;
1333
2049
  middlewareChain?: MiddlewareChain;
2050
+ /**
2051
+ * Inject the EventBus after construction. Deferred because EventBus is created
2052
+ * after SessionManager during bootstrap, so it cannot be passed to the constructor.
2053
+ */
1334
2054
  setEventBus(eventBus: EventBus): void;
1335
2055
  constructor(store?: SessionStore | null);
2056
+ /** Create a new session by spawning an agent and persisting the initial record. */
1336
2057
  createSession(channelId: string, agentName: string, workingDirectory: string, agentManager: AgentManager): Promise<Session>;
2058
+ /** Look up a live session by its OpenACP session ID. */
1337
2059
  getSession(sessionId: string): Session | undefined;
2060
+ /** Look up a live session by adapter channel and thread ID (checks per-adapter threadIds map first, then legacy fields). */
1338
2061
  getSessionByThread(channelId: string, threadId: string): Session | undefined;
2062
+ /** Look up a live session by the agent's internal session ID (assigned by the ACP subprocess). */
1339
2063
  getSessionByAgentSessionId(agentSessionId: string): Session | undefined;
2064
+ /** Look up the persisted SessionRecord by the agent's internal session ID. */
1340
2065
  getRecordByAgentSessionId(agentSessionId: string): SessionRecord | undefined;
2066
+ /** Look up the persisted SessionRecord by channel and thread ID. */
1341
2067
  getRecordByThread(channelId: string, threadId: string): SessionRecord | undefined;
2068
+ /** Register a session that was created externally (e.g. restored from store on startup). */
1342
2069
  registerSession(session: Session): void;
2070
+ /**
2071
+ * Merge a partial update into the stored SessionRecord. If no record exists yet and
2072
+ * the patch includes `sessionId`, it is treated as an initial save.
2073
+ * Pass `{ immediate: true }` to flush the store to disk synchronously.
2074
+ */
1343
2075
  patchRecord(sessionId: string, patch: Partial<SessionRecord>, options?: {
1344
2076
  immediate?: boolean;
1345
2077
  }): Promise<void>;
2078
+ /** Retrieve the persisted SessionRecord for a given session ID. Returns undefined if no store or record not found. */
1346
2079
  getSessionRecord(sessionId: string): SessionRecord | undefined;
2080
+ /** Cancel a session: abort in-flight prompt, transition to cancelled, destroy agent, and persist. */
1347
2081
  cancelSession(sessionId: string): Promise<void>;
2082
+ /** List live (in-memory) sessions, optionally filtered by channel. Excludes assistant sessions. */
1348
2083
  listSessions(channelId?: string): Session[];
2084
+ /**
2085
+ * List all sessions (live + stored) as SessionSummary. Live sessions take precedence
2086
+ * over stored records — their real-time state (queueDepth, promptRunning) is used.
2087
+ */
1349
2088
  listAllSessions(channelId?: string): SessionSummary[];
2089
+ /** List all stored SessionRecords, optionally filtered by status. Excludes assistant sessions. */
1350
2090
  listRecords(filter?: {
1351
2091
  statuses?: string[];
1352
2092
  }): SessionRecord[];
2093
+ /** Remove a session's stored record and emit a SESSION_DELETED event. */
1353
2094
  removeRecord(sessionId: string): Promise<void>;
1354
2095
  /**
1355
2096
  * Graceful shutdown: persist session state without killing agent subprocesses.
@@ -1365,13 +2106,38 @@ declare class SessionManager {
1365
2106
  destroyAll(): Promise<void>;
1366
2107
  }
1367
2108
 
2109
+ /**
2110
+ * Routes cross-session notifications to the appropriate channel adapter.
2111
+ *
2112
+ * Notifications are triggered by `SessionBridge` when a session completes,
2113
+ * errors, or hits a budget threshold. Unlike regular messages, notifications
2114
+ * are not tied to a specific outgoing message stream — they are pushed to the
2115
+ * channel that owns the session (identified by `channelId` on the session).
2116
+ *
2117
+ * The adapters Map is the live registry maintained by `OpenACPCore`. Holding a
2118
+ * reference to the Map (rather than a snapshot) ensures that adapters registered
2119
+ * after this service is created are still reachable.
2120
+ */
1368
2121
  declare class NotificationManager {
1369
2122
  private adapters;
1370
2123
  constructor(adapters: Map<string, IChannelAdapter>);
2124
+ /**
2125
+ * Send a notification to a specific channel adapter.
2126
+ *
2127
+ * Failures are swallowed — notifications are best-effort and must not crash
2128
+ * the session or caller (e.g. on session completion).
2129
+ */
1371
2130
  notify(channelId: string, notification: NotificationMessage): Promise<void>;
2131
+ /**
2132
+ * Broadcast a notification to every registered adapter.
2133
+ *
2134
+ * Used for system-wide alerts (e.g. global budget exhausted). Each adapter
2135
+ * failure is isolated so one broken adapter cannot block the rest.
2136
+ */
1372
2137
  notifyAll(notification: NotificationMessage): Promise<void>;
1373
2138
  }
1374
2139
 
2140
+ /** Services required by SessionBridge for message transformation, persistence, and middleware. */
1375
2141
  interface BridgeDeps {
1376
2142
  messageTransformer: MessageTransformer;
1377
2143
  notificationManager: NotificationManager;
@@ -1380,6 +2146,18 @@ interface BridgeDeps {
1380
2146
  fileService?: FileServiceInterface;
1381
2147
  middlewareChain?: MiddlewareChain;
1382
2148
  }
2149
+ /**
2150
+ * Connects a Session to a channel adapter, forwarding agent events to the adapter's
2151
+ * stream interface and wiring up permission handling, lifecycle persistence, and middleware.
2152
+ *
2153
+ * Each adapter attached to a session gets its own bridge. The bridge subscribes to
2154
+ * Session events (agent_event, permission_request, status_change, etc.) and translates
2155
+ * them into adapter-specific calls (sendMessage, sendPermissionRequest, renameSessionThread).
2156
+ *
2157
+ * Multi-adapter routing: when a TurnContext is active, turn events (text, tool_call, etc.)
2158
+ * are forwarded only to the adapter that originated the prompt. System events (commands_update,
2159
+ * session_end, etc.) are always broadcast to all bridges.
2160
+ */
1383
2161
  declare class SessionBridge {
1384
2162
  private session;
1385
2163
  private adapter;
@@ -1393,9 +2171,20 @@ declare class SessionBridge {
1393
2171
  private listen;
1394
2172
  /** Send message to adapter, optionally running through message:outgoing middleware */
1395
2173
  private sendMessage;
1396
- /** Determine if this bridge should forward the given event based on turn routing. */
2174
+ /**
2175
+ * Determine if this bridge should forward the given event based on turn routing.
2176
+ * System events are always forwarded; turn events are routed only to the target adapter.
2177
+ */
1397
2178
  shouldForward(event: AgentEvent): boolean;
2179
+ /**
2180
+ * Subscribe to session events and start forwarding them to the adapter.
2181
+ *
2182
+ * Wires: agent events → adapter dispatch, permission UI, lifecycle persistence
2183
+ * (status changes, naming, prompt count), and EventBus notifications.
2184
+ * Also replays any commands or config options that arrived before the bridge connected.
2185
+ */
1398
2186
  connect(): void;
2187
+ /** Unsubscribe all session event listeners and clean up adapter state. */
1399
2188
  disconnect(): void;
1400
2189
  /** Dispatch an agent event through middleware and to the adapter */
1401
2190
  private dispatchAgentEvent;
@@ -1410,6 +2199,11 @@ declare class SessionBridge {
1410
2199
  private emitAfterResolve;
1411
2200
  }
1412
2201
 
2202
+ /**
2203
+ * Represents a single running (or recently failed) tunnel.
2204
+ * `type: "system"` is the main OpenACP tunnel; `type: "user"` are agent-created tunnels.
2205
+ * `status` transitions: starting → active | failed. Failed entries may auto-retry.
2206
+ */
1413
2207
  interface TunnelEntry {
1414
2208
  port: number;
1415
2209
  type: 'system' | 'user';
@@ -1422,6 +2216,12 @@ interface TunnelEntry {
1422
2216
  createdAt: string;
1423
2217
  }
1424
2218
 
2219
+ /**
2220
+ * Metadata for a single viewer entry.
2221
+ * For `type: "diff"`, `content` holds the new text and `oldContent` holds the original.
2222
+ * For `type: "output"`, `filePath` is used as the display label (not a real file path).
2223
+ * Entries expire after the configured TTL and are cleaned up lazily on read and periodically.
2224
+ */
1425
2225
  interface ViewerEntry {
1426
2226
  id: string;
1427
2227
  type: 'file' | 'diff' | 'output';
@@ -1434,6 +2234,13 @@ interface ViewerEntry {
1434
2234
  createdAt: number;
1435
2235
  expiresAt: number;
1436
2236
  }
2237
+ /**
2238
+ * In-memory store for content shared via tunnel viewer routes.
2239
+ *
2240
+ * Agents call `storeFile()` / `storeDiff()` / `storeOutput()` to get a short URL id,
2241
+ * then pass that URL to the user. The viewer routes serve HTML pages using the stored content.
2242
+ * Content is scoped to the session's working directory to avoid leaking files outside the workspace.
2243
+ */
1437
2244
  declare class ViewerStore {
1438
2245
  private entries;
1439
2246
  private cleanupTimer;
@@ -1449,6 +2256,11 @@ declare class ViewerStore {
1449
2256
  destroy(): void;
1450
2257
  }
1451
2258
 
2259
+ /**
2260
+ * Tunnel plugin settings shape.
2261
+ * `port` is deprecated — the tunnel now always points to the API server port.
2262
+ * `auth` is deprecated — viewer routes are public; authentication was removed.
2263
+ */
1452
2264
  interface TunnelConfig {
1453
2265
  enabled: boolean;
1454
2266
  port: number;
@@ -1462,6 +2274,14 @@ interface TunnelConfig {
1462
2274
  };
1463
2275
  }
1464
2276
 
2277
+ /**
2278
+ * High-level facade over TunnelRegistry and ViewerStore.
2279
+ *
2280
+ * Owns the system tunnel (started in `start()` once the API server port is known),
2281
+ * user tunnel management (add/stop/list), and viewer URL generation.
2282
+ * Registered as the `"tunnel"` service so other plugins can call `fileUrl()`,
2283
+ * `diffUrl()`, etc. to share content via the public tunnel URL.
2284
+ */
1465
2285
  declare class TunnelService {
1466
2286
  private registry;
1467
2287
  private store;
@@ -1488,12 +2308,26 @@ declare class TunnelService {
1488
2308
  outputUrl(entryId: string): string;
1489
2309
  }
1490
2310
 
2311
+ /**
2312
+ * Abstract interface for conversation context sources.
2313
+ *
2314
+ * Two providers are built-in: "local" (history recorder) and "entire" (Claude Code checkpoints).
2315
+ * ContextManager iterates providers in priority order and returns the first non-empty result.
2316
+ */
1491
2317
  interface ContextProvider {
1492
2318
  readonly name: string;
1493
2319
  isAvailable(repoPath: string): Promise<boolean>;
1494
2320
  listSessions(query: ContextQuery): Promise<SessionListResult>;
1495
2321
  buildContext(query: ContextQuery, options?: ContextOptions): Promise<ContextResult>;
1496
2322
  }
2323
+ /**
2324
+ * Describes which sessions to include in a context build.
2325
+ *
2326
+ * - `type: "latest"` with `value: "5"` returns the 5 most recent sessions.
2327
+ * - `type: "session"` with a UUID returns exactly that session.
2328
+ * - `type: "branch"` / `"commit"` / `"pr"` are only supported by the "entire" provider
2329
+ * which reads Claude Code checkpoints stored in the git repo.
2330
+ */
1497
2331
  interface ContextQuery {
1498
2332
  repoPath: string;
1499
2333
  type: "branch" | "commit" | "pr" | "latest" | "checkpoint" | "session";
@@ -1507,6 +2341,11 @@ interface ContextOptions {
1507
2341
  /** When true, skip the context cache (use for live switches where history just changed) */
1508
2342
  noCache?: boolean;
1509
2343
  }
2344
+ /**
2345
+ * Metadata for a single recorded session, used when listing available context.
2346
+ * Fields like `checkpointId` and `transcriptPath` are populated by the "entire" provider;
2347
+ * the "local" provider leaves them empty since it stores sessions in its own HistoryStore.
2348
+ */
1510
2349
  interface SessionInfo {
1511
2350
  checkpointId: string;
1512
2351
  sessionIndex: string;
@@ -1523,7 +2362,18 @@ interface SessionListResult {
1523
2362
  sessions: SessionInfo[];
1524
2363
  estimatedTokens: number;
1525
2364
  }
2365
+ /**
2366
+ * Controls how much detail is rendered per turn in the context markdown.
2367
+ * - `full`: full diffs, tool call outputs, thinking blocks, usage stats
2368
+ * - `balanced`: diffs truncated, thinking omitted
2369
+ * - `compact`: single-line summary per turn pair (user + tools used)
2370
+ */
1526
2371
  type ContextMode = "full" | "balanced" | "compact";
2372
+ /**
2373
+ * The built context block to be prepended to an agent prompt.
2374
+ * `markdown` is the formatted text; `truncated` is true when oldest sessions
2375
+ * were dropped to fit within `maxTokens`.
2376
+ */
1527
2377
  interface ContextResult {
1528
2378
  markdown: string;
1529
2379
  tokenEstimate: number;
@@ -1537,11 +2387,22 @@ interface ContextResult {
1537
2387
  };
1538
2388
  }
1539
2389
 
2390
+ /**
2391
+ * Root structure persisted to disk for one session.
2392
+ * `version` allows future schema migrations without breaking existing files.
2393
+ */
1540
2394
  interface SessionHistory {
1541
2395
  version: 1;
1542
2396
  sessionId: string;
1543
2397
  turns: Turn[];
1544
2398
  }
2399
+ /**
2400
+ * One complete user→assistant exchange within a session.
2401
+ *
2402
+ * User turns carry `content` + optional `attachments`.
2403
+ * Assistant turns carry `steps` (the sequence of actions the agent took)
2404
+ * plus optional `usage` (token/cost accounting) and `stopReason`.
2405
+ */
1545
2406
  interface Turn {
1546
2407
  index: number;
1547
2408
  role: "user" | "assistant";
@@ -1641,6 +2502,12 @@ interface ConfigChangeStep {
1641
2502
  value: string;
1642
2503
  }
1643
2504
 
2505
+ /**
2506
+ * Persists session history to disk as one JSON file per session.
2507
+ *
2508
+ * Files are stored as `<dir>/<sessionId>.json`. The path is validated to prevent
2509
+ * directory traversal — session IDs that resolve outside `dir` are rejected.
2510
+ */
1644
2511
  declare class HistoryStore {
1645
2512
  private readonly dir;
1646
2513
  constructor(dir: string);
@@ -1652,12 +2519,30 @@ declare class HistoryStore {
1652
2519
  private filePath;
1653
2520
  }
1654
2521
 
2522
+ /**
2523
+ * Orchestrates context providers and caching.
2524
+ *
2525
+ * Providers are tried in registration order — the first one that is available
2526
+ * and returns non-empty markdown wins. This lets the "local" history provider
2527
+ * take priority over the "entire" checkpoint provider for sessions that are
2528
+ * still in progress (not yet checkpointed).
2529
+ *
2530
+ * The context service is registered under the `"context"` key in the
2531
+ * ServiceRegistry so other plugins (e.g. the git-pilot plugin) can call
2532
+ * `buildContext()` before injecting context into a new agent.
2533
+ */
1655
2534
  declare class ContextManager {
1656
2535
  private providers;
1657
2536
  private cache;
1658
2537
  private historyStore?;
1659
2538
  private sessionFlusher?;
1660
- constructor(cachePath?: string);
2539
+ constructor(cachePath: string);
2540
+ /**
2541
+ * Wire in the history store after construction.
2542
+ *
2543
+ * Injected separately because the history store (backed by the context plugin's
2544
+ * recorder) may not be ready when ContextManager is first instantiated.
2545
+ */
1661
2546
  setHistoryStore(store: HistoryStore): void;
1662
2547
  /** Register a callback that flushes in-memory recorder state for a session to disk. */
1663
2548
  registerFlusher(fn: (sessionId: string) => Promise<void>): void;
@@ -1667,13 +2552,45 @@ declare class ContextManager {
1667
2552
  * where the last turn hasn't been persisted yet.
1668
2553
  */
1669
2554
  flushSession(sessionId: string): Promise<void>;
2555
+ /**
2556
+ * Read the raw history for a session directly from the history store.
2557
+ *
2558
+ * Returns null if no historyStore has been configured via `setHistoryStore()`,
2559
+ * or if the session has no recorded history.
2560
+ */
1670
2561
  getHistory(sessionId: string): Promise<SessionHistory | null>;
2562
+ /**
2563
+ * Register a provider. Providers are queried in insertion order.
2564
+ * Register higher-priority sources (e.g. local history) before lower-priority ones (e.g. entire).
2565
+ */
1671
2566
  register(provider: ContextProvider): void;
2567
+ /**
2568
+ * Return the first provider that reports itself available for the given repo.
2569
+ *
2570
+ * This is a availability check — it returns the highest-priority available provider
2571
+ * (i.e. the first registered one that passes `isAvailable`), not necessarily the
2572
+ * one that would yield the richest context for a specific query.
2573
+ */
1672
2574
  getProvider(repoPath: string): Promise<ContextProvider | null>;
2575
+ /**
2576
+ * List sessions using the same provider-waterfall logic as `buildContext`.
2577
+ *
2578
+ * Tries each registered provider in order, returning the first non-empty result.
2579
+ * Unlike `buildContext`, results are not cached — callers should avoid calling
2580
+ * this in hot paths.
2581
+ */
1673
2582
  listSessions(query: ContextQuery): Promise<SessionListResult | null>;
2583
+ /**
2584
+ * Build a context block for injection into an agent prompt.
2585
+ *
2586
+ * Tries each registered provider in order. Results are cached by (repoPath + queryKey)
2587
+ * to avoid redundant disk reads. Pass `options.noCache = true` when the caller knows
2588
+ * the history just changed (e.g. immediately after an agent switch + flush).
2589
+ */
1674
2590
  buildContext(query: ContextQuery, options?: ContextOptions): Promise<ContextResult | null>;
1675
2591
  }
1676
2592
 
2593
+ /** Parameters for creating a new session — used by SessionFactory.create() and Core.createFullSession(). */
1677
2594
  interface SessionCreateParams {
1678
2595
  channelId: string;
1679
2596
  agentName: string;
@@ -1688,6 +2605,14 @@ interface SideEffectDeps {
1688
2605
  notificationManager: NotificationManager;
1689
2606
  tunnelService?: TunnelService;
1690
2607
  }
2608
+ /**
2609
+ * Constructs new Sessions with the right agent, working directory, and initial state.
2610
+ *
2611
+ * Handles agent spawning (or resuming from a previous ACP session), middleware integration,
2612
+ * ACP state hydration, and side-effect wiring (usage tracking, tunnel cleanup).
2613
+ * Also provides lazy resume: when a message arrives for a stored (not live) session,
2614
+ * the factory transparently resumes it by re-spawning the agent with the stored session ID.
2615
+ */
1691
2616
  declare class SessionFactory {
1692
2617
  private agentManager;
1693
2618
  private sessionManager;
@@ -1717,6 +2642,10 @@ declare class SessionFactory {
1717
2642
  getAgentAllowedPaths?: () => string[];
1718
2643
  constructor(agentManager: AgentManager, sessionManager: SessionManager, speechServiceAccessor: SpeechService | (() => SpeechService), eventBus: EventBus, instanceRoot?: string | undefined);
1719
2644
  private get speechService();
2645
+ /**
2646
+ * Create a new Session: spawn agent → create Session instance → hydrate ACP state → register.
2647
+ * Runs session:beforeCreate middleware (which can modify params or block creation).
2648
+ */
1720
2649
  create(params: SessionCreateParams): Promise<Session>;
1721
2650
  /**
1722
2651
  * Get active session by thread, or attempt lazy resume from store.
@@ -1724,7 +2653,13 @@ declare class SessionFactory {
1724
2653
  */
1725
2654
  getOrResume(channelId: string, threadId: string): Promise<Session | null>;
1726
2655
  getOrResumeById(sessionId: string): Promise<Session | null>;
2656
+ /**
2657
+ * Attempt to resume a session from disk when a message arrives on a thread with
2658
+ * no live session. Deduplicates concurrent resume attempts for the same thread
2659
+ * via resumeLocks to avoid spawning multiple agents.
2660
+ */
1727
2661
  private lazyResume;
2662
+ /** Create a brand-new session, resolving agent name and workspace from config if not provided. */
1728
2663
  handleNewSession(channelId: string, agentName?: string, workspacePath?: string, options?: {
1729
2664
  createThread?: boolean;
1730
2665
  threadId?: string;
@@ -1732,6 +2667,7 @@ declare class SessionFactory {
1732
2667
  /** NOTE: handleNewChat is currently dead code — never called outside core.ts itself.
1733
2668
  * Moving it anyway for completeness; can be removed in a future cleanup. */
1734
2669
  handleNewChat(channelId: string, currentThreadId: string): Promise<Session | null>;
2670
+ /** Create a session and inject conversation context from a ContextProvider (e.g., history from a previous session). */
1735
2671
  createSessionWithContext(params: {
1736
2672
  channelId: string;
1737
2673
  agentName: string;
@@ -1744,13 +2680,29 @@ declare class SessionFactory {
1744
2680
  session: Session;
1745
2681
  contextResult: ContextResult | null;
1746
2682
  }>;
2683
+ /** Wire session-level side effects: usage tracking (via EventBus) and tunnel cleanup on session end. */
1747
2684
  wireSideEffects(session: Session, deps: SideEffectDeps): void;
1748
2685
  }
1749
2686
 
2687
+ /**
2688
+ * Configuration for the SecurityGuard access policy.
2689
+ *
2690
+ * `allowedUserIds`: if non-empty, only users whose string ID appears in this list
2691
+ * are permitted. An empty array means "allow all" (open access).
2692
+ * `maxConcurrentSessions`: caps how many active/initializing sessions may exist
2693
+ * at once across all users, preventing resource exhaustion.
2694
+ */
1750
2695
  interface SecurityConfig {
1751
2696
  allowedUserIds: string[];
1752
2697
  maxConcurrentSessions: number;
1753
2698
  }
2699
+ /**
2700
+ * Enforces user allowlist and global session-count limits on every incoming message.
2701
+ *
2702
+ * Implemented as a plugin service (rather than baked into core) so that the access
2703
+ * policy is swappable — deployments can replace or extend it without touching core.
2704
+ * The plugin registers this guard as a `message:incoming` middleware handler.
2705
+ */
1754
2706
  declare class SecurityGuard {
1755
2707
  private getSecurityConfig;
1756
2708
  private sessionManager;
@@ -1759,6 +2711,16 @@ declare class SecurityGuard {
1759
2711
  status: string;
1760
2712
  }>;
1761
2713
  });
2714
+ /**
2715
+ * Returns `{ allowed: true }` when the message may proceed, or
2716
+ * `{ allowed: false, reason }` when it should be blocked.
2717
+ *
2718
+ * Two checks run in order:
2719
+ * 1. **Allowlist** — if `allowedUserIds` is non-empty, the user's ID (coerced to string)
2720
+ * must appear in the list. Telegram/Slack IDs are numbers, so coercion is required.
2721
+ * 2. **Session cap** — counts sessions in `active` or `initializing` state. `initializing`
2722
+ * is included because a session holds resources before it reaches `active`.
2723
+ */
1762
2724
  checkAccess(message: {
1763
2725
  userId: string | number;
1764
2726
  }): Promise<{
@@ -1769,46 +2731,91 @@ declare class SecurityGuard {
1769
2731
  }>;
1770
2732
  }
1771
2733
 
2734
+ /**
2735
+ * Central service discovery for the plugin system.
2736
+ *
2737
+ * Plugins register service implementations by string key (e.g., 'security', 'speech').
2738
+ * Core and other plugins retrieve them via typed accessors:
2739
+ * `registry.get<SecurityService>('security')`
2740
+ *
2741
+ * Each service key is unique — registering a duplicate throws unless `registerOverride` is used.
2742
+ * Services are tracked by the owning plugin name so they can be bulk-removed on plugin unload.
2743
+ */
1772
2744
  declare class ServiceRegistry {
1773
2745
  private services;
2746
+ /**
2747
+ * Register a service. Throws if the service name is already taken.
2748
+ * Use `registerOverride` to intentionally replace an existing service.
2749
+ */
1774
2750
  register<T>(name: string, implementation: T, pluginName: string): void;
2751
+ /** Register a service, replacing any existing registration (used by override plugins). */
1775
2752
  registerOverride<T>(name: string, implementation: T, pluginName: string): void;
2753
+ /** Retrieve a service by name. Returns undefined if not registered. */
1776
2754
  get<T>(name: string): T | undefined;
2755
+ /** Check whether a service is registered. */
1777
2756
  has(name: string): boolean;
2757
+ /** List all registered services with their owning plugin names. */
1778
2758
  list(): Array<{
1779
2759
  name: string;
1780
2760
  pluginName: string;
1781
2761
  }>;
2762
+ /** Remove a single service by name. */
1782
2763
  unregister(name: string): void;
2764
+ /** Remove all services owned by a specific plugin (called during plugin unload). */
1783
2765
  unregisterByPlugin(pluginName: string): void;
1784
2766
  }
1785
2767
 
2768
+ /**
2769
+ * Persisted metadata about an installed plugin.
2770
+ *
2771
+ * This is the registry's view of a plugin — install state, version, source.
2772
+ * Distinct from `OpenACPPlugin` which is the runtime instance with setup/teardown hooks.
2773
+ */
1786
2774
  interface PluginEntry {
1787
2775
  version: string;
1788
2776
  installedAt: string;
1789
2777
  updatedAt: string;
2778
+ /** How the plugin was installed: bundled with core, from npm, or from a local path */
1790
2779
  source: 'builtin' | 'npm' | 'local';
1791
2780
  enabled: boolean;
1792
2781
  settingsPath: string;
1793
2782
  description?: string;
1794
2783
  }
1795
2784
  type RegisterInput = Omit<PluginEntry, 'installedAt' | 'updatedAt'>;
2785
+ /**
2786
+ * Tracks which plugins are installed, their versions, and enabled state.
2787
+ * Persisted as JSON at `~/.openacp/plugins/registry.json`.
2788
+ *
2789
+ * Used by LifecycleManager to detect version changes (triggering migration)
2790
+ * and to skip disabled plugins at boot time.
2791
+ */
1796
2792
  declare class PluginRegistry {
1797
2793
  private registryPath;
1798
2794
  private data;
1799
2795
  constructor(registryPath: string);
2796
+ /** Return all installed plugins as a Map. */
1800
2797
  list(): Map<string, PluginEntry>;
2798
+ /** Look up a plugin by name. Returns undefined if not installed. */
1801
2799
  get(name: string): PluginEntry | undefined;
2800
+ /** Record a newly installed plugin. Timestamps are set automatically. */
1802
2801
  register(name: string, entry: RegisterInput): void;
2802
+ /** Remove a plugin from the registry. */
1803
2803
  remove(name: string): void;
2804
+ /** Enable or disable a plugin. Disabled plugins are skipped at boot. */
1804
2805
  setEnabled(name: string, enabled: boolean): void;
2806
+ /** Update the stored version (called after successful migration). */
1805
2807
  updateVersion(name: string, version: string): void;
2808
+ /** Return only enabled plugins. */
1806
2809
  listEnabled(): Map<string, PluginEntry>;
2810
+ /** Filter plugins by installation source. */
1807
2811
  listBySource(source: PluginEntry['source']): Map<string, PluginEntry>;
2812
+ /** Load registry data from disk. Silently starts empty if file doesn't exist. */
1808
2813
  load(): Promise<void>;
2814
+ /** Persist registry data to disk. */
1809
2815
  save(): Promise<void>;
1810
2816
  }
1811
2817
 
2818
+ /** Options for constructing a LifecycleManager. All fields are optional with sensible defaults. */
1812
2819
  interface LifecycleManagerOpts {
1813
2820
  serviceRegistry?: ServiceRegistry;
1814
2821
  middlewareChain?: MiddlewareChain;
@@ -1828,6 +2835,21 @@ interface LifecycleManagerOpts {
1828
2835
  /** Root directory for this OpenACP instance (default: ~/.openacp) */
1829
2836
  instanceRoot?: string;
1830
2837
  }
2838
+ /**
2839
+ * Orchestrates plugin boot, teardown, and hot-reload.
2840
+ *
2841
+ * Boot sequence:
2842
+ * 1. Topological sort by `pluginDependencies` — ensures a plugin's deps are ready first
2843
+ * 2. Version migration if registry version != plugin version
2844
+ * 3. Settings validation against Zod schema
2845
+ * 4. Create scoped PluginContext, call `plugin.setup(ctx)` with timeout
2846
+ *
2847
+ * Error isolation: if a plugin's setup() fails, it is marked as failed and skipped.
2848
+ * Any plugin that depends on a failed plugin is also skipped (cascade failure).
2849
+ * Other independent plugins continue booting normally.
2850
+ *
2851
+ * Shutdown calls teardown() in reverse boot order — dependencies are torn down last.
2852
+ */
1831
2853
  declare class LifecycleManager {
1832
2854
  readonly serviceRegistry: ServiceRegistry;
1833
2855
  readonly middlewareChain: MiddlewareChain;
@@ -1845,8 +2867,11 @@ declare class LifecycleManager {
1845
2867
  private loadOrder;
1846
2868
  private _loaded;
1847
2869
  private _failed;
2870
+ /** Names of plugins that successfully completed setup(). */
1848
2871
  get loadedPlugins(): string[];
2872
+ /** Names of plugins whose setup() threw an error. These plugins are skipped but don't crash the system. */
1849
2873
  get failedPlugins(): string[];
2874
+ /** The PluginRegistry tracking installed and enabled plugin state. */
1850
2875
  get registry(): PluginRegistry | undefined;
1851
2876
  /** Plugin definitions currently in load order (loaded + failed). */
1852
2877
  get plugins(): OpenACPPlugin[];
@@ -1854,20 +2879,46 @@ declare class LifecycleManager {
1854
2879
  get instanceRoot(): string | undefined;
1855
2880
  constructor(opts?: LifecycleManagerOpts);
1856
2881
  private getPluginLogger;
2882
+ /**
2883
+ * Boot a set of plugins in dependency order.
2884
+ *
2885
+ * Can be called multiple times (e.g., core plugins first, then dev plugins later).
2886
+ * Already-loaded plugins are included in dependency resolution but not re-booted.
2887
+ */
1857
2888
  boot(plugins: OpenACPPlugin[]): Promise<void>;
2889
+ /**
2890
+ * Unload a single plugin: call teardown(), clean up its context
2891
+ * (listeners, middleware, services), and remove from tracked state.
2892
+ * Used for hot-reload: unload → rebuild → re-boot.
2893
+ */
1858
2894
  unloadPlugin(name: string): Promise<void>;
2895
+ /**
2896
+ * Gracefully shut down all loaded plugins.
2897
+ * Teardown runs in reverse boot order so that dependencies outlive their dependents.
2898
+ */
1859
2899
  shutdown(): Promise<void>;
1860
2900
  }
1861
2901
 
2902
+ /**
2903
+ * Registry for interactive menu items displayed to users (e.g. Telegram inline keyboards).
2904
+ *
2905
+ * Core registers default items (new session, agents, help, etc.) during construction.
2906
+ * Plugins can add their own items. Items are sorted by priority (lower = higher position)
2907
+ * and filtered by an optional `visible()` predicate at render time.
2908
+ */
1862
2909
  declare class MenuRegistry {
1863
2910
  private items;
2911
+ /** Register or replace a menu item by its unique ID. */
1864
2912
  register(item: MenuItem): void;
2913
+ /** Remove a menu item by ID. */
1865
2914
  unregister(id: string): void;
2915
+ /** Look up a single menu item by ID. */
1866
2916
  getItem(id: string): MenuItem | undefined;
1867
- /** Get all visible items sorted by priority */
2917
+ /** Get all visible items sorted by priority (lower number = shown first). */
1868
2918
  getItems(): MenuItem[];
1869
2919
  }
1870
2920
 
2921
+ /** Subset of OpenACPCore methods needed by AssistantManager, avoiding a circular import. */
1871
2922
  interface AssistantManagerCore {
1872
2923
  createSession(params: {
1873
2924
  channelId: string;
@@ -1887,46 +2938,96 @@ interface AssistantManagerCore {
1887
2938
  };
1888
2939
  sessionStore: SessionStore | null;
1889
2940
  }
2941
+ /**
2942
+ * Manages the OpenACP built-in assistant session.
2943
+ *
2944
+ * The assistant is a special session (marked with `isAssistant: true`) that
2945
+ * can answer questions about the running OpenACP system — sessions, agents,
2946
+ * config, etc. Unlike user-created sessions, it is created and managed by
2947
+ * core, and its system prompt is dynamically composed from registry sections
2948
+ * that inject live system state.
2949
+ *
2950
+ * One assistant session exists per channel. The system prompt is built at
2951
+ * spawn time and deferred — it's prepended to the first user message rather
2952
+ * than sent immediately, so the agent receives it alongside real context.
2953
+ */
1890
2954
  declare class AssistantManager {
1891
2955
  private core;
1892
2956
  private registry;
1893
2957
  private sessions;
1894
2958
  private pendingSystemPrompts;
1895
2959
  constructor(core: AssistantManagerCore, registry: AssistantRegistry);
2960
+ /**
2961
+ * Returns the assistant session for a channel, creating one if needed.
2962
+ *
2963
+ * If a persisted assistant session exists in the store, it is reused
2964
+ * (same session ID) to preserve conversation history. The system prompt
2965
+ * is always rebuilt fresh and deferred until the first user message.
2966
+ */
1896
2967
  getOrSpawn(channelId: string, threadId: string): Promise<Session>;
2968
+ /** Returns the active assistant session for a channel, or null if none exists. */
1897
2969
  get(channelId: string): Session | null;
1898
2970
  /**
1899
2971
  * Consume and return any pending system prompt for a channel.
1900
2972
  * Should be prepended to the first real user message.
1901
2973
  */
1902
2974
  consumePendingSystemPrompt(channelId: string): string | undefined;
2975
+ /** Checks whether a given session ID belongs to the built-in assistant. */
1903
2976
  isAssistant(sessionId: string): boolean;
1904
2977
  }
1905
2978
 
2979
+ /**
2980
+ * Describes a single OpenACP instance and all its filesystem paths.
2981
+ *
2982
+ * An "instance" is one running OpenACP server process, identified by a
2983
+ * unique ID. Multiple instances can run on the same machine with different
2984
+ * configs, ports, and data directories. The instance root is typically
2985
+ * `<workspace>/.openacp/`.
2986
+ */
1906
2987
  interface InstanceContext {
2988
+ /** Unique identifier for this instance (UUID). */
1907
2989
  id: string;
2990
+ /** Absolute path to the instance root directory (e.g. `~/my-project/.openacp/`). */
1908
2991
  root: string;
1909
- isGlobal: boolean;
2992
+ /** Pre-resolved paths to all instance files and directories. */
1910
2993
  paths: {
1911
2994
  config: string;
1912
2995
  sessions: string;
1913
2996
  agents: string;
2997
+ /** Shared across all instances — lives under ~/.openacp/cache/. */
1914
2998
  registryCache: string;
1915
2999
  plugins: string;
1916
3000
  pluginsData: string;
1917
3001
  pluginRegistry: string;
1918
3002
  logs: string;
3003
+ /** PID file written by the daemon process to track the running server. */
1919
3004
  pid: string;
3005
+ /** Marker file that indicates the daemon was intentionally started. */
1920
3006
  running: string;
3007
+ /** Written at startup with the API server's port number. */
1921
3008
  apiPort: string;
1922
3009
  apiSecret: string;
3010
+ /** Shared across all instances — lives under ~/.openacp/bin/. */
1923
3011
  bin: string;
1924
3012
  cache: string;
1925
3013
  tunnels: string;
3014
+ /** Shared across all instances — lives under ~/.openacp/agents/. */
1926
3015
  agentsDir: string;
1927
3016
  };
1928
3017
  }
1929
3018
 
3019
+ /**
3020
+ * Top-level orchestrator that wires all OpenACP modules together.
3021
+ *
3022
+ * Responsibilities:
3023
+ * - Registers messaging adapters (Telegram, Slack, SSE, etc.)
3024
+ * - Routes incoming messages to the correct Session (by channel + thread)
3025
+ * - Manages the full session lifecycle: creation, agent switch, archive, shutdown
3026
+ * - Connects agent events to adapter callbacks via SessionBridge
3027
+ * - Accesses plugin-provided services (security, notifications, speech, etc.)
3028
+ * through ServiceRegistry rather than direct references, since plugins
3029
+ * register their services asynchronously during boot
3030
+ */
1930
3031
  declare class OpenACPCore {
1931
3032
  configManager: ConfigManager;
1932
3033
  agentCatalog: AgentCatalog;
@@ -1944,30 +3045,93 @@ declare class OpenACPCore {
1944
3045
  sessionFactory: SessionFactory;
1945
3046
  readonly lifecycleManager: LifecycleManager;
1946
3047
  private agentSwitchHandler;
1947
- readonly instanceContext?: InstanceContext;
3048
+ readonly instanceContext: InstanceContext;
1948
3049
  readonly menuRegistry: MenuRegistry;
1949
3050
  readonly assistantRegistry: AssistantRegistry;
1950
3051
  assistantManager: AssistantManager;
3052
+ /** @throws if the service hasn't been registered by its plugin yet */
1951
3053
  private getService;
3054
+ /** Access control and rate-limiting guard (provided by security plugin). */
1952
3055
  get securityGuard(): SecurityGuard;
3056
+ /** Cross-session notification delivery (provided by notifications plugin). */
1953
3057
  get notificationManager(): NotificationManager;
3058
+ /** File I/O service for agent attachment storage (provided by file-service plugin). */
1954
3059
  get fileService(): FileServiceInterface;
3060
+ /** Text-to-speech / speech-to-text engine (provided by speech plugin). */
1955
3061
  get speechService(): SpeechService;
3062
+ /** Conversation history builder for context injection (provided by context plugin). */
1956
3063
  get contextManager(): ContextManager;
3064
+ /** Per-plugin persistent settings (e.g. API keys). */
1957
3065
  get settingsManager(): SettingsManager | undefined;
1958
- constructor(configManager: ConfigManager, ctx?: InstanceContext);
3066
+ /**
3067
+ * Bootstrap all core subsystems. The boot order matters:
3068
+ * 1. AgentCatalog + AgentManager (agent definitions)
3069
+ * 2. SessionStore + SessionManager (session persistence and lookup)
3070
+ * 3. EventBus (inter-module communication)
3071
+ * 4. SessionFactory (session creation pipeline)
3072
+ * 5. LifecycleManager (plugin infrastructure)
3073
+ * 6. Wire middleware chain into factory + manager
3074
+ * 7. AgentSwitchHandler, config listeners, menu/assistant registries
3075
+ */
3076
+ constructor(configManager: ConfigManager, ctx: InstanceContext);
3077
+ /** Optional tunnel for generating public URLs (code viewer links, etc.). */
1959
3078
  get tunnelService(): TunnelService | undefined;
3079
+ /** Propagate tunnel service to MessageTransformer so it can generate viewer links. */
1960
3080
  set tunnelService(service: TunnelService | undefined);
3081
+ /**
3082
+ * Register a messaging adapter (e.g. Telegram, Slack, SSE).
3083
+ *
3084
+ * Adapters must be registered before `start()`. The adapter name serves as its
3085
+ * channel ID throughout the system — used in session records, bridge keys, and routing.
3086
+ */
1961
3087
  registerAdapter(name: string, adapter: IChannelAdapter): void;
3088
+ /**
3089
+ * Start all registered adapters. Adapters that fail are logged but do not
3090
+ * prevent others from starting. Throws only if ALL adapters fail.
3091
+ */
1962
3092
  start(): Promise<void>;
3093
+ /**
3094
+ * Graceful shutdown: notify users, persist session state, stop adapters.
3095
+ * Agent subprocesses are not explicitly killed — they exit with the parent process.
3096
+ */
1963
3097
  stop(): Promise<void>;
3098
+ /**
3099
+ * Archive a session: delete its adapter topic/thread and cancel the session.
3100
+ *
3101
+ * Only sessions in archivable states (active, cancelled, error) can be archived —
3102
+ * initializing and finished sessions are excluded.
3103
+ * The adapter handles platform-side cleanup (e.g. deleting a Telegram topic).
3104
+ */
1964
3105
  archiveSession(sessionId: string): Promise<{
1965
3106
  ok: true;
1966
3107
  } | {
1967
3108
  ok: false;
1968
3109
  error: string;
1969
3110
  }>;
3111
+ /**
3112
+ * Route an incoming platform message to the appropriate session.
3113
+ *
3114
+ * Flow:
3115
+ * 1. Run `message:incoming` middleware (plugins can modify or block)
3116
+ * 2. SecurityGuard checks user access and per-user session limits
3117
+ * 3. Find session by channel+thread (in-memory lookup, then lazy resume from disk)
3118
+ * 4. For assistant sessions, prepend any deferred system prompt
3119
+ * 5. Emit `message:queued` for SSE clients, then enqueue the prompt on the session
3120
+ *
3121
+ * If no session is found, the user is told to start one with /new.
3122
+ */
1970
3123
  handleMessage(message: IncomingMessage): Promise<void>;
3124
+ /**
3125
+ * Create (or resume) a session with full wiring: agent, adapter thread, bridge, persistence.
3126
+ *
3127
+ * This is the single entry point for session creation. The pipeline:
3128
+ * 1. SessionFactory spawns/resumes the agent process
3129
+ * 2. Adapter creates a thread/topic if requested
3130
+ * 3. Initial session record is persisted (so lazy resume can find it by threadId)
3131
+ * 4. SessionBridge connects agent events to the adapter
3132
+ * 5. For headless sessions (no adapter), fallback event handlers are wired inline
3133
+ * 6. Side effects (usage tracking, tunnel cleanup) are attached
3134
+ */
1971
3135
  createSession(params: {
1972
3136
  channelId: string;
1973
3137
  agentName: string;
@@ -1979,10 +3143,17 @@ declare class OpenACPCore {
1979
3143
  threadId?: string;
1980
3144
  isAssistant?: boolean;
1981
3145
  }): Promise<Session>;
3146
+ /** Convenience wrapper: create a new session with default agent/workspace resolution. */
1982
3147
  handleNewSession(channelId: string, agentName?: string, workspacePath?: string, options?: {
1983
3148
  createThread?: boolean;
1984
3149
  threadId?: string;
1985
3150
  }): Promise<Session>;
3151
+ /**
3152
+ * Adopt an externally-started agent session (e.g. from a CLI `openacp adopt` command).
3153
+ *
3154
+ * Validates that the agent supports resume, checks session limits, avoids duplicates,
3155
+ * then creates a full session via the unified pipeline with resume semantics.
3156
+ */
1986
3157
  adoptSession(agentName: string, agentSessionId: string, cwd: string, channelId?: string): Promise<{
1987
3158
  ok: true;
1988
3159
  sessionId: string;
@@ -1993,7 +3164,9 @@ declare class OpenACPCore {
1993
3164
  error: string;
1994
3165
  message: string;
1995
3166
  }>;
3167
+ /** Start a new chat within the same agent and workspace as the current session's thread. */
1996
3168
  handleNewChat(channelId: string, currentThreadId: string): Promise<Session | null>;
3169
+ /** Create a session and inject conversation context from a prior session or repo. */
1997
3170
  createSessionWithContext(params: {
1998
3171
  channelId: string;
1999
3172
  agentName: string;
@@ -2006,15 +3179,29 @@ declare class OpenACPCore {
2006
3179
  session: Session;
2007
3180
  contextResult: ContextResult | null;
2008
3181
  }>;
3182
+ /** Switch a session's active agent. Delegates to AgentSwitchHandler for state coordination. */
2009
3183
  switchSessionAgent(sessionId: string, toAgent: string): Promise<{
2010
3184
  resumed: boolean;
2011
3185
  }>;
3186
+ /** Find a session by channel+thread, resuming from disk if not in memory. */
2012
3187
  getOrResumeSession(channelId: string, threadId: string): Promise<Session | null>;
3188
+ /** Find a session by ID, resuming from disk if not in memory. */
2013
3189
  getOrResumeSessionById(sessionId: string): Promise<Session | null>;
3190
+ /**
3191
+ * Attach an additional adapter to an existing session (multi-adapter support).
3192
+ *
3193
+ * Creates a thread on the target adapter and connects a SessionBridge so the
3194
+ * session's agent events are forwarded to both the primary and attached adapters.
3195
+ */
2014
3196
  attachAdapter(sessionId: string, adapterId: string): Promise<{
2015
3197
  threadId: string;
2016
3198
  }>;
3199
+ /**
3200
+ * Detach a secondary adapter from a session. The primary adapter (channelId) cannot
3201
+ * be detached. Disconnects the bridge and cleans up thread mappings.
3202
+ */
2017
3203
  detachAdapter(sessionId: string, adapterId: string): Promise<void>;
3204
+ /** Build the platforms map (adapter → thread/topic IDs) for persistence. */
2018
3205
  private buildPlatformsFromSession;
2019
3206
  /** Composite bridge key: "adapterId:sessionId" */
2020
3207
  private bridgeKey;
@@ -2022,11 +3209,20 @@ declare class OpenACPCore {
2022
3209
  private getSessionBridgeKeys;
2023
3210
  /** Connect a session bridge for the given session (used by AssistantManager) */
2024
3211
  connectSessionBridge(session: Session): void;
2025
- /** Create a SessionBridge for the given session and adapter.
2026
- * Disconnects any existing bridge for the same adapter+session first. */
3212
+ /**
3213
+ * Create a SessionBridge for the given session and adapter.
3214
+ *
3215
+ * The bridge subscribes to Session events (agent output, status changes, naming)
3216
+ * and forwards them to the adapter for platform delivery. Disconnects any existing
3217
+ * bridge for the same adapter+session first to avoid duplicate event handlers.
3218
+ */
2027
3219
  createBridge(session: Session, adapter: IChannelAdapter, adapterId?: string): SessionBridge;
2028
3220
  }
2029
3221
 
3222
+ /**
3223
+ * Internal representation of a registered command, extending CommandDef with
3224
+ * a `scope` derived from the plugin name for namespace-qualified lookups.
3225
+ */
2030
3226
  interface RegisteredCommand extends CommandDef {
2031
3227
  /** Scope extracted from pluginName, e.g. '@openacp/speech' → 'speech' */
2032
3228
  scope?: string;
@@ -2041,6 +3237,11 @@ interface RegisteredCommand extends CommandDef {
2041
3237
  * - Adapter plugins (@openacp/telegram, @openacp/discord)
2042
3238
  * registering a command that already exists → stored as an override
2043
3239
  * keyed by `channelId:commandName`, used when channelId matches.
3240
+ *
3241
+ * Note: this registry only handles slash commands (e.g. /new, /session).
3242
+ * Callback button routing (inline keyboard buttons) is handled separately
3243
+ * at the adapter level using a `c/` prefix — it is not part of command dispatch
3244
+ * and does not go through this registry.
2044
3245
  */
2045
3246
  declare class CommandRegistry {
2046
3247
  /** Main registry: short names + qualified names → RegisteredCommand */
@@ -2065,27 +3266,53 @@ declare class CommandRegistry {
2065
3266
  /** Filter commands by category. */
2066
3267
  getByCategory(category: 'system' | 'plugin'): RegisteredCommand[];
2067
3268
  /**
2068
- * Parse and execute a command string.
2069
- * @param commandString - Full command string, e.g. "/greet hello world"
2070
- * @param baseArgs - Base arguments (channelId, userId, etc.)
2071
- * @returns CommandResponse
3269
+ * Parse and execute a command string (e.g. "/greet hello world").
3270
+ *
3271
+ * Resolution order:
3272
+ * 1. Adapter-specific override (e.g. Telegram's version of /new)
3273
+ * 2. Short name or qualified name in the main registry
3274
+ *
3275
+ * Strips Telegram-style bot mentions (e.g. "/help@MyBot" → "help").
3276
+ * Returns `{ type: 'delegated' }` if the handler returns null/undefined
3277
+ * (meaning it handled the response itself, e.g. via assistant).
2072
3278
  */
2073
3279
  execute(commandString: string, baseArgs: CommandArgs): Promise<CommandResponse>;
2074
3280
  /** Extract scope from plugin name: '@openacp/speech' → 'speech', 'my-plugin' → 'my-plugin' */
2075
3281
  static extractScope(pluginName: string): string;
2076
3282
  }
2077
3283
 
3284
+ /**
3285
+ * Type definitions for the doctor diagnostic system.
3286
+ *
3287
+ * The doctor runs a collection of checks, each producing CheckResults
3288
+ * with a severity level. The CLI renders these results and optionally
3289
+ * applies automatic fixes:
3290
+ *
3291
+ * - **pass** — check succeeded, shown as green checkmark
3292
+ * - **warn** — non-critical issue, shown as yellow warning
3293
+ * - **fail** — critical issue that will prevent OpenACP from working
3294
+ *
3295
+ * Fixable results include a `fix()` function with a risk level:
3296
+ * - **safe** — auto-applied without prompting (e.g. creating missing dirs)
3297
+ * - **risky** — requires user confirmation (e.g. resetting corrupted data)
3298
+ */
3299
+
3300
+ /** Result of a single diagnostic check within a category. */
2078
3301
  interface CheckResult {
2079
3302
  status: "pass" | "warn" | "fail";
2080
3303
  message: string;
3304
+ /** Whether this result has an associated automatic fix. */
2081
3305
  fixable?: boolean;
3306
+ /** "safe" fixes are applied automatically; "risky" fixes require user confirmation. */
2082
3307
  fixRisk?: "safe" | "risky";
2083
3308
  fix?: () => Promise<FixResult>;
2084
3309
  }
3310
+ /** Outcome of applying a fix. */
2085
3311
  interface FixResult {
2086
3312
  success: boolean;
2087
3313
  message: string;
2088
3314
  }
3315
+ /** Aggregated report returned by DoctorEngine.runAll(). */
2089
3316
  interface DoctorReport {
2090
3317
  categories: CategoryResult[];
2091
3318
  summary: {
@@ -2094,18 +3321,28 @@ interface DoctorReport {
2094
3321
  failed: number;
2095
3322
  fixed: number;
2096
3323
  };
3324
+ /** Risky fixes that were deferred for user confirmation. */
2097
3325
  pendingFixes: PendingFix[];
2098
3326
  }
3327
+ /** All results for a single check category (e.g. "Config", "Agents"). */
2099
3328
  interface CategoryResult {
2100
3329
  name: string;
2101
3330
  results: CheckResult[];
2102
3331
  }
3332
+ /** A risky fix deferred for interactive user confirmation. */
2103
3333
  interface PendingFix {
2104
3334
  category: string;
2105
3335
  message: string;
2106
3336
  fix: () => Promise<FixResult>;
2107
3337
  }
2108
3338
 
3339
+ /**
3340
+ * Orchestrates diagnostic checks for an OpenACP installation.
3341
+ *
3342
+ * Runs all registered checks in order, collects results, and automatically
3343
+ * applies safe fixes (unless in dry-run mode). Risky fixes are collected
3344
+ * as `pendingFixes` for the CLI to present interactively.
3345
+ */
2109
3346
  declare class DoctorEngine {
2110
3347
  private dryRun;
2111
3348
  private dataDir;
@@ -2113,55 +3350,153 @@ declare class DoctorEngine {
2113
3350
  dryRun?: boolean;
2114
3351
  dataDir?: string;
2115
3352
  });
3353
+ /**
3354
+ * Executes all checks and returns an aggregated report.
3355
+ *
3356
+ * Safe fixes are applied inline (mutating CheckResult.message to show "Fixed").
3357
+ * Risky fixes are deferred to `report.pendingFixes` for user confirmation.
3358
+ */
2116
3359
  runAll(): Promise<DoctorReport>;
3360
+ /** Constructs the shared context used by all checks — loads config if available. */
2117
3361
  private buildContext;
2118
3362
  }
2119
3363
 
3364
+ /**
3365
+ * Config registry — declarative metadata for config fields.
3366
+ *
3367
+ * Each registered field describes its UI type, whether it can be hot-reloaded,
3368
+ * and whether it's safe to expose via the API. This drives:
3369
+ * - The API server's PATCH /api/config endpoint (validates + applies changes)
3370
+ * - Hot-reload: fields marked `hotReload: true` take effect immediately via
3371
+ * ConfigManager's `config:changed` event, without requiring a restart
3372
+ * - Security: only `scope: "safe"` fields are exposed to the REST API
3373
+ */
3374
+
3375
+ /**
3376
+ * Metadata for a single config field, describing how it should be
3377
+ * displayed, validated, and applied at runtime.
3378
+ */
2120
3379
  interface ConfigFieldDef {
3380
+ /** Dot-path into the Config object (e.g. "logging.level"). */
2121
3381
  path: string;
3382
+ /** Human-readable label for UI display. */
2122
3383
  displayName: string;
3384
+ /** Grouping key for organizing fields in the editor (e.g. "agent", "logging"). */
2123
3385
  group: string;
3386
+ /** UI control type — determines validation and rendering. */
2124
3387
  type: "toggle" | "select" | "number" | "string";
3388
+ /** For "select" type: allowed values, either static or derived from current config. */
2125
3389
  options?: string[] | ((config: Config) => string[]);
3390
+ /** "safe" fields can be exposed via the API; "sensitive" fields require direct file access. */
2126
3391
  scope: "safe" | "sensitive";
3392
+ /** Whether changes to this field take effect without restarting the server. */
2127
3393
  hotReload: boolean;
2128
3394
  }
3395
+ /**
3396
+ * Static registry of all config fields that support programmatic access.
3397
+ * Fields not listed here can still exist in config.json but won't be
3398
+ * editable via the API or shown in the config UI.
3399
+ */
2129
3400
  declare const CONFIG_REGISTRY: ConfigFieldDef[];
3401
+ /** Looks up a field definition by its dot-path. */
2130
3402
  declare function getFieldDef(path: string): ConfigFieldDef | undefined;
3403
+ /** Returns only fields safe to expose via the REST API. */
2131
3404
  declare function getSafeFields(): ConfigFieldDef[];
3405
+ /** Checks whether a config field can be changed without restarting the server. */
2132
3406
  declare function isHotReloadable(path: string): boolean;
3407
+ /** Resolves select options — evaluates the function form against the current config if needed. */
2133
3408
  declare function resolveOptions(def: ConfigFieldDef, config: Config): string[] | undefined;
3409
+ /** Traverses a config object by dot-path (e.g. "logging.level") and returns the value. */
2134
3410
  declare function getConfigValue(config: Config, path: string): unknown;
2135
3411
 
3412
+ /**
3413
+ * Launches the interactive config editor.
3414
+ *
3415
+ * In `file` mode, changes accumulate and are written to disk on exit.
3416
+ * In `api` mode, each section's changes are sent to the running daemon
3417
+ * via the REST API, enabling hot-reload without restart.
3418
+ */
2136
3419
  declare function runConfigEditor(configManager: ConfigManager, mode?: 'file' | 'api', apiPort?: number, settingsManager?: SettingsManager): Promise<void>;
2137
3420
 
2138
- declare function getPidPath(root?: string): string;
2139
- declare function getStatus(pidPath?: string): {
3421
+ /** Returns the path to the PID file for the given instance root. */
3422
+ declare function getPidPath(root: string): string;
3423
+ /**
3424
+ * Return the running status and PID of the daemon.
3425
+ * Cleans up stale PID files if the recorded process is no longer alive.
3426
+ */
3427
+ declare function getStatus(pidPath: string): {
2140
3428
  running: boolean;
2141
3429
  pid?: number;
2142
3430
  };
2143
- declare function startDaemon(pidPath?: string, logDir?: string, instanceRoot?: string): {
3431
+ /**
3432
+ * Fork the daemon as a detached background process.
3433
+ *
3434
+ * Spawn mechanics:
3435
+ * - The child runs `node <cli> --daemon-child` with OPENACP_INSTANCE_ROOT in its env.
3436
+ * - `detached: true` creates a new process group so the child survives terminal close.
3437
+ * - stdout/stderr are redirected to the log file (append mode); the parent closes its
3438
+ * copies immediately after spawn so the child holds the only references.
3439
+ * - `child.unref()` removes the child from the parent's event loop reference count,
3440
+ * allowing the parent (`openacp start`) to exit while the child keeps running.
3441
+ * - The PID file is written by both parent (early report) and child (authoritative write
3442
+ * in main.ts startServer) to handle race conditions with launchd/systemd starts.
3443
+ */
3444
+ declare function startDaemon(pidPath: string, logDir: string | undefined, instanceRoot: string): {
2144
3445
  pid: number;
2145
3446
  } | {
2146
3447
  error: string;
2147
3448
  };
2148
- declare function stopDaemon(pidPath?: string, instanceRoot?: string): Promise<{
3449
+ /**
3450
+ * Gracefully stop the daemon, escalating to SIGKILL if it doesn't exit.
3451
+ *
3452
+ * Stop sequence:
3453
+ * 1. Send SIGTERM; poll every 100ms for up to 5 seconds.
3454
+ * 2. If still alive after 5s, send SIGKILL; poll for up to 1 more second.
3455
+ * 3. If still alive after SIGKILL, the process is in uninterruptible I/O — very rare.
3456
+ *
3457
+ * Also clears the "running" marker so the daemon is not restarted on next login.
3458
+ */
3459
+ declare function stopDaemon(pidPath: string, instanceRoot: string): Promise<{
2149
3460
  stopped: boolean;
2150
3461
  pid?: number;
2151
3462
  error?: string;
2152
3463
  }>;
2153
3464
 
3465
+ /** Returns true if the current platform supports auto-start management. */
2154
3466
  declare function isAutoStartSupported(): boolean;
2155
- declare function installAutoStart(logDir: string): {
3467
+ /**
3468
+ * Register the daemon as a login-time service for the given instance.
3469
+ *
3470
+ * macOS: writes a LaunchAgent plist and bootstraps it into the user's GUI session.
3471
+ * Linux: writes a systemd user unit, reloads the daemon, and enables the service.
3472
+ * Runs `migrateLegacy()` first to remove the old single-instance service if present.
3473
+ */
3474
+ declare function installAutoStart(logDir: string, instanceRoot: string, instanceId: string): {
2156
3475
  success: boolean;
2157
3476
  error?: string;
2158
3477
  };
2159
- declare function uninstallAutoStart(): {
3478
+ /**
3479
+ * Remove the login-time service registration for the given instance.
3480
+ * No-op if the service is not installed.
3481
+ */
3482
+ declare function uninstallAutoStart(instanceId: string): {
2160
3483
  success: boolean;
2161
3484
  error?: string;
2162
3485
  };
2163
- declare function isAutoStartInstalled(): boolean;
3486
+ /** Returns true if the login-time service is currently registered for this instance. */
3487
+ declare function isAutoStartInstalled(instanceId: string): boolean;
2164
3488
 
3489
+ /**
3490
+ * Provides file I/O for session attachments and agent-generated files.
3491
+ *
3492
+ * All files are stored under `baseDir/<sessionId>/` so that access is naturally
3493
+ * scoped per session and cleanup is a single directory removal. File names are
3494
+ * sanitized and prefixed with a timestamp to prevent collisions and path traversal.
3495
+ *
3496
+ * This service acts as the security boundary between adapters/agents and the
3497
+ * filesystem — callers never write directly; they go through `saveFile` so that
3498
+ * naming, MIME classification, and path normalization are always applied.
3499
+ */
2165
3500
  declare class FileService {
2166
3501
  readonly baseDir: string;
2167
3502
  constructor(baseDir: string);
@@ -2170,7 +3505,19 @@ declare class FileService {
2170
3505
  * Called on startup to prevent unbounded disk growth.
2171
3506
  */
2172
3507
  cleanupOldFiles(maxAgeDays: number): Promise<number>;
3508
+ /**
3509
+ * Persist a file to the session's directory and return an `Attachment` descriptor.
3510
+ *
3511
+ * The file name is sanitized (non-safe characters replaced with `_`) and prefixed
3512
+ * with a millisecond timestamp to prevent name collisions across multiple saves in
3513
+ * the same session. The original `fileName` is preserved in the returned descriptor
3514
+ * so the user-facing name is not lost.
3515
+ */
2173
3516
  saveFile(sessionId: string, fileName: string, data: Buffer, mimeType: string): Promise<Attachment>;
3517
+ /**
3518
+ * Build an `Attachment` descriptor for a file that already exists on disk.
3519
+ * Returns `null` if the path does not exist or is not a regular file.
3520
+ */
2174
3521
  resolveFile(filePath: string): Promise<Attachment | null>;
2175
3522
  /**
2176
3523
  * Convert OGG Opus audio to WAV format.
@@ -2188,6 +3535,7 @@ declare class FileService {
2188
3535
  }): Promise<string>;
2189
3536
  /** Instance method — delegates to static for FileServiceInterface compliance */
2190
3537
  extensionFromMime(mimeType: string): string;
3538
+ /** Returns the canonical file extension for a given MIME type (e.g. `"image/png"` → `".png"`). */
2191
3539
  static extensionFromMime(mimeType: string): string;
2192
3540
  }
2193
3541
 
@@ -2196,12 +3544,15 @@ interface ApiConfig {
2196
3544
  host: string;
2197
3545
  }
2198
3546
 
3547
+ /** A token record persisted in tokens.json. Tokens are never deleted; they are revoked by flag. */
2199
3548
  interface StoredToken {
2200
3549
  id: string;
2201
3550
  name: string;
2202
3551
  role: string;
3552
+ /** Custom scope overrides; when absent, role defaults from ROLES apply. */
2203
3553
  scopes?: string[];
2204
3554
  createdAt: string;
3555
+ /** Absolute deadline after which the token cannot be refreshed — requires re-authentication. */
2205
3556
  refreshDeadline: string;
2206
3557
  lastUsedAt?: string;
2207
3558
  revoked: boolean;
@@ -2209,14 +3560,20 @@ interface StoredToken {
2209
3560
  interface CreateTokenOpts {
2210
3561
  role: string;
2211
3562
  name: string;
3563
+ /** Duration string, e.g. "24h", "7d". Parsed by parseDuration(). */
2212
3564
  expire: string;
2213
3565
  scopes?: string[];
2214
3566
  }
3567
+ /**
3568
+ * A one-time authorization code stored until exchange.
3569
+ * The code is a 32-char hex string; it becomes unusable after `expiresAt` or first use.
3570
+ */
2215
3571
  interface StoredCode {
2216
3572
  code: string;
2217
3573
  role: string;
2218
3574
  scopes?: string[];
2219
3575
  name: string;
3576
+ /** Duration that the resulting JWT will be valid for. */
2220
3577
  expire: string;
2221
3578
  createdAt: string;
2222
3579
  expiresAt: string;
@@ -2227,9 +3584,21 @@ interface CreateCodeOpts {
2227
3584
  name: string;
2228
3585
  expire: string;
2229
3586
  scopes?: string[];
3587
+ /** How long the code itself is valid; defaults to 30 minutes. */
2230
3588
  codeTtlMs?: number;
2231
3589
  }
2232
3590
 
3591
+ /**
3592
+ * Persists JWT tokens and one-time authorization codes to a JSON file.
3593
+ *
3594
+ * Revocation is flag-based: tokens are marked `revoked: true` rather than deleted,
3595
+ * so the auth middleware can distinguish a revoked token from an unknown one.
3596
+ * Periodic cleanup (via `cleanup()`) removes tokens past their refresh deadline
3597
+ * and expired/used codes to prevent unbounded file growth.
3598
+ *
3599
+ * Saves are asynchronous and coalesced: concurrent mutations schedule a single write
3600
+ * to avoid thundering-herd disk I/O under bursty auth traffic.
3601
+ */
2233
3602
  declare class TokenStore {
2234
3603
  private filePath;
2235
3604
  private tokens;
@@ -2237,64 +3606,150 @@ declare class TokenStore {
2237
3606
  private savePromise;
2238
3607
  private savePending;
2239
3608
  constructor(filePath: string);
3609
+ /** Loads token and code state from disk. Safe to call at startup; missing file is not an error. */
2240
3610
  load(): Promise<void>;
2241
3611
  save(): Promise<void>;
3612
+ /**
3613
+ * Coalesces concurrent writes: if a save is in-flight, sets a pending flag
3614
+ * so the next save fires immediately after the current one completes.
3615
+ */
2242
3616
  private scheduleSave;
3617
+ /** Creates a new token record and schedules a persist. Returns the stored token including its generated id. */
2243
3618
  create(opts: CreateTokenOpts): StoredToken;
2244
3619
  get(id: string): StoredToken | undefined;
3620
+ /** Marks a token as revoked; future auth checks will reject it immediately. */
2245
3621
  revoke(id: string): void;
3622
+ /** Returns all non-revoked tokens. Revoked tokens are retained until cleanup() removes them. */
2246
3623
  list(): StoredToken[];
2247
3624
  private lastUsedSaveTimer;
3625
+ /**
3626
+ * Records the current timestamp as `lastUsedAt` for the given token.
3627
+ *
3628
+ * Writes are debounced to 60 seconds — every API request updates this field,
3629
+ * so flushing on every call would cause excessive disk I/O.
3630
+ */
2248
3631
  updateLastUsed(id: string): void;
2249
3632
  /** Wait for any in-flight and pending saves to complete */
2250
3633
  flush(): Promise<void>;
2251
3634
  destroy(): void;
3635
+ /**
3636
+ * Generates a one-time authorization code that can be exchanged for a JWT.
3637
+ *
3638
+ * Used for the CLI login flow: the server emits a code that the user copies into
3639
+ * the App, which exchanges it for a proper JWT without ever exposing the raw API secret.
3640
+ */
2252
3641
  createCode(opts: CreateCodeOpts): StoredCode;
2253
3642
  getCode(code: string): StoredCode | undefined;
3643
+ /**
3644
+ * Atomically marks a code as used and returns it.
3645
+ *
3646
+ * Returns undefined if the code is unknown, already used, or expired.
3647
+ * The one-time-use flag is set before returning, so concurrent calls for the
3648
+ * same code will only succeed once.
3649
+ */
2254
3650
  exchangeCode(code: string): StoredCode | undefined;
2255
3651
  listCodes(): StoredCode[];
2256
3652
  revokeCode(code: string): void;
3653
+ /**
3654
+ * Removes tokens past their refresh deadline and expired/used codes.
3655
+ *
3656
+ * Called on a 1-hour interval from the plugin setup to prevent unbounded file growth.
3657
+ * Tokens within their refresh deadline are retained even if revoked, so that the
3658
+ * "token revoked" error can be returned instead of "token unknown".
3659
+ */
2257
3660
  cleanup(): void;
2258
3661
  }
2259
3662
 
2260
3663
  interface ApiServerOptions {
2261
3664
  port: number;
2262
3665
  host: string;
3666
+ /** Returns the raw API secret used for full-admin Bearer auth. Fetched lazily so rotation is safe. */
2263
3667
  getSecret: () => string;
3668
+ /** Returns the HMAC secret used to sign and verify JWTs. */
2264
3669
  getJwtSecret: () => string;
2265
3670
  tokenStore: TokenStore;
2266
3671
  logger?: boolean;
2267
3672
  }
3673
+ /**
3674
+ * The handle returned by `createApiServer`.
3675
+ *
3676
+ * Route plugins are registered before `start()` is called; Fastify's plugin
3677
+ * encapsulation keeps each prefix isolated with its own auth hooks.
3678
+ */
2268
3679
  interface ApiServerInstance {
2269
3680
  app: FastifyInstance;
3681
+ /** Binds to the configured host/port, auto-incrementing if EADDRINUSE. */
2270
3682
  start(): Promise<{
2271
3683
  port: number;
2272
3684
  host: string;
2273
3685
  }>;
2274
3686
  stop(): Promise<void>;
3687
+ /**
3688
+ * Registers a Fastify plugin scoped to `prefix`.
3689
+ *
3690
+ * Auth is applied by default. Pass `{ auth: false }` only for public endpoints
3691
+ * (e.g. `/api/v1/system` health, `/api/v1/auth/exchange`).
3692
+ */
2275
3693
  registerPlugin(prefix: string, plugin: FastifyPluginAsync, opts?: {
2276
3694
  auth?: boolean;
2277
3695
  }): void;
2278
3696
  }
3697
+ /**
3698
+ * Creates and configures the Fastify HTTP server.
3699
+ *
3700
+ * Sets up in order: Zod validation, CORS, rate limiting, Swagger docs,
3701
+ * backward-compat redirects (`/api/*` → `/api/v1/*`), and the global error handler.
3702
+ * Route plugins are registered after this returns via `ApiServerInstance.registerPlugin`.
3703
+ */
2279
3704
  declare function createApiServer(options: ApiServerOptions): Promise<ApiServerInstance>;
2280
3705
 
3706
+ /**
3707
+ * The `api-server` service interface exposed to other plugins via ServiceRegistry.
3708
+ *
3709
+ * Other plugins (e.g. SSE adapter, tunnel plugin) use this to register their own
3710
+ * Fastify route plugins under the shared HTTP server without needing a direct
3711
+ * reference to the Fastify instance.
3712
+ */
2281
3713
  interface ApiServerService {
3714
+ /** Register a Fastify plugin at the given prefix, with optional auth bypass. */
2282
3715
  registerPlugin(prefix: string, plugin: FastifyPluginAsync, opts?: {
2283
3716
  auth?: boolean;
2284
3717
  }): void;
3718
+ /** Pre-handler that validates Bearer/JWT auth — attach to routes that need custom auth. */
2285
3719
  authPreHandler: preHandlerHookHandler;
3720
+ /** Creates a pre-handler that requires all listed scopes. */
2286
3721
  requireScopes(...scopes: string[]): preHandlerHookHandler;
3722
+ /** Creates a pre-handler that requires a minimum role level. */
2287
3723
  requireRole(role: string): preHandlerHookHandler;
2288
3724
  getPort(): number;
2289
3725
  getBaseUrl(): string;
3726
+ /** Returns the public tunnel URL, or null if no tunnel is active. */
2290
3727
  getTunnelUrl(): string | null;
2291
3728
  }
3729
+ /**
3730
+ * Wraps an `ApiServerInstance` as a service object registered in the ServiceRegistry.
3731
+ *
3732
+ * The getPort/getBaseUrl/getTunnelUrl callbacks are evaluated lazily so callers always
3733
+ * get the current values (port is only known after the server starts listening).
3734
+ */
2292
3735
  declare function createApiServerService(server: ApiServerInstance, getPort: () => number, getBaseUrl: () => string, getTunnelUrl: () => string | null, authPreHandler: preHandlerHookHandler): ApiServerService;
2293
3736
 
2294
3737
  interface SessionStats {
2295
3738
  active: number;
2296
3739
  total: number;
2297
3740
  }
3741
+ /**
3742
+ * Manages Server-Sent Events (SSE) connections and broadcasts OpenACP bus events to clients.
3743
+ *
3744
+ * SSE is used instead of WebSockets because:
3745
+ * - It works natively through HTTP/1.1 proxies (Cloudflare, nginx) without upgrade negotiation.
3746
+ * - The communication is unidirectional (server → client); clients send commands via REST.
3747
+ * - It requires no additional dependencies and is built into all modern browsers.
3748
+ *
3749
+ * Clients can subscribe to a specific session by passing `?sessionId=` to filter the stream.
3750
+ * Health heartbeats are sent every 15 seconds to keep the connection alive through idle-timeout
3751
+ * proxies and to deliver periodic memory/session stats to the dashboard.
3752
+ */
2298
3753
  declare class SSEManager {
2299
3754
  private eventBus;
2300
3755
  private getSessionStats;
@@ -2304,24 +3759,74 @@ declare class SSEManager {
2304
3759
  private healthInterval?;
2305
3760
  private boundHandlers;
2306
3761
  constructor(eventBus: EventBus | undefined, getSessionStats: () => SessionStats, startedAt: number);
3762
+ /**
3763
+ * Subscribes to EventBus events and starts the health heartbeat interval.
3764
+ *
3765
+ * Must be called after the HTTP server is listening, not during plugin setup —
3766
+ * before `setup()` runs, no EventBus listeners are registered, so any events
3767
+ * emitted in the interim are silently missed.
3768
+ */
2307
3769
  setup(): void;
3770
+ /**
3771
+ * Handles an incoming SSE request by upgrading the HTTP response to an event stream.
3772
+ *
3773
+ * The response is kept open indefinitely; cleanup runs when the client disconnects.
3774
+ * An initial `: connected` comment is written immediately so proxies and browsers
3775
+ * flush the response headers before the first real event arrives.
3776
+ */
2308
3777
  handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void;
3778
+ /**
3779
+ * Broadcasts an event to all connected SSE clients.
3780
+ *
3781
+ * Session-scoped events (agent_event, permission_request, etc.) are filtered per-connection:
3782
+ * a client that subscribed with `?sessionId=X` only receives events for session X.
3783
+ * Global events (session_created, health) are delivered to every client.
3784
+ */
2309
3785
  broadcast(event: string, data: unknown): void;
2310
3786
  /**
2311
3787
  * Returns a Fastify route handler that hijacks the response
2312
3788
  * and delegates to the raw http SSE handler.
2313
3789
  */
2314
3790
  createFastifyHandler(): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
3791
+ /**
3792
+ * Stops the heartbeat, removes all EventBus listeners, and closes all open SSE connections.
3793
+ *
3794
+ * Only listeners registered by this instance are removed — other consumers on the same
3795
+ * EventBus are unaffected.
3796
+ */
2315
3797
  stop(): void;
2316
3798
  }
2317
3799
 
3800
+ /**
3801
+ * Serves the bundled OpenACP App (a Vite SPA) from the local filesystem.
3802
+ *
3803
+ * Two directory layouts are probed at startup to support both the development
3804
+ * build (`ui/dist/`) and the npm publish layout (`../ui/`). If neither exists,
3805
+ * `isAvailable()` returns false and the server skips static file serving.
3806
+ *
3807
+ * Path traversal is blocked in two stages:
3808
+ * 1. String prefix check before resolving symlinks (catches obvious `..` segments).
3809
+ * 2. `realpathSync` check after resolution (catches symlinks that escape the UI dir).
3810
+ *
3811
+ * Vite content-hashed assets (`*.{hash}.js/css`) receive a 1-year immutable cache.
3812
+ * All other files use `no-cache` so updates roll out on the next page refresh.
3813
+ *
3814
+ * Non-asset routes fall through to `index.html` (SPA client-side routing).
3815
+ */
2318
3816
  declare class StaticServer {
2319
3817
  private uiDir;
2320
3818
  constructor(uiDir?: string);
3819
+ /** Returns true if a UI build was found and static serving is active. */
2321
3820
  isAvailable(): boolean;
3821
+ /**
3822
+ * Attempts to serve a static file or SPA fallback for the given request.
3823
+ *
3824
+ * @returns true if the response was handled, false if the caller should return a 404.
3825
+ */
2322
3826
  serve(req: http.IncomingMessage, res: http.ServerResponse): boolean;
2323
3827
  }
2324
3828
 
3829
+ /** Flat view of a session record, enriched with its Telegram topic ID. */
2325
3830
  interface TopicInfo {
2326
3831
  sessionId: string;
2327
3832
  topicId: number | null;
@@ -2330,8 +3835,10 @@ interface TopicInfo {
2330
3835
  agentName: string;
2331
3836
  lastActiveAt: string;
2332
3837
  }
3838
+ /** Result of a single-topic deletion operation. */
2333
3839
  interface DeleteTopicResult {
2334
3840
  ok: boolean;
3841
+ /** True when deletion requires explicit confirmation (session is still active). */
2335
3842
  needsConfirmation?: boolean;
2336
3843
  topicId?: number | null;
2337
3844
  session?: {
@@ -2341,6 +3848,7 @@ interface DeleteTopicResult {
2341
3848
  };
2342
3849
  error?: string;
2343
3850
  }
3851
+ /** Aggregate result for a bulk cleanup operation. */
2344
3852
  interface CleanupResult {
2345
3853
  deleted: string[];
2346
3854
  failed: {
@@ -2352,21 +3860,54 @@ interface SystemTopicIds {
2352
3860
  notificationTopicId: number | null;
2353
3861
  assistantTopicId: number | null;
2354
3862
  }
3863
+ /**
3864
+ * High-level topic management for the Telegram adapter.
3865
+ *
3866
+ * Sits above the raw Telegram API (`topics.ts`) and adds session-level concerns:
3867
+ * guarding system topics, requiring confirmation for active sessions, and
3868
+ * coordinating with the SessionManager to remove session records after deletion.
3869
+ */
2355
3870
  declare class TopicManager {
2356
3871
  private sessionManager;
2357
3872
  private adapter;
2358
3873
  private systemTopicIds;
2359
3874
  constructor(sessionManager: SessionManager, adapter: IChannelAdapter | null, systemTopicIds: SystemTopicIds);
3875
+ /**
3876
+ * List user-facing session topics, excluding system topics.
3877
+ * Optionally filtered to specific status values.
3878
+ */
2360
3879
  listTopics(filter?: {
2361
3880
  statuses?: string[];
2362
3881
  }): TopicInfo[];
3882
+ /**
3883
+ * Delete a session topic and its session record.
3884
+ *
3885
+ * Returns `needsConfirmation: true` when the session is still active and
3886
+ * `options.confirmed` was not set — callers must ask the user before proceeding.
3887
+ */
2363
3888
  deleteTopic(sessionId: string, options?: {
2364
3889
  confirmed?: boolean;
2365
3890
  }): Promise<DeleteTopicResult>;
3891
+ /**
3892
+ * Bulk-delete topics by status (default: finished, error, cancelled).
3893
+ * Active/initializing sessions are cancelled before deletion to prevent orphaned processes.
3894
+ */
2366
3895
  cleanup(statuses?: string[]): Promise<CleanupResult>;
2367
3896
  private isSystemTopic;
2368
3897
  }
2369
3898
 
3899
+ /**
3900
+ * Context provider backed by Claude Code checkpoint data stored in the git repo.
3901
+ *
3902
+ * Only available when the repo has an `origin/entire/checkpoints/v1` branch,
3903
+ * which is pushed by the Entire Claude Code extension. Supports all query types
3904
+ * (branch, commit, PR, session, latest) by reading checkpoint metadata and
3905
+ * reconstructing conversation turns from JSONL transcripts.
3906
+ *
3907
+ * Used as a lower-priority fallback after HistoryProvider: the live history
3908
+ * recorder takes precedence for sessions still in memory, while this provider
3909
+ * covers older sessions or cross-branch queries.
3910
+ */
2370
3911
  declare class EntireProvider implements ContextProvider {
2371
3912
  readonly name = "entire";
2372
3913
  isAvailable(repoPath: string): Promise<boolean>;
@@ -2377,26 +3918,43 @@ declare class EntireProvider implements ContextProvider {
2377
3918
  private buildTitle;
2378
3919
  }
2379
3920
 
3921
+ /**
3922
+ * A rendered message ready for platform delivery.
3923
+ * The `format` field tells the adapter how to interpret the body
3924
+ * (e.g., pass as HTML to Telegram, or as plain text to SSE).
3925
+ */
2380
3926
  interface RenderedMessage<TComponents = unknown> {
2381
3927
  body: string;
2382
3928
  format: "html" | "markdown" | "plain" | "structured";
2383
3929
  attachments?: RenderedAttachment[];
3930
+ /** Platform-specific UI components (e.g., Telegram inline keyboards). */
2384
3931
  components?: TComponents;
2385
3932
  }
3933
+ /** A rendered permission request with approve/deny action buttons. */
2386
3934
  interface RenderedPermission<TComponents = unknown> extends RenderedMessage<TComponents> {
2387
3935
  actions: RenderedAction[];
2388
3936
  }
3937
+ /** A single action button for permission requests. */
2389
3938
  interface RenderedAction {
2390
3939
  id: string;
2391
3940
  label: string;
2392
3941
  isAllow?: boolean;
2393
3942
  }
3943
+ /** A file, image, or audio attachment to include with a rendered message. */
2394
3944
  interface RenderedAttachment {
2395
3945
  type: "file" | "image" | "audio";
2396
3946
  data: Buffer | string;
2397
3947
  mimeType?: string;
2398
3948
  filename?: string;
2399
3949
  }
3950
+ /**
3951
+ * Rendering contract for platform-specific message formatting.
3952
+ *
3953
+ * Each adapter provides its own IRenderer implementation (e.g., TelegramRenderer
3954
+ * outputs HTML, a CLI renderer might output ANSI). Required methods handle the
3955
+ * core message types; optional methods handle less common types and fall back
3956
+ * to no-ops if not implemented.
3957
+ */
2400
3958
  interface IRenderer {
2401
3959
  renderText(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2402
3960
  renderToolCall(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
@@ -2417,7 +3975,11 @@ interface IRenderer {
2417
3975
  renderResourceLink?(content: OutgoingMessage): RenderedMessage;
2418
3976
  }
2419
3977
  /**
2420
- * BaseRenderer plain text defaults. Extend for platform-specific rendering.
3978
+ * Default renderer producing plain-text output for all message types.
3979
+ *
3980
+ * Platform-specific renderers (e.g., TelegramRenderer) extend this class
3981
+ * and override methods to produce HTML, markdown, or structured output.
3982
+ * Methods not overridden fall back to these plain-text defaults.
2421
3983
  */
2422
3984
  declare class BaseRenderer implements IRenderer {
2423
3985
  renderText(content: OutgoingMessage): RenderedMessage;
@@ -2436,28 +3998,60 @@ declare class BaseRenderer implements IRenderer {
2436
3998
  renderResourceLink(content: OutgoingMessage): RenderedMessage;
2437
3999
  }
2438
4000
 
4001
+ /** Runtime services available to adapters via dependency injection. */
2439
4002
  interface AdapterContext {
2440
4003
  configManager: {
2441
4004
  get(): Record<string, unknown>;
2442
4005
  };
2443
4006
  fileService?: unknown;
2444
4007
  }
4008
+ /** Configuration for adapters that extend MessagingAdapter. */
2445
4009
  interface MessagingAdapterConfig extends ChannelConfig {
4010
+ /** Platform-imposed limit on a single message body (e.g., 4096 for Telegram). */
2446
4011
  maxMessageLength: number;
4012
+ /** How often (ms) to flush buffered streaming text to the platform. */
2447
4013
  flushInterval?: number;
4014
+ /** Minimum interval (ms) between consecutive send-queue operations. */
2448
4015
  sendInterval?: number;
4016
+ /** How often (ms) to refresh the typing indicator during agent thinking. */
2449
4017
  thinkingRefreshInterval?: number;
4018
+ /** Max duration (ms) to show the typing indicator before auto-dismissing. */
2450
4019
  thinkingDuration?: number;
4020
+ /** Default output verbosity for this adapter (can be overridden per-session). */
2451
4021
  displayVerbosity?: DisplayVerbosity;
2452
4022
  }
4023
+ /**
4024
+ * Abstract base class for platform-specific messaging adapters (Telegram, Slack, etc.).
4025
+ *
4026
+ * Provides a dispatch pipeline: incoming OutgoingMessage -> verbosity filter -> type-based
4027
+ * handler. Subclasses override the `handle*` methods to implement platform-specific rendering
4028
+ * and delivery. The base implementations are no-ops, so subclasses only override what they need.
4029
+ */
2453
4030
  declare abstract class MessagingAdapter implements IChannelAdapter {
2454
4031
  protected context: AdapterContext;
2455
4032
  protected adapterConfig: MessagingAdapterConfig;
2456
4033
  abstract readonly name: string;
4034
+ /** Platform-specific renderer that converts OutgoingMessage to formatted output. */
2457
4035
  abstract readonly renderer: IRenderer;
4036
+ /**
4037
+ * Declares what this adapter can do. The platform-specific subclass sets these flags,
4038
+ * and core/stream-adapter use them to decide how to route and format output — e.g.,
4039
+ * whether to stream text in-place (streaming), send to threads/topics (threads),
4040
+ * render markdown (richFormatting), upload files (fileUpload), or play audio (voice).
4041
+ */
2458
4042
  abstract readonly capabilities: AdapterCapabilities;
2459
4043
  constructor(context: AdapterContext, adapterConfig: MessagingAdapterConfig);
4044
+ /**
4045
+ * Entry point for all outbound messages from sessions to the platform.
4046
+ * Resolves the current verbosity, filters messages that should be hidden,
4047
+ * then dispatches to the appropriate type-specific handler.
4048
+ */
2460
4049
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
4050
+ /**
4051
+ * Routes a message to its type-specific handler.
4052
+ * Subclasses can override this for custom dispatch logic, but typically
4053
+ * override individual handle* methods instead.
4054
+ */
2461
4055
  protected dispatchMessage(sessionId: string, content: OutgoingMessage, verbosity: DisplayVerbosity): Promise<void>;
2462
4056
  protected handleText(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2463
4057
  protected handleThought(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
@@ -2475,49 +4069,106 @@ declare abstract class MessagingAdapter implements IChannelAdapter {
2475
4069
  protected handleUserReplay(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2476
4070
  protected handleResource(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2477
4071
  protected handleResourceLink(_sessionId: string, _content: OutgoingMessage): Promise<void>;
4072
+ /**
4073
+ * Resolves the current output verbosity by checking (in priority order):
4074
+ * per-channel config, global config, then adapter default. Falls back to "medium".
4075
+ */
2478
4076
  protected getVerbosity(): DisplayVerbosity;
4077
+ /**
4078
+ * Determines whether a message should be displayed at the given verbosity.
4079
+ *
4080
+ * Noise filtering: tool calls matching noise rules (e.g., `ls`, `glob`, `grep`)
4081
+ * are hidden at medium/low verbosity to reduce clutter. Thoughts and usage
4082
+ * stats are hidden entirely at "low".
4083
+ */
2479
4084
  protected shouldDisplay(content: OutgoingMessage, verbosity: DisplayVerbosity): boolean;
4085
+ /** Initializes the adapter (e.g., connect to platform API, start polling). */
2480
4086
  abstract start(): Promise<void>;
4087
+ /** Gracefully shuts down the adapter and releases resources. */
2481
4088
  abstract stop(): Promise<void>;
4089
+ /** Creates a platform-specific thread/topic for a session. Returns the thread ID. */
2482
4090
  abstract createSessionThread(sessionId: string, name: string): Promise<string>;
4091
+ /** Renames an existing session thread/topic on the platform. */
2483
4092
  abstract renameSessionThread(sessionId: string, newName: string): Promise<void>;
4093
+ /** Sends a permission request to the user with approve/deny actions. */
2484
4094
  abstract sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
4095
+ /** Sends a cross-session notification (e.g., session completed, budget warning). */
2485
4096
  abstract sendNotification(notification: NotificationMessage): Promise<void>;
2486
4097
  }
2487
4098
 
4099
+ /** A structured event emitted over the stream (SSE, WebSocket, etc.). */
2488
4100
  interface StreamEvent {
2489
4101
  type: string;
2490
4102
  sessionId?: string;
2491
4103
  payload: unknown;
2492
4104
  timestamp: number;
2493
4105
  }
4106
+ /**
4107
+ * Base class for stream-based adapters (SSE, WebSocket) that push events
4108
+ * directly to connected clients rather than rendering messages on a platform.
4109
+ *
4110
+ * Unlike MessagingAdapter (which renders and sends formatted messages),
4111
+ * StreamAdapter wraps each outgoing message as a StreamEvent and emits it
4112
+ * to all connections watching that session. The client is responsible for
4113
+ * rendering.
4114
+ */
2494
4115
  declare abstract class StreamAdapter implements IChannelAdapter {
2495
4116
  abstract readonly name: string;
2496
4117
  capabilities: AdapterCapabilities;
2497
4118
  constructor(config?: Partial<AdapterCapabilities>);
4119
+ /** Wraps the outgoing message as a StreamEvent and emits it to the session's listeners. */
2498
4120
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
4121
+ /** Emits a permission request event so the client can render approve/deny UI. */
2499
4122
  sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
4123
+ /** Broadcasts a notification to all connected clients (not scoped to a session). */
2500
4124
  sendNotification(notification: NotificationMessage): Promise<void>;
4125
+ /**
4126
+ * No-op for stream adapters — threads are a platform concept (Telegram topics, Slack threads).
4127
+ * Stream clients manage their own session UI.
4128
+ */
2501
4129
  createSessionThread(_sessionId: string, _name: string): Promise<string>;
4130
+ /** Emits a rename event so connected clients can update their session title. */
2502
4131
  renameSessionThread(sessionId: string, name: string): Promise<void>;
4132
+ /** Sends an event to all connections watching a specific session. */
2503
4133
  protected abstract emit(sessionId: string, event: StreamEvent): Promise<void>;
4134
+ /** Sends an event to all connected clients regardless of session. */
2504
4135
  protected abstract broadcast(event: StreamEvent): Promise<void>;
2505
4136
  abstract start(): Promise<void>;
2506
4137
  abstract stop(): Promise<void>;
2507
4138
  }
2508
4139
 
4140
+ /**
4141
+ * Item type determines deduplication behavior:
4142
+ * - `"text"` items with the same key replace each other (only latest is sent)
4143
+ * - `"other"` items are always queued individually
4144
+ */
2509
4145
  type QueueItemType = 'text' | 'other';
4146
+ /** Configuration for the SendQueue rate limiter. */
2510
4147
  interface SendQueueConfig {
4148
+ /** Minimum interval (ms) between consecutive operations. */
2511
4149
  minInterval: number;
4150
+ /** Per-category interval overrides (e.g., separate limits for edits vs. sends). */
2512
4151
  categoryIntervals?: Record<string, number>;
2513
4152
  onRateLimited?: () => void;
2514
4153
  onError?: (error: Error) => void;
2515
4154
  }
4155
+ /** Options for enqueuing an operation. */
2516
4156
  interface EnqueueOptions {
2517
4157
  type?: QueueItemType;
4158
+ /** Deduplication key — text items with the same key replace earlier ones. */
2518
4159
  key?: string;
4160
+ /** Category for per-category rate limiting. */
2519
4161
  category?: string;
2520
4162
  }
4163
+ /**
4164
+ * Serializes outbound platform API calls to respect rate limits and maintain ordering.
4165
+ *
4166
+ * Key behaviors:
4167
+ * - Enforces a minimum interval between consecutive operations
4168
+ * - Supports per-category intervals (e.g., different limits for message edits vs. sends)
4169
+ * - Deduplicates text-type items with the same key (only the latest update is sent)
4170
+ * - On rate limit, drops all pending text items to reduce backlog
4171
+ */
2521
4172
  declare class SendQueue {
2522
4173
  private config;
2523
4174
  private items;
@@ -2526,87 +4177,166 @@ declare class SendQueue {
2526
4177
  private lastCategoryExec;
2527
4178
  constructor(config: SendQueueConfig);
2528
4179
  get pending(): number;
4180
+ /**
4181
+ * Queues an async operation for rate-limited execution.
4182
+ *
4183
+ * For text-type items with a key, replaces any existing queued item with
4184
+ * the same key (deduplication). This is used for streaming draft updates
4185
+ * where only the latest content matters.
4186
+ */
2529
4187
  enqueue<T>(fn: () => Promise<T>, opts?: EnqueueOptions): Promise<T | undefined>;
4188
+ /**
4189
+ * Called when a platform rate limit is hit. Drops all pending text items
4190
+ * (draft updates) to reduce backlog, keeping only non-text items that
4191
+ * represent important operations (e.g., permission requests).
4192
+ */
2530
4193
  onRateLimited(): void;
2531
4194
  clear(): void;
4195
+ /**
4196
+ * Schedules the next item for processing after the rate-limit delay.
4197
+ * Uses per-category timing when available, falling back to the global minInterval.
4198
+ */
2532
4199
  private scheduleProcess;
2533
4200
  private getInterval;
2534
4201
  private processNext;
2535
4202
  }
2536
4203
 
4204
+ /** Configuration for draft message flushing behavior. */
2537
4205
  interface DraftConfig {
4206
+ /** How often (ms) to flush buffered text to the platform. */
2538
4207
  flushInterval: number;
2539
4208
  maxLength: number;
4209
+ /**
4210
+ * Called to send or update a message on the platform.
4211
+ * Returns the platform message ID on first send (isEdit=false);
4212
+ * subsequent calls are edits (isEdit=true).
4213
+ */
2540
4214
  onFlush: (sessionId: string, text: string, isEdit: boolean) => Promise<string | undefined>;
2541
4215
  onError?: (sessionId: string, error: Error) => void;
2542
4216
  }
4217
+ /**
4218
+ * Manages a single in-progress (draft) message for streaming text output.
4219
+ *
4220
+ * As text chunks arrive from the agent, they are appended to an internal buffer.
4221
+ * The buffer is flushed to the platform on a timer. The first flush creates the
4222
+ * message; subsequent flushes edit it in place. This avoids sending many small
4223
+ * messages and instead shows a live-updating message.
4224
+ */
2543
4225
  declare class Draft {
2544
4226
  private sessionId;
2545
4227
  private config;
2546
4228
  private buffer;
2547
4229
  private _messageId?;
4230
+ /** Guards against concurrent first-flush — ensures only one sendMessage creates the draft. */
2548
4231
  private firstFlushPending;
2549
4232
  private flushTimer?;
2550
4233
  private flushPromise;
2551
4234
  constructor(sessionId: string, config: DraftConfig);
2552
4235
  get isEmpty(): boolean;
4236
+ /** Platform message ID, set after the first successful flush. */
2553
4237
  get messageId(): string | undefined;
4238
+ /** Appends streaming text to the buffer and schedules a flush. */
2554
4239
  append(text: string): void;
4240
+ /**
4241
+ * Flushes any remaining buffered text and returns the platform message ID.
4242
+ * Called when the streaming response completes.
4243
+ */
2555
4244
  finalize(): Promise<string | undefined>;
4245
+ /** Discards buffered text and cancels any pending flush. */
2556
4246
  destroy(): void;
2557
4247
  private scheduleFlush;
2558
4248
  private flush;
2559
4249
  }
4250
+ /**
4251
+ * Manages draft messages across multiple sessions.
4252
+ *
4253
+ * Each session gets at most one active draft. When a session's streaming
4254
+ * response completes, the draft is finalized (final flush + cleanup).
4255
+ */
2560
4256
  declare class DraftManager {
2561
4257
  private config;
2562
4258
  private drafts;
2563
4259
  constructor(config: DraftConfig);
4260
+ /** Returns the existing draft for a session, or creates a new one. */
2564
4261
  getOrCreate(sessionId: string): Draft;
4262
+ /** Finalizes and removes the draft for a session. */
2565
4263
  finalize(sessionId: string): Promise<void>;
4264
+ /** Finalizes all active drafts (e.g., during adapter shutdown). */
2566
4265
  finalizeAll(): Promise<void>;
4266
+ /** Destroys a draft without flushing (e.g., on session error). */
2567
4267
  destroy(sessionId: string): void;
4268
+ /** Destroys all drafts without flushing. */
2568
4269
  destroyAll(): void;
2569
4270
  }
2570
4271
 
4272
+ /** A tool call associated with the platform message ID where it is displayed. */
2571
4273
  interface TrackedToolCall extends ToolCallMeta {
4274
+ /** Platform message ID of the tool card message, used for in-place updates. */
2572
4275
  messageId: string;
2573
4276
  }
4277
+ /**
4278
+ * Tracks active tool calls per session, associating each with the platform
4279
+ * message that displays it. Used by adapters that render individual tool
4280
+ * cards and need to update them in-place when tool status changes.
4281
+ */
2574
4282
  declare class ToolCallTracker {
2575
4283
  private sessions;
4284
+ /** Registers a new tool call and associates it with its platform message ID. */
2576
4285
  track(sessionId: string, meta: ToolCallMeta, messageId: string): void;
4286
+ /** Updates a tracked tool call's status and optional metadata. Returns null if not found. */
2577
4287
  update(sessionId: string, toolId: string, status: string, patch?: Partial<Pick<ToolCallMeta, 'viewerLinks' | 'viewerFilePath' | 'name' | 'kind'>>): TrackedToolCall | null;
4288
+ /** Returns all tracked tool calls for a session (regardless of status). */
2578
4289
  getActive(sessionId: string): TrackedToolCall[];
4290
+ /** Removes all tracked tool calls for a session (called at turn end). */
2579
4291
  clear(sessionId: string): void;
2580
4292
  clearAll(): void;
2581
4293
  }
2582
4294
 
4295
+ /** Timing configuration for the thinking indicator lifecycle. */
2583
4296
  interface ActivityConfig {
4297
+ /** How often (ms) to refresh the typing indicator (e.g., re-send "typing..." action). */
2584
4298
  thinkingRefreshInterval: number;
4299
+ /** Maximum duration (ms) before auto-dismissing the indicator to avoid stale UI. */
2585
4300
  maxThinkingDuration: number;
2586
4301
  }
4302
+ /** Platform-specific callbacks for showing/updating/removing typing indicators. */
2587
4303
  interface ActivityCallbacks {
2588
4304
  sendThinkingIndicator(): Promise<void>;
2589
4305
  updateThinkingIndicator(): Promise<void>;
2590
4306
  removeThinkingIndicator(): Promise<void>;
2591
4307
  }
4308
+ /**
4309
+ * Manages typing/thinking indicators across sessions.
4310
+ *
4311
+ * When the agent starts processing, a typing indicator is shown. It is
4312
+ * periodically refreshed (platforms like Telegram expire typing status after
4313
+ * ~5 seconds) and auto-dismissed either when text output begins or when
4314
+ * the max thinking duration is reached.
4315
+ */
2592
4316
  declare class ActivityTracker {
2593
4317
  private config;
2594
4318
  private sessions;
2595
4319
  constructor(config: ActivityConfig);
4320
+ /** Shows the typing indicator and starts the periodic refresh timer. */
2596
4321
  onThinkingStart(sessionId: string, callbacks: ActivityCallbacks): void;
4322
+ /** Dismisses the typing indicator when the agent starts producing text output. */
2597
4323
  onTextStart(sessionId: string): void;
4324
+ /** Cleans up the typing indicator when the session ends. */
2598
4325
  onSessionEnd(sessionId: string): void;
4326
+ /** Cleans up all sessions (e.g., during adapter shutdown). */
2599
4327
  destroy(): void;
2600
4328
  private cleanup;
2601
4329
  private startRefresh;
2602
4330
  private stopRefresh;
2603
4331
  }
2604
4332
 
4333
+ /** Accumulated state for a single tool call during a streaming response. */
2605
4334
  interface ToolEntry {
2606
4335
  id: string;
2607
4336
  name: string;
2608
4337
  kind: string;
2609
4338
  rawInput: unknown;
4339
+ /** Tool output content, populated when the tool completes. */
2610
4340
  content: string | null;
2611
4341
  status: string;
2612
4342
  viewerLinks?: ViewerLinks;
@@ -2617,8 +4347,16 @@ interface ToolEntry {
2617
4347
  displaySummary?: string;
2618
4348
  displayTitle?: string;
2619
4349
  displayKind?: string;
4350
+ /** Whether this tool is considered noise (e.g., ls, glob) and should be hidden at lower verbosities. */
2620
4351
  isNoise: boolean;
2621
4352
  }
4353
+ /**
4354
+ * Tracks all tool calls within a single streaming turn.
4355
+ *
4356
+ * Tool call events and update events can arrive out of order (e.g., a
4357
+ * `tool_update` may arrive before the corresponding `tool_call`). This map
4358
+ * handles that by buffering updates until the initial call arrives.
4359
+ */
2622
4360
  declare class ToolStateMap {
2623
4361
  private entries;
2624
4362
  private pendingUpdates;
@@ -2636,19 +4374,37 @@ declare class ToolStateMap {
2636
4374
  removed: number;
2637
4375
  }): ToolEntry | undefined;
2638
4376
  private _applyUpdate;
4377
+ /** Retrieves a tool entry by ID, or undefined if not yet tracked. */
2639
4378
  get(id: string): ToolEntry | undefined;
4379
+ /** Resets all state between turns. */
2640
4380
  clear(): void;
2641
4381
  }
4382
+ /**
4383
+ * Buffers partial thought text chunks from the agent's extended thinking.
4384
+ *
4385
+ * Chunks are appended until `seal()` is called, which marks the thought
4386
+ * as complete and returns the full text. Once sealed, further appends
4387
+ * are ignored.
4388
+ */
2642
4389
  declare class ThoughtBuffer {
2643
4390
  private chunks;
2644
4391
  private sealed;
4392
+ /** Appends a thought text chunk. Ignored if already sealed. */
2645
4393
  append(chunk: string): void;
4394
+ /** Marks the thought as complete and returns the full accumulated text. */
2646
4395
  seal(): string;
4396
+ /** Returns the text accumulated so far without sealing. */
2647
4397
  getText(): string;
2648
4398
  isSealed(): boolean;
4399
+ /** Resets the buffer for reuse in a new turn. */
2649
4400
  reset(): void;
2650
4401
  }
2651
4402
 
4403
+ /**
4404
+ * A fully resolved, platform-agnostic description of how to display a tool call.
4405
+ * Built by DisplaySpecBuilder from a ToolEntry + output mode, then consumed
4406
+ * by adapters to render tool cards.
4407
+ */
2652
4408
  interface ToolDisplaySpec {
2653
4409
  id: string;
2654
4410
  kind: string;
@@ -2673,36 +4429,68 @@ interface ToolDisplaySpec {
2673
4429
  isNoise: boolean;
2674
4430
  isHidden: boolean;
2675
4431
  }
4432
+ /** Display specification for an agent's extended thinking block. */
2676
4433
  interface ThoughtDisplaySpec {
2677
4434
  indicator: string;
4435
+ /** Full thought text, only populated at high verbosity. */
2678
4436
  content: string | null;
2679
4437
  }
4438
+ /**
4439
+ * Transforms raw ToolEntry state into display-ready ToolDisplaySpec objects.
4440
+ *
4441
+ * This is the central place where output mode (low/medium/high) controls what
4442
+ * information is included in tool cards. Low mode strips metadata; medium mode
4443
+ * includes summaries; high mode includes full output content and viewer links.
4444
+ */
2680
4445
  declare class DisplaySpecBuilder {
2681
4446
  private tunnelService?;
2682
4447
  constructor(tunnelService?: TunnelServiceInterface | undefined);
4448
+ /**
4449
+ * Builds a display spec for a single tool call entry.
4450
+ *
4451
+ * Deduplicates fields to avoid repeating the same info (e.g., if the title
4452
+ * was derived from the command, the command field is omitted). For long
4453
+ * output, generates a viewer link via the tunnel service when available.
4454
+ */
2683
4455
  buildToolSpec(entry: ToolEntry, mode: OutputMode, sessionContext?: {
2684
4456
  id: string;
2685
4457
  workingDirectory: string;
2686
4458
  }): ToolDisplaySpec;
4459
+ /** Builds a display spec for an agent thought. Content is only included at high verbosity. */
2687
4460
  buildThoughtSpec(content: string, mode: OutputMode): ThoughtDisplaySpec;
2688
4461
  }
2689
4462
 
4463
+ /** Token usage and cost data appended to the tool card after the turn completes. */
2690
4464
  interface UsageData {
2691
4465
  tokensUsed?: number;
2692
4466
  contextSize?: number;
2693
4467
  cost?: number;
2694
4468
  }
4469
+ /**
4470
+ * A point-in-time snapshot of all tool cards in a turn, used for rendering.
4471
+ * Includes completion counts so adapters can show progress (e.g., "3/5 tools done").
4472
+ */
2695
4473
  interface ToolCardSnapshot {
2696
4474
  specs: ToolDisplaySpec[];
2697
4475
  planEntries?: PlanEntry[];
2698
4476
  usage?: UsageData;
2699
4477
  totalVisible: number;
2700
4478
  completedVisible: number;
4479
+ /** True when all visible tools have reached a terminal status. */
2701
4480
  allComplete: boolean;
2702
4481
  }
2703
4482
  interface ToolCardStateConfig {
4483
+ /** Called with a snapshot whenever the tool card content changes. */
2704
4484
  onFlush: (snapshot: ToolCardSnapshot) => void;
2705
4485
  }
4486
+ /**
4487
+ * Aggregates tool display specs, plan entries, and usage data for a single
4488
+ * turn, then flushes snapshots to the adapter for rendering.
4489
+ *
4490
+ * Uses debouncing to batch rapid updates (e.g., multiple tools starting
4491
+ * in quick succession) into a single render pass. The first update flushes
4492
+ * immediately for responsiveness; subsequent updates are debounced.
4493
+ */
2706
4494
  declare class ToolCardState {
2707
4495
  private specs;
2708
4496
  private planEntries?;
@@ -2712,10 +4500,15 @@ declare class ToolCardState {
2712
4500
  private debounceTimer?;
2713
4501
  private onFlush;
2714
4502
  constructor(config: ToolCardStateConfig);
4503
+ /** Adds or updates a tool spec. First call flushes immediately; subsequent calls are debounced. */
2715
4504
  updateFromSpec(spec: ToolDisplaySpec): void;
4505
+ /** Updates the plan entries displayed alongside tool cards. */
2716
4506
  updatePlan(entries: PlanEntry[]): void;
4507
+ /** Appends token usage data to the tool card (typically at end of turn). */
2717
4508
  appendUsage(usage: UsageData): void;
4509
+ /** Marks the turn as complete and flushes the final snapshot immediately. */
2718
4510
  finalize(): void;
4511
+ /** Stops all pending flushes without emitting a final snapshot. */
2719
4512
  destroy(): void;
2720
4513
  hasContent(): boolean;
2721
4514
  private snapshot;
@@ -2724,15 +4517,54 @@ declare class ToolCardState {
2724
4517
  private clearDebounce;
2725
4518
  }
2726
4519
 
4520
+ /** Renders a text-based progress bar (e.g., "▓▓▓▓░░░░░░") for token usage display. */
2727
4521
  declare function progressBar(ratio: number, length?: number): string;
4522
+ /** Formats a token count with SI suffixes (e.g., 1500 -> "1.5k", 2000000 -> "2M"). */
2728
4523
  declare function formatTokens(n: number): string;
4524
+ /** Strips markdown code fences (```lang ... ```) from text, leaving only the content. */
2729
4525
  declare function stripCodeFences(text: string): string;
4526
+ /** Truncates text to maxLen characters, appending a truncation marker if cut. */
2730
4527
  declare function truncateContent(text: string, maxLen: number): string;
4528
+ /**
4529
+ * Splits a long message into chunks that fit within maxLength.
4530
+ *
4531
+ * Splitting strategy:
4532
+ * 1. Prefer paragraph boundaries (\n\n), then line boundaries (\n)
4533
+ * 2. If remaining text is only slightly over the limit (< 1.3x), split
4534
+ * near the midpoint to avoid leaving a tiny trailing chunk
4535
+ * 3. Avoid splitting inside markdown code fences — extend past the closing
4536
+ * fence if it doesn't exceed 2x maxLength
4537
+ */
2731
4538
  declare function splitMessage(text: string, maxLength: number): string[];
2732
4539
 
4540
+ /**
4541
+ * Recursively extracts plain text from an agent's response content.
4542
+ *
4543
+ * Agent responses can be strings, arrays of content blocks, or nested
4544
+ * objects with `text`, `content`, `input`, or `output` fields. This
4545
+ * function normalizes all variants into a single string. Falls back
4546
+ * to JSON serialization for unrecognized structures to avoid silently
4547
+ * dropping edge-case responses.
4548
+ */
2733
4549
  declare function extractContentText(content: unknown, depth?: number): string;
4550
+ /**
4551
+ * Builds a human-readable summary line for a tool call (used at medium/high verbosity).
4552
+ *
4553
+ * Includes an icon and key arguments (e.g., "Read src/foo.ts (50 lines)").
4554
+ * If the agent provides a `displaySummary` override, it takes precedence.
4555
+ */
2734
4556
  declare function formatToolSummary(name: string, rawInput: unknown, displaySummary?: string): string;
4557
+ /**
4558
+ * Builds a compact title for a tool call (used at low verbosity).
4559
+ *
4560
+ * Returns just the key identifier (file path, command, pattern) without
4561
+ * icons or extra decoration. If `displayTitle` is provided, it takes precedence.
4562
+ */
2735
4563
  declare function formatToolTitle(name: string, rawInput: unknown, displayTitle?: string): string;
4564
+ /**
4565
+ * Selects the appropriate emoji icon for a tool call card.
4566
+ * Priority: status icon (e.g., running, done, error) > kind icon (e.g., read, execute) > default.
4567
+ */
2736
4568
  declare function resolveToolIcon(tool: {
2737
4569
  status?: string;
2738
4570
  displayKind?: string;
@@ -2747,23 +4579,71 @@ interface SessionManagerLike {
2747
4579
  outputMode?: OutputMode;
2748
4580
  } | undefined;
2749
4581
  }
4582
+ /**
4583
+ * Resolves the effective output mode (low/medium/high) for a session.
4584
+ *
4585
+ * Override cascade (most specific wins):
4586
+ * 1. Global config `outputMode` (default: "medium")
4587
+ * 2. Per-adapter config `channels.<adapterName>.outputMode`
4588
+ * 3. Per-session override stored on the session record
4589
+ */
2750
4590
  declare class OutputModeResolver {
4591
+ /** Resolves the effective output mode by walking the override cascade. */
2751
4592
  resolve(configManager: ConfigManagerLike, adapterName: string, sessionId?: string, sessionManager?: SessionManagerLike): OutputMode;
2752
4593
  }
2753
4594
 
2754
4595
  /**
2755
- * OpenACP Product Guide — comprehensive reference for the AI assistant.
2756
- * The assistant reads this at runtime to answer user questions about features.
4596
+ * OpenACP Product Guide — comprehensive reference text injected into the AI assistant's system prompt.
4597
+ *
4598
+ * The assistant (in the Assistant topic/thread) reads this at startup to answer user questions
4599
+ * about features, CLI commands, configuration, and troubleshooting without hitting the network.
4600
+ *
4601
+ * **How it's used:** The assistant plugin reads `PRODUCT_GUIDE` and prepends it to the system
4602
+ * prompt so the AI has full product knowledge baked in, not fetched per-session.
4603
+ *
4604
+ * **How to update:** Edit the Markdown content below. Keep it accurate with the current feature set —
4605
+ * outdated entries cause the assistant to give wrong answers. Sections use `---` dividers and
4606
+ * `##` headings so the assistant can navigate the content efficiently.
2757
4607
  */
2758
- 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### Bypass permissions\nAuto-approves ALL permission requests \u2014 the agent runs any command without asking.\n- Toggle: `/bypass_permissions on` or tap the \u2620\uFE0F button in the session\n- Disable: `/bypass_permissions off` 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 <agent>` to install handoff integration (one-time setup)\n2. In supported agents (for example Claude Code or OpenCode), use /openacp:handoff\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| `/bypass_permissions` | Session topic | Toggle bypass permissions (on/off) |\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| `bypass <id> on/off` | Toggle bypass permissions |\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 `/bypass_permissions on` 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";
4608
+ 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### Bypass permissions\nAuto-approves ALL permission requests \u2014 the agent runs any command without asking.\n- Toggle: `/bypass_permissions on` or tap the \u2620\uFE0F button in the session\n- Disable: `/bypass_permissions off` 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 <agent>` to install handoff integration (one-time setup)\n2. In supported agents (for example Claude Code or OpenCode), use /openacp:handoff\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| `/bypass_permissions` | Session topic | Toggle bypass permissions (on/off) |\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| `bypass <id> on/off` | Toggle bypass permissions |\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 directory is the parent of `.openacp/` (where you ran `openacp` setup)\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 `/bypass_permissions on` 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";
2759
4609
 
4610
+ /**
4611
+ * Runtime configuration for the Telegram adapter.
4612
+ *
4613
+ * `notificationTopicId` and `assistantTopicId` start as `null` on first run and are
4614
+ * populated by `ensureTopics()` when the system topics are created in the group.
4615
+ * Both IDs are persisted to plugin settings so they survive restarts.
4616
+ */
2760
4617
  interface TelegramChannelConfig extends ChannelConfig {
2761
4618
  botToken: string;
2762
4619
  chatId: number;
4620
+ /** Forum topic used for all cross-session notifications (completions, permissions). Null until first run. */
2763
4621
  notificationTopicId: number | null;
4622
+ /** Forum topic where users chat with the assistant. Null until first run. */
2764
4623
  assistantTopicId: number | null;
2765
4624
  }
2766
4625
 
4626
+ /**
4627
+ * Telegram adapter — bridges the OpenACP session system to a Telegram supergroup.
4628
+ *
4629
+ * Architecture overview:
4630
+ * - **Topic-per-session model**: each agent session lives in its own Telegram forum
4631
+ * topic. The topic ID is stored as `session.threadId` and used to route all
4632
+ * inbound and outbound messages.
4633
+ * - **Two system topics**: "📋 Notifications" (cross-session alerts) and
4634
+ * "🤖 Assistant" (conversational AI). Created once on first run, IDs persisted.
4635
+ * - **Streaming**: agent text arrives as `text_delta` chunks. A `MessageDraft`
4636
+ * accumulates chunks and edits the message in-place every 5 seconds, reducing
4637
+ * API calls while keeping the response live.
4638
+ * - **Callback routing**:
4639
+ * - `p:<key>:<optionId>` — permission approval buttons
4640
+ * - `c/<command>` (or `c/#<id>` for long commands) — command button actions
4641
+ * - `m:<itemId>` — MenuRegistry item dispatch
4642
+ * - Domain prefixes (`d:`, `v:`, `ns:`, `sw:`, etc.) — specific feature flows
4643
+ * - **Two-phase startup**: Phase 1 starts the bot and registers handlers.
4644
+ * Phase 2 checks group prerequisites (admin, topics enabled) and creates
4645
+ * system topics. If prerequisites are not met, a background watcher retries.
4646
+ */
2767
4647
  declare class TelegramAdapter extends MessagingAdapter {
2768
4648
  readonly name = "telegram";
2769
4649
  readonly renderer: IRenderer;
@@ -2796,7 +4676,11 @@ declare class TelegramAdapter extends MessagingAdapter {
2796
4676
  private _topicsInitialized;
2797
4677
  /** Background watcher timer — cancelled on stop() or when topics succeed */
2798
4678
  private _prerequisiteWatcher;
2799
- /** Store control message ID in memory + persist to session record */
4679
+ /**
4680
+ * Persist the control message ID both in-memory and to the session record.
4681
+ * The control message is the pinned status card with bypass/TTS buttons; its ID
4682
+ * is needed after a restart to edit it when config changes.
4683
+ */
2800
4684
  private storeControlMsgId;
2801
4685
  /** Get control message ID (from Map, with fallback to session record) */
2802
4686
  private getControlMsgId;
@@ -2806,6 +4690,12 @@ declare class TelegramAdapter extends MessagingAdapter {
2806
4690
  notificationTopicId?: number;
2807
4691
  assistantTopicId?: number;
2808
4692
  }) => Promise<void>);
4693
+ /**
4694
+ * Set up the grammY bot, register all callback and message handlers, then perform
4695
+ * two-phase startup: Phase 1 starts polling immediately; Phase 2 checks group
4696
+ * prerequisites (bot is admin, topics are enabled) and creates/restores system topics.
4697
+ * If prerequisites are not met, a background watcher retries until they are.
4698
+ */
2809
4699
  start(): Promise<void>;
2810
4700
  /**
2811
4701
  * Retry an async operation with exponential backoff.
@@ -2819,6 +4709,13 @@ declare class TelegramAdapter extends MessagingAdapter {
2819
4709
  private registerCommandsWithRetry;
2820
4710
  private initTopicDependentFeatures;
2821
4711
  private startPrerequisiteWatcher;
4712
+ /**
4713
+ * Tear down the bot and release all associated resources.
4714
+ *
4715
+ * Cancels the background prerequisite watcher, destroys all per-session activity
4716
+ * trackers (which hold interval timers), removes eventBus listeners, clears the
4717
+ * send queue, and stops the grammY bot polling loop.
4718
+ */
2822
4719
  stop(): Promise<void>;
2823
4720
  private renderCommandResponse;
2824
4721
  private toCallbackData;
@@ -2833,7 +4730,19 @@ declare class TelegramAdapter extends MessagingAdapter {
2833
4730
  * its creation event. This queue ensures events are processed in the order they arrive.
2834
4731
  */
2835
4732
  private _dispatchQueues;
4733
+ /**
4734
+ * Drain pending event dispatches from the previous prompt, then reset the
4735
+ * activity tracker so late tool_call events don't leak into the new card.
4736
+ */
4737
+ private drainAndResetTracker;
2836
4738
  private getTracer;
4739
+ /**
4740
+ * Primary outbound dispatch method — routes an agent message to the session's Telegram topic.
4741
+ *
4742
+ * Wraps the base class `sendMessage` in a per-session promise chain (`_dispatchQueues`)
4743
+ * so that concurrent events fired from SessionBridge are serialized and delivered in the
4744
+ * order they arrive, preventing fast handlers from overtaking slower ones.
4745
+ */
2837
4746
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
2838
4747
  protected handleThought(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2839
4748
  protected handleText(sessionId: string, content: OutgoingMessage): Promise<void>;
@@ -2851,20 +4760,71 @@ declare class TelegramAdapter extends MessagingAdapter {
2851
4760
  updateControlMessage(sessionId: string): Promise<void>;
2852
4761
  protected handleError(sessionId: string, content: OutgoingMessage): Promise<void>;
2853
4762
  protected handleSystem(sessionId: string, content: OutgoingMessage): Promise<void>;
4763
+ /**
4764
+ * Render a PermissionRequest as an inline keyboard in the session topic and
4765
+ * notify the Notifications topic. Runs inside a sendQueue item, so
4766
+ * notification is fire-and-forget to avoid deadlock.
4767
+ */
2854
4768
  sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
4769
+ /**
4770
+ * Post a notification to the Notifications topic.
4771
+ * Assistant session notifications are suppressed — the assistant topic is
4772
+ * the user's primary interface and does not need a separate alert.
4773
+ */
2855
4774
  sendNotification(notification: NotificationMessage): Promise<void>;
4775
+ /**
4776
+ * Create a new Telegram forum topic for a session and return its thread ID as a string.
4777
+ * Called by the core when a session is created via the API or CLI (not from the Telegram UI).
4778
+ */
2856
4779
  createSessionThread(sessionId: string, name: string): Promise<string>;
4780
+ /**
4781
+ * Rename the forum topic for a session and update the session record's display name.
4782
+ * No-ops silently if the session doesn't have a threadId yet (e.g. still initializing).
4783
+ */
2857
4784
  renameSessionThread(sessionId: string, newName: string): Promise<void>;
4785
+ /** Delete the forum topic associated with a session. */
2858
4786
  deleteSessionThread(sessionId: string): Promise<void>;
4787
+ /**
4788
+ * Display or update the pinned skill commands message for a session.
4789
+ * If the session's threadId is not yet set (e.g. session created from API),
4790
+ * the commands are queued and flushed once the thread becomes available.
4791
+ */
2859
4792
  sendSkillCommands(sessionId: string, commands: AgentCommand[]): Promise<void>;
2860
4793
  /** Flush any skill commands that were queued before threadId was available */
2861
4794
  flushPendingSkillCommands(sessionId: string): Promise<void>;
2862
4795
  private resolveSessionId;
2863
4796
  private downloadTelegramFile;
2864
4797
  private handleIncomingMedia;
4798
+ /**
4799
+ * Remove skill slash commands from the Telegram bot command list for a session.
4800
+ *
4801
+ * Clears any queued pending commands that hadn't been sent yet, then delegates
4802
+ * to `SkillCommandManager` to delete the commands from the Telegram API. Called
4803
+ * when a session with registered skill commands ends.
4804
+ */
2865
4805
  cleanupSkillCommands(sessionId: string): Promise<void>;
4806
+ /**
4807
+ * Clean up all adapter state associated with a session.
4808
+ *
4809
+ * Finalizes and discards any in-flight draft, destroys the activity tracker
4810
+ * (stopping ThinkingIndicator timers and finalizing any open ToolCard), and
4811
+ * clears pending skill commands. Called when a session ends or is reset.
4812
+ */
2866
4813
  cleanupSessionState(sessionId: string): Promise<void>;
4814
+ /**
4815
+ * Remove `[TTS]...[/TTS]` blocks from the active or finalized draft for a session.
4816
+ *
4817
+ * The agent embeds these blocks so the speech plugin can extract the TTS text, but
4818
+ * they should never appear in the chat message. Called after TTS audio has been sent.
4819
+ */
2867
4820
  stripTTSBlock(sessionId: string): Promise<void>;
4821
+ /**
4822
+ * Archive a session by deleting its forum topic.
4823
+ *
4824
+ * Sets `session.archiving = true` to suppress any outgoing messages while the
4825
+ * topic is being torn down, finalizes pending drafts, cleans up all trackers,
4826
+ * then deletes the Telegram topic (which removes all messages).
4827
+ */
2868
4828
  archiveSessionTopic(sessionId: string): Promise<void>;
2869
4829
  }
2870
4830