@excitedjs/dreamux 0.2.0 → 0.3.1
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 +155 -160
- package/bin/dreamux +2 -1
- package/bin/tm +30 -0
- package/dist/admin/client.js +98 -0
- package/dist/admin/client.js.map +1 -0
- package/dist/admin/methods.js +83 -38
- package/dist/admin/methods.js.map +1 -1
- package/dist/admin/socket.js +2 -2
- package/dist/admin/socket.js.map +1 -1
- package/dist/channel/feishu-gate.js +187 -18
- package/dist/channel/feishu-gate.js.map +1 -1
- package/dist/channel/feishu-message.js +92 -0
- package/dist/channel/feishu-message.js.map +1 -0
- package/dist/cli/doctor.js +242 -62
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/dreamux.js +33 -51
- package/dist/cli/dreamux.js.map +1 -1
- package/dist/cli/server-ctl.js +6 -8
- package/dist/cli/server-ctl.js.map +1 -1
- package/dist/cli/server.js +22 -32
- package/dist/cli/server.js.map +1 -1
- package/dist/codex/events.js +3 -2
- package/dist/codex/events.js.map +1 -1
- package/dist/codex/mcp-config.js +24 -0
- package/dist/codex/mcp-config.js.map +1 -0
- package/dist/codex/supervisor.js +16 -0
- package/dist/codex/supervisor.js.map +1 -1
- package/dist/dispatcher/approval.js +2 -3
- package/dist/dispatcher/approval.js.map +1 -1
- package/dist/dispatcher/runtime.js +193 -141
- package/dist/dispatcher/runtime.js.map +1 -1
- package/dist/dispatcher/turn-manager.js +78 -97
- package/dist/dispatcher/turn-manager.js.map +1 -1
- package/dist/feishu/bot.js +71 -9
- package/dist/feishu/bot.js.map +1 -1
- package/dist/mcp/feishu-mcp.js +269 -0
- package/dist/mcp/feishu-mcp.js.map +1 -0
- package/dist/onboard/commands.js +10 -6
- package/dist/onboard/commands.js.map +1 -1
- package/dist/onboard/config-files.js +52 -22
- package/dist/onboard/config-files.js.map +1 -1
- package/dist/onboard/dispatcher-skill.js +18 -0
- package/dist/onboard/dispatcher-skill.js.map +1 -0
- package/dist/onboard/run.js +36 -52
- package/dist/onboard/run.js.map +1 -1
- package/dist/onboard/service.js +106 -9
- package/dist/onboard/service.js.map +1 -1
- package/dist/onboard/uninstall.js +47 -13
- package/dist/onboard/uninstall.js.map +1 -1
- package/dist/onboard/wizard.js +3 -27
- package/dist/onboard/wizard.js.map +1 -1
- package/dist/runtime/config.js +158 -64
- package/dist/runtime/config.js.map +1 -1
- package/dist/runtime/dispatcher-codex-home.js +14 -51
- package/dist/runtime/dispatcher-codex-home.js.map +1 -1
- package/dist/runtime/dispatcher-id.js +9 -0
- package/dist/runtime/dispatcher-id.js.map +1 -0
- package/dist/runtime/dispatcher-store.js +202 -0
- package/dist/runtime/dispatcher-store.js.map +1 -0
- package/dist/runtime/package-bin.js +41 -0
- package/dist/runtime/package-bin.js.map +1 -0
- package/dist/runtime/paths.js +82 -48
- package/dist/runtime/paths.js.map +1 -1
- package/dist/runtime/secrets.js +4 -3
- package/dist/runtime/secrets.js.map +1 -1
- package/dist/server.js +110 -36
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
- package/skills/dispatcher/SKILL.md +107 -0
- package/db/migrations/0001_init.sql +0 -49
- package/dist/channel/outbound.js +0 -12
- package/dist/channel/outbound.js.map +0 -1
- package/dist/db/repository.js +0 -223
- package/dist/db/repository.js.map +0 -1
- package/dist/db/schema.js +0 -29
- package/dist/db/schema.js.map +0 -1
- package/dist/db/types.js +0 -2
- package/dist/db/types.js.map +0 -1
- package/dist/onboard/plugins.js +0 -202
- package/dist/onboard/plugins.js.map +0 -1
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-dispatcher
|
|
2
|
+
* Per-dispatcher in-memory turn worker.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* └─[turn/completed]→ awaiting_outbound
|
|
12
|
-
* ├─[feishu send OK]→ completed
|
|
13
|
-
* └─[feishu send fail]→ outbound_failed → retry → completed
|
|
14
|
-
* └─[turn/start RPC failure]→ failed
|
|
15
|
-
*
|
|
16
|
-
* Server crash recovery is handled separately by `recoverDispatcher()`:
|
|
17
|
-
* running → unknown (at-most-once, see issue #2 §"崩溃与异常恢复")
|
|
18
|
-
* awaiting_outbound → safe to retry the outbound
|
|
19
|
-
* outbound_failed → safe to retry the outbound
|
|
4
|
+
* Contract:
|
|
5
|
+
* - one serialized worker per dispatcher;
|
|
6
|
+
* - accepted inbound messages are not persisted;
|
|
7
|
+
* - consecutive pending messages from the same chat are coalesced into one
|
|
8
|
+
* Codex turn;
|
|
9
|
+
* - Feishu message_id redelivery is deduped within this server process;
|
|
10
|
+
* - Codex text output alone does not send anything to Feishu.
|
|
20
11
|
*/
|
|
21
|
-
import {
|
|
22
|
-
|
|
12
|
+
import { runTurn } from '../codex/events.js';
|
|
13
|
+
export const DEFAULT_MESSAGE_ID_DEDUPE_WINDOW = 1024;
|
|
23
14
|
export class TurnManager {
|
|
24
15
|
opts;
|
|
16
|
+
queue = [];
|
|
17
|
+
seenMessageIds = new Set();
|
|
18
|
+
seenMessageIdOrder = [];
|
|
25
19
|
running = false;
|
|
26
20
|
stopped = false;
|
|
21
|
+
drainScheduled = false;
|
|
27
22
|
wakeup = null;
|
|
23
|
+
nextBatchId = 1;
|
|
28
24
|
log;
|
|
29
|
-
|
|
30
|
-
outboundRetryDelayMs;
|
|
31
|
-
emptyTurnPlaceholder;
|
|
25
|
+
messageIdDedupeWindow;
|
|
32
26
|
constructor(opts) {
|
|
33
27
|
this.opts = opts;
|
|
34
28
|
this.log = opts.log ?? ((lvl, msg, err) => {
|
|
@@ -38,15 +32,36 @@ export class TurnManager {
|
|
|
38
32
|
else
|
|
39
33
|
console.error(prefix, msg);
|
|
40
34
|
});
|
|
41
|
-
this.
|
|
42
|
-
this.outboundRetryDelayMs = opts.outboundRetryDelayMs ?? 1000;
|
|
43
|
-
this.emptyTurnPlaceholder =
|
|
44
|
-
opts.emptyTurnPlaceholder ?? '本轮没有文本回复。';
|
|
35
|
+
this.messageIdDedupeWindow = Math.max(0, opts.messageIdDedupeWindow ?? DEFAULT_MESSAGE_ID_DEDUPE_WINDOW);
|
|
45
36
|
}
|
|
46
37
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
38
|
+
* Queue one accepted inbound message. Returns false when the message_id was
|
|
39
|
+
* already seen in this server process.
|
|
49
40
|
*/
|
|
41
|
+
enqueue(input) {
|
|
42
|
+
if (this.stopped)
|
|
43
|
+
return false;
|
|
44
|
+
if (!this.rememberMessageId(input.source_message_id))
|
|
45
|
+
return false;
|
|
46
|
+
const pending = this.queue.find((batch) => batch.source_chat_id === input.source_chat_id);
|
|
47
|
+
if (pending !== undefined) {
|
|
48
|
+
pending.messages.push(input);
|
|
49
|
+
pending.source_message_id = input.source_message_id;
|
|
50
|
+
pending.sender_id = input.sender_id;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.queue.push({
|
|
54
|
+
id: this.nextBatchId++,
|
|
55
|
+
source_chat_id: input.source_chat_id,
|
|
56
|
+
source_message_id: input.source_message_id,
|
|
57
|
+
sender_id: input.sender_id,
|
|
58
|
+
messages: [input],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
this.notify();
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
/** Notify the worker that new work may be available. */
|
|
50
65
|
notify() {
|
|
51
66
|
if (this.stopped)
|
|
52
67
|
return;
|
|
@@ -54,24 +69,32 @@ export class TurnManager {
|
|
|
54
69
|
const w = this.wakeup;
|
|
55
70
|
this.wakeup = null;
|
|
56
71
|
w();
|
|
72
|
+
return;
|
|
57
73
|
}
|
|
58
|
-
|
|
74
|
+
if (this.running || this.drainScheduled)
|
|
75
|
+
return;
|
|
76
|
+
this.drainScheduled = true;
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
this.drainScheduled = false;
|
|
79
|
+
if (!this.stopped)
|
|
80
|
+
void this.drainLoop();
|
|
81
|
+
}, 0);
|
|
59
82
|
}
|
|
60
|
-
/** Drain queued
|
|
83
|
+
/** Drain queued batches until the queue is empty or we're stopped. */
|
|
61
84
|
async drainLoop() {
|
|
62
85
|
if (this.running)
|
|
63
86
|
return;
|
|
64
87
|
this.running = true;
|
|
65
88
|
try {
|
|
66
89
|
while (!this.stopped) {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
90
|
+
const batch = this.queue.shift();
|
|
91
|
+
if (batch === undefined) {
|
|
69
92
|
await this.waitForNotify();
|
|
70
93
|
if (this.stopped)
|
|
71
94
|
return;
|
|
72
95
|
continue;
|
|
73
96
|
}
|
|
74
|
-
await this.
|
|
97
|
+
await this.processBatch(batch);
|
|
75
98
|
}
|
|
76
99
|
}
|
|
77
100
|
finally {
|
|
@@ -83,87 +106,45 @@ export class TurnManager {
|
|
|
83
106
|
this.wakeup = res;
|
|
84
107
|
});
|
|
85
108
|
}
|
|
86
|
-
async
|
|
109
|
+
async processBatch(batch) {
|
|
87
110
|
const threadId = this.opts.getThreadId();
|
|
88
111
|
if (threadId === null) {
|
|
89
|
-
|
|
90
|
-
this.log('error', `inbound row ${row.id} dequeued without thread_id`);
|
|
91
|
-
this.opts.inbound.markFailed(row.id, 'dispatcher has no thread_id');
|
|
112
|
+
this.log('error', `turn batch ${batch.id} dequeued without thread_id`);
|
|
92
113
|
return;
|
|
93
114
|
}
|
|
94
|
-
// Mark running first (before turn/start) so a crash mid-RPC still
|
|
95
|
-
// leaves a recoverable trace.
|
|
96
|
-
this.opts.inbound.markRunning(row.id, null);
|
|
97
|
-
let assistantText;
|
|
98
115
|
try {
|
|
99
|
-
|
|
100
|
-
// Record the turn id for diagnostics — non-fatal if this column
|
|
101
|
-
// can't be updated (e.g. row was already advanced by another path).
|
|
102
|
-
this.opts.inbound.markRunning(row.id, turn.turnId);
|
|
103
|
-
assistantText =
|
|
104
|
-
extractAssistantText(turn) ?? this.emptyTurnPlaceholder;
|
|
116
|
+
await runTurn(this.opts.client, threadId, batchPrompt(batch), this.opts.turnCwd ?? null);
|
|
105
117
|
}
|
|
106
118
|
catch (err) {
|
|
107
119
|
const msg = err instanceof Error ? err.message : String(err);
|
|
108
|
-
this.log('error', `turn execution failed for
|
|
109
|
-
this.opts.inbound.markFailed(row.id, msg);
|
|
110
|
-
// Best-effort tell the user something went wrong.
|
|
111
|
-
try {
|
|
112
|
-
await this.opts.outbound.send(outboundTargetForInbound(row), `本次请求执行失败:${msg}`);
|
|
113
|
-
}
|
|
114
|
-
catch (sendErr) {
|
|
115
|
-
this.log('warn', `error notification also failed`, sendErr);
|
|
116
|
-
}
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
this.opts.inbound.markAwaitingOutbound(row.id, assistantText);
|
|
120
|
-
await this.sendOutbound(row, assistantText);
|
|
121
|
-
}
|
|
122
|
-
/** Send assistant text to feishu with bounded retry. */
|
|
123
|
-
async sendOutbound(row, text) {
|
|
124
|
-
let lastError;
|
|
125
|
-
const target = outboundTargetForInbound(row);
|
|
126
|
-
for (let attempt = 0; attempt <= this.outboundRetries; attempt++) {
|
|
127
|
-
try {
|
|
128
|
-
const ids = await this.opts.outbound.send(target, text);
|
|
129
|
-
this.opts.inbound.markCompleted(row.id, ids);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
lastError = err;
|
|
134
|
-
if (attempt < this.outboundRetries) {
|
|
135
|
-
await new Promise((r) => setTimeout(r, this.outboundRetryDelayMs));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
140
|
-
this.log('error', `outbound send failed for inbound ${row.id}: ${msg}`);
|
|
141
|
-
this.opts.inbound.markOutboundFailed(row.id, msg);
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Retry rows previously left in awaiting_outbound / outbound_failed
|
|
145
|
-
* (no Codex turn re-runs — assistant_text is already in the DB).
|
|
146
|
-
* Called once at dispatcher startup, after thread/resume succeeds.
|
|
147
|
-
*/
|
|
148
|
-
async retryPendingOutbound() {
|
|
149
|
-
const pending = this.opts.inbound.listAwaitingOrFailedOutbound(this.opts.dispatcherId);
|
|
150
|
-
for (const row of pending) {
|
|
151
|
-
if (row.assistant_text === null) {
|
|
152
|
-
// Should not happen — awaiting_outbound implies assistant_text was set.
|
|
153
|
-
this.log('warn', `pending outbound ${row.id} has no assistant_text; marking failed`);
|
|
154
|
-
this.opts.inbound.markFailed(row.id, 'awaiting_outbound row missing assistant_text');
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
await this.sendOutbound(row, row.assistant_text);
|
|
120
|
+
this.log('error', `turn execution failed for batch ${batch.id}: ${msg}`);
|
|
158
121
|
}
|
|
159
122
|
}
|
|
160
123
|
async stop() {
|
|
161
124
|
this.stopped = true;
|
|
125
|
+
this.queue.length = 0;
|
|
162
126
|
if (this.wakeup !== null) {
|
|
163
127
|
const w = this.wakeup;
|
|
164
128
|
this.wakeup = null;
|
|
165
129
|
w();
|
|
166
130
|
}
|
|
167
131
|
}
|
|
132
|
+
rememberMessageId(messageId) {
|
|
133
|
+
if (messageId === null || messageId === '')
|
|
134
|
+
return true;
|
|
135
|
+
if (this.seenMessageIds.has(messageId))
|
|
136
|
+
return false;
|
|
137
|
+
this.seenMessageIds.add(messageId);
|
|
138
|
+
this.seenMessageIdOrder.push(messageId);
|
|
139
|
+
while (this.seenMessageIdOrder.length > this.messageIdDedupeWindow) {
|
|
140
|
+
const evicted = this.seenMessageIdOrder.shift();
|
|
141
|
+
if (evicted !== undefined)
|
|
142
|
+
this.seenMessageIds.delete(evicted);
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function batchPrompt(batch) {
|
|
148
|
+
return batch.messages.map((message) => message.parsed_text).join('\n\n');
|
|
168
149
|
}
|
|
169
150
|
//# sourceMappingURL=turn-manager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn-manager.js","sourceRoot":"","sources":["../../src/dispatcher/turn-manager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"turn-manager.js","sourceRoot":"","sources":["../../src/dispatcher/turn-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/feishu/bot.js
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* the core's surface into the `FeishuBot` interface the server already wires:
|
|
9
9
|
* - `start(handler)` registers the `im.message.receive_v1` route, normalizes
|
|
10
10
|
* each raw event with the core's `parseInbound`, and forwards a
|
|
11
|
-
* `FeishuInboundEvent`. The route handler awaits `handler`, so the message
|
|
12
|
-
*
|
|
11
|
+
* `FeishuInboundEvent`. The route handler awaits `handler`, so the message
|
|
12
|
+
* reaches the server-owned in-memory queue before the SDK acks.
|
|
13
13
|
* - `send(target, text)` delegates to the core transport, preserving reply
|
|
14
|
-
* threading / @-back metadata from the
|
|
14
|
+
* threading / @-back metadata from the in-memory inbound batch.
|
|
15
15
|
* - `botOpenId` surfaces the core transport's `selfId`.
|
|
16
16
|
*
|
|
17
17
|
* Tests inject a `FakeFeishuBot` via `createFakeFeishuBot()` instead of opening
|
|
@@ -20,11 +20,12 @@
|
|
|
20
20
|
import { createFeishuTransport, narrowMetaFromEvent, parseInbound, toChannelInbound, } from '@excitedjs/feishu-transport';
|
|
21
21
|
/** The Feishu event_type carrying inbound chat messages. */
|
|
22
22
|
const IM_MESSAGE_EVENT_TYPE = 'im.message.receive_v1';
|
|
23
|
-
export function createFeishuBot(opts) {
|
|
24
|
-
const transport =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
export function createFeishuBot(opts, deps = {}) {
|
|
24
|
+
const transport = deps.createTransport?.(opts) ??
|
|
25
|
+
createFeishuTransport({
|
|
26
|
+
appId: opts.appId,
|
|
27
|
+
appSecret: opts.appSecret,
|
|
28
|
+
});
|
|
28
29
|
return {
|
|
29
30
|
get appId() {
|
|
30
31
|
return transport.appId;
|
|
@@ -34,7 +35,7 @@ export function createFeishuBot(opts) {
|
|
|
34
35
|
},
|
|
35
36
|
async start(handler) {
|
|
36
37
|
// The core opens the WebSocket and awaits this route handler before the
|
|
37
|
-
// SDK acks; awaiting `handler` here keeps
|
|
38
|
+
// SDK acks; awaiting `handler` here keeps gate/queue work before ACK.
|
|
38
39
|
// `start` rejects if the connection does not come up, so the server's
|
|
39
40
|
// try/catch can fail the dispatcher loudly rather than leave it dark.
|
|
40
41
|
await transport.start({
|
|
@@ -50,6 +51,12 @@ export function createFeishuBot(opts) {
|
|
|
50
51
|
const { messageIds } = await transport.send(target, text);
|
|
51
52
|
return { messageIds };
|
|
52
53
|
},
|
|
54
|
+
addReaction(messageId, emoji) {
|
|
55
|
+
return transport.addReaction(messageId, emoji);
|
|
56
|
+
},
|
|
57
|
+
removeReaction(messageId, reactionId) {
|
|
58
|
+
return transport.removeReaction(messageId, reactionId);
|
|
59
|
+
},
|
|
53
60
|
close() {
|
|
54
61
|
return transport.close();
|
|
55
62
|
},
|
|
@@ -99,6 +106,7 @@ function normalizeInboundEvent(raw) {
|
|
|
99
106
|
const senderId = payload.meta['sender_id'] ?? '';
|
|
100
107
|
const senderType = payload.meta['sender_type'] ?? '';
|
|
101
108
|
const createTime = payload.meta['create_time'] ?? '';
|
|
109
|
+
const senderName = extractSenderName(raw);
|
|
102
110
|
if (messageId === '' || chatId === '')
|
|
103
111
|
return null;
|
|
104
112
|
return {
|
|
@@ -107,6 +115,7 @@ function normalizeInboundEvent(raw) {
|
|
|
107
115
|
chatType,
|
|
108
116
|
senderId,
|
|
109
117
|
senderType,
|
|
118
|
+
senderName,
|
|
110
119
|
messageType,
|
|
111
120
|
rawContent,
|
|
112
121
|
parsedText: payload.text,
|
|
@@ -115,12 +124,39 @@ function normalizeInboundEvent(raw) {
|
|
|
115
124
|
raw,
|
|
116
125
|
};
|
|
117
126
|
}
|
|
127
|
+
function extractSenderName(raw) {
|
|
128
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
129
|
+
return '';
|
|
130
|
+
const root = raw;
|
|
131
|
+
const event = asRecord(root['event']) ?? root;
|
|
132
|
+
const sender = asRecord(event['sender']);
|
|
133
|
+
if (sender === undefined)
|
|
134
|
+
return '';
|
|
135
|
+
return firstString(sender['sender_name'], sender['display_name'], sender['name'], sender['user_name']);
|
|
136
|
+
}
|
|
137
|
+
function firstString(...values) {
|
|
138
|
+
for (const value of values) {
|
|
139
|
+
if (typeof value === 'string')
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
return '';
|
|
143
|
+
}
|
|
144
|
+
function asRecord(value) {
|
|
145
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
146
|
+
? value
|
|
147
|
+
: undefined;
|
|
148
|
+
}
|
|
118
149
|
export function createFakeFeishuBot(appId = 'fake-bot') {
|
|
119
150
|
const sent = [];
|
|
120
151
|
let handler = null;
|
|
121
152
|
let nextMessageId = 1;
|
|
153
|
+
let nextReactionId = 1;
|
|
122
154
|
let sendError = null;
|
|
155
|
+
let reactionError = null;
|
|
156
|
+
let removeReactionError = null;
|
|
123
157
|
const openId = `fake-open-id-${appId}`;
|
|
158
|
+
const reactions = [];
|
|
159
|
+
const removedReactions = [];
|
|
124
160
|
return {
|
|
125
161
|
appId,
|
|
126
162
|
get botOpenId() {
|
|
@@ -137,12 +173,32 @@ export function createFakeFeishuBot(appId = 'fake-bot') {
|
|
|
137
173
|
sent.push({ chatId: target.chatId, target, text, messageIds: [id] });
|
|
138
174
|
return { messageIds: [id] };
|
|
139
175
|
},
|
|
176
|
+
async addReaction(messageId, emoji) {
|
|
177
|
+
if (reactionError !== null) {
|
|
178
|
+
throw reactionError;
|
|
179
|
+
}
|
|
180
|
+
const reactionId = `reaction-fake-${nextReactionId++}`;
|
|
181
|
+
reactions.push({ messageId, emoji, reactionId });
|
|
182
|
+
return reactionId;
|
|
183
|
+
},
|
|
184
|
+
async removeReaction(messageId, reactionId) {
|
|
185
|
+
if (removeReactionError !== null) {
|
|
186
|
+
throw removeReactionError;
|
|
187
|
+
}
|
|
188
|
+
removedReactions.push({ messageId, reactionId });
|
|
189
|
+
},
|
|
140
190
|
async close() {
|
|
141
191
|
handler = null;
|
|
142
192
|
},
|
|
143
193
|
get sentMessages() {
|
|
144
194
|
return sent;
|
|
145
195
|
},
|
|
196
|
+
get reactions() {
|
|
197
|
+
return reactions;
|
|
198
|
+
},
|
|
199
|
+
get removedReactions() {
|
|
200
|
+
return removedReactions;
|
|
201
|
+
},
|
|
146
202
|
async inject(event) {
|
|
147
203
|
if (handler === null)
|
|
148
204
|
throw new Error('fake bot not started');
|
|
@@ -151,6 +207,12 @@ export function createFakeFeishuBot(appId = 'fake-bot') {
|
|
|
151
207
|
setSendError(err) {
|
|
152
208
|
sendError = err;
|
|
153
209
|
},
|
|
210
|
+
setReactionError(err) {
|
|
211
|
+
reactionError = err;
|
|
212
|
+
},
|
|
213
|
+
setRemoveReactionError(err) {
|
|
214
|
+
removeReactionError = err;
|
|
215
|
+
},
|
|
154
216
|
};
|
|
155
217
|
}
|
|
156
218
|
//# sourceMappingURL=bot.js.map
|
package/dist/feishu/bot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bot.js","sourceRoot":"","sources":["../../src/feishu/bot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,
|
|
1
|
+
{"version":3,"file":"bot.js","sourceRoot":"","sources":["../../src/feishu/bot.ts"],"names":[],"mappings":"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"}
|