@openacp/cli 2026.410.1 → 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,8 +701,22 @@ 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. */
549
720
  id: z.ZodOptional<z.ZodString>;
550
721
  instanceName: z.ZodOptional<z.ZodString>;
551
722
  defaultAgent: z.ZodString;
@@ -683,27 +854,72 @@ declare const ConfigSchema: z.ZodObject<{
683
854
  labelHistory?: boolean | undefined;
684
855
  } | undefined;
685
856
  }>;
857
+ /** Validated config object used throughout the codebase. Always obtained via `ConfigManager.get()` to ensure it's up-to-date. */
686
858
  type Config = z.infer<typeof ConfigSchema>;
859
+ /** Expands a leading `~` to the user's home directory. Returns the path unchanged if no `~` prefix. */
687
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
+ */
688
868
  declare class ConfigManager extends EventEmitter {
689
869
  private config;
690
870
  private configPath;
691
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
+ */
692
879
  load(): Promise<void>;
880
+ /** Returns a deep clone of the current config to prevent external mutation. */
693
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
+ */
694
889
  save(updates: Record<string, unknown>, changePath?: string): Promise<void>;
695
890
  /**
696
- * Set a single config value by dot-path (e.g. "logging.level").
697
- * Builds the nested update object, validates, and saves.
698
- * 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.
699
897
  */
700
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
+ */
701
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. */
702
908
  exists(): Promise<boolean>;
909
+ /** Returns the resolved path to the config JSON file. */
703
910
  getConfigPath(): string;
911
+ /** Writes a complete config object to disk, creating the directory if needed. Used during initial setup. */
704
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
+ */
705
919
  applyEnvToPluginSettings(settingsManager: SettingsManager): Promise<void>;
920
+ /** Applies env var overrides to the raw config object before Zod validation. */
706
921
  private applyEnvOverrides;
922
+ /** Recursively merges source into target, skipping prototype-pollution keys. */
707
923
  private deepMerge;
708
924
  }
709
925
 
@@ -716,30 +932,94 @@ declare const log: {
716
932
  fatal: (...args: unknown[]) => void;
717
933
  child: (bindings: pino.Bindings) => pino.Logger<never, boolean>;
718
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
+ */
719
944
  declare function initLogger(config: LoggingConfig): Logger;
720
945
  /** Change log level at runtime. Pino transport targets respect parent level changes automatically. */
721
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
+ */
722
954
  declare function createChildLogger(context: {
723
955
  module: string;
724
956
  [key: string]: unknown;
725
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
+ */
726
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
+ */
727
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
+ */
728
982
  declare function cleanupOldSessionLogs(retentionDays: number): Promise<void>;
729
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
+ */
730
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
+ */
731
996
  declare function nodeToWebReadable(nodeStream: NodeJS.ReadableStream): ReadableStream<Uint8Array>;
732
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
+ */
733
1006
  declare class StderrCapture {
734
1007
  private maxLines;
735
1008
  private lines;
736
1009
  constructor(maxLines?: number);
1010
+ /** Append a chunk of stderr output, splitting on newlines and trimming to maxLines. */
737
1011
  append(chunk: string): void;
1012
+ /** Return all captured lines joined as a single string. */
738
1013
  getLastLines(): string;
739
1014
  }
740
1015
 
741
1016
  /**
742
- * 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.
743
1023
  *
744
1024
  * Usage:
745
1025
  * interface MyEvents {
@@ -755,8 +1035,16 @@ declare class TypedEmitter<T extends Record<string & keyof T, (...args: any[]) =
755
1035
  private listeners;
756
1036
  private paused;
757
1037
  private buffer;
1038
+ /** Register a listener for the given event. Returns `this` for chaining. */
758
1039
  on<K extends keyof T>(event: K, listener: T[K]): this;
1040
+ /** Remove a specific listener for the given event. */
759
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
+ */
760
1048
  emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): void;
761
1049
  /**
762
1050
  * Pause event delivery. Events emitted while paused are buffered.
@@ -770,49 +1058,99 @@ declare class TypedEmitter<T extends Record<string & keyof T, (...args: any[]) =
770
1058
  clearBuffer(): void;
771
1059
  get isPaused(): boolean;
772
1060
  get bufferSize(): number;
1061
+ /** Remove all listeners for a specific event, or all events if none specified. */
773
1062
  removeAllListeners(event?: keyof T): void;
1063
+ /** Deliver an event to listeners, isolating errors so one broken listener doesn't break others. */
774
1064
  private deliver;
775
1065
  }
776
1066
 
1067
+ /** Configuration for the sliding-window error budget. */
777
1068
  interface ErrorBudgetConfig {
1069
+ /** Maximum errors allowed within the window before disabling the plugin. Default: 10. */
778
1070
  maxErrors: number;
1071
+ /** Sliding window duration in milliseconds. Default: 3600000 (1 hour). */
779
1072
  windowMs: number;
780
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
+ */
781
1084
  declare class ErrorTracker {
782
1085
  private errors;
783
1086
  private disabled;
784
1087
  private exempt;
785
1088
  private config;
1089
+ /** Callback fired when a plugin is auto-disabled due to error budget exhaustion. */
786
1090
  onDisabled?: (pluginName: string, reason: string) => void;
787
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
+ */
788
1096
  increment(pluginName: string): void;
1097
+ /** Check if a plugin has been disabled due to errors. */
789
1098
  isDisabled(pluginName: string): boolean;
1099
+ /** Re-enable a plugin and clear its error history. */
790
1100
  reset(pluginName: string): void;
1101
+ /** Mark a plugin as exempt from circuit-breaking (e.g., essential plugins). */
791
1102
  setExempt(pluginName: string): void;
792
1103
  }
793
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
+ */
794
1116
  declare class MiddlewareChain {
795
1117
  private chains;
796
1118
  private errorHandler?;
797
1119
  private errorTracker?;
1120
+ /** Register a middleware handler for a hook. Handlers are kept sorted by priority. */
798
1121
  add(hook: string, pluginName: string, opts: {
799
1122
  priority?: number;
800
1123
  handler: Function;
801
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
+ */
802
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. */
803
1136
  removeAll(pluginName: string): void;
1137
+ /** Set a callback for middleware errors (e.g., logging). */
804
1138
  setErrorHandler(fn: (pluginName: string, error: Error) => void): void;
1139
+ /** Attach an ErrorTracker for circuit-breaking misbehaving plugins. */
805
1140
  setErrorTracker(tracker: ErrorTracker): void;
806
1141
  }
807
1142
 
808
1143
  type TraceLayer = "acp" | "core" | "telegram";
809
1144
  /**
810
- * Per-session debug trace logger. Writes JSONL files to <workingDirectory>/.log/.
811
- * 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.
812
1150
  *
813
- * Note: Uses appendFileSync for simplicity. This blocks the event loop briefly per write,
814
- * which is acceptable for a debug-only tool. The DEBUG_ENABLED guard ensures zero overhead
815
- * 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.
816
1154
  */
817
1155
  declare class DebugTracer {
818
1156
  private sessionId;
@@ -820,21 +1158,45 @@ declare class DebugTracer {
820
1158
  private dirCreated;
821
1159
  private logDir;
822
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
+ */
823
1167
  log(layer: TraceLayer, data: Record<string, unknown>): void;
824
1168
  /** No-op cleanup — establishes the pattern for future async implementations */
825
1169
  destroy(): void;
826
1170
  }
827
1171
 
1172
+ /** Events emitted by AgentInstance — consumed by Session to relay to adapters. */
828
1173
  interface AgentInstanceEvents {
829
1174
  agent_event: (event: AgentEvent) => void;
830
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
+ */
831
1189
  declare class AgentInstance extends TypedEmitter<AgentInstanceEvents> {
832
1190
  private connection;
833
1191
  private child;
834
1192
  private stderrCapture;
1193
+ /** Manages terminal subprocesses that agents can spawn for shell commands. */
835
1194
  private terminalManager;
1195
+ /** Shared across all instances — resolves MCP server configs for ACP sessions. */
836
1196
  private static mcpManager;
1197
+ /** Guards against emitting crash events during intentional shutdown. */
837
1198
  private _destroying;
1199
+ /** Restricts agent file I/O to the workspace directory and explicitly allowed paths. */
838
1200
  private pathGuard;
839
1201
  sessionId: string;
840
1202
  agentName: string;
@@ -851,30 +1213,132 @@ declare class AgentInstance extends TypedEmitter<AgentInstanceEvents> {
851
1213
  };
852
1214
  middlewareChain?: MiddlewareChain;
853
1215
  debugTracer: DebugTracer | null;
854
- /** 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
+ */
855
1222
  addAllowedPath(p: string): void;
856
1223
  onPermissionRequest: (request: PermissionRequest) => Promise<string>;
857
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
+ */
858
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
+ */
859
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
+ */
860
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
+ */
861
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
+ */
862
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
+ */
863
1288
  setConfigOption(configId: string, value: SetConfigOptionValue): Promise<SetSessionConfigOptionResponse>;
1289
+ /** List the agent's known sessions, optionally filtered by working directory. */
864
1290
  listSessions(cwd?: string, cursor?: string): Promise<ListSessionsResponse>;
1291
+ /** Load an existing agent session by ID into this subprocess. */
865
1292
  loadSession(sessionId: string, cwd: string, mcpServers?: McpServerConfig[]): Promise<LoadSessionResponse>;
1293
+ /** Trigger agent-managed authentication (e.g., OAuth flow). */
866
1294
  authenticate(methodId: string): Promise<void>;
1295
+ /** Fork an existing session, creating a new branch with shared history. */
867
1296
  forkSession(sessionId: string, cwd: string, mcpServers?: McpServerConfig[]): Promise<ForkSessionResponse>;
1297
+ /** Close a session on the agent side (cleanup agent-internal state). */
868
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
+ */
869
1313
  prompt(text: string, attachments?: Attachment[]): Promise<PromptResponse>;
1314
+ /** Cancel the currently running prompt. The agent should stop and return partial results. */
870
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
+ */
871
1324
  destroy(): Promise<void>;
872
1325
  }
873
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`). */
874
1337
  declare class AgentStore {
875
1338
  private data;
876
1339
  readonly filePath: string;
877
1340
  constructor(filePath: string);
1341
+ /** Load and validate the store from disk. Starts fresh if file is missing or invalid. */
878
1342
  load(): void;
879
1343
  exists(): boolean;
880
1344
  getInstalled(): Record<string, InstalledAgent>;
@@ -882,26 +1346,69 @@ declare class AgentStore {
882
1346
  addAgent(key: string, agent: InstalledAgent): void;
883
1347
  removeAgent(key: string): void;
884
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
+ */
885
1354
  private save;
886
1355
  }
887
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
+ */
888
1368
  declare class AgentCatalog {
889
1369
  private store;
1370
+ /** Agents available in the remote registry (cached in memory after load). */
890
1371
  private registryAgents;
891
1372
  private cachePath;
1373
+ /** Directory where binary agent archives are extracted to. */
892
1374
  private agentsDir;
893
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
+ */
894
1382
  load(): void;
1383
+ /** Fetch the latest agent registry from the CDN and update the local cache. */
895
1384
  fetchRegistry(): Promise<void>;
1385
+ /** Re-fetch registry only if the local cache has expired (24-hour TTL). */
896
1386
  refreshRegistryIfStale(): Promise<void>;
897
1387
  getRegistryAgents(): RegistryAgent[];
898
1388
  getRegistryAgent(registryId: string): RegistryAgent | undefined;
1389
+ /** Find a registry agent by registry ID or by its short alias (e.g., "claude"). */
899
1390
  findRegistryAgent(keyOrId: string): RegistryAgent | undefined;
900
1391
  getInstalled(): InstalledAgent[];
901
1392
  getInstalledEntries(): Record<string, InstalledAgent>;
902
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
+ */
903
1403
  getAvailable(): AgentListItem[];
1404
+ /** Check if an agent can be installed on this system (platform + dependencies). */
904
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
+ */
905
1412
  install(keyOrId: string, progress?: InstallProgress, force?: boolean): Promise<InstallResult>;
906
1413
  /**
907
1414
  * Register an agent directly into the catalog store without going through
@@ -909,10 +1416,12 @@ declare class AgentCatalog {
909
1416
  * when their CLI dependency is not yet installed.
910
1417
  */
911
1418
  registerFallbackAgent(key: string, data: InstalledAgent): void;
1419
+ /** Remove an installed agent and delete its binary directory if applicable. */
912
1420
  uninstall(key: string): Promise<{
913
1421
  ok: boolean;
914
1422
  error?: string;
915
1423
  }>;
1424
+ /** Convert an installed agent's short key to an AgentDefinition for spawning. */
916
1425
  resolve(key: string): AgentDefinition | undefined;
917
1426
  /**
918
1427
  * Enrich installed agents (especially migrated ones) with registry data.
@@ -924,17 +1433,52 @@ declare class AgentCatalog {
924
1433
  private loadRegistryFromCacheOrSnapshot;
925
1434
  }
926
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
+ */
927
1446
  declare class AgentManager {
928
1447
  private catalog;
929
1448
  constructor(catalog: AgentCatalog);
1449
+ /** Return definitions for all installed agents. */
930
1450
  getAvailableAgents(): AgentDefinition[];
1451
+ /** Look up a single agent definition by its short name (e.g., "claude", "gemini"). */
931
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
+ */
932
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
+ */
933
1464
  resume(agentName: string, workingDirectory: string, agentSessionId: string, allowedPaths?: string[]): Promise<AgentInstance>;
934
1465
  }
935
1466
 
936
1467
  /**
937
- * 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.
938
1482
  */
939
1483
  declare class PermissionGate {
940
1484
  private request?;
@@ -944,8 +1488,14 @@ declare class PermissionGate {
944
1488
  private timeoutTimer?;
945
1489
  private timeoutMs;
946
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
+ */
947
1495
  setPending(request: PermissionRequest): Promise<string>;
1496
+ /** Approve the pending request with the given option ID. No-op if already settled. */
948
1497
  resolve(optionId: string): void;
1498
+ /** Deny the pending request. No-op if already settled. */
949
1499
  reject(reason?: string): void;
950
1500
  get isPending(): boolean;
951
1501
  get currentRequest(): PermissionRequest | undefined;
@@ -955,37 +1505,57 @@ declare class PermissionGate {
955
1505
  private cleanup;
956
1506
  }
957
1507
 
1508
+ /** Options passed to an STT provider for a single transcription request. */
958
1509
  interface STTOptions {
1510
+ /** BCP-47 language code hint (e.g. `"en"`, `"vi"`). Improves accuracy when known. */
959
1511
  language?: string;
1512
+ /** Override the default model for this request. */
960
1513
  model?: string;
961
1514
  }
1515
+ /** Result returned by an STT provider after transcription. */
962
1516
  interface STTResult {
963
1517
  text: string;
1518
+ /** Detected or confirmed language (BCP-47). */
964
1519
  language?: string;
1520
+ /** Audio duration in seconds. */
965
1521
  duration?: number;
966
1522
  }
1523
+ /** Options passed to a TTS provider for a single synthesis request. */
967
1524
  interface TTSOptions {
968
1525
  language?: string;
1526
+ /** Voice identifier (provider-specific, e.g. `"en-US-AriaNeural"` for Edge TTS). */
969
1527
  voice?: string;
970
1528
  model?: string;
971
1529
  }
1530
+ /** Audio data produced by a TTS provider. */
972
1531
  interface TTSResult {
973
1532
  audioBuffer: Buffer;
1533
+ /** MIME type of the audio (e.g. `"audio/mp3"`, `"audio/wav"`). */
974
1534
  mimeType: string;
975
1535
  }
1536
+ /** Contract for a speech-to-text provider. */
976
1537
  interface STTProvider {
977
1538
  readonly name: string;
978
1539
  transcribe(audioBuffer: Buffer, mimeType: string, options?: STTOptions): Promise<STTResult>;
979
1540
  }
1541
+ /** Contract for a text-to-speech provider. */
980
1542
  interface TTSProvider {
981
1543
  readonly name: string;
982
1544
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
983
1545
  }
1546
+ /** Provider-level configuration stored in plugin settings (API key, model override, etc.). */
984
1547
  interface SpeechProviderConfig {
985
1548
  apiKey?: string;
986
1549
  model?: string;
987
1550
  [key: string]: unknown;
988
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
+ */
989
1559
  interface SpeechServiceConfig {
990
1560
  stt: {
991
1561
  provider: string | null;
@@ -997,10 +1567,28 @@ interface SpeechServiceConfig {
997
1567
  };
998
1568
  }
999
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
+ */
1000
1579
  type ProviderFactory = (config: SpeechServiceConfig) => {
1001
1580
  stt: Map<string, STTProvider>;
1002
1581
  tts: Map<string, TTSProvider>;
1003
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
+ */
1004
1592
  declare class SpeechService {
1005
1593
  private config;
1006
1594
  private sttProviders;
@@ -1009,26 +1597,69 @@ declare class SpeechService {
1009
1597
  constructor(config: SpeechServiceConfig);
1010
1598
  /** Set a factory function that can recreate providers from config (for hot-reload) */
1011
1599
  setProviderFactory(factory: ProviderFactory): void;
1600
+ /** Register an STT provider by name. Overwrites any existing provider with the same name. */
1012
1601
  registerSTTProvider(name: string, provider: STTProvider): void;
1602
+ /** Register a TTS provider by name. Called by external TTS plugins (e.g. msedge-tts-plugin). */
1013
1603
  registerTTSProvider(name: string, provider: TTSProvider): void;
1604
+ /** Remove a TTS provider — called by external plugins on teardown. */
1014
1605
  unregisterTTSProvider(name: string): void;
1606
+ /** Returns true if an STT provider is configured and has credentials. */
1015
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
+ */
1016
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
+ */
1017
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
+ */
1018
1626
  synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
1627
+ /** Replace the active config without rebuilding providers. Use `refreshProviders` to also rebuild. */
1019
1628
  updateConfig(config: SpeechServiceConfig): void;
1020
- /** 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
+ */
1021
1636
  refreshProviders(newConfig: SpeechServiceConfig): void;
1022
1637
  }
1023
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
+ */
1024
1648
  declare class GroqSTT implements STTProvider {
1025
1649
  private apiKey;
1026
1650
  private defaultModel;
1027
1651
  readonly name = "groq";
1028
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
+ */
1029
1659
  transcribe(audioBuffer: Buffer, mimeType: string, options?: STTOptions): Promise<STTResult>;
1030
1660
  }
1031
1661
 
1662
+ /** Events emitted by a Session instance — SessionBridge subscribes to relay them to adapters. */
1032
1663
  interface SessionEvents {
1033
1664
  agent_event: (event: AgentEvent) => void;
1034
1665
  permission_request: (request: PermissionRequest) => void;
@@ -1039,6 +1670,17 @@ interface SessionEvents {
1039
1670
  prompt_count_changed: (count: number) => void;
1040
1671
  turn_started: (ctx: TurnContext) => void;
1041
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
+ */
1042
1684
  declare class Session extends TypedEmitter<SessionEvents> {
1043
1685
  id: string;
1044
1686
  channelId: string;
@@ -1049,6 +1691,8 @@ declare class Session extends TypedEmitter<SessionEvents> {
1049
1691
  workingDirectory: string;
1050
1692
  private _agentInstance;
1051
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). */
1052
1696
  set agentInstance(agent: AgentInstance);
1053
1697
  agentSessionId: string;
1054
1698
  private _status;
@@ -1105,17 +1749,32 @@ declare class Session extends TypedEmitter<SessionEvents> {
1105
1749
  fail(reason: string): void;
1106
1750
  /** Transition to finished — from active only. Emits session_end for backward compat. */
1107
1751
  finish(reason?: string): void;
1108
- /** Transition to cancelled — from active only (terminal session cancel) */
1752
+ /** Transition to cancelled — from active or error (terminal session cancel) */
1109
1753
  markCancelled(): void;
1110
1754
  private transition;
1111
1755
  /** Number of prompts waiting in queue */
1112
1756
  get queueDepth(): number;
1757
+ /** Whether a prompt is currently being processed by the agent */
1113
1758
  get promptRunning(): boolean;
1759
+ /** Store context markdown to be prepended to the next prompt (used for session resume with history). */
1114
1760
  setContext(markdown: string): void;
1761
+ /** Set TTS mode: "off" = disabled, "next" = one-shot (auto-resets after prompt), "on" = persistent. */
1115
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
+ */
1116
1770
  enqueuePrompt(text: string, attachments?: Attachment[], routing?: TurnRouting, externalTurnId?: string): Promise<string>;
1117
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
+ */
1118
1776
  private maybeTranscribeAudio;
1777
+ /** Extract [TTS] block from agent response, synthesize speech, and emit audio_content event. */
1119
1778
  private processTTSResponse;
1120
1779
  private autoName;
1121
1780
  setInitialConfigOptions(options: ConfigOption[]): void;
@@ -1147,11 +1806,17 @@ declare class Session extends TypedEmitter<SessionEvents> {
1147
1806
  findLastSwitchEntry(agentName: string): AgentSwitchEntry | undefined;
1148
1807
  /** Switch the agent instance in-place, preserving session identity */
1149
1808
  switchAgent(agentName: string, createAgent: () => Promise<AgentInstance>): Promise<void>;
1809
+ /** Tear down the session: reject pending permissions, clear queue, destroy agent subprocess. */
1150
1810
  destroy(): Promise<void>;
1151
1811
  }
1152
1812
 
1153
1813
  /**
1154
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.
1155
1820
  */
1156
1821
  declare class PromptQueue {
1157
1822
  private processor;
@@ -1162,14 +1827,37 @@ declare class PromptQueue {
1162
1827
  /** Set when abort is triggered; drainNext waits for the current processor to settle before starting the next item. */
1163
1828
  private processorSettled;
1164
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
+ */
1165
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. */
1166
1837
  private process;
1838
+ /** Dequeue and process the next pending prompt, if any. Called after each prompt completes. */
1167
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
+ */
1168
1844
  clear(): void;
1169
1845
  get pending(): number;
1170
1846
  get isProcessing(): boolean;
1171
1847
  }
1172
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
+ */
1173
1861
  declare class MessageTransformer {
1174
1862
  tunnelService?: TunnelServiceInterface;
1175
1863
  /** Cache rawInput from tool_call so it's available in tool_update (which often lacks it) */
@@ -1177,6 +1865,12 @@ declare class MessageTransformer {
1177
1865
  /** Cache viewer links generated from intermediate updates so completion events carry them */
1178
1866
  private toolViewerCache;
1179
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
+ */
1180
1874
  transform(event: AgentEvent, sessionContext?: {
1181
1875
  id: string;
1182
1876
  workingDirectory: string;
@@ -1190,6 +1884,7 @@ declare class MessageTransformer {
1190
1884
  private enrichWithViewerLinks;
1191
1885
  }
1192
1886
 
1887
+ /** Persistence interface for session records. Implementations handle serialization format and storage. */
1193
1888
  interface SessionStore {
1194
1889
  save(record: SessionRecord): Promise<void>;
1195
1890
  /** Immediately flush pending writes to disk (no debounce). */
@@ -1202,6 +1897,13 @@ interface SessionStore {
1202
1897
  remove(sessionId: string): Promise<void>;
1203
1898
  }
1204
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
+ */
1205
1907
  interface EventBusEvents {
1206
1908
  "session:created": (data: {
1207
1909
  sessionId: string;
@@ -1304,9 +2006,18 @@ interface EventBusEvents {
1304
2006
  error?: string;
1305
2007
  }) => void;
1306
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
+ */
1307
2017
  declare class EventBus extends TypedEmitter<EventBusEvents> {
1308
2018
  }
1309
2019
 
2020
+ /** Flattened view of a session for API consumers — merges live state with stored record. */
1310
2021
  interface SessionSummary {
1311
2022
  id: string;
1312
2023
  agent: string;
@@ -1323,30 +2034,63 @@ interface SessionSummary {
1323
2034
  capabilities: AgentCapabilities | null;
1324
2035
  isLive: boolean;
1325
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
+ */
1326
2045
  declare class SessionManager {
1327
2046
  private sessions;
1328
2047
  private store;
1329
2048
  private eventBus?;
1330
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
+ */
1331
2054
  setEventBus(eventBus: EventBus): void;
1332
2055
  constructor(store?: SessionStore | null);
2056
+ /** Create a new session by spawning an agent and persisting the initial record. */
1333
2057
  createSession(channelId: string, agentName: string, workingDirectory: string, agentManager: AgentManager): Promise<Session>;
2058
+ /** Look up a live session by its OpenACP session ID. */
1334
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). */
1335
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). */
1336
2063
  getSessionByAgentSessionId(agentSessionId: string): Session | undefined;
2064
+ /** Look up the persisted SessionRecord by the agent's internal session ID. */
1337
2065
  getRecordByAgentSessionId(agentSessionId: string): SessionRecord | undefined;
2066
+ /** Look up the persisted SessionRecord by channel and thread ID. */
1338
2067
  getRecordByThread(channelId: string, threadId: string): SessionRecord | undefined;
2068
+ /** Register a session that was created externally (e.g. restored from store on startup). */
1339
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
+ */
1340
2075
  patchRecord(sessionId: string, patch: Partial<SessionRecord>, options?: {
1341
2076
  immediate?: boolean;
1342
2077
  }): Promise<void>;
2078
+ /** Retrieve the persisted SessionRecord for a given session ID. Returns undefined if no store or record not found. */
1343
2079
  getSessionRecord(sessionId: string): SessionRecord | undefined;
2080
+ /** Cancel a session: abort in-flight prompt, transition to cancelled, destroy agent, and persist. */
1344
2081
  cancelSession(sessionId: string): Promise<void>;
2082
+ /** List live (in-memory) sessions, optionally filtered by channel. Excludes assistant sessions. */
1345
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
+ */
1346
2088
  listAllSessions(channelId?: string): SessionSummary[];
2089
+ /** List all stored SessionRecords, optionally filtered by status. Excludes assistant sessions. */
1347
2090
  listRecords(filter?: {
1348
2091
  statuses?: string[];
1349
2092
  }): SessionRecord[];
2093
+ /** Remove a session's stored record and emit a SESSION_DELETED event. */
1350
2094
  removeRecord(sessionId: string): Promise<void>;
1351
2095
  /**
1352
2096
  * Graceful shutdown: persist session state without killing agent subprocesses.
@@ -1362,13 +2106,38 @@ declare class SessionManager {
1362
2106
  destroyAll(): Promise<void>;
1363
2107
  }
1364
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
+ */
1365
2121
  declare class NotificationManager {
1366
2122
  private adapters;
1367
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
+ */
1368
2130
  notify(channelId: string, notification: NotificationMessage): Promise<void>;
1369
- notifyAll(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
+ */
2137
+ notifyAll(notification: NotificationMessage): Promise<void>;
1370
2138
  }
1371
2139
 
2140
+ /** Services required by SessionBridge for message transformation, persistence, and middleware. */
1372
2141
  interface BridgeDeps {
1373
2142
  messageTransformer: MessageTransformer;
1374
2143
  notificationManager: NotificationManager;
@@ -1377,6 +2146,18 @@ interface BridgeDeps {
1377
2146
  fileService?: FileServiceInterface;
1378
2147
  middlewareChain?: MiddlewareChain;
1379
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
+ */
1380
2161
  declare class SessionBridge {
1381
2162
  private session;
1382
2163
  private adapter;
@@ -1390,9 +2171,20 @@ declare class SessionBridge {
1390
2171
  private listen;
1391
2172
  /** Send message to adapter, optionally running through message:outgoing middleware */
1392
2173
  private sendMessage;
1393
- /** 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
+ */
1394
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
+ */
1395
2186
  connect(): void;
2187
+ /** Unsubscribe all session event listeners and clean up adapter state. */
1396
2188
  disconnect(): void;
1397
2189
  /** Dispatch an agent event through middleware and to the adapter */
1398
2190
  private dispatchAgentEvent;
@@ -1407,6 +2199,11 @@ declare class SessionBridge {
1407
2199
  private emitAfterResolve;
1408
2200
  }
1409
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
+ */
1410
2207
  interface TunnelEntry {
1411
2208
  port: number;
1412
2209
  type: 'system' | 'user';
@@ -1419,6 +2216,12 @@ interface TunnelEntry {
1419
2216
  createdAt: string;
1420
2217
  }
1421
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
+ */
1422
2225
  interface ViewerEntry {
1423
2226
  id: string;
1424
2227
  type: 'file' | 'diff' | 'output';
@@ -1431,6 +2234,13 @@ interface ViewerEntry {
1431
2234
  createdAt: number;
1432
2235
  expiresAt: number;
1433
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
+ */
1434
2244
  declare class ViewerStore {
1435
2245
  private entries;
1436
2246
  private cleanupTimer;
@@ -1446,6 +2256,11 @@ declare class ViewerStore {
1446
2256
  destroy(): void;
1447
2257
  }
1448
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
+ */
1449
2264
  interface TunnelConfig {
1450
2265
  enabled: boolean;
1451
2266
  port: number;
@@ -1459,6 +2274,14 @@ interface TunnelConfig {
1459
2274
  };
1460
2275
  }
1461
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
+ */
1462
2285
  declare class TunnelService {
1463
2286
  private registry;
1464
2287
  private store;
@@ -1485,12 +2308,26 @@ declare class TunnelService {
1485
2308
  outputUrl(entryId: string): string;
1486
2309
  }
1487
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
+ */
1488
2317
  interface ContextProvider {
1489
2318
  readonly name: string;
1490
2319
  isAvailable(repoPath: string): Promise<boolean>;
1491
2320
  listSessions(query: ContextQuery): Promise<SessionListResult>;
1492
2321
  buildContext(query: ContextQuery, options?: ContextOptions): Promise<ContextResult>;
1493
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
+ */
1494
2331
  interface ContextQuery {
1495
2332
  repoPath: string;
1496
2333
  type: "branch" | "commit" | "pr" | "latest" | "checkpoint" | "session";
@@ -1504,6 +2341,11 @@ interface ContextOptions {
1504
2341
  /** When true, skip the context cache (use for live switches where history just changed) */
1505
2342
  noCache?: boolean;
1506
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
+ */
1507
2349
  interface SessionInfo {
1508
2350
  checkpointId: string;
1509
2351
  sessionIndex: string;
@@ -1520,7 +2362,18 @@ interface SessionListResult {
1520
2362
  sessions: SessionInfo[];
1521
2363
  estimatedTokens: number;
1522
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
+ */
1523
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
+ */
1524
2377
  interface ContextResult {
1525
2378
  markdown: string;
1526
2379
  tokenEstimate: number;
@@ -1534,11 +2387,22 @@ interface ContextResult {
1534
2387
  };
1535
2388
  }
1536
2389
 
2390
+ /**
2391
+ * Root structure persisted to disk for one session.
2392
+ * `version` allows future schema migrations without breaking existing files.
2393
+ */
1537
2394
  interface SessionHistory {
1538
2395
  version: 1;
1539
2396
  sessionId: string;
1540
2397
  turns: Turn[];
1541
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
+ */
1542
2406
  interface Turn {
1543
2407
  index: number;
1544
2408
  role: "user" | "assistant";
@@ -1638,6 +2502,12 @@ interface ConfigChangeStep {
1638
2502
  value: string;
1639
2503
  }
1640
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
+ */
1641
2511
  declare class HistoryStore {
1642
2512
  private readonly dir;
1643
2513
  constructor(dir: string);
@@ -1649,12 +2519,30 @@ declare class HistoryStore {
1649
2519
  private filePath;
1650
2520
  }
1651
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
+ */
1652
2534
  declare class ContextManager {
1653
2535
  private providers;
1654
2536
  private cache;
1655
2537
  private historyStore?;
1656
2538
  private sessionFlusher?;
1657
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
+ */
1658
2546
  setHistoryStore(store: HistoryStore): void;
1659
2547
  /** Register a callback that flushes in-memory recorder state for a session to disk. */
1660
2548
  registerFlusher(fn: (sessionId: string) => Promise<void>): void;
@@ -1664,13 +2552,45 @@ declare class ContextManager {
1664
2552
  * where the last turn hasn't been persisted yet.
1665
2553
  */
1666
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
+ */
1667
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
+ */
1668
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
+ */
1669
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
+ */
1670
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
+ */
1671
2590
  buildContext(query: ContextQuery, options?: ContextOptions): Promise<ContextResult | null>;
1672
2591
  }
1673
2592
 
2593
+ /** Parameters for creating a new session — used by SessionFactory.create() and Core.createFullSession(). */
1674
2594
  interface SessionCreateParams {
1675
2595
  channelId: string;
1676
2596
  agentName: string;
@@ -1685,6 +2605,14 @@ interface SideEffectDeps {
1685
2605
  notificationManager: NotificationManager;
1686
2606
  tunnelService?: TunnelService;
1687
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
+ */
1688
2616
  declare class SessionFactory {
1689
2617
  private agentManager;
1690
2618
  private sessionManager;
@@ -1714,6 +2642,10 @@ declare class SessionFactory {
1714
2642
  getAgentAllowedPaths?: () => string[];
1715
2643
  constructor(agentManager: AgentManager, sessionManager: SessionManager, speechServiceAccessor: SpeechService | (() => SpeechService), eventBus: EventBus, instanceRoot?: string | undefined);
1716
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
+ */
1717
2649
  create(params: SessionCreateParams): Promise<Session>;
1718
2650
  /**
1719
2651
  * Get active session by thread, or attempt lazy resume from store.
@@ -1721,7 +2653,13 @@ declare class SessionFactory {
1721
2653
  */
1722
2654
  getOrResume(channelId: string, threadId: string): Promise<Session | null>;
1723
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
+ */
1724
2661
  private lazyResume;
2662
+ /** Create a brand-new session, resolving agent name and workspace from config if not provided. */
1725
2663
  handleNewSession(channelId: string, agentName?: string, workspacePath?: string, options?: {
1726
2664
  createThread?: boolean;
1727
2665
  threadId?: string;
@@ -1729,6 +2667,7 @@ declare class SessionFactory {
1729
2667
  /** NOTE: handleNewChat is currently dead code — never called outside core.ts itself.
1730
2668
  * Moving it anyway for completeness; can be removed in a future cleanup. */
1731
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). */
1732
2671
  createSessionWithContext(params: {
1733
2672
  channelId: string;
1734
2673
  agentName: string;
@@ -1741,13 +2680,29 @@ declare class SessionFactory {
1741
2680
  session: Session;
1742
2681
  contextResult: ContextResult | null;
1743
2682
  }>;
2683
+ /** Wire session-level side effects: usage tracking (via EventBus) and tunnel cleanup on session end. */
1744
2684
  wireSideEffects(session: Session, deps: SideEffectDeps): void;
1745
2685
  }
1746
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
+ */
1747
2695
  interface SecurityConfig {
1748
2696
  allowedUserIds: string[];
1749
2697
  maxConcurrentSessions: number;
1750
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
+ */
1751
2706
  declare class SecurityGuard {
1752
2707
  private getSecurityConfig;
1753
2708
  private sessionManager;
@@ -1756,6 +2711,16 @@ declare class SecurityGuard {
1756
2711
  status: string;
1757
2712
  }>;
1758
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
+ */
1759
2724
  checkAccess(message: {
1760
2725
  userId: string | number;
1761
2726
  }): Promise<{
@@ -1766,46 +2731,91 @@ declare class SecurityGuard {
1766
2731
  }>;
1767
2732
  }
1768
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
+ */
1769
2744
  declare class ServiceRegistry {
1770
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
+ */
1771
2750
  register<T>(name: string, implementation: T, pluginName: string): void;
2751
+ /** Register a service, replacing any existing registration (used by override plugins). */
1772
2752
  registerOverride<T>(name: string, implementation: T, pluginName: string): void;
2753
+ /** Retrieve a service by name. Returns undefined if not registered. */
1773
2754
  get<T>(name: string): T | undefined;
2755
+ /** Check whether a service is registered. */
1774
2756
  has(name: string): boolean;
2757
+ /** List all registered services with their owning plugin names. */
1775
2758
  list(): Array<{
1776
2759
  name: string;
1777
2760
  pluginName: string;
1778
2761
  }>;
2762
+ /** Remove a single service by name. */
1779
2763
  unregister(name: string): void;
2764
+ /** Remove all services owned by a specific plugin (called during plugin unload). */
1780
2765
  unregisterByPlugin(pluginName: string): void;
1781
2766
  }
1782
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
+ */
1783
2774
  interface PluginEntry {
1784
2775
  version: string;
1785
2776
  installedAt: string;
1786
2777
  updatedAt: string;
2778
+ /** How the plugin was installed: bundled with core, from npm, or from a local path */
1787
2779
  source: 'builtin' | 'npm' | 'local';
1788
2780
  enabled: boolean;
1789
2781
  settingsPath: string;
1790
2782
  description?: string;
1791
2783
  }
1792
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
+ */
1793
2792
  declare class PluginRegistry {
1794
2793
  private registryPath;
1795
2794
  private data;
1796
2795
  constructor(registryPath: string);
2796
+ /** Return all installed plugins as a Map. */
1797
2797
  list(): Map<string, PluginEntry>;
2798
+ /** Look up a plugin by name. Returns undefined if not installed. */
1798
2799
  get(name: string): PluginEntry | undefined;
2800
+ /** Record a newly installed plugin. Timestamps are set automatically. */
1799
2801
  register(name: string, entry: RegisterInput): void;
2802
+ /** Remove a plugin from the registry. */
1800
2803
  remove(name: string): void;
2804
+ /** Enable or disable a plugin. Disabled plugins are skipped at boot. */
1801
2805
  setEnabled(name: string, enabled: boolean): void;
2806
+ /** Update the stored version (called after successful migration). */
1802
2807
  updateVersion(name: string, version: string): void;
2808
+ /** Return only enabled plugins. */
1803
2809
  listEnabled(): Map<string, PluginEntry>;
2810
+ /** Filter plugins by installation source. */
1804
2811
  listBySource(source: PluginEntry['source']): Map<string, PluginEntry>;
2812
+ /** Load registry data from disk. Silently starts empty if file doesn't exist. */
1805
2813
  load(): Promise<void>;
2814
+ /** Persist registry data to disk. */
1806
2815
  save(): Promise<void>;
1807
2816
  }
1808
2817
 
2818
+ /** Options for constructing a LifecycleManager. All fields are optional with sensible defaults. */
1809
2819
  interface LifecycleManagerOpts {
1810
2820
  serviceRegistry?: ServiceRegistry;
1811
2821
  middlewareChain?: MiddlewareChain;
@@ -1825,6 +2835,21 @@ interface LifecycleManagerOpts {
1825
2835
  /** Root directory for this OpenACP instance (default: ~/.openacp) */
1826
2836
  instanceRoot?: string;
1827
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
+ */
1828
2853
  declare class LifecycleManager {
1829
2854
  readonly serviceRegistry: ServiceRegistry;
1830
2855
  readonly middlewareChain: MiddlewareChain;
@@ -1842,8 +2867,11 @@ declare class LifecycleManager {
1842
2867
  private loadOrder;
1843
2868
  private _loaded;
1844
2869
  private _failed;
2870
+ /** Names of plugins that successfully completed setup(). */
1845
2871
  get loadedPlugins(): string[];
2872
+ /** Names of plugins whose setup() threw an error. These plugins are skipped but don't crash the system. */
1846
2873
  get failedPlugins(): string[];
2874
+ /** The PluginRegistry tracking installed and enabled plugin state. */
1847
2875
  get registry(): PluginRegistry | undefined;
1848
2876
  /** Plugin definitions currently in load order (loaded + failed). */
1849
2877
  get plugins(): OpenACPPlugin[];
@@ -1851,20 +2879,46 @@ declare class LifecycleManager {
1851
2879
  get instanceRoot(): string | undefined;
1852
2880
  constructor(opts?: LifecycleManagerOpts);
1853
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
+ */
1854
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
+ */
1855
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
+ */
1856
2899
  shutdown(): Promise<void>;
1857
2900
  }
1858
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
+ */
1859
2909
  declare class MenuRegistry {
1860
2910
  private items;
2911
+ /** Register or replace a menu item by its unique ID. */
1861
2912
  register(item: MenuItem): void;
2913
+ /** Remove a menu item by ID. */
1862
2914
  unregister(id: string): void;
2915
+ /** Look up a single menu item by ID. */
1863
2916
  getItem(id: string): MenuItem | undefined;
1864
- /** Get all visible items sorted by priority */
2917
+ /** Get all visible items sorted by priority (lower number = shown first). */
1865
2918
  getItems(): MenuItem[];
1866
2919
  }
1867
2920
 
2921
+ /** Subset of OpenACPCore methods needed by AssistantManager, avoiding a circular import. */
1868
2922
  interface AssistantManagerCore {
1869
2923
  createSession(params: {
1870
2924
  channelId: string;
@@ -1884,45 +2938,96 @@ interface AssistantManagerCore {
1884
2938
  };
1885
2939
  sessionStore: SessionStore | null;
1886
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
+ */
1887
2954
  declare class AssistantManager {
1888
2955
  private core;
1889
2956
  private registry;
1890
2957
  private sessions;
1891
2958
  private pendingSystemPrompts;
1892
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
+ */
1893
2967
  getOrSpawn(channelId: string, threadId: string): Promise<Session>;
2968
+ /** Returns the active assistant session for a channel, or null if none exists. */
1894
2969
  get(channelId: string): Session | null;
1895
2970
  /**
1896
2971
  * Consume and return any pending system prompt for a channel.
1897
2972
  * Should be prepended to the first real user message.
1898
2973
  */
1899
2974
  consumePendingSystemPrompt(channelId: string): string | undefined;
2975
+ /** Checks whether a given session ID belongs to the built-in assistant. */
1900
2976
  isAssistant(sessionId: string): boolean;
1901
2977
  }
1902
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
+ */
1903
2987
  interface InstanceContext {
2988
+ /** Unique identifier for this instance (UUID). */
1904
2989
  id: string;
2990
+ /** Absolute path to the instance root directory (e.g. `~/my-project/.openacp/`). */
1905
2991
  root: string;
2992
+ /** Pre-resolved paths to all instance files and directories. */
1906
2993
  paths: {
1907
2994
  config: string;
1908
2995
  sessions: string;
1909
2996
  agents: string;
2997
+ /** Shared across all instances — lives under ~/.openacp/cache/. */
1910
2998
  registryCache: string;
1911
2999
  plugins: string;
1912
3000
  pluginsData: string;
1913
3001
  pluginRegistry: string;
1914
3002
  logs: string;
3003
+ /** PID file written by the daemon process to track the running server. */
1915
3004
  pid: string;
3005
+ /** Marker file that indicates the daemon was intentionally started. */
1916
3006
  running: string;
3007
+ /** Written at startup with the API server's port number. */
1917
3008
  apiPort: string;
1918
3009
  apiSecret: string;
3010
+ /** Shared across all instances — lives under ~/.openacp/bin/. */
1919
3011
  bin: string;
1920
3012
  cache: string;
1921
3013
  tunnels: string;
3014
+ /** Shared across all instances — lives under ~/.openacp/agents/. */
1922
3015
  agentsDir: string;
1923
3016
  };
1924
3017
  }
1925
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
+ */
1926
3031
  declare class OpenACPCore {
1927
3032
  configManager: ConfigManager;
1928
3033
  agentCatalog: AgentCatalog;
@@ -1944,26 +3049,89 @@ declare class OpenACPCore {
1944
3049
  readonly menuRegistry: MenuRegistry;
1945
3050
  readonly assistantRegistry: AssistantRegistry;
1946
3051
  assistantManager: AssistantManager;
3052
+ /** @throws if the service hasn't been registered by its plugin yet */
1947
3053
  private getService;
3054
+ /** Access control and rate-limiting guard (provided by security plugin). */
1948
3055
  get securityGuard(): SecurityGuard;
3056
+ /** Cross-session notification delivery (provided by notifications plugin). */
1949
3057
  get notificationManager(): NotificationManager;
3058
+ /** File I/O service for agent attachment storage (provided by file-service plugin). */
1950
3059
  get fileService(): FileServiceInterface;
3060
+ /** Text-to-speech / speech-to-text engine (provided by speech plugin). */
1951
3061
  get speechService(): SpeechService;
3062
+ /** Conversation history builder for context injection (provided by context plugin). */
1952
3063
  get contextManager(): ContextManager;
3064
+ /** Per-plugin persistent settings (e.g. API keys). */
1953
3065
  get settingsManager(): SettingsManager | undefined;
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
+ */
1954
3076
  constructor(configManager: ConfigManager, ctx: InstanceContext);
3077
+ /** Optional tunnel for generating public URLs (code viewer links, etc.). */
1955
3078
  get tunnelService(): TunnelService | undefined;
3079
+ /** Propagate tunnel service to MessageTransformer so it can generate viewer links. */
1956
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
+ */
1957
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
+ */
1958
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
+ */
1959
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
+ */
1960
3105
  archiveSession(sessionId: string): Promise<{
1961
3106
  ok: true;
1962
3107
  } | {
1963
3108
  ok: false;
1964
3109
  error: string;
1965
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
+ */
1966
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
+ */
1967
3135
  createSession(params: {
1968
3136
  channelId: string;
1969
3137
  agentName: string;
@@ -1975,10 +3143,17 @@ declare class OpenACPCore {
1975
3143
  threadId?: string;
1976
3144
  isAssistant?: boolean;
1977
3145
  }): Promise<Session>;
3146
+ /** Convenience wrapper: create a new session with default agent/workspace resolution. */
1978
3147
  handleNewSession(channelId: string, agentName?: string, workspacePath?: string, options?: {
1979
3148
  createThread?: boolean;
1980
3149
  threadId?: string;
1981
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
+ */
1982
3157
  adoptSession(agentName: string, agentSessionId: string, cwd: string, channelId?: string): Promise<{
1983
3158
  ok: true;
1984
3159
  sessionId: string;
@@ -1989,7 +3164,9 @@ declare class OpenACPCore {
1989
3164
  error: string;
1990
3165
  message: string;
1991
3166
  }>;
3167
+ /** Start a new chat within the same agent and workspace as the current session's thread. */
1992
3168
  handleNewChat(channelId: string, currentThreadId: string): Promise<Session | null>;
3169
+ /** Create a session and inject conversation context from a prior session or repo. */
1993
3170
  createSessionWithContext(params: {
1994
3171
  channelId: string;
1995
3172
  agentName: string;
@@ -2002,15 +3179,29 @@ declare class OpenACPCore {
2002
3179
  session: Session;
2003
3180
  contextResult: ContextResult | null;
2004
3181
  }>;
3182
+ /** Switch a session's active agent. Delegates to AgentSwitchHandler for state coordination. */
2005
3183
  switchSessionAgent(sessionId: string, toAgent: string): Promise<{
2006
3184
  resumed: boolean;
2007
3185
  }>;
3186
+ /** Find a session by channel+thread, resuming from disk if not in memory. */
2008
3187
  getOrResumeSession(channelId: string, threadId: string): Promise<Session | null>;
3188
+ /** Find a session by ID, resuming from disk if not in memory. */
2009
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
+ */
2010
3196
  attachAdapter(sessionId: string, adapterId: string): Promise<{
2011
3197
  threadId: string;
2012
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
+ */
2013
3203
  detachAdapter(sessionId: string, adapterId: string): Promise<void>;
3204
+ /** Build the platforms map (adapter → thread/topic IDs) for persistence. */
2014
3205
  private buildPlatformsFromSession;
2015
3206
  /** Composite bridge key: "adapterId:sessionId" */
2016
3207
  private bridgeKey;
@@ -2018,11 +3209,20 @@ declare class OpenACPCore {
2018
3209
  private getSessionBridgeKeys;
2019
3210
  /** Connect a session bridge for the given session (used by AssistantManager) */
2020
3211
  connectSessionBridge(session: Session): void;
2021
- /** Create a SessionBridge for the given session and adapter.
2022
- * 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
+ */
2023
3219
  createBridge(session: Session, adapter: IChannelAdapter, adapterId?: string): SessionBridge;
2024
3220
  }
2025
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
+ */
2026
3226
  interface RegisteredCommand extends CommandDef {
2027
3227
  /** Scope extracted from pluginName, e.g. '@openacp/speech' → 'speech' */
2028
3228
  scope?: string;
@@ -2037,6 +3237,11 @@ interface RegisteredCommand extends CommandDef {
2037
3237
  * - Adapter plugins (@openacp/telegram, @openacp/discord)
2038
3238
  * registering a command that already exists → stored as an override
2039
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.
2040
3245
  */
2041
3246
  declare class CommandRegistry {
2042
3247
  /** Main registry: short names + qualified names → RegisteredCommand */
@@ -2061,27 +3266,53 @@ declare class CommandRegistry {
2061
3266
  /** Filter commands by category. */
2062
3267
  getByCategory(category: 'system' | 'plugin'): RegisteredCommand[];
2063
3268
  /**
2064
- * Parse and execute a command string.
2065
- * @param commandString - Full command string, e.g. "/greet hello world"
2066
- * @param baseArgs - Base arguments (channelId, userId, etc.)
2067
- * @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).
2068
3278
  */
2069
3279
  execute(commandString: string, baseArgs: CommandArgs): Promise<CommandResponse>;
2070
3280
  /** Extract scope from plugin name: '@openacp/speech' → 'speech', 'my-plugin' → 'my-plugin' */
2071
3281
  static extractScope(pluginName: string): string;
2072
3282
  }
2073
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. */
2074
3301
  interface CheckResult {
2075
3302
  status: "pass" | "warn" | "fail";
2076
3303
  message: string;
3304
+ /** Whether this result has an associated automatic fix. */
2077
3305
  fixable?: boolean;
3306
+ /** "safe" fixes are applied automatically; "risky" fixes require user confirmation. */
2078
3307
  fixRisk?: "safe" | "risky";
2079
3308
  fix?: () => Promise<FixResult>;
2080
3309
  }
3310
+ /** Outcome of applying a fix. */
2081
3311
  interface FixResult {
2082
3312
  success: boolean;
2083
3313
  message: string;
2084
3314
  }
3315
+ /** Aggregated report returned by DoctorEngine.runAll(). */
2085
3316
  interface DoctorReport {
2086
3317
  categories: CategoryResult[];
2087
3318
  summary: {
@@ -2090,18 +3321,28 @@ interface DoctorReport {
2090
3321
  failed: number;
2091
3322
  fixed: number;
2092
3323
  };
3324
+ /** Risky fixes that were deferred for user confirmation. */
2093
3325
  pendingFixes: PendingFix[];
2094
3326
  }
3327
+ /** All results for a single check category (e.g. "Config", "Agents"). */
2095
3328
  interface CategoryResult {
2096
3329
  name: string;
2097
3330
  results: CheckResult[];
2098
3331
  }
3332
+ /** A risky fix deferred for interactive user confirmation. */
2099
3333
  interface PendingFix {
2100
3334
  category: string;
2101
3335
  message: string;
2102
3336
  fix: () => Promise<FixResult>;
2103
3337
  }
2104
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
+ */
2105
3346
  declare class DoctorEngine {
2106
3347
  private dryRun;
2107
3348
  private dataDir;
@@ -2109,55 +3350,153 @@ declare class DoctorEngine {
2109
3350
  dryRun?: boolean;
2110
3351
  dataDir?: string;
2111
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
+ */
2112
3359
  runAll(): Promise<DoctorReport>;
3360
+ /** Constructs the shared context used by all checks — loads config if available. */
2113
3361
  private buildContext;
2114
3362
  }
2115
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
+ */
2116
3379
  interface ConfigFieldDef {
3380
+ /** Dot-path into the Config object (e.g. "logging.level"). */
2117
3381
  path: string;
3382
+ /** Human-readable label for UI display. */
2118
3383
  displayName: string;
3384
+ /** Grouping key for organizing fields in the editor (e.g. "agent", "logging"). */
2119
3385
  group: string;
3386
+ /** UI control type — determines validation and rendering. */
2120
3387
  type: "toggle" | "select" | "number" | "string";
3388
+ /** For "select" type: allowed values, either static or derived from current config. */
2121
3389
  options?: string[] | ((config: Config) => string[]);
3390
+ /** "safe" fields can be exposed via the API; "sensitive" fields require direct file access. */
2122
3391
  scope: "safe" | "sensitive";
3392
+ /** Whether changes to this field take effect without restarting the server. */
2123
3393
  hotReload: boolean;
2124
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
+ */
2125
3400
  declare const CONFIG_REGISTRY: ConfigFieldDef[];
3401
+ /** Looks up a field definition by its dot-path. */
2126
3402
  declare function getFieldDef(path: string): ConfigFieldDef | undefined;
3403
+ /** Returns only fields safe to expose via the REST API. */
2127
3404
  declare function getSafeFields(): ConfigFieldDef[];
3405
+ /** Checks whether a config field can be changed without restarting the server. */
2128
3406
  declare function isHotReloadable(path: string): boolean;
3407
+ /** Resolves select options — evaluates the function form against the current config if needed. */
2129
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. */
2130
3410
  declare function getConfigValue(config: Config, path: string): unknown;
2131
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
+ */
2132
3419
  declare function runConfigEditor(configManager: ConfigManager, mode?: 'file' | 'api', apiPort?: number, settingsManager?: SettingsManager): Promise<void>;
2133
3420
 
3421
+ /** Returns the path to the PID file for the given instance root. */
2134
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
+ */
2135
3427
  declare function getStatus(pidPath: string): {
2136
3428
  running: boolean;
2137
3429
  pid?: number;
2138
3430
  };
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
+ */
2139
3444
  declare function startDaemon(pidPath: string, logDir: string | undefined, instanceRoot: string): {
2140
3445
  pid: number;
2141
3446
  } | {
2142
3447
  error: string;
2143
3448
  };
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
+ */
2144
3459
  declare function stopDaemon(pidPath: string, instanceRoot: string): Promise<{
2145
3460
  stopped: boolean;
2146
3461
  pid?: number;
2147
3462
  error?: string;
2148
3463
  }>;
2149
3464
 
3465
+ /** Returns true if the current platform supports auto-start management. */
2150
3466
  declare function isAutoStartSupported(): boolean;
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
+ */
2151
3474
  declare function installAutoStart(logDir: string, instanceRoot: string, instanceId: string): {
2152
3475
  success: boolean;
2153
3476
  error?: string;
2154
3477
  };
3478
+ /**
3479
+ * Remove the login-time service registration for the given instance.
3480
+ * No-op if the service is not installed.
3481
+ */
2155
3482
  declare function uninstallAutoStart(instanceId: string): {
2156
3483
  success: boolean;
2157
3484
  error?: string;
2158
3485
  };
3486
+ /** Returns true if the login-time service is currently registered for this instance. */
2159
3487
  declare function isAutoStartInstalled(instanceId: string): boolean;
2160
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
+ */
2161
3500
  declare class FileService {
2162
3501
  readonly baseDir: string;
2163
3502
  constructor(baseDir: string);
@@ -2166,7 +3505,19 @@ declare class FileService {
2166
3505
  * Called on startup to prevent unbounded disk growth.
2167
3506
  */
2168
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
+ */
2169
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
+ */
2170
3521
  resolveFile(filePath: string): Promise<Attachment | null>;
2171
3522
  /**
2172
3523
  * Convert OGG Opus audio to WAV format.
@@ -2184,6 +3535,7 @@ declare class FileService {
2184
3535
  }): Promise<string>;
2185
3536
  /** Instance method — delegates to static for FileServiceInterface compliance */
2186
3537
  extensionFromMime(mimeType: string): string;
3538
+ /** Returns the canonical file extension for a given MIME type (e.g. `"image/png"` → `".png"`). */
2187
3539
  static extensionFromMime(mimeType: string): string;
2188
3540
  }
2189
3541
 
@@ -2192,12 +3544,15 @@ interface ApiConfig {
2192
3544
  host: string;
2193
3545
  }
2194
3546
 
3547
+ /** A token record persisted in tokens.json. Tokens are never deleted; they are revoked by flag. */
2195
3548
  interface StoredToken {
2196
3549
  id: string;
2197
3550
  name: string;
2198
3551
  role: string;
3552
+ /** Custom scope overrides; when absent, role defaults from ROLES apply. */
2199
3553
  scopes?: string[];
2200
3554
  createdAt: string;
3555
+ /** Absolute deadline after which the token cannot be refreshed — requires re-authentication. */
2201
3556
  refreshDeadline: string;
2202
3557
  lastUsedAt?: string;
2203
3558
  revoked: boolean;
@@ -2205,14 +3560,20 @@ interface StoredToken {
2205
3560
  interface CreateTokenOpts {
2206
3561
  role: string;
2207
3562
  name: string;
3563
+ /** Duration string, e.g. "24h", "7d". Parsed by parseDuration(). */
2208
3564
  expire: string;
2209
3565
  scopes?: string[];
2210
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
+ */
2211
3571
  interface StoredCode {
2212
3572
  code: string;
2213
3573
  role: string;
2214
3574
  scopes?: string[];
2215
3575
  name: string;
3576
+ /** Duration that the resulting JWT will be valid for. */
2216
3577
  expire: string;
2217
3578
  createdAt: string;
2218
3579
  expiresAt: string;
@@ -2223,9 +3584,21 @@ interface CreateCodeOpts {
2223
3584
  name: string;
2224
3585
  expire: string;
2225
3586
  scopes?: string[];
3587
+ /** How long the code itself is valid; defaults to 30 minutes. */
2226
3588
  codeTtlMs?: number;
2227
3589
  }
2228
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
+ */
2229
3602
  declare class TokenStore {
2230
3603
  private filePath;
2231
3604
  private tokens;
@@ -2233,64 +3606,150 @@ declare class TokenStore {
2233
3606
  private savePromise;
2234
3607
  private savePending;
2235
3608
  constructor(filePath: string);
3609
+ /** Loads token and code state from disk. Safe to call at startup; missing file is not an error. */
2236
3610
  load(): Promise<void>;
2237
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
+ */
2238
3616
  private scheduleSave;
3617
+ /** Creates a new token record and schedules a persist. Returns the stored token including its generated id. */
2239
3618
  create(opts: CreateTokenOpts): StoredToken;
2240
3619
  get(id: string): StoredToken | undefined;
3620
+ /** Marks a token as revoked; future auth checks will reject it immediately. */
2241
3621
  revoke(id: string): void;
3622
+ /** Returns all non-revoked tokens. Revoked tokens are retained until cleanup() removes them. */
2242
3623
  list(): StoredToken[];
2243
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
+ */
2244
3631
  updateLastUsed(id: string): void;
2245
3632
  /** Wait for any in-flight and pending saves to complete */
2246
3633
  flush(): Promise<void>;
2247
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
+ */
2248
3641
  createCode(opts: CreateCodeOpts): StoredCode;
2249
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
+ */
2250
3650
  exchangeCode(code: string): StoredCode | undefined;
2251
3651
  listCodes(): StoredCode[];
2252
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
+ */
2253
3660
  cleanup(): void;
2254
3661
  }
2255
3662
 
2256
3663
  interface ApiServerOptions {
2257
3664
  port: number;
2258
3665
  host: string;
3666
+ /** Returns the raw API secret used for full-admin Bearer auth. Fetched lazily so rotation is safe. */
2259
3667
  getSecret: () => string;
3668
+ /** Returns the HMAC secret used to sign and verify JWTs. */
2260
3669
  getJwtSecret: () => string;
2261
3670
  tokenStore: TokenStore;
2262
3671
  logger?: boolean;
2263
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
+ */
2264
3679
  interface ApiServerInstance {
2265
3680
  app: FastifyInstance;
3681
+ /** Binds to the configured host/port, auto-incrementing if EADDRINUSE. */
2266
3682
  start(): Promise<{
2267
3683
  port: number;
2268
3684
  host: string;
2269
3685
  }>;
2270
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
+ */
2271
3693
  registerPlugin(prefix: string, plugin: FastifyPluginAsync, opts?: {
2272
3694
  auth?: boolean;
2273
3695
  }): void;
2274
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
+ */
2275
3704
  declare function createApiServer(options: ApiServerOptions): Promise<ApiServerInstance>;
2276
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
+ */
2277
3713
  interface ApiServerService {
3714
+ /** Register a Fastify plugin at the given prefix, with optional auth bypass. */
2278
3715
  registerPlugin(prefix: string, plugin: FastifyPluginAsync, opts?: {
2279
3716
  auth?: boolean;
2280
3717
  }): void;
3718
+ /** Pre-handler that validates Bearer/JWT auth — attach to routes that need custom auth. */
2281
3719
  authPreHandler: preHandlerHookHandler;
3720
+ /** Creates a pre-handler that requires all listed scopes. */
2282
3721
  requireScopes(...scopes: string[]): preHandlerHookHandler;
3722
+ /** Creates a pre-handler that requires a minimum role level. */
2283
3723
  requireRole(role: string): preHandlerHookHandler;
2284
3724
  getPort(): number;
2285
3725
  getBaseUrl(): string;
3726
+ /** Returns the public tunnel URL, or null if no tunnel is active. */
2286
3727
  getTunnelUrl(): string | null;
2287
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
+ */
2288
3735
  declare function createApiServerService(server: ApiServerInstance, getPort: () => number, getBaseUrl: () => string, getTunnelUrl: () => string | null, authPreHandler: preHandlerHookHandler): ApiServerService;
2289
3736
 
2290
3737
  interface SessionStats {
2291
3738
  active: number;
2292
3739
  total: number;
2293
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
+ */
2294
3753
  declare class SSEManager {
2295
3754
  private eventBus;
2296
3755
  private getSessionStats;
@@ -2300,24 +3759,74 @@ declare class SSEManager {
2300
3759
  private healthInterval?;
2301
3760
  private boundHandlers;
2302
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
+ */
2303
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
+ */
2304
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
+ */
2305
3785
  broadcast(event: string, data: unknown): void;
2306
3786
  /**
2307
3787
  * Returns a Fastify route handler that hijacks the response
2308
3788
  * and delegates to the raw http SSE handler.
2309
3789
  */
2310
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
+ */
2311
3797
  stop(): void;
2312
3798
  }
2313
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
+ */
2314
3816
  declare class StaticServer {
2315
3817
  private uiDir;
2316
3818
  constructor(uiDir?: string);
3819
+ /** Returns true if a UI build was found and static serving is active. */
2317
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
+ */
2318
3826
  serve(req: http.IncomingMessage, res: http.ServerResponse): boolean;
2319
3827
  }
2320
3828
 
3829
+ /** Flat view of a session record, enriched with its Telegram topic ID. */
2321
3830
  interface TopicInfo {
2322
3831
  sessionId: string;
2323
3832
  topicId: number | null;
@@ -2326,8 +3835,10 @@ interface TopicInfo {
2326
3835
  agentName: string;
2327
3836
  lastActiveAt: string;
2328
3837
  }
3838
+ /** Result of a single-topic deletion operation. */
2329
3839
  interface DeleteTopicResult {
2330
3840
  ok: boolean;
3841
+ /** True when deletion requires explicit confirmation (session is still active). */
2331
3842
  needsConfirmation?: boolean;
2332
3843
  topicId?: number | null;
2333
3844
  session?: {
@@ -2337,6 +3848,7 @@ interface DeleteTopicResult {
2337
3848
  };
2338
3849
  error?: string;
2339
3850
  }
3851
+ /** Aggregate result for a bulk cleanup operation. */
2340
3852
  interface CleanupResult {
2341
3853
  deleted: string[];
2342
3854
  failed: {
@@ -2348,21 +3860,54 @@ interface SystemTopicIds {
2348
3860
  notificationTopicId: number | null;
2349
3861
  assistantTopicId: number | null;
2350
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
+ */
2351
3870
  declare class TopicManager {
2352
3871
  private sessionManager;
2353
3872
  private adapter;
2354
3873
  private systemTopicIds;
2355
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
+ */
2356
3879
  listTopics(filter?: {
2357
3880
  statuses?: string[];
2358
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
+ */
2359
3888
  deleteTopic(sessionId: string, options?: {
2360
3889
  confirmed?: boolean;
2361
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
+ */
2362
3895
  cleanup(statuses?: string[]): Promise<CleanupResult>;
2363
3896
  private isSystemTopic;
2364
3897
  }
2365
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
+ */
2366
3911
  declare class EntireProvider implements ContextProvider {
2367
3912
  readonly name = "entire";
2368
3913
  isAvailable(repoPath: string): Promise<boolean>;
@@ -2373,26 +3918,43 @@ declare class EntireProvider implements ContextProvider {
2373
3918
  private buildTitle;
2374
3919
  }
2375
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
+ */
2376
3926
  interface RenderedMessage<TComponents = unknown> {
2377
3927
  body: string;
2378
3928
  format: "html" | "markdown" | "plain" | "structured";
2379
3929
  attachments?: RenderedAttachment[];
3930
+ /** Platform-specific UI components (e.g., Telegram inline keyboards). */
2380
3931
  components?: TComponents;
2381
3932
  }
3933
+ /** A rendered permission request with approve/deny action buttons. */
2382
3934
  interface RenderedPermission<TComponents = unknown> extends RenderedMessage<TComponents> {
2383
3935
  actions: RenderedAction[];
2384
3936
  }
3937
+ /** A single action button for permission requests. */
2385
3938
  interface RenderedAction {
2386
3939
  id: string;
2387
3940
  label: string;
2388
3941
  isAllow?: boolean;
2389
3942
  }
3943
+ /** A file, image, or audio attachment to include with a rendered message. */
2390
3944
  interface RenderedAttachment {
2391
3945
  type: "file" | "image" | "audio";
2392
3946
  data: Buffer | string;
2393
3947
  mimeType?: string;
2394
3948
  filename?: string;
2395
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
+ */
2396
3958
  interface IRenderer {
2397
3959
  renderText(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
2398
3960
  renderToolCall(content: OutgoingMessage, verbosity: DisplayVerbosity): RenderedMessage;
@@ -2413,7 +3975,11 @@ interface IRenderer {
2413
3975
  renderResourceLink?(content: OutgoingMessage): RenderedMessage;
2414
3976
  }
2415
3977
  /**
2416
- * 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.
2417
3983
  */
2418
3984
  declare class BaseRenderer implements IRenderer {
2419
3985
  renderText(content: OutgoingMessage): RenderedMessage;
@@ -2432,28 +3998,60 @@ declare class BaseRenderer implements IRenderer {
2432
3998
  renderResourceLink(content: OutgoingMessage): RenderedMessage;
2433
3999
  }
2434
4000
 
4001
+ /** Runtime services available to adapters via dependency injection. */
2435
4002
  interface AdapterContext {
2436
4003
  configManager: {
2437
4004
  get(): Record<string, unknown>;
2438
4005
  };
2439
4006
  fileService?: unknown;
2440
4007
  }
4008
+ /** Configuration for adapters that extend MessagingAdapter. */
2441
4009
  interface MessagingAdapterConfig extends ChannelConfig {
4010
+ /** Platform-imposed limit on a single message body (e.g., 4096 for Telegram). */
2442
4011
  maxMessageLength: number;
4012
+ /** How often (ms) to flush buffered streaming text to the platform. */
2443
4013
  flushInterval?: number;
4014
+ /** Minimum interval (ms) between consecutive send-queue operations. */
2444
4015
  sendInterval?: number;
4016
+ /** How often (ms) to refresh the typing indicator during agent thinking. */
2445
4017
  thinkingRefreshInterval?: number;
4018
+ /** Max duration (ms) to show the typing indicator before auto-dismissing. */
2446
4019
  thinkingDuration?: number;
4020
+ /** Default output verbosity for this adapter (can be overridden per-session). */
2447
4021
  displayVerbosity?: DisplayVerbosity;
2448
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
+ */
2449
4030
  declare abstract class MessagingAdapter implements IChannelAdapter {
2450
4031
  protected context: AdapterContext;
2451
4032
  protected adapterConfig: MessagingAdapterConfig;
2452
4033
  abstract readonly name: string;
4034
+ /** Platform-specific renderer that converts OutgoingMessage to formatted output. */
2453
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
+ */
2454
4042
  abstract readonly capabilities: AdapterCapabilities;
2455
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
+ */
2456
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
+ */
2457
4055
  protected dispatchMessage(sessionId: string, content: OutgoingMessage, verbosity: DisplayVerbosity): Promise<void>;
2458
4056
  protected handleText(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2459
4057
  protected handleThought(_sessionId: string, _content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
@@ -2471,49 +4069,106 @@ declare abstract class MessagingAdapter implements IChannelAdapter {
2471
4069
  protected handleUserReplay(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2472
4070
  protected handleResource(_sessionId: string, _content: OutgoingMessage): Promise<void>;
2473
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
+ */
2474
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
+ */
2475
4084
  protected shouldDisplay(content: OutgoingMessage, verbosity: DisplayVerbosity): boolean;
4085
+ /** Initializes the adapter (e.g., connect to platform API, start polling). */
2476
4086
  abstract start(): Promise<void>;
4087
+ /** Gracefully shuts down the adapter and releases resources. */
2477
4088
  abstract stop(): Promise<void>;
4089
+ /** Creates a platform-specific thread/topic for a session. Returns the thread ID. */
2478
4090
  abstract createSessionThread(sessionId: string, name: string): Promise<string>;
4091
+ /** Renames an existing session thread/topic on the platform. */
2479
4092
  abstract renameSessionThread(sessionId: string, newName: string): Promise<void>;
4093
+ /** Sends a permission request to the user with approve/deny actions. */
2480
4094
  abstract sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
4095
+ /** Sends a cross-session notification (e.g., session completed, budget warning). */
2481
4096
  abstract sendNotification(notification: NotificationMessage): Promise<void>;
2482
4097
  }
2483
4098
 
4099
+ /** A structured event emitted over the stream (SSE, WebSocket, etc.). */
2484
4100
  interface StreamEvent {
2485
4101
  type: string;
2486
4102
  sessionId?: string;
2487
4103
  payload: unknown;
2488
4104
  timestamp: number;
2489
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
+ */
2490
4115
  declare abstract class StreamAdapter implements IChannelAdapter {
2491
4116
  abstract readonly name: string;
2492
4117
  capabilities: AdapterCapabilities;
2493
4118
  constructor(config?: Partial<AdapterCapabilities>);
4119
+ /** Wraps the outgoing message as a StreamEvent and emits it to the session's listeners. */
2494
4120
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
4121
+ /** Emits a permission request event so the client can render approve/deny UI. */
2495
4122
  sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void>;
4123
+ /** Broadcasts a notification to all connected clients (not scoped to a session). */
2496
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
+ */
2497
4129
  createSessionThread(_sessionId: string, _name: string): Promise<string>;
4130
+ /** Emits a rename event so connected clients can update their session title. */
2498
4131
  renameSessionThread(sessionId: string, name: string): Promise<void>;
4132
+ /** Sends an event to all connections watching a specific session. */
2499
4133
  protected abstract emit(sessionId: string, event: StreamEvent): Promise<void>;
4134
+ /** Sends an event to all connected clients regardless of session. */
2500
4135
  protected abstract broadcast(event: StreamEvent): Promise<void>;
2501
4136
  abstract start(): Promise<void>;
2502
4137
  abstract stop(): Promise<void>;
2503
4138
  }
2504
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
+ */
2505
4145
  type QueueItemType = 'text' | 'other';
4146
+ /** Configuration for the SendQueue rate limiter. */
2506
4147
  interface SendQueueConfig {
4148
+ /** Minimum interval (ms) between consecutive operations. */
2507
4149
  minInterval: number;
4150
+ /** Per-category interval overrides (e.g., separate limits for edits vs. sends). */
2508
4151
  categoryIntervals?: Record<string, number>;
2509
4152
  onRateLimited?: () => void;
2510
4153
  onError?: (error: Error) => void;
2511
4154
  }
4155
+ /** Options for enqueuing an operation. */
2512
4156
  interface EnqueueOptions {
2513
4157
  type?: QueueItemType;
4158
+ /** Deduplication key — text items with the same key replace earlier ones. */
2514
4159
  key?: string;
4160
+ /** Category for per-category rate limiting. */
2515
4161
  category?: string;
2516
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
+ */
2517
4172
  declare class SendQueue {
2518
4173
  private config;
2519
4174
  private items;
@@ -2522,87 +4177,166 @@ declare class SendQueue {
2522
4177
  private lastCategoryExec;
2523
4178
  constructor(config: SendQueueConfig);
2524
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
+ */
2525
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
+ */
2526
4193
  onRateLimited(): void;
2527
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
+ */
2528
4199
  private scheduleProcess;
2529
4200
  private getInterval;
2530
4201
  private processNext;
2531
4202
  }
2532
4203
 
4204
+ /** Configuration for draft message flushing behavior. */
2533
4205
  interface DraftConfig {
4206
+ /** How often (ms) to flush buffered text to the platform. */
2534
4207
  flushInterval: number;
2535
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
+ */
2536
4214
  onFlush: (sessionId: string, text: string, isEdit: boolean) => Promise<string | undefined>;
2537
4215
  onError?: (sessionId: string, error: Error) => void;
2538
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
+ */
2539
4225
  declare class Draft {
2540
4226
  private sessionId;
2541
4227
  private config;
2542
4228
  private buffer;
2543
4229
  private _messageId?;
4230
+ /** Guards against concurrent first-flush — ensures only one sendMessage creates the draft. */
2544
4231
  private firstFlushPending;
2545
4232
  private flushTimer?;
2546
4233
  private flushPromise;
2547
4234
  constructor(sessionId: string, config: DraftConfig);
2548
4235
  get isEmpty(): boolean;
4236
+ /** Platform message ID, set after the first successful flush. */
2549
4237
  get messageId(): string | undefined;
4238
+ /** Appends streaming text to the buffer and schedules a flush. */
2550
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
+ */
2551
4244
  finalize(): Promise<string | undefined>;
4245
+ /** Discards buffered text and cancels any pending flush. */
2552
4246
  destroy(): void;
2553
4247
  private scheduleFlush;
2554
4248
  private flush;
2555
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
+ */
2556
4256
  declare class DraftManager {
2557
4257
  private config;
2558
4258
  private drafts;
2559
4259
  constructor(config: DraftConfig);
4260
+ /** Returns the existing draft for a session, or creates a new one. */
2560
4261
  getOrCreate(sessionId: string): Draft;
4262
+ /** Finalizes and removes the draft for a session. */
2561
4263
  finalize(sessionId: string): Promise<void>;
4264
+ /** Finalizes all active drafts (e.g., during adapter shutdown). */
2562
4265
  finalizeAll(): Promise<void>;
4266
+ /** Destroys a draft without flushing (e.g., on session error). */
2563
4267
  destroy(sessionId: string): void;
4268
+ /** Destroys all drafts without flushing. */
2564
4269
  destroyAll(): void;
2565
4270
  }
2566
4271
 
4272
+ /** A tool call associated with the platform message ID where it is displayed. */
2567
4273
  interface TrackedToolCall extends ToolCallMeta {
4274
+ /** Platform message ID of the tool card message, used for in-place updates. */
2568
4275
  messageId: string;
2569
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
+ */
2570
4282
  declare class ToolCallTracker {
2571
4283
  private sessions;
4284
+ /** Registers a new tool call and associates it with its platform message ID. */
2572
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. */
2573
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). */
2574
4289
  getActive(sessionId: string): TrackedToolCall[];
4290
+ /** Removes all tracked tool calls for a session (called at turn end). */
2575
4291
  clear(sessionId: string): void;
2576
4292
  clearAll(): void;
2577
4293
  }
2578
4294
 
4295
+ /** Timing configuration for the thinking indicator lifecycle. */
2579
4296
  interface ActivityConfig {
4297
+ /** How often (ms) to refresh the typing indicator (e.g., re-send "typing..." action). */
2580
4298
  thinkingRefreshInterval: number;
4299
+ /** Maximum duration (ms) before auto-dismissing the indicator to avoid stale UI. */
2581
4300
  maxThinkingDuration: number;
2582
4301
  }
4302
+ /** Platform-specific callbacks for showing/updating/removing typing indicators. */
2583
4303
  interface ActivityCallbacks {
2584
4304
  sendThinkingIndicator(): Promise<void>;
2585
4305
  updateThinkingIndicator(): Promise<void>;
2586
4306
  removeThinkingIndicator(): Promise<void>;
2587
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
+ */
2588
4316
  declare class ActivityTracker {
2589
4317
  private config;
2590
4318
  private sessions;
2591
4319
  constructor(config: ActivityConfig);
4320
+ /** Shows the typing indicator and starts the periodic refresh timer. */
2592
4321
  onThinkingStart(sessionId: string, callbacks: ActivityCallbacks): void;
4322
+ /** Dismisses the typing indicator when the agent starts producing text output. */
2593
4323
  onTextStart(sessionId: string): void;
4324
+ /** Cleans up the typing indicator when the session ends. */
2594
4325
  onSessionEnd(sessionId: string): void;
4326
+ /** Cleans up all sessions (e.g., during adapter shutdown). */
2595
4327
  destroy(): void;
2596
4328
  private cleanup;
2597
4329
  private startRefresh;
2598
4330
  private stopRefresh;
2599
4331
  }
2600
4332
 
4333
+ /** Accumulated state for a single tool call during a streaming response. */
2601
4334
  interface ToolEntry {
2602
4335
  id: string;
2603
4336
  name: string;
2604
4337
  kind: string;
2605
4338
  rawInput: unknown;
4339
+ /** Tool output content, populated when the tool completes. */
2606
4340
  content: string | null;
2607
4341
  status: string;
2608
4342
  viewerLinks?: ViewerLinks;
@@ -2613,8 +4347,16 @@ interface ToolEntry {
2613
4347
  displaySummary?: string;
2614
4348
  displayTitle?: string;
2615
4349
  displayKind?: string;
4350
+ /** Whether this tool is considered noise (e.g., ls, glob) and should be hidden at lower verbosities. */
2616
4351
  isNoise: boolean;
2617
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
+ */
2618
4360
  declare class ToolStateMap {
2619
4361
  private entries;
2620
4362
  private pendingUpdates;
@@ -2632,19 +4374,37 @@ declare class ToolStateMap {
2632
4374
  removed: number;
2633
4375
  }): ToolEntry | undefined;
2634
4376
  private _applyUpdate;
4377
+ /** Retrieves a tool entry by ID, or undefined if not yet tracked. */
2635
4378
  get(id: string): ToolEntry | undefined;
4379
+ /** Resets all state between turns. */
2636
4380
  clear(): void;
2637
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
+ */
2638
4389
  declare class ThoughtBuffer {
2639
4390
  private chunks;
2640
4391
  private sealed;
4392
+ /** Appends a thought text chunk. Ignored if already sealed. */
2641
4393
  append(chunk: string): void;
4394
+ /** Marks the thought as complete and returns the full accumulated text. */
2642
4395
  seal(): string;
4396
+ /** Returns the text accumulated so far without sealing. */
2643
4397
  getText(): string;
2644
4398
  isSealed(): boolean;
4399
+ /** Resets the buffer for reuse in a new turn. */
2645
4400
  reset(): void;
2646
4401
  }
2647
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
+ */
2648
4408
  interface ToolDisplaySpec {
2649
4409
  id: string;
2650
4410
  kind: string;
@@ -2669,36 +4429,68 @@ interface ToolDisplaySpec {
2669
4429
  isNoise: boolean;
2670
4430
  isHidden: boolean;
2671
4431
  }
4432
+ /** Display specification for an agent's extended thinking block. */
2672
4433
  interface ThoughtDisplaySpec {
2673
4434
  indicator: string;
4435
+ /** Full thought text, only populated at high verbosity. */
2674
4436
  content: string | null;
2675
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
+ */
2676
4445
  declare class DisplaySpecBuilder {
2677
4446
  private tunnelService?;
2678
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
+ */
2679
4455
  buildToolSpec(entry: ToolEntry, mode: OutputMode, sessionContext?: {
2680
4456
  id: string;
2681
4457
  workingDirectory: string;
2682
4458
  }): ToolDisplaySpec;
4459
+ /** Builds a display spec for an agent thought. Content is only included at high verbosity. */
2683
4460
  buildThoughtSpec(content: string, mode: OutputMode): ThoughtDisplaySpec;
2684
4461
  }
2685
4462
 
4463
+ /** Token usage and cost data appended to the tool card after the turn completes. */
2686
4464
  interface UsageData {
2687
4465
  tokensUsed?: number;
2688
4466
  contextSize?: number;
2689
4467
  cost?: number;
2690
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
+ */
2691
4473
  interface ToolCardSnapshot {
2692
4474
  specs: ToolDisplaySpec[];
2693
4475
  planEntries?: PlanEntry[];
2694
4476
  usage?: UsageData;
2695
4477
  totalVisible: number;
2696
4478
  completedVisible: number;
4479
+ /** True when all visible tools have reached a terminal status. */
2697
4480
  allComplete: boolean;
2698
4481
  }
2699
4482
  interface ToolCardStateConfig {
4483
+ /** Called with a snapshot whenever the tool card content changes. */
2700
4484
  onFlush: (snapshot: ToolCardSnapshot) => void;
2701
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
+ */
2702
4494
  declare class ToolCardState {
2703
4495
  private specs;
2704
4496
  private planEntries?;
@@ -2708,10 +4500,15 @@ declare class ToolCardState {
2708
4500
  private debounceTimer?;
2709
4501
  private onFlush;
2710
4502
  constructor(config: ToolCardStateConfig);
4503
+ /** Adds or updates a tool spec. First call flushes immediately; subsequent calls are debounced. */
2711
4504
  updateFromSpec(spec: ToolDisplaySpec): void;
4505
+ /** Updates the plan entries displayed alongside tool cards. */
2712
4506
  updatePlan(entries: PlanEntry[]): void;
4507
+ /** Appends token usage data to the tool card (typically at end of turn). */
2713
4508
  appendUsage(usage: UsageData): void;
4509
+ /** Marks the turn as complete and flushes the final snapshot immediately. */
2714
4510
  finalize(): void;
4511
+ /** Stops all pending flushes without emitting a final snapshot. */
2715
4512
  destroy(): void;
2716
4513
  hasContent(): boolean;
2717
4514
  private snapshot;
@@ -2720,15 +4517,54 @@ declare class ToolCardState {
2720
4517
  private clearDebounce;
2721
4518
  }
2722
4519
 
4520
+ /** Renders a text-based progress bar (e.g., "▓▓▓▓░░░░░░") for token usage display. */
2723
4521
  declare function progressBar(ratio: number, length?: number): string;
4522
+ /** Formats a token count with SI suffixes (e.g., 1500 -> "1.5k", 2000000 -> "2M"). */
2724
4523
  declare function formatTokens(n: number): string;
4524
+ /** Strips markdown code fences (```lang ... ```) from text, leaving only the content. */
2725
4525
  declare function stripCodeFences(text: string): string;
4526
+ /** Truncates text to maxLen characters, appending a truncation marker if cut. */
2726
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
+ */
2727
4538
  declare function splitMessage(text: string, maxLength: number): string[];
2728
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
+ */
2729
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
+ */
2730
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
+ */
2731
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
+ */
2732
4568
  declare function resolveToolIcon(tool: {
2733
4569
  status?: string;
2734
4570
  displayKind?: string;
@@ -2743,23 +4579,71 @@ interface SessionManagerLike {
2743
4579
  outputMode?: OutputMode;
2744
4580
  } | undefined;
2745
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
+ */
2746
4590
  declare class OutputModeResolver {
4591
+ /** Resolves the effective output mode by walking the override cascade. */
2747
4592
  resolve(configManager: ConfigManagerLike, adapterName: string, sessionId?: string, sessionManager?: SessionManagerLike): OutputMode;
2748
4593
  }
2749
4594
 
2750
4595
  /**
2751
- * OpenACP Product Guide — comprehensive reference for the AI assistant.
2752
- * 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.
2753
4607
  */
2754
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";
2755
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
+ */
2756
4617
  interface TelegramChannelConfig extends ChannelConfig {
2757
4618
  botToken: string;
2758
4619
  chatId: number;
4620
+ /** Forum topic used for all cross-session notifications (completions, permissions). Null until first run. */
2759
4621
  notificationTopicId: number | null;
4622
+ /** Forum topic where users chat with the assistant. Null until first run. */
2760
4623
  assistantTopicId: number | null;
2761
4624
  }
2762
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
+ */
2763
4647
  declare class TelegramAdapter extends MessagingAdapter {
2764
4648
  readonly name = "telegram";
2765
4649
  readonly renderer: IRenderer;
@@ -2792,7 +4676,11 @@ declare class TelegramAdapter extends MessagingAdapter {
2792
4676
  private _topicsInitialized;
2793
4677
  /** Background watcher timer — cancelled on stop() or when topics succeed */
2794
4678
  private _prerequisiteWatcher;
2795
- /** 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
+ */
2796
4684
  private storeControlMsgId;
2797
4685
  /** Get control message ID (from Map, with fallback to session record) */
2798
4686
  private getControlMsgId;
@@ -2802,6 +4690,12 @@ declare class TelegramAdapter extends MessagingAdapter {
2802
4690
  notificationTopicId?: number;
2803
4691
  assistantTopicId?: number;
2804
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
+ */
2805
4699
  start(): Promise<void>;
2806
4700
  /**
2807
4701
  * Retry an async operation with exponential backoff.
@@ -2815,6 +4709,13 @@ declare class TelegramAdapter extends MessagingAdapter {
2815
4709
  private registerCommandsWithRetry;
2816
4710
  private initTopicDependentFeatures;
2817
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
+ */
2818
4719
  stop(): Promise<void>;
2819
4720
  private renderCommandResponse;
2820
4721
  private toCallbackData;
@@ -2829,7 +4730,19 @@ declare class TelegramAdapter extends MessagingAdapter {
2829
4730
  * its creation event. This queue ensures events are processed in the order they arrive.
2830
4731
  */
2831
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;
2832
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
+ */
2833
4746
  sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>;
2834
4747
  protected handleThought(sessionId: string, content: OutgoingMessage, _verbosity: DisplayVerbosity): Promise<void>;
2835
4748
  protected handleText(sessionId: string, content: OutgoingMessage): Promise<void>;
@@ -2847,20 +4760,71 @@ declare class TelegramAdapter extends MessagingAdapter {
2847
4760
  updateControlMessage(sessionId: string): Promise<void>;
2848
4761
  protected handleError(sessionId: string, content: OutgoingMessage): Promise<void>;
2849
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
+ */
2850
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
+ */
2851
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
+ */
2852
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
+ */
2853
4784
  renameSessionThread(sessionId: string, newName: string): Promise<void>;
4785
+ /** Delete the forum topic associated with a session. */
2854
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
+ */
2855
4792
  sendSkillCommands(sessionId: string, commands: AgentCommand[]): Promise<void>;
2856
4793
  /** Flush any skill commands that were queued before threadId was available */
2857
4794
  flushPendingSkillCommands(sessionId: string): Promise<void>;
2858
4795
  private resolveSessionId;
2859
4796
  private downloadTelegramFile;
2860
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
+ */
2861
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
+ */
2862
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
+ */
2863
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
+ */
2864
4828
  archiveSessionTopic(sessionId: string): Promise<void>;
2865
4829
  }
2866
4830