feat: support per-bot workspace switching

This commit is contained in:
xmanrui 2026-08-17 23:56:27 +08:00
parent adcace3512
commit 44dd50216a
64 changed files with 3878 additions and 712 deletions

View file

@ -5,12 +5,15 @@ import {
isBotSender,
splitText,
} from './message-utils.mjs';
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
const HELP_TEXT = [
'北汇星河 AIOS 已连接 DeepSeek Harness。',
'',
'直接发送问题即可继续当前会话。',
'/new 开启一个全新会话',
'/workspace 工作区绝对路径 切换工作区',
'/status 检查连接状态',
'/help 显示本帮助',
].join('\n');
@ -106,25 +109,30 @@ export class FeishuHarnessBridge {
await this.#send(event.message.chat_id, '飞书机器人与 DeepSeek Harness 连接正常。');
return;
}
let sessionId = this.#state.sessionFor(key);
if (!sessionId || !(await this.#harness.sessionExists(sessionId))) {
sessionId = await this.#harness.createSession();
await this.#state.setSession(key, sessionId);
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
if (workspaceCommand) {
await this.#send(event.message.chat_id, workspaceCommand.message);
return;
}
console.info(`[bridge] processing ${event.message.chat_type} message ${messageId} in ${sessionId}`);
await this.#answerWithStream(event, sessionId, text);
console.info(`[bridge] processing ${event.message.chat_type} message ${messageId}`);
await this.#answerWithStream(event, key, text);
this.#status.messagesReplied += 1;
this.#status.lastReplyAt = new Date().toISOString();
this.#status.lastError = null;
}
async #answerWithStream(event, sessionId, text) {
async #answerWithStream(event, key, text) {
const chatId = event.message.chat_id;
const messageId = event.message.message_id;
if (!this.#channel?.stream) {
const answer = await this.#harness.ask(sessionId, text, { timeoutMs: this.#replyTimeoutMs });
const { answer } = await askInWorkspaceSession({
harness: this.#harness,
state: this.#state,
key,
text,
askOptions: { timeoutMs: this.#replyTimeoutMs },
});
for (const chunk of splitText(answer)) await this.#send(chatId, chunk);
this.#status.streamFallbacks = (this.#status.streamFallbacks ?? 0) + 1;
return;
@ -136,13 +144,19 @@ export class FeishuHarnessBridge {
await this.#channel.stream(chatId, {
markdown: async (controller) => {
promptStarted = true;
completedAnswer = await this.#harness.ask(sessionId, text, {
timeoutMs: this.#replyTimeoutMs,
onUpdate: async (update) => {
await controller.setContent(this.#progressText(update));
this.#status.streamUpdates = (this.#status.streamUpdates ?? 0) + 1;
({ answer: completedAnswer } = await askInWorkspaceSession({
harness: this.#harness,
state: this.#state,
key,
text,
askOptions: {
timeoutMs: this.#replyTimeoutMs,
onUpdate: async (update) => {
await controller.setContent(this.#progressText(update));
this.#status.streamUpdates = (this.#status.streamUpdates ?? 0) + 1;
},
},
});
}));
await controller.setContent(completedAnswer);
},
}, { replyTo: messageId });
@ -158,7 +172,13 @@ export class FeishuHarnessBridge {
if (promptStarted) throw error;
console.warn('[bridge] native Feishu stream unavailable; using text fallback:', error.message);
const answer = await this.#harness.ask(sessionId, text, { timeoutMs: this.#replyTimeoutMs });
const { answer } = await askInWorkspaceSession({
harness: this.#harness,
state: this.#state,
key,
text,
askOptions: { timeoutMs: this.#replyTimeoutMs },
});
for (const chunk of splitText(answer)) await this.#send(chatId, chunk);
this.#status.streamFallbacks = (this.#status.streamFallbacks ?? 0) + 1;
}

View file

@ -197,17 +197,18 @@ export class HarnessClient {
throw new Error(`Harness did not become ready: ${lastError?.message ?? 'timeout'}`);
}
async workspaceId() {
async workspaceId(options = {}) {
const workspace = options.workspace ?? this.#workspace;
const { items } = await this.rpc('workspace.list', {});
const existing = items.find((item) => item.path === this.#workspace);
const existing = items.find((item) => item.path === workspace);
if (existing) return existing.workspaceId;
const created = await this.rpc('workspace.create', { path: this.#workspace });
const created = await this.rpc('workspace.create', { path: workspace });
return created.workspace.workspaceId;
}
async createSession() {
async createSession(options = {}) {
await this.ensureRunning();
const workspaceId = await this.workspaceId();
const workspaceId = await this.workspaceId(options);
const created = await this.rpc('session.create', {
workspaceId,
agentPreset: this.#agentPreset,

View file

@ -41,6 +41,11 @@ export class StateStore {
await this.#persist();
}
async clearSessions() {
this.#state.sessions = {};
await this.#persist();
}
hasSeen(messageId) {
return this.#state.seenMessageIds.includes(messageId);
}