@qwen-code/channel-base 0.14.0-nightly.20260402.a5f17ee39
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 +295 -0
- package/dist/AcpBridge.d.ts +38 -0
- package/dist/AcpBridge.js +170 -0
- package/dist/AcpBridge.js.map +1 -0
- package/dist/BlockStreamer.d.ts +54 -0
- package/dist/BlockStreamer.js +115 -0
- package/dist/BlockStreamer.js.map +1 -0
- package/dist/ChannelBase.d.ts +74 -0
- package/dist/ChannelBase.js +332 -0
- package/dist/ChannelBase.js.map +1 -0
- package/dist/GroupGate.d.ts +21 -0
- package/dist/GroupGate.js +41 -0
- package/dist/GroupGate.js.map +1 -0
- package/dist/PairingStore.d.ts +31 -0
- package/dist/PairingStore.js +115 -0
- package/dist/PairingStore.js.map +1 -0
- package/dist/SenderGate.d.ts +13 -0
- package/dist/SenderGate.js +34 -0
- package/dist/SenderGate.js.map +1 -0
- package/dist/SessionRouter.d.ts +44 -0
- package/dist/SessionRouter.js +186 -0
- package/dist/SessionRouter.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +26 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
// Alphabet without ambiguous chars: 0/O, 1/I
|
|
6
|
+
const SAFE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
|
7
|
+
const CODE_LENGTH = 8;
|
|
8
|
+
const EXPIRY_MS = 60 * 60 * 1000; // 1 hour
|
|
9
|
+
const MAX_PENDING = 3;
|
|
10
|
+
export class PairingStore {
|
|
11
|
+
dir;
|
|
12
|
+
pendingPath;
|
|
13
|
+
allowlistPath;
|
|
14
|
+
constructor(channelName) {
|
|
15
|
+
this.dir = path.join(os.homedir(), '.qwen', 'channels');
|
|
16
|
+
this.pendingPath = path.join(this.dir, `${channelName}-pairing.json`);
|
|
17
|
+
this.allowlistPath = path.join(this.dir, `${channelName}-allowlist.json`);
|
|
18
|
+
}
|
|
19
|
+
isApproved(senderId) {
|
|
20
|
+
const list = this.readAllowlist();
|
|
21
|
+
return list.includes(senderId);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a pairing request for an unknown sender.
|
|
25
|
+
* Returns the code if created, or null if the pending cap is reached.
|
|
26
|
+
* If the sender already has a non-expired pending request, returns that code.
|
|
27
|
+
*/
|
|
28
|
+
createRequest(senderId, senderName) {
|
|
29
|
+
const pending = this.readPending();
|
|
30
|
+
// Purge expired
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
const active = pending.filter((r) => now - r.createdAt < EXPIRY_MS);
|
|
33
|
+
// Check if sender already has a pending request
|
|
34
|
+
const existing = active.find((r) => r.senderId === senderId);
|
|
35
|
+
if (existing) {
|
|
36
|
+
return existing.code;
|
|
37
|
+
}
|
|
38
|
+
// Cap check
|
|
39
|
+
if (active.length >= MAX_PENDING) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const code = generateCode();
|
|
43
|
+
active.push({ senderId, senderName, code, createdAt: now });
|
|
44
|
+
this.writePending(active);
|
|
45
|
+
return code;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Approve a pairing request by code.
|
|
49
|
+
* Returns the sender ID if found, or null if not found / expired.
|
|
50
|
+
*/
|
|
51
|
+
approve(code) {
|
|
52
|
+
const pending = this.readPending();
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
const idx = pending.findIndex((r) => r.code === code.toUpperCase() && now - r.createdAt < EXPIRY_MS);
|
|
55
|
+
if (idx === -1)
|
|
56
|
+
return null;
|
|
57
|
+
const request = pending[idx];
|
|
58
|
+
pending.splice(idx, 1);
|
|
59
|
+
this.writePending(pending);
|
|
60
|
+
// Add to allowlist
|
|
61
|
+
const list = this.readAllowlist();
|
|
62
|
+
if (!list.includes(request.senderId)) {
|
|
63
|
+
list.push(request.senderId);
|
|
64
|
+
this.writeAllowlist(list);
|
|
65
|
+
}
|
|
66
|
+
return request;
|
|
67
|
+
}
|
|
68
|
+
listPending() {
|
|
69
|
+
const pending = this.readPending();
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
return pending.filter((r) => now - r.createdAt < EXPIRY_MS);
|
|
72
|
+
}
|
|
73
|
+
getAllowlist() {
|
|
74
|
+
return this.readAllowlist();
|
|
75
|
+
}
|
|
76
|
+
ensureDir() {
|
|
77
|
+
if (!fs.existsSync(this.dir)) {
|
|
78
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
readPending() {
|
|
82
|
+
try {
|
|
83
|
+
const data = fs.readFileSync(this.pendingPath, 'utf-8');
|
|
84
|
+
return JSON.parse(data);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
writePending(requests) {
|
|
91
|
+
this.ensureDir();
|
|
92
|
+
fs.writeFileSync(this.pendingPath, JSON.stringify(requests, null, 2));
|
|
93
|
+
}
|
|
94
|
+
readAllowlist() {
|
|
95
|
+
try {
|
|
96
|
+
const data = fs.readFileSync(this.allowlistPath, 'utf-8');
|
|
97
|
+
return JSON.parse(data);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
writeAllowlist(list) {
|
|
104
|
+
this.ensureDir();
|
|
105
|
+
fs.writeFileSync(this.allowlistPath, JSON.stringify(list, null, 2));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function generateCode() {
|
|
109
|
+
let code = '';
|
|
110
|
+
for (let i = 0; i < CODE_LENGTH; i++) {
|
|
111
|
+
code += SAFE_ALPHABET[crypto.randomInt(SAFE_ALPHABET.length)];
|
|
112
|
+
}
|
|
113
|
+
return code;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=PairingStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PairingStore.js","sourceRoot":"","sources":["../src/PairingStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SenderPolicy } from './types.js';
|
|
2
|
+
import type { PairingStore } from './PairingStore.js';
|
|
3
|
+
export interface SenderCheckResult {
|
|
4
|
+
allowed: boolean;
|
|
5
|
+
pairingCode?: string | null;
|
|
6
|
+
}
|
|
7
|
+
export declare class SenderGate {
|
|
8
|
+
private policy;
|
|
9
|
+
private allowedUsers;
|
|
10
|
+
private pairingStore;
|
|
11
|
+
constructor(policy: SenderPolicy, allowedUsers?: string[], pairingStore?: PairingStore);
|
|
12
|
+
check(senderId: string, senderName?: string): SenderCheckResult;
|
|
13
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class SenderGate {
|
|
2
|
+
policy;
|
|
3
|
+
allowedUsers;
|
|
4
|
+
pairingStore;
|
|
5
|
+
constructor(policy, allowedUsers = [], pairingStore) {
|
|
6
|
+
this.policy = policy;
|
|
7
|
+
this.allowedUsers = new Set(allowedUsers);
|
|
8
|
+
this.pairingStore = pairingStore || null;
|
|
9
|
+
}
|
|
10
|
+
check(senderId, senderName) {
|
|
11
|
+
switch (this.policy) {
|
|
12
|
+
case 'open':
|
|
13
|
+
return { allowed: true };
|
|
14
|
+
case 'allowlist':
|
|
15
|
+
return { allowed: this.allowedUsers.has(senderId) };
|
|
16
|
+
case 'pairing': {
|
|
17
|
+
// Check static allowlist first
|
|
18
|
+
if (this.allowedUsers.has(senderId)) {
|
|
19
|
+
return { allowed: true };
|
|
20
|
+
}
|
|
21
|
+
// Check dynamic approved list
|
|
22
|
+
if (this.pairingStore?.isApproved(senderId)) {
|
|
23
|
+
return { allowed: true };
|
|
24
|
+
}
|
|
25
|
+
// Generate pairing code
|
|
26
|
+
const code = this.pairingStore?.createRequest(senderId, senderName || senderId);
|
|
27
|
+
return { allowed: false, pairingCode: code ?? null };
|
|
28
|
+
}
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Unknown sender policy: ${this.policy}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=SenderGate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SenderGate.js","sourceRoot":"","sources":["../src/SenderGate.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,UAAU;IACb,MAAM,CAAe;IACrB,YAAY,CAAc;IAC1B,YAAY,CAAsB;IAE1C,YACE,MAAoB,EACpB,eAAyB,EAAE,EAC3B,YAA2B;QAE3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;QAC1C,IAAI,CAAC,YAAY,GAAG,YAAY,IAAI,IAAI,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,QAAgB,EAAE,UAAmB;QACzC,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;YACpB,KAAK,MAAM;gBACT,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YAC3B,KAAK,WAAW;gBACd,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACtD,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,+BAA+B;gBAC/B,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;gBAC3B,CAAC;gBACD,8BAA8B;gBAC9B,IAAI,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;gBAC3B,CAAC;gBACD,wBAAwB;gBACxB,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,aAAa,CAC3C,QAAQ,EACR,UAAU,IAAI,QAAQ,CACvB,CAAC;gBACF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC;YACvD,CAAC;YACD;gBACE,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SessionScope, SessionTarget } from './types.js';
|
|
2
|
+
import type { AcpBridge } from './AcpBridge.js';
|
|
3
|
+
export declare class SessionRouter {
|
|
4
|
+
private toSession;
|
|
5
|
+
private toTarget;
|
|
6
|
+
private toCwd;
|
|
7
|
+
private bridge;
|
|
8
|
+
private defaultCwd;
|
|
9
|
+
private defaultScope;
|
|
10
|
+
private channelScopes;
|
|
11
|
+
private persistPath;
|
|
12
|
+
constructor(bridge: AcpBridge, defaultCwd: string, scope?: SessionScope, persistPath?: string);
|
|
13
|
+
/** Replace the bridge instance (used after crash recovery restart). */
|
|
14
|
+
setBridge(bridge: AcpBridge): void;
|
|
15
|
+
/** Set scope override for a specific channel. */
|
|
16
|
+
setChannelScope(channelName: string, scope: SessionScope): void;
|
|
17
|
+
private routingKey;
|
|
18
|
+
resolve(channelName: string, senderId: string, chatId: string, threadId?: string, cwd?: string): Promise<string>;
|
|
19
|
+
getTarget(sessionId: string): SessionTarget | undefined;
|
|
20
|
+
hasSession(channelName: string, senderId: string, chatId?: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Remove session(s) for the given sender. Returns the removed session IDs.
|
|
23
|
+
*/
|
|
24
|
+
removeSession(channelName: string, senderId: string, chatId?: string): string[];
|
|
25
|
+
private deleteByKey;
|
|
26
|
+
/** Get all session entries for crash recovery. */
|
|
27
|
+
getAll(): Array<{
|
|
28
|
+
key: string;
|
|
29
|
+
sessionId: string;
|
|
30
|
+
target: SessionTarget;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Restore session mappings from a previous bridge.
|
|
34
|
+
* Called after bridge restart — attempts loadSession for each saved mapping.
|
|
35
|
+
* Failed loads are silently dropped (new session on next message).
|
|
36
|
+
*/
|
|
37
|
+
restoreSessions(): Promise<{
|
|
38
|
+
restored: number;
|
|
39
|
+
failed: number;
|
|
40
|
+
}>;
|
|
41
|
+
/** Clear in-memory state and delete persist file. Used on clean shutdown. */
|
|
42
|
+
clearAll(): void;
|
|
43
|
+
private persist;
|
|
44
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
2
|
+
export class SessionRouter {
|
|
3
|
+
toSession = new Map(); // routing key → session ID
|
|
4
|
+
toTarget = new Map(); // session ID → target
|
|
5
|
+
toCwd = new Map(); // session ID → cwd
|
|
6
|
+
bridge;
|
|
7
|
+
defaultCwd;
|
|
8
|
+
defaultScope;
|
|
9
|
+
channelScopes = new Map();
|
|
10
|
+
persistPath;
|
|
11
|
+
constructor(bridge, defaultCwd, scope = 'user', persistPath) {
|
|
12
|
+
this.bridge = bridge;
|
|
13
|
+
this.defaultCwd = defaultCwd;
|
|
14
|
+
this.defaultScope = scope;
|
|
15
|
+
this.persistPath = persistPath;
|
|
16
|
+
}
|
|
17
|
+
/** Replace the bridge instance (used after crash recovery restart). */
|
|
18
|
+
setBridge(bridge) {
|
|
19
|
+
this.bridge = bridge;
|
|
20
|
+
}
|
|
21
|
+
/** Set scope override for a specific channel. */
|
|
22
|
+
setChannelScope(channelName, scope) {
|
|
23
|
+
this.channelScopes.set(channelName, scope);
|
|
24
|
+
}
|
|
25
|
+
routingKey(channelName, senderId, chatId, threadId) {
|
|
26
|
+
const scope = this.channelScopes.get(channelName) || this.defaultScope;
|
|
27
|
+
switch (scope) {
|
|
28
|
+
case 'thread':
|
|
29
|
+
return `${channelName}:${threadId || chatId}`;
|
|
30
|
+
case 'single':
|
|
31
|
+
return `${channelName}:__single__`;
|
|
32
|
+
case 'user':
|
|
33
|
+
default:
|
|
34
|
+
return `${channelName}:${senderId}:${chatId}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async resolve(channelName, senderId, chatId, threadId, cwd) {
|
|
38
|
+
const key = this.routingKey(channelName, senderId, chatId, threadId);
|
|
39
|
+
const existing = this.toSession.get(key);
|
|
40
|
+
if (existing) {
|
|
41
|
+
return existing;
|
|
42
|
+
}
|
|
43
|
+
const sessionCwd = cwd || this.defaultCwd;
|
|
44
|
+
const sessionId = await this.bridge.newSession(sessionCwd);
|
|
45
|
+
this.toSession.set(key, sessionId);
|
|
46
|
+
this.toTarget.set(sessionId, { channelName, senderId, chatId, threadId });
|
|
47
|
+
this.toCwd.set(sessionId, sessionCwd);
|
|
48
|
+
this.persist();
|
|
49
|
+
return sessionId;
|
|
50
|
+
}
|
|
51
|
+
getTarget(sessionId) {
|
|
52
|
+
return this.toTarget.get(sessionId);
|
|
53
|
+
}
|
|
54
|
+
hasSession(channelName, senderId, chatId) {
|
|
55
|
+
const key = chatId
|
|
56
|
+
? this.routingKey(channelName, senderId, chatId)
|
|
57
|
+
: `${channelName}:${senderId}`;
|
|
58
|
+
// If chatId is provided, do exact lookup; otherwise prefix-scan for any match
|
|
59
|
+
if (chatId)
|
|
60
|
+
return this.toSession.has(key);
|
|
61
|
+
for (const k of this.toSession.keys()) {
|
|
62
|
+
if (k.startsWith(`${channelName}:${senderId}`))
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Remove session(s) for the given sender. Returns the removed session IDs.
|
|
69
|
+
*/
|
|
70
|
+
removeSession(channelName, senderId, chatId) {
|
|
71
|
+
const removedIds = [];
|
|
72
|
+
if (chatId) {
|
|
73
|
+
const key = this.routingKey(channelName, senderId, chatId);
|
|
74
|
+
const sessionId = this.deleteByKey(key);
|
|
75
|
+
if (sessionId)
|
|
76
|
+
removedIds.push(sessionId);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// No chatId: remove all sessions for this sender on this channel
|
|
80
|
+
const prefix = `${channelName}:${senderId}`;
|
|
81
|
+
for (const k of [...this.toSession.keys()]) {
|
|
82
|
+
if (k.startsWith(prefix)) {
|
|
83
|
+
const sessionId = this.deleteByKey(k);
|
|
84
|
+
if (sessionId)
|
|
85
|
+
removedIds.push(sessionId);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (removedIds.length > 0)
|
|
90
|
+
this.persist();
|
|
91
|
+
return removedIds;
|
|
92
|
+
}
|
|
93
|
+
deleteByKey(key) {
|
|
94
|
+
const sessionId = this.toSession.get(key);
|
|
95
|
+
if (!sessionId)
|
|
96
|
+
return null;
|
|
97
|
+
this.toSession.delete(key);
|
|
98
|
+
this.toTarget.delete(sessionId);
|
|
99
|
+
this.toCwd.delete(sessionId);
|
|
100
|
+
return sessionId;
|
|
101
|
+
}
|
|
102
|
+
/** Get all session entries for crash recovery. */
|
|
103
|
+
getAll() {
|
|
104
|
+
const entries = [];
|
|
105
|
+
for (const [key, sessionId] of this.toSession) {
|
|
106
|
+
const target = this.toTarget.get(sessionId);
|
|
107
|
+
if (target) {
|
|
108
|
+
entries.push({ key, sessionId, target });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return entries;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Restore session mappings from a previous bridge.
|
|
115
|
+
* Called after bridge restart — attempts loadSession for each saved mapping.
|
|
116
|
+
* Failed loads are silently dropped (new session on next message).
|
|
117
|
+
*/
|
|
118
|
+
async restoreSessions() {
|
|
119
|
+
if (!this.persistPath || !existsSync(this.persistPath)) {
|
|
120
|
+
return { restored: 0, failed: 0 };
|
|
121
|
+
}
|
|
122
|
+
let entries;
|
|
123
|
+
try {
|
|
124
|
+
entries = JSON.parse(readFileSync(this.persistPath, 'utf-8'));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return { restored: 0, failed: 0 };
|
|
128
|
+
}
|
|
129
|
+
let restored = 0;
|
|
130
|
+
let failed = 0;
|
|
131
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
132
|
+
try {
|
|
133
|
+
const sessionId = await this.bridge.loadSession(entry.sessionId, entry.cwd);
|
|
134
|
+
this.toSession.set(key, sessionId);
|
|
135
|
+
this.toTarget.set(sessionId, entry.target);
|
|
136
|
+
this.toCwd.set(sessionId, entry.cwd);
|
|
137
|
+
restored++;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Session can't be loaded — will create fresh on next message
|
|
141
|
+
failed++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Update persist file to only include successfully restored sessions
|
|
145
|
+
if (failed > 0) {
|
|
146
|
+
this.persist();
|
|
147
|
+
}
|
|
148
|
+
return { restored, failed };
|
|
149
|
+
}
|
|
150
|
+
/** Clear in-memory state and delete persist file. Used on clean shutdown. */
|
|
151
|
+
clearAll() {
|
|
152
|
+
this.toSession.clear();
|
|
153
|
+
this.toTarget.clear();
|
|
154
|
+
this.toCwd.clear();
|
|
155
|
+
if (this.persistPath && existsSync(this.persistPath)) {
|
|
156
|
+
try {
|
|
157
|
+
unlinkSync(this.persistPath);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// best-effort
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
persist() {
|
|
165
|
+
if (!this.persistPath)
|
|
166
|
+
return;
|
|
167
|
+
const data = {};
|
|
168
|
+
for (const [key, sessionId] of this.toSession) {
|
|
169
|
+
const target = this.toTarget.get(sessionId);
|
|
170
|
+
if (target) {
|
|
171
|
+
data[key] = {
|
|
172
|
+
sessionId,
|
|
173
|
+
target,
|
|
174
|
+
cwd: this.toCwd.get(sessionId) || this.defaultCwd,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
writeFileSync(this.persistPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// best-effort — don't break message flow for persistence failure
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=SessionRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionRouter.js","sourceRoot":"","sources":["../src/SessionRouter.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { AcpBridge } from './AcpBridge.js';
|
|
2
|
+
export type { AcpBridgeOptions, AvailableCommand, ToolCallEvent, } from './AcpBridge.js';
|
|
3
|
+
export { BlockStreamer } from './BlockStreamer.js';
|
|
4
|
+
export type { BlockStreamerOptions } from './BlockStreamer.js';
|
|
5
|
+
export { ChannelBase } from './ChannelBase.js';
|
|
6
|
+
export type { ChannelBaseOptions } from './ChannelBase.js';
|
|
7
|
+
export { PairingStore } from './PairingStore.js';
|
|
8
|
+
export type { PairingRequest } from './PairingStore.js';
|
|
9
|
+
export { GroupGate } from './GroupGate.js';
|
|
10
|
+
export type { GroupCheckResult } from './GroupGate.js';
|
|
11
|
+
export { SenderGate } from './SenderGate.js';
|
|
12
|
+
export type { SenderCheckResult } from './SenderGate.js';
|
|
13
|
+
export { SessionRouter } from './SessionRouter.js';
|
|
14
|
+
export type { Attachment, BlockStreamingChunkConfig, BlockStreamingCoalesceConfig, ChannelConfig, ChannelPlugin, ChannelType, DispatchMode, Envelope, GroupConfig, GroupPolicy, SenderPolicy, SessionScope, SessionTarget, } from './types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AcpBridge } from './AcpBridge.js';
|
|
2
|
+
export { BlockStreamer } from './BlockStreamer.js';
|
|
3
|
+
export { ChannelBase } from './ChannelBase.js';
|
|
4
|
+
export { PairingStore } from './PairingStore.js';
|
|
5
|
+
export { GroupGate } from './GroupGate.js';
|
|
6
|
+
export { SenderGate } from './SenderGate.js';
|
|
7
|
+
export { SessionRouter } from './SessionRouter.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAM3C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { AcpBridge } from './AcpBridge.js';
|
|
2
|
+
import type { ChannelBase, ChannelBaseOptions } from './ChannelBase.js';
|
|
3
|
+
export type SenderPolicy = 'allowlist' | 'pairing' | 'open';
|
|
4
|
+
export type SessionScope = 'user' | 'thread' | 'single';
|
|
5
|
+
export type ChannelType = string;
|
|
6
|
+
export type GroupPolicy = 'disabled' | 'allowlist' | 'open';
|
|
7
|
+
export type DispatchMode = 'collect' | 'steer' | 'followup';
|
|
8
|
+
export interface GroupConfig {
|
|
9
|
+
requireMention?: boolean;
|
|
10
|
+
dispatchMode?: DispatchMode;
|
|
11
|
+
}
|
|
12
|
+
export interface BlockStreamingChunkConfig {
|
|
13
|
+
/** Minimum characters before emitting a block. Default: 400. */
|
|
14
|
+
minChars?: number;
|
|
15
|
+
/** Force-emit when buffer exceeds this size. Default: 1000. */
|
|
16
|
+
maxChars?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface BlockStreamingCoalesceConfig {
|
|
19
|
+
/** Emit buffered text after this many ms of inactivity. Default: 1500. */
|
|
20
|
+
idleMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ChannelConfig {
|
|
23
|
+
type: ChannelType;
|
|
24
|
+
token: string;
|
|
25
|
+
clientId?: string;
|
|
26
|
+
clientSecret?: string;
|
|
27
|
+
senderPolicy: SenderPolicy;
|
|
28
|
+
allowedUsers: string[];
|
|
29
|
+
sessionScope: SessionScope;
|
|
30
|
+
cwd: string;
|
|
31
|
+
approvalMode?: string;
|
|
32
|
+
instructions?: string;
|
|
33
|
+
model?: string;
|
|
34
|
+
groupPolicy: GroupPolicy;
|
|
35
|
+
groups: Record<string, GroupConfig>;
|
|
36
|
+
/** Dispatch mode for concurrent messages. Default: 'collect'. */
|
|
37
|
+
dispatchMode?: DispatchMode;
|
|
38
|
+
/** Enable block streaming — emit completed blocks as separate messages. */
|
|
39
|
+
blockStreaming?: 'on' | 'off';
|
|
40
|
+
/** Chunk size bounds for block streaming. */
|
|
41
|
+
blockStreamingChunk?: BlockStreamingChunkConfig;
|
|
42
|
+
/** Idle coalescing for block streaming. */
|
|
43
|
+
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
|
|
44
|
+
}
|
|
45
|
+
export interface Attachment {
|
|
46
|
+
/** Content category. */
|
|
47
|
+
type: 'image' | 'file' | 'audio' | 'video';
|
|
48
|
+
/** Base64-encoded data (for images or small files). */
|
|
49
|
+
data?: string;
|
|
50
|
+
/** Absolute path to a local file (for large files saved to disk). */
|
|
51
|
+
filePath?: string;
|
|
52
|
+
/** MIME type (e.g. "image/jpeg", "application/pdf"). */
|
|
53
|
+
mimeType: string;
|
|
54
|
+
/** Original file name from the platform. */
|
|
55
|
+
fileName?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface Envelope {
|
|
58
|
+
channelName: string;
|
|
59
|
+
senderId: string;
|
|
60
|
+
senderName: string;
|
|
61
|
+
chatId: string;
|
|
62
|
+
text: string;
|
|
63
|
+
threadId?: string;
|
|
64
|
+
/** Platform-specific message ID for response correlation. */
|
|
65
|
+
messageId?: string;
|
|
66
|
+
isGroup: boolean;
|
|
67
|
+
isMentioned: boolean;
|
|
68
|
+
isReplyToBot: boolean;
|
|
69
|
+
/** Text of the message being replied to (quoted/referenced message). */
|
|
70
|
+
referencedText?: string;
|
|
71
|
+
/** Base64-encoded image data (e.g. from WeChat CDN download). */
|
|
72
|
+
imageBase64?: string;
|
|
73
|
+
/** MIME type for the image (e.g. "image/jpeg", "image/png"). */
|
|
74
|
+
imageMimeType?: string;
|
|
75
|
+
/** Structured attachments (images, files, audio, video). */
|
|
76
|
+
attachments?: Attachment[];
|
|
77
|
+
}
|
|
78
|
+
export interface SessionTarget {
|
|
79
|
+
channelName: string;
|
|
80
|
+
senderId: string;
|
|
81
|
+
chatId: string;
|
|
82
|
+
threadId?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A channel plugin registers a channel type and provides a factory
|
|
86
|
+
* to create adapter instances. Both built-in adapters and external
|
|
87
|
+
* plugins conform to this interface.
|
|
88
|
+
*/
|
|
89
|
+
export interface ChannelPlugin {
|
|
90
|
+
/** Unique channel type ID (e.g., "telegram", "tmcp-dingtalk"). */
|
|
91
|
+
channelType: string;
|
|
92
|
+
/** Human-readable name for CLI output. */
|
|
93
|
+
displayName: string;
|
|
94
|
+
/**
|
|
95
|
+
* Config fields required by this channel type, beyond the shared
|
|
96
|
+
* ChannelConfig fields. Validated at startup.
|
|
97
|
+
*/
|
|
98
|
+
requiredConfigFields?: string[];
|
|
99
|
+
/** Create a channel adapter instance. */
|
|
100
|
+
createChannel(name: string, config: ChannelConfig & Record<string, unknown>, bridge: AcpBridge, options?: ChannelBaseOptions): ChannelBase;
|
|
101
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qwen-code/channel-base",
|
|
3
|
+
"version": "0.14.0-nightly.20260402.a5f17ee39",
|
|
4
|
+
"description": "Base channel infrastructure for Qwen Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc --build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@agentclientprotocol/sdk": "^0.14.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|