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