@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.
Files changed (74) hide show
  1. package/README.md +25 -3
  2. package/docs/architecture.md +2 -0
  3. package/docs/direct-channels.md +24 -0
  4. package/docs/notification-delivery.md +24 -0
  5. package/docs/releases/1.15.0.md +20 -0
  6. package/docs/releases/1.16.0.md +22 -0
  7. package/lib/agent-runtime.d.ts.map +1 -1
  8. package/lib/agent-runtime.js +27 -9
  9. package/lib/agent-runtime.js.map +1 -1
  10. package/lib/api.d.ts.map +1 -1
  11. package/lib/api.js +109 -1
  12. package/lib/api.js.map +1 -1
  13. package/lib/channels/direct/discovery.d.ts +5 -0
  14. package/lib/channels/direct/discovery.d.ts.map +1 -0
  15. package/lib/channels/direct/discovery.js +84 -0
  16. package/lib/channels/direct/discovery.js.map +1 -0
  17. package/lib/channels/direct/login-cache.d.ts +21 -0
  18. package/lib/channels/direct/login-cache.d.ts.map +1 -0
  19. package/lib/channels/direct/login-cache.js +42 -0
  20. package/lib/channels/direct/login-cache.js.map +1 -0
  21. package/lib/channels/direct/login.d.ts +15 -0
  22. package/lib/channels/direct/login.d.ts.map +1 -0
  23. package/lib/channels/direct/login.js +78 -0
  24. package/lib/channels/direct/login.js.map +1 -0
  25. package/lib/channels/direct/transport.d.ts +49 -0
  26. package/lib/channels/direct/transport.d.ts.map +1 -0
  27. package/lib/channels/direct/transport.js +152 -0
  28. package/lib/channels/direct/transport.js.map +1 -0
  29. package/lib/channels/manager.d.ts +9 -1
  30. package/lib/channels/manager.d.ts.map +1 -1
  31. package/lib/channels/manager.js +169 -20
  32. package/lib/channels/manager.js.map +1 -1
  33. package/lib/channels/notification-delivery.d.ts +28 -0
  34. package/lib/channels/notification-delivery.d.ts.map +1 -0
  35. package/lib/channels/notification-delivery.js +69 -0
  36. package/lib/channels/notification-delivery.js.map +1 -0
  37. package/lib/channels/notification-route.d.ts +12 -0
  38. package/lib/channels/notification-route.d.ts.map +1 -0
  39. package/lib/channels/notification-route.js +35 -0
  40. package/lib/channels/notification-route.js.map +1 -0
  41. package/lib/client-api.d.ts +20 -0
  42. package/lib/client-api.d.ts.map +1 -1
  43. package/lib/client-api.js.map +1 -1
  44. package/lib/client.d.ts.map +1 -1
  45. package/lib/client.js +807 -296
  46. package/lib/client.js.map +4 -4
  47. package/lib/concern-store.d.ts +2 -0
  48. package/lib/concern-store.d.ts.map +1 -1
  49. package/lib/concern-store.js +13 -0
  50. package/lib/concern-store.js.map +1 -1
  51. package/lib/domain.d.ts +24 -0
  52. package/lib/domain.d.ts.map +1 -1
  53. package/lib/domain.js.map +1 -1
  54. package/lib/heartbeat.d.ts +1 -0
  55. package/lib/heartbeat.d.ts.map +1 -1
  56. package/lib/heartbeat.js +25 -13
  57. package/lib/heartbeat.js.map +1 -1
  58. package/lib/store.js +15 -0
  59. package/lib/store.js.map +1 -1
  60. package/lib/ui/channels-panel.d.ts +9 -0
  61. package/lib/ui/channels-panel.d.ts.map +1 -0
  62. package/lib/ui/channels-panel.js +66 -0
  63. package/lib/ui/channels-panel.js.map +1 -0
  64. package/lib/ui/notification-settings.d.ts +7 -0
  65. package/lib/ui/notification-settings.d.ts.map +1 -0
  66. package/lib/ui/notification-settings.js +33 -0
  67. package/lib/ui/notification-settings.js.map +1 -0
  68. package/lib/ui/partner-components.js +1 -1
  69. package/lib/ui/partner-components.js.map +1 -1
  70. package/lib/ui/use-channel-status.d.ts +4 -0
  71. package/lib/ui/use-channel-status.d.ts.map +1 -0
  72. package/lib/ui/use-channel-status.js +46 -0
  73. package/lib/ui/use-channel-status.js.map +1 -0
  74. 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":"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"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/channels/manager.ts"],"names":[],"mappings":"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"}
@@ -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
- await this.start(channel.id);
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 = this.pollLoop(channel, new WeixinApi(credential.baseUrl, credential.botToken), controller.signal)
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 new WeixinApi(credential.baseUrl, credential.botToken).sendAttachment(route.userId, file, this.contextTokens.get(`${route.channelId}:${route.userId}`), signal);
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.store.snapshot().sessions.find(route => route.sessionId === item.creatorSessionId);
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.store.snapshot().sessions.find(item => item.sessionId === session.id);
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
- async sendProactiveReply(channelId, userId, reply) {
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 = new WeixinApi(credential.baseUrl, credential.botToken);
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 = new WeixinApi(credential.baseUrl, credential.botToken);
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);