mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-16 00:13:48 +08:00
feat: add model and turn control commands
This commit is contained in:
parent
fc8f120e13
commit
99ce38e9a4
38 changed files with 3451 additions and 169 deletions
|
|
@ -6,6 +6,14 @@ import {
|
|||
} from '../shared/harness-question.mjs';
|
||||
import { HarnessApprovalQueue } from '../shared/harness-approval.mjs';
|
||||
import { runCompactCommand } from '../shared/compact-command.mjs';
|
||||
import {
|
||||
isControlCommand,
|
||||
runControlCommand,
|
||||
} from '../shared/control-command.mjs';
|
||||
import {
|
||||
isModelCommand,
|
||||
runModelCommand,
|
||||
} from '../shared/model-command.mjs';
|
||||
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
||||
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
||||
import {
|
||||
|
|
@ -26,6 +34,10 @@ const HELP_TEXT = [
|
|||
'/workspacelist 列出工作区绝对路径',
|
||||
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
||||
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
||||
'/models 列出所有可用模型',
|
||||
'/model [模型ID] 查看或切换当前会话模型',
|
||||
'/stop 停止当前任务',
|
||||
'/steer 补充指令 纠偏当前任务',
|
||||
'/status 检查连接状态',
|
||||
'/help 显示本帮助',
|
||||
].join('\n');
|
||||
|
|
@ -223,6 +235,7 @@ export class WecomHarnessBridge {
|
|||
#interactionKeys = new Map();
|
||||
#acceptedMessageIds = new Set();
|
||||
#approvalTasks = new Set();
|
||||
#commandTasks = new Set();
|
||||
#approvals;
|
||||
#prefetchedImageCount = 0;
|
||||
|
||||
|
|
@ -274,6 +287,33 @@ export class WecomHarnessBridge {
|
|||
rememberConnectionTestTarget(this.#state, { chatId });
|
||||
}
|
||||
const pending = this.#pendingInteractions.get(key);
|
||||
const commandMessage = wecomInboundMessage(frame, this.#client);
|
||||
const commandText = nonEmptyString(commandMessage.content) ?? '';
|
||||
const commandRunner = isControlCommand(commandText)
|
||||
? runControlCommand
|
||||
: (isModelCommand(commandText) ? runModelCommand : null);
|
||||
if (commandRunner) {
|
||||
let task;
|
||||
task = this.#processFastCommand(
|
||||
frame,
|
||||
messageId,
|
||||
chatId,
|
||||
key,
|
||||
commandMessage,
|
||||
commandRunner,
|
||||
).catch((error) => {
|
||||
if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
|
||||
this.#status.lastError = error?.message ?? String(error);
|
||||
this.#logger.error?.('[dsh-im:wecom] failed to process a command');
|
||||
return this.#sendImmediate(frame, chatId, '消息处理失败,请稍后重试。')
|
||||
.catch(() => undefined);
|
||||
}).finally(() => {
|
||||
this.#acceptedMessageIds.delete(messageId);
|
||||
this.#commandTasks.delete(task);
|
||||
});
|
||||
this.#commandTasks.add(task);
|
||||
return task;
|
||||
}
|
||||
const approval = this.#approvals.claimReply({
|
||||
key,
|
||||
actor: senderId,
|
||||
|
|
@ -377,9 +417,35 @@ export class WecomHarnessBridge {
|
|||
pending.queue ? [pending.queue] : []
|
||||
)),
|
||||
...this.#approvalTasks,
|
||||
...this.#commandTasks,
|
||||
]);
|
||||
}
|
||||
|
||||
async #processFastCommand(frame, messageId, chatId, key, message, runner) {
|
||||
this.#signal?.throwIfAborted();
|
||||
if (this.#state.hasSeen(messageId)) return;
|
||||
await this.#state.markSeen(messageId);
|
||||
this.#status.messagesReceived += 1;
|
||||
this.#status.lastMessageAt = new Date().toISOString();
|
||||
const result = await runner(message.content, this.#harness, this.#state, key, {
|
||||
signal: this.#signal,
|
||||
hasImages: hasInboundImages(message),
|
||||
pendingInteraction: this.#pendingInteractions.has(key)
|
||||
|| this.#approvals.hasPending(key),
|
||||
control: { owner: this, key },
|
||||
});
|
||||
if (result?.stopped) {
|
||||
await Promise.allSettled([
|
||||
this.#cancelPendingInteraction(key),
|
||||
this.#approvals.closeRoute(key),
|
||||
]);
|
||||
}
|
||||
for (const reply of result?.messages ?? [result?.message]) {
|
||||
if (reply) await this.#sendImmediate(frame, chatId, reply);
|
||||
}
|
||||
this.#status.lastError = null;
|
||||
}
|
||||
|
||||
async #sendActive(chatId, text) {
|
||||
for (const chunk of splitUtf8(text)) {
|
||||
this.#signal?.throwIfAborted();
|
||||
|
|
@ -490,6 +556,7 @@ export class WecomHarnessBridge {
|
|||
askOptions: {
|
||||
timeoutMs: this.#replyTimeoutMs,
|
||||
signal: this.#signal,
|
||||
control: { owner: this, key },
|
||||
onUpdate: streamStarted && typeof this.#client.replyStreamNonBlocking === 'function'
|
||||
? async (update) => {
|
||||
const progress = splitUtf8(progressText(update))[0];
|
||||
|
|
@ -525,6 +592,14 @@ export class WecomHarnessBridge {
|
|||
this.#status.lastReplyAt = new Date().toISOString();
|
||||
this.#status.lastError = null;
|
||||
} catch (error) {
|
||||
if (error?.code === 'turn-stopped') {
|
||||
if (streamStarted && streamId) {
|
||||
await this.#client.replyStream(frame, streamId, '已停止。', true)
|
||||
.catch(() => undefined);
|
||||
}
|
||||
await this.#state.markSeen(messageId);
|
||||
return;
|
||||
}
|
||||
if (this.#signal?.aborted) return;
|
||||
this.#status.lastError = error?.message ?? String(error);
|
||||
this.#logger.error?.('[dsh-im:wecom] failed to process an inbound message');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue