@xmanrui/dsh-im 0.7.1 → 0.7.2

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.
@@ -4,6 +4,7 @@ import {
4
4
  harnessQuestionText,
5
5
  validHarnessQuestion,
6
6
  } from '../shared/harness-question.mjs';
7
+ import { HarnessApprovalQueue } from '../shared/harness-approval.mjs';
7
8
  import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
8
9
  import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
9
10
 
@@ -114,6 +115,8 @@ export class WecomHarnessBridge {
114
115
  #pendingInteractions = new Map();
115
116
  #interactionKeys = new Map();
116
117
  #acceptedMessageIds = new Set();
118
+ #approvalTasks = new Set();
119
+ #approvals;
117
120
 
118
121
  constructor({
119
122
  client,
@@ -137,6 +140,7 @@ export class WecomHarnessBridge {
137
140
  this.#replyTimeoutMs = replyTimeoutMs;
138
141
  this.#generateReqId = generateStreamId;
139
142
  this.#signal = signal;
143
+ this.#approvals = new HarnessApprovalQueue({ label: 'wecom', logger });
140
144
  }
141
145
 
142
146
  get status() {
@@ -159,6 +163,35 @@ export class WecomHarnessBridge {
159
163
  const key = conversationKey(frame);
160
164
  this.#acceptedMessageIds.add(messageId);
161
165
  const pending = this.#pendingInteractions.get(key);
166
+ const approval = this.#approvals.claimReply({
167
+ key,
168
+ actor: senderId,
169
+ messageId,
170
+ text: messageText(frame),
171
+ addressed: true,
172
+ hasPendingQuestion: Boolean(pending),
173
+ questionCompletion: pending?.submitting || pending?.claimedReplyMessageId
174
+ ? pending.queue
175
+ : null,
176
+ isQuestionPending: () => this.#pendingInteractions.has(key),
177
+ send: (text) => this.#sendImmediate(frame, chatId, text),
178
+ });
179
+ if (approval) {
180
+ let task;
181
+ task = approval.process(async () => {
182
+ if (this.#state.hasSeen(messageId)) return false;
183
+ await this.#state.markSeen(messageId);
184
+ this.#status.messagesReceived += 1;
185
+ this.#status.lastMessageAt = new Date().toISOString();
186
+ return true;
187
+ })
188
+ .finally(() => {
189
+ this.#acceptedMessageIds.delete(messageId);
190
+ this.#approvalTasks.delete(task);
191
+ });
192
+ this.#approvalTasks.add(task);
193
+ return task;
194
+ }
162
195
  if (pending && pending.actor !== senderId) {
163
196
  return this.#enqueueMessage(frame, messageId, key);
164
197
  }
@@ -215,6 +248,7 @@ export class WecomHarnessBridge {
215
248
  ...[...this.#pendingInteractions.values()].flatMap((pending) => (
216
249
  pending.queue ? [pending.queue] : []
217
250
  )),
251
+ ...this.#approvalTasks,
218
252
  ]);
219
253
  }
220
254
 
@@ -355,7 +389,10 @@ export class WecomHarnessBridge {
355
389
  this.#logger.error?.('[dsh-im:wecom] failed to send the safe error reply');
356
390
  }
357
391
  } finally {
358
- await this.#cancelPendingInteraction(key);
392
+ await Promise.allSettled([
393
+ this.#cancelPendingInteraction(key),
394
+ this.#approvals.closeRoute(key),
395
+ ]);
359
396
  }
360
397
  }
361
398
 
@@ -481,7 +518,14 @@ export class WecomHarnessBridge {
481
518
  chatId,
482
519
  requiresMention,
483
520
  }) {
484
- // Approval remains unanswered until #5 supplies an authenticated policy.
521
+ if (interaction?.kind === 'approval') {
522
+ return this.#approvals.handleRequested(interaction, {
523
+ key,
524
+ actor,
525
+ requiresMention,
526
+ send: (text) => this.#sendActive(chatId, text),
527
+ });
528
+ }
485
529
  if (interaction?.kind !== 'question') return;
486
530
  const questions = interaction?.payload?.questions;
487
531
  const interactionId = typeof interaction?.interactionId === 'string'
@@ -555,7 +599,11 @@ export class WecomHarnessBridge {
555
599
  await this.#presentInteraction(pending);
556
600
  }
557
601
 
558
- #handleInteractionResolved(resolution) {
602
+ async #handleInteractionResolved(resolution) {
603
+ if (resolution?.kind === 'approval') {
604
+ await this.#approvals.handleResolved(resolution);
605
+ return;
606
+ }
559
607
  const interactionId = resolution?.interactionId;
560
608
  if (resolution?.kind !== 'question' || typeof interactionId !== 'string') return;
561
609
  const key = this.#interactionKeys.get(interactionId);
