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