@qwen-code/channel-base 0.19.3 → 0.19.4
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/README.md +111 -35
- package/dist/AcpBridge.d.ts +10 -16
- package/dist/AcpBridge.js +30 -1
- package/dist/AcpBridge.js.map +1 -1
- package/dist/ChannelAgentBridge.d.ts +48 -0
- package/dist/ChannelAgentBridge.js +2 -0
- package/dist/ChannelAgentBridge.js.map +1 -0
- package/dist/ChannelBase.d.ts +157 -6
- package/dist/ChannelBase.js +1372 -51
- package/dist/ChannelBase.js.map +1 -1
- package/dist/ChannelLoopScheduler.d.ts +44 -0
- package/dist/ChannelLoopScheduler.js +275 -0
- package/dist/ChannelLoopScheduler.js.map +1 -0
- package/dist/ChannelLoopStore.d.ts +47 -0
- package/dist/ChannelLoopStore.js +195 -0
- package/dist/ChannelLoopStore.js.map +1 -0
- package/dist/DaemonChannelBridge.d.ts +2 -2
- package/dist/DaemonChannelBridge.js +16 -2
- package/dist/DaemonChannelBridge.js.map +1 -1
- package/dist/SenderGate.d.ts +1 -0
- package/dist/SenderGate.js +13 -0
- package/dist/SenderGate.js.map +1 -1
- package/dist/SessionRouter.d.ts +14 -13
- package/dist/SessionRouter.js +202 -48
- package/dist/SessionRouter.js.map +1 -1
- package/dist/group-history-store.d.ts +26 -0
- package/dist/group-history-store.js +242 -0
- package/dist/group-history-store.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/sanitize.d.ts +60 -0
- package/dist/sanitize.js +117 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/types.d.ts +30 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @qwen-code/channel-base
|
|
2
2
|
|
|
3
|
-
Base infrastructure for building Qwen Code channel adapters. Provides the abstract base class, access control, session routing, and the
|
|
3
|
+
Base infrastructure for building Qwen Code channel adapters. Provides the abstract base class, access control, session routing, and the adapter-facing bridge interface used to communicate with the agent.
|
|
4
4
|
|
|
5
5
|
If you're building a channel plugin, this is your only dependency.
|
|
6
6
|
|
|
@@ -17,12 +17,22 @@ Subclass `ChannelBase` and implement three methods:
|
|
|
17
17
|
```typescript
|
|
18
18
|
import { ChannelBase } from '@qwen-code/channel-base';
|
|
19
19
|
import type {
|
|
20
|
+
ChannelAgentBridge,
|
|
21
|
+
ChannelBaseOptions,
|
|
20
22
|
ChannelConfig,
|
|
21
23
|
Envelope,
|
|
22
|
-
AcpBridge,
|
|
23
24
|
} from '@qwen-code/channel-base';
|
|
24
25
|
|
|
25
26
|
class MyChannel extends ChannelBase {
|
|
27
|
+
constructor(
|
|
28
|
+
name: string,
|
|
29
|
+
config: ChannelConfig,
|
|
30
|
+
bridge: ChannelAgentBridge,
|
|
31
|
+
options?: ChannelBaseOptions,
|
|
32
|
+
) {
|
|
33
|
+
super(name, config, bridge, options);
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
async connect(): Promise<void> {
|
|
27
37
|
// Connect to platform API, register message handlers.
|
|
28
38
|
// When a message arrives, build an Envelope and call:
|
|
@@ -55,6 +65,17 @@ export const plugin: ChannelPlugin = {
|
|
|
55
65
|
|
|
56
66
|
For a complete working example, see [`@qwen-code/channel-plugin-example`](../plugin-example/).
|
|
57
67
|
|
|
68
|
+
Migration note for existing TypeScript plugins: if your adapter constructor or factory explicitly types `bridge` as `AcpBridge`, change that annotation to `ChannelAgentBridge` and keep using only the methods exposed by that contract. JavaScript plugins are unaffected at runtime, and standalone `qwen channel start` still passes the current `AcpBridge` implementation.
|
|
69
|
+
|
|
70
|
+
## Runtime modes
|
|
71
|
+
|
|
72
|
+
Channel adapters can run in two host modes:
|
|
73
|
+
|
|
74
|
+
- `qwen channel start [name]` is the standalone service. It uses `AcpBridge` over a `qwen-code --acp` child process and remains the default channel command.
|
|
75
|
+
- `qwen serve --channel <name>` and `qwen serve --channel all` are experimental daemon-managed modes. `qwen serve` starts one out-of-process channel worker, the worker connects back to the daemon through the SDK, and adapters receive a `DaemonChannelBridge`-backed `ChannelAgentBridge` facade.
|
|
76
|
+
|
|
77
|
+
In daemon-managed mode, one daemon is bound to one workspace. Every selected channel's `cwd` must resolve to that same workspace. The optional `shellCommand` method is exposed to adapters only when the daemon advertises the `session_shell_command` capability.
|
|
78
|
+
|
|
58
79
|
## Architecture
|
|
59
80
|
|
|
60
81
|
```
|
|
@@ -63,9 +84,9 @@ Inbound: Platform message
|
|
|
63
84
|
→ GroupGate (group policy + mention gating)
|
|
64
85
|
→ SenderGate (allowlist / pairing / open)
|
|
65
86
|
→ Slash commands (/clear, /help, /status)
|
|
66
|
-
→ SessionRouter (resolve or create
|
|
87
|
+
→ SessionRouter (resolve or create agent session)
|
|
67
88
|
→ Resolve attachments (images → bridge, files → prompt text)
|
|
68
|
-
→
|
|
89
|
+
→ ChannelAgentBridge.prompt() → agent
|
|
69
90
|
|
|
70
91
|
Outbound: Agent response
|
|
71
92
|
→ BlockStreamer (if enabled: split into blocks at paragraph boundaries)
|
|
@@ -78,36 +99,39 @@ Everything between `handleInbound()` and `sendMessage()` is handled by the base
|
|
|
78
99
|
|
|
79
100
|
### Classes
|
|
80
101
|
|
|
81
|
-
| Class | Purpose
|
|
82
|
-
| --------------- |
|
|
83
|
-
| `ChannelBase` | Abstract base class — extend this to build a channel adapter
|
|
84
|
-
| `AcpBridge` |
|
|
85
|
-
| `BlockStreamer` | Progressive multi-message delivery for block streaming
|
|
86
|
-
| `SessionRouter` | Maps senders to
|
|
87
|
-
| `SenderGate` | DM access control (allowlist / pairing / open)
|
|
88
|
-
| `GroupGate` | Group chat policy and @mention gating
|
|
89
|
-
| `PairingStore` | Pairing code generation, approval, and allowlist persistence
|
|
102
|
+
| Class | Purpose |
|
|
103
|
+
| --------------- | ------------------------------------------------------------------------------------ |
|
|
104
|
+
| `ChannelBase` | Abstract base class — extend this to build a channel adapter |
|
|
105
|
+
| `AcpBridge` | Current standalone `qwen channel start` bridge implementation over `qwen-code --acp` |
|
|
106
|
+
| `BlockStreamer` | Progressive multi-message delivery for block streaming |
|
|
107
|
+
| `SessionRouter` | Maps senders to agent sessions with configurable scoping |
|
|
108
|
+
| `SenderGate` | DM access control (allowlist / pairing / open) |
|
|
109
|
+
| `GroupGate` | Group chat policy and @mention gating |
|
|
110
|
+
| `PairingStore` | Pairing code generation, approval, and allowlist persistence |
|
|
90
111
|
|
|
91
112
|
### Types
|
|
92
113
|
|
|
93
|
-
| Type
|
|
94
|
-
|
|
|
95
|
-
| `Attachment`
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
114
|
+
| Type | Description |
|
|
115
|
+
| -------------------- | ------------------------------------------------------------------------ |
|
|
116
|
+
| `Attachment` | Structured file/image/audio/video attachment |
|
|
117
|
+
| `AvailableCommand` | Agent command advertised through the bridge |
|
|
118
|
+
| `ChannelAgentBridge` | Adapter-facing bridge contract used by `ChannelBase` and `SessionRouter` |
|
|
119
|
+
| `ChannelConfig` | Channel configuration from `settings.json` |
|
|
120
|
+
| `ChannelPlugin` | Plugin factory interface (what you export) |
|
|
121
|
+
| `Envelope` | Normalized inbound message format |
|
|
122
|
+
| `SenderPolicy` | `'allowlist' \| 'pairing' \| 'open'` |
|
|
123
|
+
| `GroupPolicy` | `'disabled' \| 'allowlist' \| 'open'` |
|
|
124
|
+
| `SessionScope` | `'user' \| 'thread' \| 'single'` |
|
|
125
|
+
| `GroupConfig` | Per-group settings (e.g. `requireMention`) |
|
|
126
|
+
| `SessionTarget` | Maps a session back to its channel/sender/chat |
|
|
127
|
+
| `ToolCallEvent` | Agent tool-call event delivered to adapters |
|
|
104
128
|
|
|
105
129
|
## API reference
|
|
106
130
|
|
|
107
131
|
### ChannelBase
|
|
108
132
|
|
|
109
133
|
```typescript
|
|
110
|
-
constructor(name: string, config: ChannelConfig, bridge:
|
|
134
|
+
constructor(name: string, config: ChannelConfig, bridge: ChannelAgentBridge, options?: ChannelBaseOptions)
|
|
111
135
|
```
|
|
112
136
|
|
|
113
137
|
**Abstract methods** (you must implement):
|
|
@@ -123,7 +147,7 @@ constructor(name: string, config: ChannelConfig, bridge: AcpBridge, options?: Ch
|
|
|
123
147
|
| Method | Description |
|
|
124
148
|
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
125
149
|
| `handleInbound(envelope)` | Route an inbound message through the full pipeline (gate checks, commands, session, prompt). Call this from your message handler. |
|
|
126
|
-
| `setBridge(bridge)` | Replace the
|
|
150
|
+
| `setBridge(bridge)` | Replace the agent bridge after crash recovery |
|
|
127
151
|
| `registerCommand(name, handler)` | Register a custom slash command (e.g. `/mycommand`) |
|
|
128
152
|
| `onToolCall(chatId, event)` | Hook called on agent tool invocations — override to show indicators |
|
|
129
153
|
| `onResponseChunk(chatId, chunk, sessionId)` | Hook called per streaming text chunk — override for progressive display (default: no-op) |
|
|
@@ -133,9 +157,60 @@ constructor(name: string, config: ChannelConfig, bridge: AcpBridge, options?: Ch
|
|
|
133
157
|
|
|
134
158
|
**Built-in slash commands:** `/clear` (`/reset`, `/new`), `/help`, `/status`
|
|
135
159
|
|
|
160
|
+
**ChannelBaseOptions:**
|
|
161
|
+
|
|
162
|
+
| Option | Description |
|
|
163
|
+
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
164
|
+
| `router` | Optional `SessionRouter`. Omit for the default standalone router. |
|
|
165
|
+
| `proxy` | Optional proxy URL made available to adapters. |
|
|
166
|
+
| `registerBridgeEvents` | Set only when the adapter owns a supplied router and `ChannelBase` should consume bridge events directly. Leave unset for routers supplied by the gateway. |
|
|
167
|
+
|
|
168
|
+
### ChannelAgentBridge
|
|
169
|
+
|
|
170
|
+
`ChannelAgentBridge` is the adapter-facing contract. Channel adapters, channel plugins, `ChannelBase`, and `SessionRouter` should depend on this type instead of a concrete bridge implementation.
|
|
171
|
+
|
|
172
|
+
`shellCommand` is optional. Adapters should check for it before enabling `!cmd`-style features because daemon-managed hosts expose it only when the connected daemon supports shell execution.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
interface ChannelAgentBridge {
|
|
176
|
+
readonly availableCommands: AvailableCommand[];
|
|
177
|
+
on(eventName: 'toolCall', listener: (event: ToolCallEvent) => void): unknown;
|
|
178
|
+
on(
|
|
179
|
+
eventName: 'textChunk',
|
|
180
|
+
listener: (sessionId: string, chunk: string) => void,
|
|
181
|
+
): unknown;
|
|
182
|
+
on(
|
|
183
|
+
eventName: 'sessionDied',
|
|
184
|
+
listener: (event: { sessionId: string; reason?: string }) => void,
|
|
185
|
+
): unknown;
|
|
186
|
+
off(eventName: 'toolCall', listener: (event: ToolCallEvent) => void): unknown;
|
|
187
|
+
off(
|
|
188
|
+
eventName: 'textChunk',
|
|
189
|
+
listener: (sessionId: string, chunk: string) => void,
|
|
190
|
+
): unknown;
|
|
191
|
+
off(
|
|
192
|
+
eventName: 'sessionDied',
|
|
193
|
+
listener: (event: { sessionId: string; reason?: string }) => void,
|
|
194
|
+
): unknown;
|
|
195
|
+
newSession(cwd: string): Promise<string>;
|
|
196
|
+
loadSession(sessionId: string, cwd: string): Promise<string>;
|
|
197
|
+
prompt(
|
|
198
|
+
sessionId: string,
|
|
199
|
+
text: string,
|
|
200
|
+
options?: { imageBase64?: string; imageMimeType?: string },
|
|
201
|
+
): Promise<string>;
|
|
202
|
+
cancelSession(sessionId: string): Promise<void>;
|
|
203
|
+
shellCommand?(
|
|
204
|
+
sessionId: string,
|
|
205
|
+
command: string,
|
|
206
|
+
signal?: AbortSignal,
|
|
207
|
+
): Promise<{ exitCode: number | null; output: string; aborted: boolean }>;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
136
211
|
### AcpBridge
|
|
137
212
|
|
|
138
|
-
|
|
213
|
+
`AcpBridge` is the current implementation used by standalone `qwen channel start`. It manages the `qwen-code --acp` child process and implements `ChannelAgentBridge`.
|
|
139
214
|
|
|
140
215
|
```typescript
|
|
141
216
|
constructor(options: { cliEntryPath: string; cwd: string; model?: string })
|
|
@@ -160,10 +235,10 @@ constructor(options: { cliEntryPath: string; cwd: string; model?: string })
|
|
|
160
235
|
|
|
161
236
|
### SessionRouter
|
|
162
237
|
|
|
163
|
-
Maps senders to
|
|
238
|
+
Maps senders to agent sessions based on the configured scope.
|
|
164
239
|
|
|
165
240
|
```typescript
|
|
166
|
-
constructor(bridge:
|
|
241
|
+
constructor(bridge: ChannelAgentBridge, defaultCwd: string, scope?: SessionScope, persistPath?: string)
|
|
167
242
|
```
|
|
168
243
|
|
|
169
244
|
**Routing keys by scope:**
|
|
@@ -174,12 +249,13 @@ constructor(bridge: AcpBridge, defaultCwd: string, scope?: SessionScope, persist
|
|
|
174
249
|
| `thread` | `channel:threadId` | One session per thread |
|
|
175
250
|
| `single` | `channel:__single__` | One shared session for the entire channel |
|
|
176
251
|
|
|
177
|
-
| Method
|
|
178
|
-
|
|
|
179
|
-
| `resolve(channelName, senderId, chatId, threadId?, cwd?)`
|
|
180
|
-
| `removeSession(channelName, senderId, chatId?)`
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
252
|
+
| Method | Description |
|
|
253
|
+
| ---------------------------------------------------------- | ----------------------------------------------------------- |
|
|
254
|
+
| `resolve(channelName, senderId, chatId, threadId?, cwd?)` | Get or create a session for the given sender |
|
|
255
|
+
| `removeSession(channelName, senderId, chatId?, threadId?)` | Remove session(s) — used by `/clear` |
|
|
256
|
+
| `removeSessionId(sessionId)` | Remove all routing state for a bridge session ID |
|
|
257
|
+
| `restoreSessions()` | Reload sessions from disk after bridge restart |
|
|
258
|
+
| `clearAll()` | Clear all sessions and delete persist file (clean shutdown) |
|
|
183
259
|
|
|
184
260
|
### SenderGate
|
|
185
261
|
|
package/dist/AcpBridge.d.ts
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { AvailableCommand, ChannelAgentBridge } from './ChannelAgentBridge.js';
|
|
3
|
+
export type { AvailableCommand, ToolCallEvent } from './ChannelAgentBridge.js';
|
|
2
4
|
export interface AcpBridgeOptions {
|
|
3
5
|
cliEntryPath: string;
|
|
4
6
|
cwd: string;
|
|
5
7
|
model?: string;
|
|
6
8
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
sessionId: string;
|
|
16
|
-
toolCallId: string;
|
|
17
|
-
kind: string;
|
|
18
|
-
title: string;
|
|
19
|
-
status: string;
|
|
20
|
-
rawInput?: Record<string, unknown>;
|
|
21
|
-
}
|
|
22
|
-
export declare class AcpBridge extends EventEmitter {
|
|
9
|
+
/**
|
|
10
|
+
* Read a command's aliases off a raw wire `available_commands_update` entry. ACP
|
|
11
|
+
* carries them in `_meta` (its only extension point); a top-level `altNames` is
|
|
12
|
+
* also accepted for forward-compat. Returns undefined when absent so the field
|
|
13
|
+
* stays optional and entries without aliases are left byte-identical.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readAvailableCommandAltNames(raw: unknown): string[] | undefined;
|
|
16
|
+
export declare class AcpBridge extends EventEmitter implements ChannelAgentBridge {
|
|
23
17
|
private child;
|
|
24
18
|
private connection;
|
|
25
19
|
private options;
|
package/dist/AcpBridge.js
CHANGED
|
@@ -2,6 +2,30 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { Readable, Writable } from 'node:stream';
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION, } from '@agentclientprotocol/sdk';
|
|
5
|
+
/**
|
|
6
|
+
* Read a command's aliases off a raw wire `available_commands_update` entry. ACP
|
|
7
|
+
* carries them in `_meta` (its only extension point); a top-level `altNames` is
|
|
8
|
+
* also accepted for forward-compat. Returns undefined when absent so the field
|
|
9
|
+
* stays optional and entries without aliases are left byte-identical.
|
|
10
|
+
*/
|
|
11
|
+
export function readAvailableCommandAltNames(raw) {
|
|
12
|
+
if (typeof raw !== 'object' || raw === null)
|
|
13
|
+
return undefined;
|
|
14
|
+
const record = raw;
|
|
15
|
+
const meta = record['_meta'];
|
|
16
|
+
const fromMeta = typeof meta === 'object' && meta !== null
|
|
17
|
+
? meta['altNames']
|
|
18
|
+
: undefined;
|
|
19
|
+
const source = Array.isArray(record['altNames'])
|
|
20
|
+
? record['altNames']
|
|
21
|
+
: Array.isArray(fromMeta)
|
|
22
|
+
? fromMeta
|
|
23
|
+
: undefined;
|
|
24
|
+
if (!source)
|
|
25
|
+
return undefined;
|
|
26
|
+
const names = source.filter((n) => typeof n === 'string');
|
|
27
|
+
return names.length > 0 ? names : undefined;
|
|
28
|
+
}
|
|
5
29
|
export class AcpBridge extends EventEmitter {
|
|
6
30
|
child = null;
|
|
7
31
|
connection = null;
|
|
@@ -38,6 +62,8 @@ export class AcpBridge extends EventEmitter {
|
|
|
38
62
|
});
|
|
39
63
|
this.child.on('exit', (code, signal) => {
|
|
40
64
|
process.stderr.write(`[AcpBridge] Process exited (code=${code}, signal=${signal})\n`);
|
|
65
|
+
// Do not emit sessionDied here: a full ACP process exit is handled by
|
|
66
|
+
// channel start crash recovery, which reloads the persisted sessions.
|
|
41
67
|
this.connection = null;
|
|
42
68
|
this.child = null;
|
|
43
69
|
this.emit('disconnected', code, signal);
|
|
@@ -154,7 +180,10 @@ export class AcpBridge extends EventEmitter {
|
|
|
154
180
|
}
|
|
155
181
|
case 'available_commands_update': {
|
|
156
182
|
if (Array.isArray(update['availableCommands'])) {
|
|
157
|
-
this._availableCommands = update['availableCommands']
|
|
183
|
+
this._availableCommands = update['availableCommands'].map((cmd) => {
|
|
184
|
+
const altNames = readAvailableCommandAltNames(cmd);
|
|
185
|
+
return altNames ? { ...cmd, altNames } : cmd;
|
|
186
|
+
});
|
|
158
187
|
}
|
|
159
188
|
break;
|
|
160
189
|
}
|
package/dist/AcpBridge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AcpBridge.js","sourceRoot":"","sources":["../src/AcpBridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,GACjB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"AcpBridge.js","sourceRoot":"","sources":["../src/AcpBridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface AvailableCommand {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
input?: {
|
|
5
|
+
hint: string;
|
|
6
|
+
} | null;
|
|
7
|
+
/**
|
|
8
|
+
* Aliases the agent's parser also accepts for this command (for example
|
|
9
|
+
* `summarize` for `compress`).
|
|
10
|
+
*/
|
|
11
|
+
altNames?: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface ToolCallEvent {
|
|
14
|
+
sessionId: string;
|
|
15
|
+
toolCallId: string;
|
|
16
|
+
kind: string;
|
|
17
|
+
title: string;
|
|
18
|
+
status: string;
|
|
19
|
+
rawInput?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export interface SessionDiedEvent {
|
|
22
|
+
sessionId: string;
|
|
23
|
+
reason?: string;
|
|
24
|
+
}
|
|
25
|
+
interface ChannelAgentBridgeEventMap {
|
|
26
|
+
sessionDied: [SessionDiedEvent];
|
|
27
|
+
textChunk: [sessionId: string, chunk: string];
|
|
28
|
+
toolCall: [ToolCallEvent];
|
|
29
|
+
}
|
|
30
|
+
export interface ChannelAgentBridge {
|
|
31
|
+
readonly availableCommands: AvailableCommand[];
|
|
32
|
+
getAvailableCommands?(sessionId: string): AvailableCommand[];
|
|
33
|
+
on<K extends keyof ChannelAgentBridgeEventMap>(eventName: K, listener: (...args: ChannelAgentBridgeEventMap[K]) => void): unknown;
|
|
34
|
+
off<K extends keyof ChannelAgentBridgeEventMap>(eventName: K, listener: (...args: ChannelAgentBridgeEventMap[K]) => void): unknown;
|
|
35
|
+
newSession(cwd: string): Promise<string>;
|
|
36
|
+
loadSession(sessionId: string, cwd: string): Promise<string>;
|
|
37
|
+
prompt(sessionId: string, text: string, options?: {
|
|
38
|
+
imageBase64?: string;
|
|
39
|
+
imageMimeType?: string;
|
|
40
|
+
}): Promise<string>;
|
|
41
|
+
cancelSession(sessionId: string): Promise<void>;
|
|
42
|
+
shellCommand?(sessionId: string, command: string, signal?: AbortSignal): Promise<{
|
|
43
|
+
exitCode: number | null;
|
|
44
|
+
output: string;
|
|
45
|
+
aborted: boolean;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChannelAgentBridge.js","sourceRoot":"","sources":["../src/ChannelAgentBridge.ts"],"names":[],"mappings":""}
|
package/dist/ChannelBase.d.ts
CHANGED
|
@@ -1,38 +1,89 @@
|
|
|
1
|
-
import type { ChannelConfig, Envelope } from './types.js';
|
|
1
|
+
import type { ChannelConfig, ChannelMemoryCallbacks, Envelope, SessionTarget } from './types.js';
|
|
2
2
|
import { GroupGate } from './GroupGate.js';
|
|
3
3
|
import { SenderGate } from './SenderGate.js';
|
|
4
4
|
import { SessionRouter } from './SessionRouter.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type { ChannelAgentBridge, ToolCallEvent } from './ChannelAgentBridge.js';
|
|
6
|
+
import type { ChannelLoop, ChannelLoopInput } from './ChannelLoopStore.js';
|
|
7
|
+
/**
|
|
8
|
+
* Max time /clear waits for a cancelled in-flight turn to wind down before
|
|
9
|
+
* purging anyway. A wedged ACP child (stuck tool call, not reading stdin, or
|
|
10
|
+
* crashed without closing) can leave active.done unresolved forever; without
|
|
11
|
+
* this bound /clear — and the whole channel — would hang. Safe because the
|
|
12
|
+
* purge runs regardless and the generation is bumped, so a turn that settles
|
|
13
|
+
* later is already invalidated.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CLEAR_CANCEL_TIMEOUT_MS = 3000;
|
|
6
16
|
export interface ChannelBaseOptions {
|
|
7
17
|
router?: SessionRouter;
|
|
8
18
|
proxy?: string;
|
|
19
|
+
channelMemory?: ChannelMemoryCallbacks;
|
|
20
|
+
/**
|
|
21
|
+
* Set when a channel owns a supplied router and should consume bridge
|
|
22
|
+
* events directly.
|
|
23
|
+
*/
|
|
24
|
+
registerBridgeEvents?: boolean;
|
|
25
|
+
groupHistoryPath?: string;
|
|
26
|
+
loopController?: ChannelLoopController;
|
|
27
|
+
}
|
|
28
|
+
export interface ChannelLoopController {
|
|
29
|
+
create(input: ChannelLoopInput): Promise<ChannelLoop>;
|
|
30
|
+
createForTarget?(input: ChannelLoopInput, maxEnabledLoops: number): Promise<ChannelLoop | undefined>;
|
|
31
|
+
listForTarget(channelName: string, target: SessionTarget): Promise<ChannelLoop[]>;
|
|
32
|
+
disable(id: string): Promise<boolean>;
|
|
33
|
+
validateCron(cron: string): void;
|
|
34
|
+
nextFireTime?(job: ChannelLoop): Date;
|
|
35
|
+
}
|
|
36
|
+
export interface ChannelLoopPromptOptions {
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
shouldContinue?: () => Promise<boolean>;
|
|
9
39
|
}
|
|
10
40
|
/** Handler for a slash command. Return true if handled, false to forward to agent. */
|
|
11
41
|
type CommandHandler = (envelope: Envelope, args: string) => Promise<boolean>;
|
|
12
42
|
export declare abstract class ChannelBase {
|
|
13
43
|
protected config: ChannelConfig;
|
|
14
|
-
protected bridge:
|
|
44
|
+
protected bridge: ChannelAgentBridge;
|
|
15
45
|
protected groupGate: GroupGate;
|
|
16
46
|
protected gate: SenderGate;
|
|
17
47
|
protected router: SessionRouter;
|
|
18
48
|
protected name: string;
|
|
19
49
|
/** Resolved proxy URL, available to subclasses for adapter-specific clients. */
|
|
20
50
|
protected proxy?: string;
|
|
51
|
+
private readonly channelMemory?;
|
|
52
|
+
private groupHistory;
|
|
53
|
+
private readonly loopController?;
|
|
21
54
|
private instructedSessions;
|
|
22
55
|
private commands;
|
|
23
56
|
/** Per-session promise chain to serialize prompt + send (followup mode). */
|
|
24
57
|
private sessionQueues;
|
|
58
|
+
private readonly registerBridgeEvents;
|
|
59
|
+
/**
|
|
60
|
+
* Per-session generation, bumped by /clear. A queued followup turn captures the
|
|
61
|
+
* generation when it enqueues and bails if /clear bumped it before the turn ran,
|
|
62
|
+
* so a cleared session can't be resurrected by an already-queued prompt.
|
|
63
|
+
*/
|
|
64
|
+
private sessionGenerations;
|
|
25
65
|
/** Per-session active prompt tracking for dispatch modes. */
|
|
26
66
|
private activePrompts;
|
|
27
67
|
/** Per-session message buffer for collect mode. */
|
|
28
68
|
private collectBuffers;
|
|
29
|
-
|
|
69
|
+
private readonly bridgeToolCallListener;
|
|
70
|
+
private readonly bridgeSessionDiedListener;
|
|
71
|
+
constructor(name: string, config: ChannelConfig, bridge: ChannelAgentBridge, options?: ChannelBaseOptions);
|
|
30
72
|
abstract connect(): Promise<void>;
|
|
31
73
|
abstract sendMessage(chatId: string, text: string): Promise<void>;
|
|
32
74
|
abstract disconnect(): void;
|
|
75
|
+
supportsProactiveSend(): boolean;
|
|
76
|
+
protected supportsProactiveTarget(target: SessionTarget): boolean;
|
|
77
|
+
protected pushProactive(target: SessionTarget, text: string): Promise<void>;
|
|
33
78
|
/** Replace the bridge instance (used after crash recovery restart). */
|
|
34
|
-
setBridge(bridge:
|
|
79
|
+
setBridge(bridge: ChannelAgentBridge): void;
|
|
80
|
+
runLoopPrompt(job: ChannelLoop, options?: ChannelLoopPromptOptions): Promise<string | undefined>;
|
|
81
|
+
private runLoopBridgePrompt;
|
|
82
|
+
private cancelTimedOutLoopPrompt;
|
|
35
83
|
onToolCall(_chatId: string, _event: ToolCallEvent): void;
|
|
84
|
+
onSessionDied(sessionId: string): void;
|
|
85
|
+
private attachBridgeEvents;
|
|
86
|
+
private detachBridgeEvents;
|
|
36
87
|
/**
|
|
37
88
|
* Called when a prompt actually begins processing (inside the session queue).
|
|
38
89
|
* Override to show a platform-specific working indicator (e.g., typing, reaction).
|
|
@@ -65,14 +116,114 @@ export declare abstract class ChannelBase {
|
|
|
65
116
|
protected registerCancelCommand(name?: string): void;
|
|
66
117
|
/** Register shared slash commands. Called from constructor. */
|
|
67
118
|
private registerSharedCommands;
|
|
119
|
+
private handleLoopCommand;
|
|
120
|
+
private handleLoopAdd;
|
|
121
|
+
private handleLoopList;
|
|
122
|
+
private handleLoopInspect;
|
|
123
|
+
private formatLoopListLine;
|
|
124
|
+
private lastLoopStatus;
|
|
125
|
+
private formatNextFireTime;
|
|
126
|
+
private handleLoopCancel;
|
|
127
|
+
private loopTargetFromEnvelope;
|
|
128
|
+
private isStoredLoopTargetAuthorized;
|
|
68
129
|
/** Check if a message text matches a registered local command. */
|
|
69
130
|
protected isLocalCommand(text: string): boolean;
|
|
70
131
|
private findActiveSessionId;
|
|
132
|
+
private channelMemoryTarget;
|
|
133
|
+
private invalidateSessionContext;
|
|
134
|
+
private dropQueuedTurnIfStale;
|
|
135
|
+
private isAuthorizedForChannelMemory;
|
|
136
|
+
private isSenderAuthorizedForChannelMemory;
|
|
137
|
+
private ensureChannelMemoryAuthorized;
|
|
138
|
+
private getChannelMemory;
|
|
139
|
+
private channelMemoryErrorMessage;
|
|
140
|
+
private channelMemoryUserErrorMessage;
|
|
141
|
+
private logChannelMemoryError;
|
|
142
|
+
/**
|
|
143
|
+
* Whether the resolved session is SHARED across senders. `single` collapses
|
|
144
|
+
* the whole channel to one `__single__` session for EVERY sender — group OR
|
|
145
|
+
* DM — so it is ALWAYS shared (even a DM maps to `__single__`). `thread` is
|
|
146
|
+
* shared only in a group (a DM maps to the lone caller's own chat). `user` is
|
|
147
|
+
* per-sender, never shared. Drives both the destructive-/clear confirm gate
|
|
148
|
+
* and the host-shell (`!`) gate.
|
|
149
|
+
*/
|
|
150
|
+
private isSharedSession;
|
|
151
|
+
private isSharedSessionTarget;
|
|
152
|
+
/**
|
|
153
|
+
* Whether `envelope.senderId` may act on the resolved session's destructive or
|
|
154
|
+
* workspace-leaking commands (/clear, /who). A SHARED session with a non-empty
|
|
155
|
+
* allowedUsers list is restricted to those members; a per-user session, or one
|
|
156
|
+
* with no allowlist, is unrestricted. Shared verbatim by /clear and /who so the
|
|
157
|
+
* gate can't drift; each caller sends its own rejection wording.
|
|
158
|
+
*/
|
|
159
|
+
private isAuthorizedForSharedSession;
|
|
160
|
+
private stopActiveStreaming;
|
|
161
|
+
/**
|
|
162
|
+
* Cancel the active turn and wait (bounded) for it to wind down. Stops the
|
|
163
|
+
* BlockStreamer so buffered text can't leak via the idle timer, then fires a
|
|
164
|
+
* best-effort cancelSession (NOT awaited — a wedged child/daemon can leave the
|
|
165
|
+
* request pending forever). Returns true if active.done settled first, false
|
|
166
|
+
* if the CLEAR_CANCEL_TIMEOUT_MS bound won (the turn never wound down). Used by
|
|
167
|
+
* /clear, which genuinely EVICTS the session and so must proceed even when the
|
|
168
|
+
* turn is wedged. Steer no longer uses this: it best-effort cancels then chains
|
|
169
|
+
* the new turn behind the old one (see handleInbound), so it never needs to
|
|
170
|
+
* proceed past a still-active turn.
|
|
171
|
+
*/
|
|
172
|
+
private cancelAndAwaitActive;
|
|
71
173
|
/**
|
|
72
174
|
* Parse a slash command from message text.
|
|
73
|
-
* Returns { command, args } or null if not a slash command.
|
|
175
|
+
* Returns { command, raw, args } or null if not a slash command. `command` is
|
|
176
|
+
* lowercased for case-insensitive LOCAL dispatch (registerCommand lowercases the
|
|
177
|
+
* names it stores); `raw` keeps the typed case so agent-command matching can be
|
|
178
|
+
* CASE-SENSITIVE, mirroring the CLI's parseSlashCommand (`cmd.name === part`).
|
|
74
179
|
*/
|
|
75
180
|
private parseCommand;
|
|
181
|
+
/**
|
|
182
|
+
* Whether `text` is a real slash command rather than prose that merely starts
|
|
183
|
+
* with `/`. A command's first whitespace-delimited token must match
|
|
184
|
+
* parseCommand()'s charset — `[a-zA-Z0-9_:-]+`, plus an optional `@botname`
|
|
185
|
+
* suffix — and not be a `//` line comment or `/*` block comment. Slash-prefixed
|
|
186
|
+
* paths (`/tmp/foo`), comments, and a bare `/` are prose and keep their
|
|
187
|
+
* `[sender]` tag.
|
|
188
|
+
*
|
|
189
|
+
* Intentionally stricter than the CLI's looser classifier (cli
|
|
190
|
+
* `ui/utils/commandUtils.ts`), which forwards any non-comment, non-path
|
|
191
|
+
* `/<token>` (e.g. `/café`, a zero-width-laden token). Such inputs aren't
|
|
192
|
+
* runnable commands, and in a SHARED group session forwarding them unattributed
|
|
193
|
+
* is worse than a redundant tag — so anything off the command charset is
|
|
194
|
+
* treated as prose and keeps its `[sender]` tag. Purely lexical — never
|
|
195
|
+
* consults the async command list, so it can't race a fresh session.
|
|
196
|
+
*/
|
|
197
|
+
private isSlashCommand;
|
|
198
|
+
/**
|
|
199
|
+
* Whether `text` names a command this channel can actually run: a locally
|
|
200
|
+
* registered command (`this.commands`, e.g. /clear, /who) OR an agent command
|
|
201
|
+
* THIS session exposes — by canonical name OR alias (e.g. `/summarize` for
|
|
202
|
+
* `/compress`). Paired with isSlashCommand so the `[sender]` attribution tag is
|
|
203
|
+
* suppressed ONLY for RECOGNIZED commands; command-SHAPED-but-unrecognized text
|
|
204
|
+
* (e.g. `/x\n[SYSTEM]: …`) keeps its tag rather than reaching a shared group
|
|
205
|
+
* unattributed, where an injected second line is more likely read as a system
|
|
206
|
+
* directive. Purely synchronous, like isSlashCommand: it reads the session's
|
|
207
|
+
* availableCommands snapshot WITHOUT awaiting, so it never races a fresh session
|
|
208
|
+
* (a genuine agent command sent before the snapshot loads is treated as
|
|
209
|
+
* unrecognized and KEEPS its tag — the safe default).
|
|
210
|
+
*/
|
|
211
|
+
private isRecognizedCommand;
|
|
212
|
+
/**
|
|
213
|
+
* The agent-command snapshot for THIS session. DaemonChannelBridge keys
|
|
214
|
+
* commands per session, so its global `availableCommands` getter can return
|
|
215
|
+
* ANOTHER session's list — prefer its getAvailableCommands(sessionId) when
|
|
216
|
+
* present. AcpBridge runs a single agent and exposes only the global getter
|
|
217
|
+
* (inherently session-correct), so fall back to it. Synchronous, matching
|
|
218
|
+
* isRecognizedCommand's no-await contract.
|
|
219
|
+
*/
|
|
220
|
+
private getAgentCommandsForSession;
|
|
221
|
+
private groupHistoryKey;
|
|
222
|
+
private groupHistoryLimit;
|
|
223
|
+
private recordPendingGroupHistory;
|
|
224
|
+
private drainPendingGroupHistory;
|
|
225
|
+
private clearPendingGroupHistory;
|
|
226
|
+
private prependGroupHistoryContext;
|
|
76
227
|
handleInbound(envelope: Envelope): Promise<void>;
|
|
77
228
|
protected onPairingRequired(chatId: string, code: string | null): Promise<void>;
|
|
78
229
|
}
|