@lemoncat7/dsh-partner 1.14.2 → 1.16.0
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 +25 -3
- package/docs/architecture.md +2 -0
- package/docs/direct-channels.md +24 -0
- package/docs/notification-delivery.md +24 -0
- package/docs/releases/1.15.0.md +20 -0
- package/docs/releases/1.16.0.md +22 -0
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +27 -9
- package/lib/agent-runtime.js.map +1 -1
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +109 -1
- package/lib/api.js.map +1 -1
- package/lib/channels/direct/discovery.d.ts +5 -0
- package/lib/channels/direct/discovery.d.ts.map +1 -0
- package/lib/channels/direct/discovery.js +84 -0
- package/lib/channels/direct/discovery.js.map +1 -0
- package/lib/channels/direct/login-cache.d.ts +21 -0
- package/lib/channels/direct/login-cache.d.ts.map +1 -0
- package/lib/channels/direct/login-cache.js +42 -0
- package/lib/channels/direct/login-cache.js.map +1 -0
- package/lib/channels/direct/login.d.ts +15 -0
- package/lib/channels/direct/login.d.ts.map +1 -0
- package/lib/channels/direct/login.js +78 -0
- package/lib/channels/direct/login.js.map +1 -0
- package/lib/channels/direct/transport.d.ts +49 -0
- package/lib/channels/direct/transport.d.ts.map +1 -0
- package/lib/channels/direct/transport.js +152 -0
- package/lib/channels/direct/transport.js.map +1 -0
- package/lib/channels/manager.d.ts +9 -1
- package/lib/channels/manager.d.ts.map +1 -1
- package/lib/channels/manager.js +169 -20
- package/lib/channels/manager.js.map +1 -1
- package/lib/channels/notification-delivery.d.ts +28 -0
- package/lib/channels/notification-delivery.d.ts.map +1 -0
- package/lib/channels/notification-delivery.js +69 -0
- package/lib/channels/notification-delivery.js.map +1 -0
- package/lib/channels/notification-route.d.ts +12 -0
- package/lib/channels/notification-route.d.ts.map +1 -0
- package/lib/channels/notification-route.js +35 -0
- package/lib/channels/notification-route.js.map +1 -0
- package/lib/client-api.d.ts +20 -0
- package/lib/client-api.d.ts.map +1 -1
- package/lib/client-api.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +807 -296
- package/lib/client.js.map +4 -4
- package/lib/concern-store.d.ts +2 -0
- package/lib/concern-store.d.ts.map +1 -1
- package/lib/concern-store.js +13 -0
- package/lib/concern-store.js.map +1 -1
- package/lib/domain.d.ts +24 -0
- package/lib/domain.d.ts.map +1 -1
- package/lib/domain.js.map +1 -1
- package/lib/heartbeat.d.ts +1 -0
- package/lib/heartbeat.d.ts.map +1 -1
- package/lib/heartbeat.js +25 -13
- package/lib/heartbeat.js.map +1 -1
- package/lib/store.js +15 -0
- package/lib/store.js.map +1 -1
- package/lib/ui/channels-panel.d.ts +9 -0
- package/lib/ui/channels-panel.d.ts.map +1 -0
- package/lib/ui/channels-panel.js +66 -0
- package/lib/ui/channels-panel.js.map +1 -0
- package/lib/ui/notification-settings.d.ts +7 -0
- package/lib/ui/notification-settings.d.ts.map +1 -0
- package/lib/ui/notification-settings.js +33 -0
- package/lib/ui/notification-settings.js.map +1 -0
- package/lib/ui/partner-components.js +1 -1
- package/lib/ui/partner-components.js.map +1 -1
- package/lib/ui/use-channel-status.d.ts +4 -0
- package/lib/ui/use-channel-status.d.ts.map +1 -0
- package/lib/ui/use-channel-status.js +46 -0
- package/lib/ui/use-channel-status.js.map +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { pollDiscovered } from './discovery.js';
|
|
3
|
+
export class ChannelHttpError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
retryAfterMs;
|
|
6
|
+
constructor(status, retryAfterMs = 0) {
|
|
7
|
+
super(`渠道请求失败(HTTP ${status})`);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.retryAfterMs = retryAfterMs;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function directConfig(value) {
|
|
13
|
+
if (value.platform !== 'matrix' && value.platform !== 'mattermost')
|
|
14
|
+
throw new Error('请选择 Matrix 或 Mattermost');
|
|
15
|
+
if (typeof value.baseUrl !== 'string' || (value.targetId !== undefined && (typeof value.targetId !== 'string' || value.targetId.length > 512)))
|
|
16
|
+
throw new Error('服务器地址或会话 ID 无效');
|
|
17
|
+
const url = new URL(value.baseUrl);
|
|
18
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash)
|
|
19
|
+
throw new Error('服务器地址只能包含 HTTP(S) 地址和路径,不得带凭据或查询参数');
|
|
20
|
+
return { platform: value.platform, baseUrl: url.toString().replace(/\/$/, ''), targetId: value.targetId?.trim() ?? '' };
|
|
21
|
+
}
|
|
22
|
+
/** Explicit, two-member conversations only. No group inference from display names. */
|
|
23
|
+
export class DirectTransport {
|
|
24
|
+
config;
|
|
25
|
+
token;
|
|
26
|
+
expected;
|
|
27
|
+
botId = '';
|
|
28
|
+
peerId = '';
|
|
29
|
+
constructor(config, token, expected) {
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.token = token;
|
|
32
|
+
this.expected = expected;
|
|
33
|
+
}
|
|
34
|
+
async request(path, signal, body, method = body === undefined ? 'GET' : 'POST') {
|
|
35
|
+
const response = await fetch(this.config.baseUrl + path, {
|
|
36
|
+
method, redirect: 'error', headers: { Authorization: `Bearer ${this.token}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) },
|
|
37
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
38
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(35_000)]) : AbortSignal.timeout(35_000),
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
const header = response.headers.get('retry-after');
|
|
42
|
+
const retry = header ? (/^\d+$/.test(header) ? Number(header) * 1000 : Date.parse(header) - Date.now()) : 0;
|
|
43
|
+
throw new ChannelHttpError(response.status, Number.isFinite(retry) ? Math.max(0, Math.min(900_000, retry)) : 0);
|
|
44
|
+
}
|
|
45
|
+
if (!response.body)
|
|
46
|
+
throw new Error('渠道返回空响应');
|
|
47
|
+
let size = 0;
|
|
48
|
+
const chunks = [];
|
|
49
|
+
for await (const chunk of response.body) {
|
|
50
|
+
size += chunk.byteLength;
|
|
51
|
+
if (size > 4 * 1024 * 1024)
|
|
52
|
+
throw new Error('渠道响应超过大小限制');
|
|
53
|
+
chunks.push(chunk);
|
|
54
|
+
}
|
|
55
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
56
|
+
}
|
|
57
|
+
async validate(signal) {
|
|
58
|
+
if (!this.config.targetId) {
|
|
59
|
+
const who = await this.request(this.config.platform === 'matrix' ? '/_matrix/client/v3/account/whoami' : '/api/v4/users/me', signal);
|
|
60
|
+
const id = this.config.platform === 'matrix' ? who.user_id : who.id;
|
|
61
|
+
if (typeof id !== 'string' || !id || this.expected && this.expected.accountId !== id)
|
|
62
|
+
throw new Error('渠道账号身份无效或已变化');
|
|
63
|
+
this.botId = id;
|
|
64
|
+
return { accountId: id, peerId: '' };
|
|
65
|
+
}
|
|
66
|
+
const target = encodeURIComponent(this.config.targetId);
|
|
67
|
+
if (this.config.platform === 'matrix') {
|
|
68
|
+
const who = await this.request('/_matrix/client/v3/account/whoami', signal);
|
|
69
|
+
if (typeof who.user_id !== 'string')
|
|
70
|
+
throw new Error('Matrix 用户身份无效');
|
|
71
|
+
const state = await this.request(`/_matrix/client/v3/rooms/${target}/state`, signal);
|
|
72
|
+
if (!Array.isArray(state))
|
|
73
|
+
throw new Error('Matrix 房间状态无效');
|
|
74
|
+
if (state.some(e => e.type === 'm.room.encryption'))
|
|
75
|
+
throw new Error('检测到 Matrix 加密房间:暂不支持端到端加密,无法读取消息或发送配对码。请新建未开启加密的双人会话,邀请机器人后发送文字消息。');
|
|
76
|
+
const members = state.filter(e => e.type === 'm.room.member' && e.content?.membership === 'join').map(e => e.state_key);
|
|
77
|
+
if (members.length !== 2 || !members.includes(who.user_id) || members.some(id => typeof id !== 'string'))
|
|
78
|
+
throw new Error('Matrix 房间必须只有机器人和一位已加入的联系人');
|
|
79
|
+
this.botId = who.user_id;
|
|
80
|
+
this.peerId = members.find(id => id !== this.botId);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const who = await this.request('/api/v4/users/me', signal);
|
|
84
|
+
const room = await this.request(`/api/v4/channels/${target}`, signal);
|
|
85
|
+
const members = await this.request(`/api/v4/channels/${target}/members?page=0&per_page=3`, signal);
|
|
86
|
+
if (typeof who.id !== 'string' || room.type !== 'D' || !Array.isArray(members) || members.length !== 2 || !members.some(m => m.user_id === who.id))
|
|
87
|
+
throw new Error('Mattermost 目标必须是机器人已加入的双人 DM,不能是群组或公开频道');
|
|
88
|
+
const peer = members.find(m => m.user_id !== who.id)?.user_id;
|
|
89
|
+
if (typeof peer !== 'string')
|
|
90
|
+
throw new Error('Mattermost 联系人无效');
|
|
91
|
+
this.botId = who.id;
|
|
92
|
+
this.peerId = peer;
|
|
93
|
+
}
|
|
94
|
+
if (this.expected && (this.expected.accountId !== this.botId || this.expected.peerId !== this.peerId))
|
|
95
|
+
throw new Error('机器人或会话成员已变化,请重新配置,禁止沿用旧会话权限');
|
|
96
|
+
return { accountId: this.botId, peerId: this.peerId };
|
|
97
|
+
}
|
|
98
|
+
async poll(cursor, signal) {
|
|
99
|
+
await this.validate(signal);
|
|
100
|
+
if (!this.config.targetId)
|
|
101
|
+
return pollDiscovered(this.config.platform, this.botId, cursor, signal, (path, body) => this.request(path, signal, body), targetId => new DirectTransport({ ...this.config, targetId }, this.token));
|
|
102
|
+
if (this.config.platform === 'matrix') {
|
|
103
|
+
const filter = JSON.stringify({ room: { rooms: [this.config.targetId], timeline: { limit: 100 } }, presence: { types: [] } });
|
|
104
|
+
const data = await this.request('/_matrix/client/v3/sync?timeout=20000&filter=' + encodeURIComponent(filter) + (cursor ? '&since=' + encodeURIComponent(cursor) : ''), signal);
|
|
105
|
+
if (typeof data.next_batch !== 'string')
|
|
106
|
+
throw new Error('Matrix 同步游标缺失');
|
|
107
|
+
const room = data.rooms?.join?.[this.config.targetId];
|
|
108
|
+
if (cursor && room?.timeline?.limited)
|
|
109
|
+
throw new Error('Matrix 消息出现缺口,已暂停以免跳过未处理消息');
|
|
110
|
+
const messages = [];
|
|
111
|
+
// First sync establishes a watermark, never executes historical messages.
|
|
112
|
+
for (const event of cursor ? room?.timeline?.events ?? [] : []) {
|
|
113
|
+
if (event.sender !== this.peerId || event.type !== 'm.room.message' || event.content?.['m.relates_to']?.rel_type === 'm.replace')
|
|
114
|
+
continue;
|
|
115
|
+
if (typeof event.event_id !== 'string')
|
|
116
|
+
continue;
|
|
117
|
+
if (event.content?.msgtype !== 'm.text' || typeof event.content.body !== 'string') {
|
|
118
|
+
messages.push({ id: event.event_id, sender: this.peerId, text: '' });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
messages.push({ id: event.event_id, sender: this.peerId, text: event.content.body });
|
|
122
|
+
}
|
|
123
|
+
return { cursor: data.next_batch, messages };
|
|
124
|
+
}
|
|
125
|
+
const data = await this.request(`/api/v4/channels/${encodeURIComponent(this.config.targetId)}/posts?per_page=200` + (cursor ? '&since=' + encodeURIComponent(cursor) : ''), signal);
|
|
126
|
+
if (!Array.isArray(data.order) || !data.posts)
|
|
127
|
+
throw new Error('Mattermost 消息响应无效');
|
|
128
|
+
if (cursor && data.order.length >= 200)
|
|
129
|
+
throw new Error('Mattermost 消息积压超出单批上限,已暂停以免遗漏');
|
|
130
|
+
const posts = data.order.map((id) => data.posts[id]).filter((p) => p && Number.isFinite(p.create_at)).sort((a, b) => a.create_at - b.create_at);
|
|
131
|
+
const next = Math.max(Number(cursor ?? 0), ...posts.map((p) => p.create_at));
|
|
132
|
+
return { cursor: String(next), messages: cursor ? posts.filter((p) => p.user_id === this.peerId && !p.type && !p.delete_at && !p.root_id).map((p) => ({ id: p.id, sender: this.peerId, text: Array.isArray(p.file_ids) && p.file_ids.length ? '' : p.message ?? '' })) : [] };
|
|
133
|
+
}
|
|
134
|
+
async sendText(userId, text, _context, signal) {
|
|
135
|
+
if (!this.config.targetId)
|
|
136
|
+
throw new Error('联系人尚未配对,不能投递');
|
|
137
|
+
await this.validate(signal);
|
|
138
|
+
if (userId !== this.peerId)
|
|
139
|
+
throw new Error('目标联系人与当前双人会话不匹配,拒绝投递');
|
|
140
|
+
for (let offset = 0; offset < text.length; offset += 4000) {
|
|
141
|
+
const content = text.slice(offset, offset + 4000);
|
|
142
|
+
if (this.config.platform === 'matrix')
|
|
143
|
+
await this.request(`/_matrix/client/v3/rooms/${encodeURIComponent(this.config.targetId)}/send/m.room.message/${randomUUID()}`, signal, { msgtype: 'm.text', body: content }, 'PUT');
|
|
144
|
+
else
|
|
145
|
+
await this.request('/api/v4/posts', signal, { channel_id: this.config.targetId, message: content });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async sendAttachment(_userId, _file, _context, _signal) {
|
|
149
|
+
throw new Error('Matrix / Mattermost 首版仅支持文本,附件尚未交付;请在 DSH 工作区查看文件');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../src/channels/direct/transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,6 +26,7 @@ export declare class ChannelManager {
|
|
|
26
26
|
private readonly operations;
|
|
27
27
|
private readonly pendingQuestions;
|
|
28
28
|
private readonly outboundQueues;
|
|
29
|
+
private readonly notificationDelivery;
|
|
29
30
|
constructor(ctx: ChannelContext, store: PartnerStore, credentials: PartnerCredentialVault, agents: PartnerAgentRuntime, defaultCwd: string);
|
|
30
31
|
attachQuestionAnswerer(agentCtx: Context, route: ChannelSession): () => void;
|
|
31
32
|
views(): Promise<ChannelView[]>;
|
|
@@ -35,13 +36,15 @@ export declare class ChannelManager {
|
|
|
35
36
|
close(): Promise<void>;
|
|
36
37
|
delete(channelId: string): Promise<void>;
|
|
37
38
|
setEnabled(channelId: string, enabled: boolean): Promise<void>;
|
|
38
|
-
sendProactive(channelId: string, userId: string, text: string): Promise<void>;
|
|
39
|
+
sendProactive(channelId: string, userId: string, text: string, receipt?: string): Promise<void>;
|
|
39
40
|
sendExplicitAttachment(sessionId: string, file: PartnerOutboundAttachment, signal: AbortSignal): Promise<void>;
|
|
40
41
|
notifyTaskResult(task: BoardTask): Promise<void>;
|
|
41
42
|
notifyRequirementResult(item: BoardRequirement): Promise<void>;
|
|
42
43
|
observeAutonomousResult(session: Session, event: SessionEvent): Promise<void>;
|
|
43
44
|
private queueProactive;
|
|
45
|
+
private routeForSession;
|
|
44
46
|
private sendProactiveReply;
|
|
47
|
+
private sendNotificationTarget;
|
|
45
48
|
private pollLoop;
|
|
46
49
|
private scheduleInbound;
|
|
47
50
|
private handleInbound;
|
|
@@ -49,6 +52,11 @@ export declare class ChannelManager {
|
|
|
49
52
|
private answerPendingQuestion;
|
|
50
53
|
private clearPendingQuestion;
|
|
51
54
|
private rememberReceipt;
|
|
55
|
+
private sender;
|
|
56
|
+
/** Independent connector loop. Cursor is persisted only after receipt handoff. */
|
|
57
|
+
private directLoop;
|
|
58
|
+
private handleDirect;
|
|
59
|
+
private markInbound;
|
|
52
60
|
}
|
|
53
61
|
export declare function requiredChannel(store: PartnerStore, id: string): WeixinChannel;
|
|
54
62
|
export declare function requiredCompanion(store: PartnerStore, id: string): Companion;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/channels/manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAkB,aAAa,EAAE,MAAM,cAAc,CAAA;AACzG,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/channels/manager.ts"],"names":[],"mappings":"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"}
|
package/lib/channels/manager.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { WeixinApi } from './weixin/api.js';
|
|
3
|
+
import { DirectTransport, ChannelHttpError } from './direct/transport.js';
|
|
4
|
+
import { notificationRoutes } from './notification-route.js';
|
|
5
|
+
import { NotificationDelivery } from './notification-delivery.js';
|
|
3
6
|
import { receiveWeixinMedia } from './weixin/media.js';
|
|
4
7
|
import { completedTurnEvents, extractOutboundAttachments, partnerCwd, selectTaskNotificationRoute } from '../agent-runtime.js';
|
|
5
8
|
import { concernCreatedNoticeFromEvent } from '../concern-notification.js';
|
|
@@ -8,6 +11,9 @@ import { requirementIsIdle, requirementProgressKey } from '../requirements/progr
|
|
|
8
11
|
import { channelReplyPartsAfter, isAutonomousDeliveryTurn } from './delivery-policy.js';
|
|
9
12
|
import { prepareChannelReply } from './outbound-media.js';
|
|
10
13
|
export { isAutonomousDeliveryTurn } from './delivery-policy.js';
|
|
14
|
+
class DirectDeliveryError extends Error {
|
|
15
|
+
constructor() { super('消息处理或发送未完成,渠道已暂停。请在 DSH 查看执行结果后重连;同一消息不会自动重复执行。'); }
|
|
16
|
+
}
|
|
11
17
|
export class ChannelManager {
|
|
12
18
|
ctx;
|
|
13
19
|
store;
|
|
@@ -20,12 +26,14 @@ export class ChannelManager {
|
|
|
20
26
|
operations = new Map();
|
|
21
27
|
pendingQuestions = new Map();
|
|
22
28
|
outboundQueues = new Map();
|
|
29
|
+
notificationDelivery;
|
|
23
30
|
constructor(ctx, store, credentials, agents, defaultCwd) {
|
|
24
31
|
this.ctx = ctx;
|
|
25
32
|
this.store = store;
|
|
26
33
|
this.credentials = credentials;
|
|
27
34
|
this.agents = agents;
|
|
28
35
|
this.defaultCwd = defaultCwd;
|
|
36
|
+
this.notificationDelivery = new NotificationDelivery(store);
|
|
29
37
|
}
|
|
30
38
|
attachQuestionAnswerer(agentCtx, route) {
|
|
31
39
|
return agentCtx.on('user-questions/request', (request, next) => this.askThroughChannel(route, request, next), { prepend: true });
|
|
@@ -43,8 +51,14 @@ export class ChannelManager {
|
|
|
43
51
|
}
|
|
44
52
|
async startEnabled() {
|
|
45
53
|
for (const channel of this.store.snapshot().channels)
|
|
46
|
-
if (channel.enabled)
|
|
47
|
-
|
|
54
|
+
if (channel.enabled) {
|
|
55
|
+
try {
|
|
56
|
+
await this.start(channel.id);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
this.runtime.set(channel.id, { status: 'error', lastError: '渠道启动失败,请检查凭据和配置' });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
48
62
|
}
|
|
49
63
|
async start(channelId) {
|
|
50
64
|
if (this.tasks.has(channelId))
|
|
@@ -53,7 +67,9 @@ export class ChannelManager {
|
|
|
53
67
|
const credential = await this.credentials.read(channelId);
|
|
54
68
|
const controller = new AbortController();
|
|
55
69
|
this.runtime.set(channelId, { status: 'starting' });
|
|
56
|
-
const task =
|
|
70
|
+
const task = (channel.platform && channel.platform !== 'weixin'
|
|
71
|
+
? this.directLoop(channel, controller.signal)
|
|
72
|
+
: this.pollLoop(channel, new WeixinApi(credential.baseUrl, credential.botToken), controller.signal))
|
|
57
73
|
.catch(error => {
|
|
58
74
|
if (!controller.signal.aborted) {
|
|
59
75
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -103,8 +119,8 @@ export class ChannelManager {
|
|
|
103
119
|
else
|
|
104
120
|
await this.stop(channelId);
|
|
105
121
|
}
|
|
106
|
-
async sendProactive(channelId, userId, text) {
|
|
107
|
-
await this.sendProactiveReply(channelId, userId, { text, attachments: [] });
|
|
122
|
+
async sendProactive(channelId, userId, text, receipt) {
|
|
123
|
+
await this.sendProactiveReply(channelId, userId, { text, attachments: [] }, receipt);
|
|
108
124
|
}
|
|
109
125
|
async sendExplicitAttachment(sessionId, file, signal) {
|
|
110
126
|
const route = this.store.snapshot().sessions.find(s => s.sessionId === sessionId);
|
|
@@ -116,7 +132,7 @@ export class ChannelManager {
|
|
|
116
132
|
if (this.store.snapshot().pairings.find(p => p.channelId === route.channelId && p.userId === route.userId)?.status !== 'approved')
|
|
117
133
|
throw new Error('渠道联系人尚未批准');
|
|
118
134
|
const credential = await this.credentials.read(route.channelId);
|
|
119
|
-
await
|
|
135
|
+
await this.sender(channel, credential, route.userId).sendAttachment(route.userId, file, this.contextTokens.get(`${route.channelId}:${route.userId}`), signal);
|
|
120
136
|
}
|
|
121
137
|
async notifyTaskResult(task) {
|
|
122
138
|
if (task.requirementId)
|
|
@@ -125,7 +141,7 @@ export class ChannelManager {
|
|
|
125
141
|
return;
|
|
126
142
|
const routes = this.store.snapshot().sessions.filter(item => item.companionId === task.creatorCompanionId);
|
|
127
143
|
const route = selectTaskNotificationRoute(routes, task.creatorSessionId, () => false);
|
|
128
|
-
if (!route || route.kind === 'local')
|
|
144
|
+
if (!route || (route.kind === 'local' && !this.store.snapshot().companions.find(c => c.id === route.companionId)?.notificationDelivery))
|
|
129
145
|
return;
|
|
130
146
|
const cwd = route.cwd ?? partnerCwd(this.defaultCwd, task.creatorCompanionId);
|
|
131
147
|
const delivery = await prepareTaskResultDelivery(task, cwd);
|
|
@@ -140,8 +156,8 @@ export class ChannelManager {
|
|
|
140
156
|
if (!summary || !item.creatorSessionId)
|
|
141
157
|
return;
|
|
142
158
|
// A manually created board requirement must not leak into an unrelated chat.
|
|
143
|
-
const route = this.
|
|
144
|
-
if (!route || route.kind === 'local')
|
|
159
|
+
const route = this.routeForSession(item.creatorSessionId);
|
|
160
|
+
if (!route || (route.kind === 'local' && !this.store.snapshot().companions.find(c => c.id === route.companionId)?.notificationDelivery))
|
|
145
161
|
return;
|
|
146
162
|
const cwd = route.cwd ?? partnerCwd(this.defaultCwd, route.companionId);
|
|
147
163
|
const delivery = await prepareTaskResultDelivery({ id: stage ? `${item.id}-stage-${item.stageReport.key.slice(0, 16)}` : item.id, title: item.title, description: item.description, status: 'done', priority: 'normal', createdBy: 'companion', skillIds: [], dependencyTaskIds: [], revision: item.revision, createdAt: item.createdAt, updatedAt: item.updatedAt, resultSummary: summary }, cwd);
|
|
@@ -159,8 +175,8 @@ export class ChannelManager {
|
|
|
159
175
|
});
|
|
160
176
|
}
|
|
161
177
|
async observeAutonomousResult(session, event) {
|
|
162
|
-
const route = this.
|
|
163
|
-
if (route === undefined || route.kind === 'local')
|
|
178
|
+
const route = this.routeForSession(session.id);
|
|
179
|
+
if (route === undefined || (route.kind === 'local' && !this.store.snapshot().companions.find(c => c.id === route.companionId)?.notificationDelivery))
|
|
164
180
|
return;
|
|
165
181
|
const concernNotice = concernCreatedNoticeFromEvent(event);
|
|
166
182
|
if (concernNotice !== undefined) {
|
|
@@ -189,7 +205,7 @@ export class ChannelManager {
|
|
|
189
205
|
return;
|
|
190
206
|
if (stillValid && !stillValid())
|
|
191
207
|
throw new Error('需求已调整,取消旧结果投递');
|
|
192
|
-
await this.sendProactiveReply(route.channelId, route.userId, reply);
|
|
208
|
+
await this.sendProactiveReply(route.channelId, route.userId, reply, receipt, route.companionId);
|
|
193
209
|
await this.rememberReceipt(receipt);
|
|
194
210
|
});
|
|
195
211
|
this.outboundQueues.set(key, current);
|
|
@@ -201,20 +217,38 @@ export class ChannelManager {
|
|
|
201
217
|
this.outboundQueues.delete(key);
|
|
202
218
|
}
|
|
203
219
|
}
|
|
204
|
-
|
|
220
|
+
routeForSession(sessionId) {
|
|
221
|
+
const state = this.store.snapshot();
|
|
222
|
+
const candidates = state.sessions.filter(item => item.sessionId === sessionId);
|
|
223
|
+
const inbound = (route) => state.pairings.find(p => p.channelId === route.channelId && p.userId === route.userId)?.lastInboundAt ?? 0;
|
|
224
|
+
return candidates.filter(item => item.kind === 'channel').sort((a, b) => inbound(b) - inbound(a))[0] ?? candidates[0];
|
|
225
|
+
}
|
|
226
|
+
async sendProactiveReply(channelId, userId, reply, receipt, owner) {
|
|
227
|
+
const state = this.store.snapshot();
|
|
228
|
+
const companionId = state.channels.find(c => c.id === channelId)?.companionId ?? owner;
|
|
229
|
+
if (!companionId)
|
|
230
|
+
throw new Error('通知来源伙伴不存在');
|
|
231
|
+
await this.notificationDelivery.deliver(receipt ?? randomBytes(16).toString('hex'), companionId, () => notificationRoutes(this.store.snapshot(), channelId, userId, companionId), reply, async (target, payload, sendPart) => {
|
|
232
|
+
const current = this.store.snapshot();
|
|
233
|
+
if (!current.channels.some(c => c.id === target.channelId && c.companionId === companionId))
|
|
234
|
+
throw new Error('通知目标不属于当前伙伴');
|
|
235
|
+
await this.sendNotificationTarget(target.channelId, target.userId, payload, sendPart);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
async sendNotificationTarget(channelId, userId, reply, sendPart) {
|
|
205
239
|
const channel = requiredChannel(this.store, channelId);
|
|
206
240
|
if (!channel.enabled)
|
|
207
|
-
throw new Error('
|
|
241
|
+
throw new Error('通知渠道已停用');
|
|
208
242
|
const pairing = this.store.snapshot().pairings.find(item => item.channelId === channelId && item.userId === userId);
|
|
209
243
|
if (pairing?.status !== 'approved')
|
|
210
|
-
throw new Error('
|
|
244
|
+
throw new Error('通知接收人尚未批准');
|
|
211
245
|
const credential = await this.credentials.read(channelId);
|
|
212
246
|
const token = this.contextTokens.get(`${channelId}:${userId}`);
|
|
213
|
-
const api =
|
|
247
|
+
const api = this.sender(channel, credential, userId);
|
|
214
248
|
const signal = AbortSignal.timeout(30_000);
|
|
215
|
-
await api.sendText(userId, reply.text, token, signal);
|
|
216
|
-
for (const attachment of reply.attachments)
|
|
217
|
-
await api.sendAttachment(userId, attachment, token, signal);
|
|
249
|
+
await sendPart(0, () => api.sendText(userId, reply.text, token, signal));
|
|
250
|
+
for (const [index, attachment] of reply.attachments.entries())
|
|
251
|
+
await sendPart(index + 1, () => api.sendAttachment(userId, attachment, token, signal));
|
|
218
252
|
}
|
|
219
253
|
async pollLoop(channel, api, signal) {
|
|
220
254
|
let buffer = '';
|
|
@@ -288,6 +322,7 @@ export class ChannelManager {
|
|
|
288
322
|
return;
|
|
289
323
|
}
|
|
290
324
|
const route = this.store.snapshot().sessions.find(item => item.channelId === channel.id && item.userId === userId);
|
|
325
|
+
await this.markInbound(channel.id, userId);
|
|
291
326
|
if (route !== undefined && text && await this.answerPendingQuestion(route.sessionId, text, channel, api, userId, signal)) {
|
|
292
327
|
await this.rememberReceipt(receipt);
|
|
293
328
|
return;
|
|
@@ -335,6 +370,10 @@ export class ChannelManager {
|
|
|
335
370
|
const state = this.store.snapshot();
|
|
336
371
|
const current = state.sessions.find(item => item.id === route.id && item.sessionId === route.sessionId);
|
|
337
372
|
const channel = state.channels.find(item => item.id === route.channelId);
|
|
373
|
+
// Direct connectors currently serialize reads with replies; interactive questions
|
|
374
|
+
// stay in DSH rather than waiting for an answer on a blocked receive loop.
|
|
375
|
+
if (channel?.platform && channel.platform !== 'weixin')
|
|
376
|
+
return next();
|
|
338
377
|
const pairing = state.pairings.find(item => item.channelId === route.channelId && item.userId === route.userId);
|
|
339
378
|
if (current === undefined || channel === undefined || !channel.enabled || pairing?.status !== 'approved')
|
|
340
379
|
return next();
|
|
@@ -359,7 +398,7 @@ export class ChannelManager {
|
|
|
359
398
|
this.pendingQuestions.set(route.sessionId, pending);
|
|
360
399
|
try {
|
|
361
400
|
const credential = await this.credentials.read(channel.id);
|
|
362
|
-
const api =
|
|
401
|
+
const api = this.sender(channel, credential, route.userId);
|
|
363
402
|
await api.sendText(route.userId, renderQuestions(request.questions), this.contextTokens.get(`${channel.id}:${route.userId}`), request.signal ?? AbortSignal.timeout(30_000));
|
|
364
403
|
}
|
|
365
404
|
catch (error) {
|
|
@@ -397,6 +436,116 @@ export class ChannelManager {
|
|
|
397
436
|
state.recentReceipts.splice(0, state.recentReceipts.length - 800);
|
|
398
437
|
});
|
|
399
438
|
}
|
|
439
|
+
sender(channel, credential, userId) {
|
|
440
|
+
if (!channel.platform || channel.platform === 'weixin')
|
|
441
|
+
return new WeixinApi(credential.baseUrl, credential.botToken);
|
|
442
|
+
if (!channel.direct)
|
|
443
|
+
throw new Error('渠道会话配置缺失');
|
|
444
|
+
const pairing = this.store.snapshot().pairings.find(p => p.channelId === channel.id && p.userId === userId && p.status === 'approved');
|
|
445
|
+
const targetId = channel.direct.targetId || pairing?.directTargetId;
|
|
446
|
+
if (!targetId)
|
|
447
|
+
throw new Error('联系人尚未绑定私聊会话');
|
|
448
|
+
return new DirectTransport({ platform: channel.platform, baseUrl: credential.baseUrl, targetId }, credential.botToken, { accountId: channel.accountId, peerId: channel.direct.peerId || userId });
|
|
449
|
+
}
|
|
450
|
+
/** Independent connector loop. Cursor is persisted only after receipt handoff. */
|
|
451
|
+
async directLoop(channel, signal) {
|
|
452
|
+
if (!channel.direct || !channel.platform || channel.platform === 'weixin')
|
|
453
|
+
throw new Error('渠道配置无效');
|
|
454
|
+
const credential = await this.credentials.read(channel.id);
|
|
455
|
+
const api = new DirectTransport({ platform: channel.platform, baseUrl: credential.baseUrl, targetId: channel.direct.targetId }, credential.botToken, { accountId: channel.accountId, peerId: channel.direct.peerId });
|
|
456
|
+
let cursor = channel.direct.cursor;
|
|
457
|
+
let failures = 0;
|
|
458
|
+
let roomWarning;
|
|
459
|
+
while (!signal.aborted) {
|
|
460
|
+
try {
|
|
461
|
+
const batch = await api.poll(cursor, signal);
|
|
462
|
+
if (signal.aborted)
|
|
463
|
+
break;
|
|
464
|
+
if (batch.warning)
|
|
465
|
+
roomWarning = batch.warning;
|
|
466
|
+
else if (batch.messages.length)
|
|
467
|
+
roomWarning = undefined;
|
|
468
|
+
this.runtime.set(channel.id, { status: 'running', ...(roomWarning ? { lastError: roomWarning } : {}) });
|
|
469
|
+
for (const message of batch.messages) {
|
|
470
|
+
if (signal.aborted)
|
|
471
|
+
break;
|
|
472
|
+
try {
|
|
473
|
+
const targetApi = message.targetId ? new DirectTransport({ platform: channel.platform, baseUrl: credential.baseUrl, targetId: message.targetId }, credential.botToken, { accountId: channel.accountId, peerId: message.sender }) : api;
|
|
474
|
+
await targetApi.validate(signal);
|
|
475
|
+
await this.handleDirect(channel, targetApi, message, signal);
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
throw new DirectDeliveryError();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (signal.aborted)
|
|
482
|
+
break;
|
|
483
|
+
await this.store.update(state => {
|
|
484
|
+
const current = state.channels.find(c => c.id === channel.id && c.enabled);
|
|
485
|
+
if (current?.direct)
|
|
486
|
+
current.direct.cursor = batch.cursor;
|
|
487
|
+
});
|
|
488
|
+
cursor = batch.cursor;
|
|
489
|
+
failures = 0;
|
|
490
|
+
await delay(channel.platform === 'mattermost' ? 10_000 : 1000, signal);
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
if (signal.aborted)
|
|
494
|
+
break;
|
|
495
|
+
if (error instanceof DirectDeliveryError || (error instanceof ChannelHttpError && [401, 403].includes(error.status)))
|
|
496
|
+
throw error;
|
|
497
|
+
if (++failures >= 6)
|
|
498
|
+
throw error;
|
|
499
|
+
const reason = error instanceof ChannelHttpError ? `HTTP ${error.status}` : error instanceof Error && error.message === 'fetch failed' ? '网络连接失败,请检查地址、端口和容器 DNS' : error instanceof Error && error.name === 'TimeoutError' ? '服务器请求超时' : error instanceof Error ? error.message : '连接异常';
|
|
500
|
+
this.runtime.set(channel.id, { status: 'starting', lastError: `${reason}(重试 ${failures}/6)` });
|
|
501
|
+
await delay(Math.max(error instanceof ChannelHttpError ? error.retryAfterMs : 0, Math.min(60_000, 2000 * 2 ** failures)), signal);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async handleDirect(channel, api, event, signal) {
|
|
506
|
+
const receipt = `${channel.id}:${event.id}`;
|
|
507
|
+
if (this.store.snapshot().recentReceipts.includes(receipt))
|
|
508
|
+
return;
|
|
509
|
+
const pairing = this.store.snapshot().pairings.find(p => p.channelId === channel.id && p.userId === event.sender);
|
|
510
|
+
// A different room must not silently inherit the contact's existing grant.
|
|
511
|
+
if (pairing?.directTargetId && event.targetId && pairing.directTargetId !== event.targetId) {
|
|
512
|
+
await this.rememberReceipt(receipt);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (!pairing) {
|
|
516
|
+
const now = Date.now();
|
|
517
|
+
const pairingCode = randomBytes(4).toString('hex').toUpperCase();
|
|
518
|
+
await this.store.update(state => {
|
|
519
|
+
if (state.pairings.filter(p => p.channelId === channel.id).length >= 100)
|
|
520
|
+
throw new Error('配对请求已达上限,请在面板清理');
|
|
521
|
+
if (!state.pairings.some(p => p.channelId === channel.id && p.userId === event.sender))
|
|
522
|
+
state.pairings.push({ id: `pairing-${randomBytes(10).toString('hex')}`, channelId: channel.id, userId: event.sender, displayName: event.sender, directTargetId: event.targetId || channel.direct?.targetId || '', pairingCode, status: 'pending', createdAt: now, updatedAt: now });
|
|
523
|
+
});
|
|
524
|
+
await api.sendText(event.sender, `配对码:${pairingCode}\n请在「伙伴 → 渠道 → 配置」核对该码并批准联系人,然后重新发送消息。未批准前不会交给伙伴处理。`, undefined, signal);
|
|
525
|
+
}
|
|
526
|
+
else if (pairing.status === 'approved') {
|
|
527
|
+
await this.markInbound(channel.id, event.sender);
|
|
528
|
+
if (!event.text)
|
|
529
|
+
await api.sendText(event.sender, '此渠道首版仅支持文本;附件未交给伙伴处理。', undefined, signal);
|
|
530
|
+
else {
|
|
531
|
+
// Claim before starting tools. A delivery failure must not execute the same
|
|
532
|
+
// user command twice on reconnect; its result remains in the DSH session.
|
|
533
|
+
await this.rememberReceipt(receipt);
|
|
534
|
+
const companion = requiredCompanion(this.store, channel.companionId);
|
|
535
|
+
const reply = await this.agents.reply(companion, channel.id, event.sender, { text: event.text, attachments: [] });
|
|
536
|
+
signal.throwIfAborted();
|
|
537
|
+
const current = this.store.snapshot();
|
|
538
|
+
if (!current.channels.some(c => c.id === channel.id && c.enabled && c.companionId === channel.companionId) || !current.pairings.some(p => p.channelId === channel.id && p.userId === event.sender && p.status === 'approved'))
|
|
539
|
+
throw new Error('渠道或联系人授权已撤销,取消回复');
|
|
540
|
+
await api.sendText(event.sender, reply.text + (reply.attachments.length ? '\n\n附件未交付:此渠道首版只发送文本,请在 DSH 工作区查看文件。' : ''), undefined, signal);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
await this.rememberReceipt(receipt);
|
|
544
|
+
}
|
|
545
|
+
async markInbound(channelId, userId) {
|
|
546
|
+
await this.store.update(state => { const p = state.pairings.find(p => p.channelId === channelId && p.userId === userId && p.status === 'approved'); if (p)
|
|
547
|
+
p.lastInboundAt = Date.now(); });
|
|
548
|
+
}
|
|
400
549
|
}
|
|
401
550
|
export function requiredChannel(store, id) {
|
|
402
551
|
const channel = store.snapshot().channels.find(item => item.id === id);
|