@@ -92,7 +92,7 @@ function authenticatedHeaders(token) {
92
92
  function baseInfo() {
93
93
  return {
94
94
  channel_version: WEIXIN_PROTOCOL_VERSION,
95
- bot_agent: 'DeepSeekHarness/0.7.1',
95
+ bot_agent: 'DeepSeekHarness/0.7.2',
96
96
  };
97
97
  }
98
98
 
@@ -8,6 +8,7 @@ import {
8
8
  harnessQuestionText,
9
9
  validHarnessQuestion,
10
10
  } from '../shared/harness-question.mjs';
11
+ import { HarnessApprovalQueue } from '../shared/harness-approval.mjs';
11
12
  import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
12
13
  import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
13
14
 
@@ -68,6 +69,8 @@ export class WeixinHarnessBridge {
68
69
  #pendingInteractions = new Map();
69
70
  #interactionKeys = new Map();
70
71
  #acceptedMessageIds = new Set();
72
+ #approvalTasks = new Set();
73
+ #approvals;
71
74
 
72
75
  constructor({
73
76
  api,
@@ -96,6 +99,7 @@ export class WeixinHarnessBridge {
96
99
  this.#replyTimeoutMs = replyTimeoutMs;
97
100
  this.#maxMessageChars = maxMessageChars;
98
101
  this.#signal = signal;
102
+ this.#approvals = new HarnessApprovalQueue({ label: 'weixin', logger });
99
103
  }
100
104
 
101
105
  get status() {
@@ -111,7 +115,38 @@ export class WeixinHarnessBridge {
111
115
  || this.#acceptedMessageIds.has(messageId)) return Promise.resolve();
112
116
  this.#acceptedMessageIds.add(messageId);
113
117
  const key = conversationKey(sender);
118
+ const contextToken = nonEmptyString(message?.context_token) ?? undefined;
119
+ const runId = nonEmptyString(message?.run_id) ?? undefined;
114
120
  const pending = this.#pendingInteractions.get(key);
121
+ const approval = this.#approvals.claimReply({
122
+ key,
123
+ actor: sender,
124
+ messageId,
125
+ text: extractWeixinText(message),
126
+ addressed: true,
127
+ hasPendingQuestion: Boolean(pending),
128
+ questionCompletion: pending?.submitting || pending?.claimedReplyMessageId
129
+ ? pending.queue
130
+ : null,
131
+ isQuestionPending: () => this.#pendingInteractions.has(key),
132
+ send: (text) => this.#send(sender, text, contextToken, runId),
133
+ });
134
+ if (approval) {
135
+ let task;
136
+ task = approval.process(async () => {
137
+ if (this.#state.hasSeen(messageId)) return false;
138
+ await this.#state.markSeen(messageId);
139
+ this.#status.messagesReceived += 1;
140
+ this.#status.lastMessageAt = new Date().toISOString();
141
+ return true;
142
+ })
143
+ .finally(() => {
144
+ this.#acceptedMessageIds.delete(messageId);
145
+ this.#approvalTasks.delete(task);
146
+ });
147
+ this.#approvalTasks.add(task);
148
+ return task;
149
+ }
115
150
  if (pending?.submitting || pending?.claimedReplyMessageId) {
116
151
  return this.#enqueueMessage(message, messageId, key);
117
152
  }
@@ -157,6 +192,7 @@ export class WeixinHarnessBridge {
157
192
  ...[...this.#pendingInteractions.values()].flatMap((pending) => (
158
193
  pending.queue ? [pending.queue] : []
159
194
  )),
195
+ ...this.#approvalTasks,
160
196
  ]);
161
197
  }
162
198
 
@@ -235,7 +271,10 @@ export class WeixinHarnessBridge {
235
271
  },
236
272
  }));
237
273
  } finally {
238
- await this.#cancelPendingInteraction(key);
274
+ await Promise.allSettled([
275
+ this.#cancelPendingInteraction(key),
276
+ this.#approvals.closeRoute(key),
277
+ ]);
239
278
  }
240
279
  await this.#send(sender, answer, contextToken, runId);
241
280
  await this.#state.markSeen(messageId);
@@ -391,7 +430,13 @@ export class WeixinHarnessBridge {
391
430
  contextToken,
392
431
  runId,
393
432
  }) {
394
- // Approval remains fail-closed until #5 adds an authenticated policy.
433
+ if (interaction?.kind === 'approval') {
434
+ return this.#approvals.handleRequested(interaction, {
435
+ key,
436
+ actor,
437
+ send: (text) => this.#send(actor, text, contextToken, runId),
438
+ });
439
+ }
395
440
  if (interaction?.kind !== 'question') return;
396
441
  const questions = interaction?.payload?.questions;
397
442
  const interactionId = typeof interaction?.interactionId === 'string'
@@ -466,7 +511,11 @@ export class WeixinHarnessBridge {
466
511
  await this.#presentInteraction(pending);
467
512
  }
468
513
 
469
- #handleInteractionResolved(resolution) {
514
+ async #handleInteractionResolved(resolution) {
515
+ if (resolution?.kind === 'approval') {
516
+ await this.#approvals.handleResolved(resolution);
517
+ return;
518
+ }
470
519
  const interactionId = resolution?.interactionId;
471
520
  if (resolution?.kind !== 'question' || typeof interactionId !== 'string') return;
472
521
  const key = this.#interactionKeys.get(interactionId);