lark-remote 0.5.20 → 0.5.21
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/CHANGELOG.md +10 -0
- package/dist/bridge/inbound-media.js +71 -45
- package/dist/bridge/index.d.ts +9 -0
- package/dist/bridge/index.js +230 -203
- package/dist/card/approval-render.js +32 -33
- package/dist/card/card-budget.js +123 -98
- package/dist/card/run-renderer.js +82 -68
- package/dist/card/run-state.d.ts +0 -14
- package/dist/card/run-state.js +335 -207
- package/dist/card/tool-render.js +20 -16
- package/dist/card/usage-format.d.ts +12 -9
- package/dist/card/usage-format.js +111 -97
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/index.js +226 -190
- package/dist/message-handlers.js +253 -236
- package/dist/router/commands/ls.d.ts +3 -0
- package/dist/router/commands/ls.js +256 -175
- package/dist/router/commands/order.js +184 -159
- package/dist/router/commands/resume.js +189 -133
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/index.js +192 -201
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +287 -221
- package/dist/runner/common/acp/base-translator.js +19 -17
- package/dist/runner/common/jsonl-stream.js +154 -187
- package/dist/runner/common/jsonrpc/client.d.ts +29 -0
- package/dist/runner/common/jsonrpc/client.js +43 -12
- package/dist/runner/common/spawning-runner.d.ts +5 -0
- package/dist/runner/common/spawning-runner.js +8 -3
- package/dist/runner/common/turn-started.d.ts +8 -0
- package/dist/runner/common/turn-started.js +11 -0
- package/dist/runner/dsh/approval.js +84 -51
- package/dist/runner/dsh/client.d.ts +7 -9
- package/dist/runner/dsh/client.js +356 -287
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +4 -3
- package/dist/runner/dsh/runner.js +88 -64
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/kimi/acp/runner.js +81 -44
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/session/claude/sessions.js +113 -101
- package/dist/session/codex/rollout-reader.js +179 -180
- package/dist/session/dsh/sessions.js +281 -231
- package/dist/session/kimi/sessions.d.ts +0 -7
- package/dist/session/kimi/sessions.js +129 -109
- package/dist/session/opencode/sessions.d.ts +0 -11
- package/dist/session/opencode/sessions.js +161 -137
- package/dist/session/session-store.d.ts +8 -0
- package/dist/session/session-store.js +98 -98
- package/package.json +1 -1
package/dist/message-handlers.js
CHANGED
|
@@ -18,260 +18,277 @@ function isStopCommand(text) {
|
|
|
18
18
|
const t = text.trim().toLowerCase();
|
|
19
19
|
return t === '/stop' || t === '/t';
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
messageId,
|
|
51
|
-
messagePreview: prompt.slice(0, 3000),
|
|
52
|
-
binding,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
},
|
|
56
|
-
onReceipt: async (ctx, text) => {
|
|
57
|
-
await bridge.sendResult({ text }, ctx);
|
|
21
|
+
/** 入站统一装配器 commit:图/文组装成 turn → 进 agent 队列。 */
|
|
22
|
+
function handleTurnCommit(ctx, turn, prompt) {
|
|
23
|
+
const messageId = turn.messageIds[turn.messageIds.length - 1] ?? '';
|
|
24
|
+
const taskCtx = {
|
|
25
|
+
userId: turn.userId,
|
|
26
|
+
chatId: turn.chatId,
|
|
27
|
+
messageId,
|
|
28
|
+
turnMessageIds: turn.messageIds,
|
|
29
|
+
};
|
|
30
|
+
let cwd = ctx.sessionStore.getCwd(turn.userId) ?? '';
|
|
31
|
+
if (!cwd) {
|
|
32
|
+
const recent = [...ctx.workspaceStore.list()].sort((a, b) => b.lastUsedAt - a.lastUsedAt);
|
|
33
|
+
if (recent.length > 0)
|
|
34
|
+
cwd = recent[0].path;
|
|
35
|
+
}
|
|
36
|
+
const binding = ctx.bridge.currentBinding(turn.userId);
|
|
37
|
+
ctx.bridge.enqueue(cwd, async () => {
|
|
38
|
+
await ctx.router.handle(prompt, taskCtx, {
|
|
39
|
+
cwdOverride: cwd,
|
|
40
|
+
binding,
|
|
41
|
+
allowCommandPrefix: false,
|
|
42
|
+
});
|
|
43
|
+
}, {
|
|
44
|
+
taskMeta: {
|
|
45
|
+
userId: turn.userId,
|
|
46
|
+
chatId: turn.chatId,
|
|
47
|
+
messageId,
|
|
48
|
+
messagePreview: prompt.slice(0, 3000),
|
|
49
|
+
binding,
|
|
58
50
|
},
|
|
59
51
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
}
|
|
53
|
+
/** 入站回执(无文本纯附件 / rejected 项):面向用户的提示。 */
|
|
54
|
+
async function handleTurnReceipt(ctx, receiptCtx, text) {
|
|
55
|
+
await ctx.bridge.sendResult({ text }, receiptCtx);
|
|
56
|
+
}
|
|
57
|
+
/** owner 绑定成功后的 onboarding 消息(fire-and-forget)。 */
|
|
58
|
+
function handleBind(ctx, userId, chatId) {
|
|
59
|
+
let resolvedCwd;
|
|
60
|
+
try {
|
|
61
|
+
resolvedCwd = fs.realpathSync(process.cwd());
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
resolvedCwd = process.cwd();
|
|
65
|
+
}
|
|
66
|
+
if (!ctx.sessionStore.getCwd(userId)) {
|
|
67
|
+
ctx.sessionStore.setCwd(userId, resolvedCwd);
|
|
68
|
+
}
|
|
69
|
+
return (async () => {
|
|
70
|
+
try {
|
|
71
|
+
await ctx.connector.sendWithRetry(chatId, {
|
|
72
|
+
text: `✅ 已绑定到本账号,此后仅你可使用本应用\n` +
|
|
73
|
+
`📂 当前工作目录: \`${resolvedCwd}\`\n` +
|
|
74
|
+
`🤖 默认 Agent: ${agentDisplayName(ctx.config.defaultAgent)}\n` +
|
|
75
|
+
`💡 直接输入消息即可开始对话,或 /cd 切换目录`,
|
|
76
|
+
});
|
|
77
|
+
const helpResult = ctx.router.cmdHelp();
|
|
78
|
+
if (helpResult.card) {
|
|
79
|
+
await ctx.connector.sendWithRetry(chatId, { card: helpResult.card });
|
|
73
80
|
}
|
|
74
|
-
return (async () => {
|
|
75
|
-
try {
|
|
76
|
-
await connector.sendWithRetry(chatId, {
|
|
77
|
-
text: `✅ 已绑定到本账号,此后仅你可使用本应用\n` +
|
|
78
|
-
`📂 当前工作目录: \`${resolvedCwd}\`\n` +
|
|
79
|
-
`🤖 默认 Agent: ${agentDisplayName(config.defaultAgent)}\n` +
|
|
80
|
-
`💡 直接输入消息即可开始对话,或 /cd 切换目录`,
|
|
81
|
-
});
|
|
82
|
-
const helpResult = router.cmdHelp();
|
|
83
|
-
if (helpResult.card) {
|
|
84
|
-
await connector.sendWithRetry(chatId, { card: helpResult.card });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (err) {
|
|
88
|
-
logger.error('[binder] onboarding send failed:', err);
|
|
89
|
-
}
|
|
90
|
-
})();
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
connector.setInboundMediaDetectedHandler((msg) => {
|
|
94
|
-
const authorization = authorizer.authorize(msg.userId, msg.chatId);
|
|
95
|
-
if (authorization === 'rejected') {
|
|
96
|
-
logger.warn(`[media] rejected inbound media from non-owner ${msg.userId}`);
|
|
97
|
-
return;
|
|
98
81
|
}
|
|
99
|
-
|
|
100
|
-
logger.
|
|
101
|
-
return;
|
|
82
|
+
catch (err) {
|
|
83
|
+
ctx.logger.error('[binder] onboarding send failed:', err);
|
|
102
84
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
maxFileSizeMb: router.config.inboundMedia.maxFileSizeMb,
|
|
120
|
-
});
|
|
121
|
-
try {
|
|
122
|
-
return await bridge.saveInboundMedia(payload);
|
|
123
|
-
}
|
|
124
|
-
catch (err) {
|
|
125
|
-
for (const item of payload.media)
|
|
126
|
-
bestEffortUnlink(item.tempPath);
|
|
127
|
-
throw err;
|
|
128
|
-
}
|
|
129
|
-
})();
|
|
130
|
-
assembler.ingest({
|
|
131
|
-
kind: 'media',
|
|
85
|
+
})();
|
|
86
|
+
}
|
|
87
|
+
/** 入站媒体(image/file)检测:鉴权 → clone 拦截 → 落盘或 rejected 回执。 */
|
|
88
|
+
function handleInboundMedia(ctx, msg) {
|
|
89
|
+
const authorization = ctx.authorizer.authorize(msg.userId, msg.chatId);
|
|
90
|
+
if (authorization === 'rejected') {
|
|
91
|
+
ctx.logger.warn(`[media] rejected inbound media from non-owner ${msg.userId}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (ctx.cloneSession.isActive()) {
|
|
95
|
+
ctx.logger.info(`[media] dropped inbound media during clone session ${msg.messageId}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!ctx.router.config.inboundMedia.enabled) {
|
|
99
|
+
ctx.assembler.ingest({
|
|
100
|
+
kind: 'rejected',
|
|
132
101
|
userId: msg.userId,
|
|
133
102
|
chatId: msg.chatId,
|
|
134
103
|
messageId: msg.messageId,
|
|
135
104
|
replyToMessageId: msg.replyToMessageId,
|
|
136
105
|
rawContentType: msg.rawContentType,
|
|
137
|
-
|
|
106
|
+
rejectedKind: 'file',
|
|
107
|
+
reason: '入站媒体保存已关闭(inboundMedia.enabled: false),未保存文件。' +
|
|
108
|
+
'如需自动保存图片/文件,请开启后重试',
|
|
138
109
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
void cloneSession
|
|
148
|
-
.handleMessage(msg.content, {
|
|
149
|
-
userId: msg.userId,
|
|
150
|
-
chatId: msg.chatId,
|
|
151
|
-
messageId: msg.messageId,
|
|
152
|
-
})
|
|
153
|
-
.catch((err) => logger.error('[clone] message handling failed:', err));
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const content = router.expandAliasMessage(msg.content);
|
|
157
|
-
if (isStopCommand(content)) {
|
|
158
|
-
void (async () => {
|
|
159
|
-
const interrupt = await bridge.interruptCurrentRunDetailed({
|
|
160
|
-
userId: msg.userId,
|
|
161
|
-
chatId: msg.chatId,
|
|
162
|
-
});
|
|
163
|
-
const ctx = { userId: msg.userId, chatId: msg.chatId, messageId: msg.messageId };
|
|
164
|
-
if (!interrupt.stopped) {
|
|
165
|
-
await bridge.sendResult({ text: '当前没有运行中的进程' }, ctx);
|
|
166
|
-
}
|
|
167
|
-
else if ((interrupt.stoppedCount ?? 1) > 1) {
|
|
168
|
-
await bridge.sendResult({ text: `✅ 已停止 ${interrupt.stoppedCount} 个运行中的任务` }, ctx);
|
|
169
|
-
}
|
|
170
|
-
})().catch((err) => logger.error('[control] /stop failed:', err));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const stripped = stripPlaceholders(content);
|
|
174
|
-
if (stripped.unknownTags.length > 0) {
|
|
175
|
-
logger.warn(`[inbound] unknown placeholder tags stripped: ${stripped.unknownTags.join(', ')} ` +
|
|
176
|
-
`(msg_type=${msg.rawContentType})`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const outcome = (async () => {
|
|
113
|
+
const payload = await ctx.connector.downloadInboundMedia(msg, {
|
|
114
|
+
maxFileSizeMb: ctx.router.config.inboundMedia.maxFileSizeMb,
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
return await ctx.bridge.saveInboundMedia(payload);
|
|
177
118
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
await router.handle(stripped.clean, {
|
|
183
|
-
userId: msg.userId,
|
|
184
|
-
chatId: msg.chatId,
|
|
185
|
-
messageId: msg.messageId,
|
|
186
|
-
}, { allowCommandPrefix: true });
|
|
187
|
-
})().catch((err) => logger.error('[control] command failed:', err));
|
|
188
|
-
return;
|
|
119
|
+
catch (err) {
|
|
120
|
+
for (const item of payload.media)
|
|
121
|
+
bestEffortUnlink(item.tempPath);
|
|
122
|
+
throw err;
|
|
189
123
|
}
|
|
190
|
-
|
|
191
|
-
|
|
124
|
+
})();
|
|
125
|
+
ctx.assembler.ingest({
|
|
126
|
+
kind: 'media',
|
|
127
|
+
userId: msg.userId,
|
|
128
|
+
chatId: msg.chatId,
|
|
129
|
+
messageId: msg.messageId,
|
|
130
|
+
replyToMessageId: msg.replyToMessageId,
|
|
131
|
+
rawContentType: msg.rawContentType,
|
|
132
|
+
outcome,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** 普通入站消息:鉴权 → clone 拦截 → /stop → 命令前缀 → 装配器。 */
|
|
136
|
+
function handleInboundMessage(ctx, msg) {
|
|
137
|
+
const decision = ctx.authorizer.authorize(msg.userId, msg.chatId);
|
|
138
|
+
if (decision === 'rejected' || decision === 'bind_success')
|
|
139
|
+
return;
|
|
140
|
+
ctx.logger.info(`message from ${msg.userId} (${msg.rawContentType}): ${msg.content.slice(0, 100)}`);
|
|
141
|
+
void ctx.connector.addReaction(msg.messageId, 'Typing');
|
|
142
|
+
if (ctx.cloneSession.isActive() && !isStopCommand(msg.content)) {
|
|
143
|
+
void ctx.cloneSession
|
|
144
|
+
.handleMessage(msg.content, {
|
|
192
145
|
userId: msg.userId,
|
|
193
146
|
chatId: msg.chatId,
|
|
194
147
|
messageId: msg.messageId,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
148
|
+
})
|
|
149
|
+
.catch((err) => ctx.logger.error('[clone] message handling failed:', err));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const content = ctx.router.expandAliasMessage(msg.content);
|
|
153
|
+
if (isStopCommand(content)) {
|
|
154
|
+
void (async () => {
|
|
155
|
+
const interrupt = await ctx.bridge.interruptCurrentRunDetailed({
|
|
156
|
+
userId: msg.userId,
|
|
157
|
+
chatId: msg.chatId,
|
|
158
|
+
});
|
|
159
|
+
const stopCtx = { userId: msg.userId, chatId: msg.chatId, messageId: msg.messageId };
|
|
160
|
+
if (!interrupt.stopped) {
|
|
161
|
+
await ctx.bridge.sendResult({ text: '当前没有运行中的进程' }, stopCtx);
|
|
162
|
+
}
|
|
163
|
+
else if ((interrupt.stoppedCount ?? 1) > 1) {
|
|
164
|
+
await ctx.bridge.sendResult({ text: `✅ 已停止 ${interrupt.stoppedCount} 个运行中的任务` }, stopCtx);
|
|
165
|
+
}
|
|
166
|
+
})().catch((err) => ctx.logger.error('[control] /stop failed:', err));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const stripped = stripPlaceholders(content);
|
|
170
|
+
if (stripped.unknownTags.length > 0) {
|
|
171
|
+
ctx.logger.warn(`[inbound] unknown placeholder tags stripped: ${stripped.unknownTags.join(', ')} ` +
|
|
172
|
+
`(msg_type=${msg.rawContentType})`);
|
|
173
|
+
}
|
|
174
|
+
const commandEligible = msg.rawContentType === 'text' && stripped.kinds.length === 0 && stripped.clean !== '';
|
|
175
|
+
if (commandEligible && (stripped.clean.startsWith('/') || stripped.clean.startsWith('!'))) {
|
|
176
|
+
void (async () => {
|
|
177
|
+
await ctx.assembler.flush(msg.userId, msg.chatId, 'flush');
|
|
178
|
+
await ctx.router.handle(stripped.clean, {
|
|
179
|
+
userId: msg.userId,
|
|
180
|
+
chatId: msg.chatId,
|
|
181
|
+
messageId: msg.messageId,
|
|
182
|
+
}, { allowCommandPrefix: true });
|
|
183
|
+
})().catch((err) => ctx.logger.error('[control] command failed:', err));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
ctx.assembler.ingest({
|
|
187
|
+
kind: 'text',
|
|
188
|
+
userId: msg.userId,
|
|
189
|
+
chatId: msg.chatId,
|
|
190
|
+
messageId: msg.messageId,
|
|
191
|
+
replyToMessageId: msg.replyToMessageId,
|
|
192
|
+
rawContentType: msg.rawContentType,
|
|
193
|
+
text: stripped.clean,
|
|
194
|
+
placeholders: stripped.kinds,
|
|
195
|
+
unknownTags: stripped.unknownTags,
|
|
201
196
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
197
|
+
}
|
|
198
|
+
/** 卡片动作:鉴权 → stop/直返/order.exec → immediate 或普通队列。 */
|
|
199
|
+
async function handleCardAction(ctx, action) {
|
|
200
|
+
if (!ctx.binder.isOwner(action.operator.openId)) {
|
|
201
|
+
ctx.binder.recordRejectedCardAction(action.operator.openId);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const actionValue = action.action.value;
|
|
205
|
+
if (!actionValue?.cmd)
|
|
206
|
+
return;
|
|
207
|
+
ctx.logger.info(actionValue.cmd === 'stop'
|
|
208
|
+
? `card action: stop runId=${actionValue.runId ?? '(none)'} messageId=${action.messageId}`
|
|
209
|
+
: `card action: ${actionValue.cmd}`);
|
|
210
|
+
const userId = action.operator.openId;
|
|
211
|
+
const chatId = action.chatId;
|
|
212
|
+
const messageId = action.messageId;
|
|
213
|
+
void ctx.connector.addReaction(messageId, 'Typing');
|
|
214
|
+
if (actionValue.cmd === 'stop') {
|
|
215
|
+
void ctx.router
|
|
216
|
+
.handleCardAction({ ...actionValue, cmd: 'stop' }, { userId, chatId, messageId })
|
|
217
|
+
.catch((err) => ctx.logger.error('[control] card stop failed:', err));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const isImmediate = isImmediateAction(actionValue.cmd);
|
|
221
|
+
const cwd = ctx.sessionStore.getCwd(userId) ?? '';
|
|
222
|
+
const fullValue = buildCardActionFullValue(actionValue, action);
|
|
223
|
+
if (SESSION_MUTATING_ACTION_CMDS.has(actionValue.cmd)) {
|
|
224
|
+
const flushP = ctx.assembler
|
|
225
|
+
.flush(userId, chatId, 'flush')
|
|
226
|
+
.catch((err) => ctx.logger.error('[control] inbound flush failed:', err));
|
|
227
|
+
await Promise.race([flushP, sleep(SESSION_MUTATING_FLUSH_BUDGET_MS)]);
|
|
228
|
+
}
|
|
229
|
+
if (DIRECT_RETURN_CMDS.has(actionValue.cmd)) {
|
|
230
|
+
return ctx.router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
231
|
+
}
|
|
232
|
+
if (actionValue.cmd === 'order.exec') {
|
|
233
|
+
void dispatchOrderExecForQueue({
|
|
234
|
+
router: ctx.router,
|
|
235
|
+
bridge: ctx.bridge,
|
|
236
|
+
cwd,
|
|
237
|
+
orderId: actionValue.orderId,
|
|
238
|
+
ctx: { userId, chatId, messageId },
|
|
239
|
+
}).catch((err) => ctx.logger.error('[control] order.exec dispatch failed:', err));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const forwardActionFeedback = (res) => {
|
|
243
|
+
const text = actionFeedbackText(res);
|
|
244
|
+
if (!text)
|
|
243
245
|
return;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
246
|
+
void ctx.bridge
|
|
247
|
+
.sendResult({ text }, { userId, chatId, messageId })
|
|
248
|
+
.catch((err) => ctx.logger.error('[control] card action feedback failed:', err));
|
|
249
|
+
};
|
|
250
|
+
if (isImmediate) {
|
|
251
|
+
ctx.bridge.enqueueImmediate(cwd, async () => {
|
|
252
|
+
const res = await ctx.router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
253
|
+
forwardActionFeedback(res);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
ctx.bridge.enqueue(cwd, async () => {
|
|
258
|
+
const res = await ctx.router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
259
|
+
forwardActionFeedback(res);
|
|
260
|
+
}, {
|
|
261
|
+
taskMeta: {
|
|
262
|
+
userId,
|
|
263
|
+
chatId,
|
|
264
|
+
messageId: ctx.router.mintQueueTaskKey('card', actionValue.cmd),
|
|
265
|
+
feishuReplyTo: messageId,
|
|
266
|
+
messagePreview: `card action: ${actionValue.cmd}`,
|
|
267
|
+
kind: COMPACT_ACTION_CMDS.has(actionValue.cmd) ? 'compact' : 'command',
|
|
268
|
+
editable: !COMPACT_ACTION_CMDS.has(actionValue.cmd),
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
export function setupMessageHandlers(deps) {
|
|
274
|
+
// ctx 在 assembler/authorizer 创建后填充;转发箭头只在事件触发时读它。
|
|
275
|
+
// 所有决策逻辑都在模块级 handler 中,本函数只做注册,圈复杂度保持最低。
|
|
276
|
+
// 循环初始化:两个实例的回调捕获 ctx,而 ctx 又需要这两个实例,无法在
|
|
277
|
+
// 声明处一次性初始化;回调只在事件到达时执行,那时填充早已完成。
|
|
278
|
+
// eslint-disable-next-line prefer-const
|
|
279
|
+
let ctx;
|
|
280
|
+
const assembler = new InboundTurnAssembler({
|
|
281
|
+
onCommit: (turn, prompt) => handleTurnCommit(ctx, turn, prompt),
|
|
282
|
+
onReceipt: (receiptCtx, text) => handleTurnReceipt(ctx, receiptCtx, text),
|
|
283
|
+
});
|
|
284
|
+
deps.router.setInboundFlusher(() => assembler.flushAll('flush'));
|
|
285
|
+
const authorizer = new InboundAuthorizer({
|
|
286
|
+
binder: deps.binder,
|
|
287
|
+
onBind: (userId, chatId) => handleBind(ctx, userId, chatId),
|
|
275
288
|
});
|
|
289
|
+
ctx = { ...deps, assembler, authorizer };
|
|
290
|
+
deps.connector.setInboundMediaDetectedHandler((msg) => handleInboundMedia(ctx, msg));
|
|
291
|
+
deps.connector.setMessageHandler((msg) => handleInboundMessage(ctx, msg));
|
|
292
|
+
deps.connector.setCardActionHandler((action) => handleCardAction(ctx, action));
|
|
276
293
|
}
|
|
277
294
|
//# sourceMappingURL=message-handlers.js.map
|
|
@@ -6,4 +6,7 @@ export interface LsCommandOptions {
|
|
|
6
6
|
rootDir?: string;
|
|
7
7
|
q?: string;
|
|
8
8
|
}
|
|
9
|
+
/** `...(filter ? { q: filter } : {})` 单源:q 恒为最后一个字段。
|
|
10
|
+
* ls / ws 两个命令共用(ws.ts 从本模块 import),避免同形实现两处维护。 */
|
|
11
|
+
export declare function withQuery(value: Record<string, unknown>, filter: string | undefined): Record<string, unknown>;
|
|
9
12
|
export declare function buildLsCommand(args: string[], _ctx: CommandContext, options: LsCommandOptions): CommandResult;
|