feat: bind bot chats to existing harness sessions

This commit is contained in:
xmanrui 2026-08-18 02:59:29 +08:00
parent 76d5430f1f
commit 1af8610cd5
21 changed files with 1807 additions and 130 deletions

View file

@ -17,6 +17,7 @@ const HELP_TEXT = [
'/workspace 工作区绝对路径 切换工作区',
'/workspacelist 列出工作区绝对路径',
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
'/session Session ID 将当前聊天绑定到指定会话',
'/status 检查连接状态',
'/help 显示本帮助',
].join('\n');
@ -218,7 +219,7 @@ export class DingtalkHarnessBridge {
await this.#send(sessionWebhook, '已开启新会话。请发送你的问题。');
return;
}
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
const workspaceCommand = await runWorkspaceCommand(text, this.#harness, key);
if (workspaceCommand) {
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
await this.#send(sessionWebhook, reply);

View file

@ -2,6 +2,8 @@ import { spawn } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { isAbsolute } from 'node:path';
import { adoptRegisteredWorkspaceSession } from '../shared/harness-session-binding.mjs';
function workspacePaths(value) {
if (!Array.isArray(value?.items)) return [];
return value.items.flatMap((item) => (
@ -279,6 +281,10 @@ export class HarnessClient {
return workspaceSessions(workspace, workspaceList.archivedSessionIds, sessionList);
}
async adoptWorkspaceSession(value, options = {}) {
return adoptRegisteredWorkspaceSession(this, value, options);
}
async workspaceId(options = {}) {
const { workspace = this.#workspace, ...rpcOptions } = options;
const { items } = await this.rpc('workspace.list', {}, 30_000, rpcOptions);

View file

@ -16,6 +16,7 @@ const HELP_TEXT = [
'/workspace 工作区绝对路径 切换工作区',
'/workspacelist 列出工作区绝对路径',
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
'/session Session ID 将当前聊天绑定到指定会话',
'/status 检查连接状态',
'/help 显示本帮助',
].join('\n');
@ -111,7 +112,7 @@ export class FeishuHarnessBridge {
await this.#send(event.message.chat_id, '飞书机器人与 DeepSeek Harness 连接正常。');
return;
}
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
const workspaceCommand = await runWorkspaceCommand(text, this.#harness, key);
if (workspaceCommand) {
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
await this.#send(event.message.chat_id, reply);

View file

@ -2,6 +2,8 @@ import { spawn } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { isAbsolute } from 'node:path';
import { adoptRegisteredWorkspaceSession } from '../shared/harness-session-binding.mjs';
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function workspacePaths(value) {
@ -260,6 +262,10 @@ export class HarnessClient {
return workspaceSessions(workspace, workspaceList.archivedSessionIds, sessionList);
}
async adoptWorkspaceSession(value, options = {}) {
return adoptRegisteredWorkspaceSession(this, value, options, 30000);
}
async workspaceId(options = {}) {
const workspace = options.workspace ?? this.#workspace;
const { items } = await this.rpc('workspace.list', {});

View file

@ -9,6 +9,7 @@ const HELP_TEXT = [
'/workspace 工作区绝对路径 切换工作区',
'/workspacelist 列出工作区绝对路径',
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
'/session Session ID 将当前聊天绑定到指定会话',
'/status 检查连接状态',
'/help 显示本帮助',
].join('\n');
@ -127,7 +128,7 @@ export class QqHarnessBridge {
await this.#state.markSeen(messageId);
return;
}
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
const workspaceCommand = await runWorkspaceCommand(text, this.#harness, key);
if (workspaceCommand) {
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
await this.#bot.sendText(target, reply);

View file

@ -1,10 +1,37 @@
import { mkdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
import {
mkdir,
readFile,
realpath,
rename,
stat,
unlink,
writeFile,
} from 'node:fs/promises';
import { dirname, isAbsolute, resolve } from 'node:path';
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
const EMPTY_DOCUMENT = Object.freeze({ version: 1, workspaces: Object.freeze({}) });
function workspaceSessionStale(message) {
const error = new Error(message);
error.code = WORKSPACE_SESSION_STALE;
return error;
}
async function canonicalWorkspacePath(value) {
return resolve(await realpath(value));
}
async function sameWorkspacePath(left, right) {
if (left === right) return true;
try {
return await canonicalWorkspacePath(left) === await canonicalWorkspacePath(right);
} catch {
return false;
}
}
function botIdOf(value) {
if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(value)) {
throw new TypeError('Invalid bot id');
@ -179,6 +206,72 @@ export class BotWorkspaceStore {
});
}
async bindWorkspaceSession(botId, value, {
conversationKey,
sessionId,
clearSessions,
setSession,
incarnation,
expectedGeneration,
} = {}) {
const id = botIdOf(botId);
if (typeof conversationKey !== 'string' || !conversationKey
|| typeof sessionId !== 'string' || !sessionId) {
throw new TypeError('conversationKey and sessionId are required');
}
if (typeof clearSessions !== 'function' || typeof setSession !== 'function') {
throw new TypeError('session state callbacks are required');
}
if (!this.has(id)
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
const error = new Error('找不到要修改的机器人。');
error.code = 'workspace-bot-not-found';
throw error;
}
const workspace = await canonicalWorkspacePath(await validateWorkspacePath(value));
return this.#enqueue(id, async () => {
if (!this.has(id)
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
const error = new Error('找不到要修改的机器人。');
error.code = 'workspace-bot-not-found';
throw error;
}
if (expectedGeneration !== undefined
&& expectedGeneration !== this.generationFor(id)) {
throw workspaceSessionStale(
'The bot workspace changed before the session binding could be committed.',
);
}
if (!(await sameWorkspacePath(workspace, this.workspaceFor(id)))) {
const previous = this.#workspaces[id];
// Fence every session resolved before this transition, then remove
// the old workspace mappings before publishing the new workspace.
this.#generations.set(id, this.#freshGeneration());
await clearSessions();
this.#workspaces[id] = workspace;
try {
await this.#persist();
} catch (error) {
// Session mappings stay cleared and the advanced generation stays
// fenced. Restoring either could pair an old session with a state
// transition whose durable outcome is unknown.
this.#workspaces[id] = previous;
throw error;
}
}
// This write remains inside the same bot transition as the workspace
// mutation, so another switch or bind cannot interleave between them.
await setSession(conversationKey, sessionId);
return {
workspace,
sessionId,
generation: this.#generations.get(id),
};
});
}
async invalidateSessions(botId, { clearSessions } = {}) {
const id = botIdOf(botId);
return this.#enqueue(id, async () => {
@ -447,6 +540,62 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
});
};
}
if (property === 'bindWorkspaceSession') {
return async (conversationKey, sessionId) => {
if (typeof conversationKey !== 'string' || !conversationKey
|| typeof sessionId !== 'string' || !sessionId) {
throw new TypeError('conversationKey and sessionId are required');
}
if (!isCurrentScope()) {
const error = new Error('找不到要修改的机器人。');
error.code = 'workspace-bot-not-found';
throw error;
}
if (typeof target.adoptWorkspaceSession !== 'function') {
throw new TypeError('Harness does not support adopting workspace sessions');
}
const expectedGeneration = workspaces.generationFor(botId);
const adopted = await target.adoptWorkspaceSession(sessionId);
if (!isCurrentScope()) {
const error = new Error('找不到要修改的机器人。');
error.code = 'workspace-bot-not-found';
throw error;
}
if (expectedGeneration !== workspaces.generationFor(botId)) {
throw workspaceSessionStale(
'The bot workspace changed while the session was being adopted.',
);
}
if (!adopted || typeof adopted !== 'object'
|| adopted.sessionId !== sessionId || typeof adopted.workspace !== 'string') {
throw new TypeError('Harness returned an invalid adopted workspace session');
}
const bound = await workspaces.bindWorkspaceSession(botId, adopted.workspace, {
conversationKey,
sessionId,
clearSessions: () => state.clearSessions(),
setSession: (key, selectedSessionId) => state.setSession(key, selectedSessionId),
incarnation,
expectedGeneration,
});
if (!isCurrentScope()) {
const error = new Error('找不到要修改的机器人。');
error.code = 'workspace-bot-not-found';
throw error;
}
if (bound.generation !== workspaces.generationFor(botId)) {
throw workspaceSessionStale(
'The bot workspace changed before the session binding completed.',
);
}
sessionGenerations.set(sessionId, bound.generation);
return {
...adopted,
workspace: bound.workspace,
sessionId: bound.sessionId,
};
};
}
if (property === 'createSession') {
return async (options = {}) => {
await workspaces.whenBotIdle(botId);
@ -464,6 +613,37 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
return sessionId;
};
}
if (property === 'workspaceSession') {
return (sessionId) => {
if (typeof sessionId !== 'string' || !sessionId) {
throw new TypeError('sessionId is required');
}
const generation = sessionGenerations.get(sessionId)
?? workspaces.generationFor(botId);
// Transfer the mutable provenance entry into this immutable handle.
// A later handle for the same id captures its own generation instead
// of sharing deletion or rebinding state with this call.
sessionGenerations.delete(sessionId);
const isCurrentSession = () => isCurrentScope()
&& generation === workspaces.generationFor(botId);
return Object.freeze({
sessionId,
async sessionExists(...args) {
if (!isCurrentSession()) return false;
const exists = await target.sessionExists(sessionId, ...args);
return isCurrentSession() && exists;
},
ask(...args) {
if (!isCurrentSession()) {
throw workspaceSessionStale(
'The bot workspace changed before this prompt started.',
);
}
return target.ask(sessionId, ...args);
},
});
};
}
if (property === 'sessionExists') {
return (sessionId, ...args) => {
if (!isCurrentScope()) return false;

View file

@ -0,0 +1,110 @@
import { isAbsolute } from 'node:path';
const MAX_SESSION_ID_LENGTH = 256;
const UNSAFE_SESSION_ID = /[\p{White_Space}\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
const UNSAFE_WORKSPACE_PATH = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
function bindingError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
function validatedSessionId(value) {
if (typeof value !== 'string' || !value || value.length > MAX_SESSION_ID_LENGTH
|| UNSAFE_SESSION_ID.test(value)) {
throw bindingError('session-id-invalid', 'A non-empty, safe session id is required');
}
return value;
}
function sessionWorkspace(sessionId, value) {
if (!Array.isArray(value?.items) || !Array.isArray(value?.archivedSessionIds)
|| value.archivedSessionIds.some((id) => typeof id !== 'string' || !id)) {
throw new Error('Harness returned an invalid response for workspace.list');
}
const owners = [];
for (const workspace of value.items) {
if (typeof workspace?.workspaceId !== 'string' || !workspace.workspaceId
|| typeof workspace.path !== 'string' || !isAbsolute(workspace.path)
|| !Array.isArray(workspace.sessionIds)
|| workspace.sessionIds.some((id) => typeof id !== 'string' || !id)) {
throw new Error('Harness returned an invalid response for workspace.list');
}
for (const accountedId of workspace.sessionIds) {
if (accountedId === sessionId) owners.push(workspace);
}
}
if (owners.length === 0) {
throw bindingError('session-not-registered', 'The session is not registered to a Harness workspace');
}
if (owners.length !== 1) {
throw bindingError(
'session-workspace-ambiguous',
'The session is registered to more than one Harness workspace',
);
}
if (UNSAFE_WORKSPACE_PATH.test(owners[0].path)) {
throw new Error('Harness returned an unsafe workspace path for the session');
}
return {
workspace: owners[0],
archived: value.archivedSessionIds.includes(sessionId),
};
}
function sessionSummary(sessionId, value) {
if (!Array.isArray(value?.items)
|| value.items.some((item) => typeof item?.sessionId !== 'string' || !item.sessionId)) {
throw new Error('Harness returned an invalid response for session.list');
}
const matches = value.items.filter((item) => item.sessionId === sessionId);
if (matches.length === 0) {
throw bindingError('session-summary-unavailable', 'The session is no longer available from Harness');
}
if (matches.length !== 1) {
throw new Error('Harness returned duplicate session summaries for session.list');
}
const [summary] = matches;
if (summary.origin === 'subagent') {
throw bindingError(
'session-subagent-unsupported',
'Subagent sessions cannot be adopted as a bot conversation',
);
}
if (summary.origin !== undefined) {
throw new Error('Harness returned an invalid session origin for session.list');
}
const title = summary.projections?.values?.title;
if (title !== undefined && title !== null && typeof title !== 'string') {
throw new Error('Harness returned an invalid session title for session.list');
}
return { title: typeof title === 'string' ? title : null };
}
export async function adoptRegisteredWorkspaceSession(client, value, options = {}, timeoutMs = 30_000) {
const sessionId = validatedSessionId(value);
await client.ensureRunning(options);
const workspaceList = await client.rpc('workspace.list', {}, timeoutMs, options);
const { workspace, archived } = sessionWorkspace(sessionId, workspaceList);
const summary = sessionSummary(
sessionId,
await client.rpc('session.list', {}, timeoutMs, options),
);
const adopted = await client.rpc('session.create', {
workspaceId: workspace.workspaceId,
sessionId,
}, timeoutMs, options);
if (!adopted || adopted.sessionId !== sessionId) {
throw new Error('Harness returned an invalid response for session.create');
}
return {
sessionId,
workspace: workspace.path,
title: summary.title,
archived,
};
}

View file

@ -103,6 +103,7 @@ export class TextHarnessBridge {
'/workspace 工作区绝对路径 切换工作区',
'/workspacelist 列出工作区绝对路径',
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
'/session Session ID 将当前聊天绑定到指定会话',
'/status 检查连接状态',
'/help 显示本帮助',
].join('\n'));
@ -115,7 +116,8 @@ export class TextHarnessBridge {
await this.#state.markSeen(messageId);
return;
}
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
const conversationKey = `${message.kind}:${message.conversationId}`;
const workspaceCommand = await runWorkspaceCommand(text, this.#harness, conversationKey);
if (workspaceCommand) {
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
await this.#bot.sendText(target, reply);
@ -123,7 +125,6 @@ export class TextHarnessBridge {
await this.#state.markSeen(messageId);
return;
}
const conversationKey = `${message.kind}:${message.conversationId}`;
if (command === '/new') {
await this.#state.clearSession(conversationKey);
await this.#bot.sendText(target, '已开启新会话。请发送你的问题。');

View file

@ -1,13 +1,19 @@
import { realpath, stat } from 'node:fs/promises';
import { isAbsolute, resolve } from 'node:path';
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
const WORKSPACE_COMMAND = /^\/workspace(?:\s+([\s\S]+))?$/i;
const WORKSPACE_LIST_COMMAND = /^\/workspacelist(?:\s+([\s\S]+))?$/i;
const SESSION_LIST_COMMAND = /^\/sessionlist(?:\s+([\s\S]+))?$/i;
const SESSION_BIND_PREFIX = /^\/session(?=$|\s)/i;
const SESSION_BIND_COMMAND = /^\/session[ \t]+([^\s]+)$/i;
const MAX_WORKSPACE_PATH_LENGTH = 4_096;
const MAX_COMMAND_MESSAGE_LENGTH = 1_800;
const MAX_SESSION_ID_LENGTH = 256;
const UNSAFE_DISPLAY_TEXT = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
const UNSAFE_DISPLAY_TEXT_GLOBAL = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]+/gu;
const SESSION_BIND_USAGE = '用法:/session Session ID';
const SESSION_LIST_USAGE = [
'用法:',
'/sessionlist 列出当前工作区会话',
@ -30,6 +36,14 @@ function safeDisplayText(value) {
return value.replace(UNSAFE_DISPLAY_TEXT_GLOBAL, ' ').replace(/\s+/gu, ' ').trim();
}
function validSessionId(value) {
return typeof value === 'string'
&& value.length > 0
&& value.length <= MAX_SESSION_ID_LENGTH
&& !/\p{White_Space}/u.test(value)
&& !UNSAFE_DISPLAY_TEXT.test(value);
}
async function existingWorkspacePaths(values) {
const checked = await Promise.all(values.map(async (value) => {
const workspace = normalizedWorkspacePath(value);
@ -176,6 +190,8 @@ function sessionListMessage(workspace, sessions) {
`会话(${rows.length}):`,
'',
...rows.map((row, index) => `${index + 1}. ${row}`),
'',
'绑定用法:/session Session ID',
].join('\n');
}
@ -203,9 +219,70 @@ async function runSessionListCommand(match, harness) {
}
}
export async function runWorkspaceCommand(text, harness) {
function sessionBindErrorMessage(error) {
if (error?.code === 'session-id-invalid') {
return `Session ID 格式无效。\n${SESSION_BIND_USAGE}`;
}
if (['session-not-registered', 'session-not-found'].includes(error?.code)) {
return '未找到该会话,请先执行 /sessionlist 确认 Session ID。';
}
if (error?.code === 'session-subagent-unsupported') {
return '子代理会话不能绑定到机器人对话,请选择普通会话。';
}
if (error?.code === 'session-workspace-ambiguous') {
return '该会话的工作区归属不明确,暂时无法绑定。';
}
if (error?.code === 'session-summary-unavailable') {
return '暂时无法读取该会话的信息,请稍后重试。';
}
if (error?.code === 'workspace-bot-not-found') {
return '机器人正在移除或已重新接入,无法绑定原对话的会话。';
}
if ([WORKSPACE_SESSION_STALE, 'agent-busy', 'session-conflict', 'workspace-conflict']
.includes(error?.code)) {
return '工作区或会话状态已发生变化,请重试。';
}
return '暂时无法绑定会话,请稍后重试。';
}
async function runSessionBindCommand(command, harness, conversationKey) {
const match = SESSION_BIND_COMMAND.exec(command);
const sessionId = match?.[1];
if (!validSessionId(sessionId)) return commandResult(SESSION_BIND_USAGE);
if (typeof harness?.bindWorkspaceSession !== 'function') {
return commandResult('当前机器人暂不支持绑定已有会话。');
}
if (typeof conversationKey !== 'string' || !conversationKey) {
return commandResult('当前消息缺少可绑定的会话上下文。');
}
try {
const bound = await harness.bindWorkspaceSession(conversationKey, sessionId);
harness.assertWorkspaceScope?.();
const workspace = normalizedWorkspacePath(bound?.workspace);
const boundSessionId = safeDisplayText(bound?.sessionId);
if (!workspace || !boundSessionId) {
throw new TypeError('Harness returned an invalid bound session');
}
const title = safeDisplayText(bound?.title) || '暂无标题';
const message = [
'当前聊天已绑定会话:',
`工作区:${workspace}`,
`标题:${title}`,
`ID:${boundSessionId}`,
`归档:${bound?.archived === true ? '是' : '否'}`,
].join('\n');
return commandResult(message, splitWorkspaceCommandMessage(message));
} catch (error) {
return commandResult(sessionBindErrorMessage(error));
}
}
export async function runWorkspaceCommand(text, harness, conversationKey) {
if (typeof text !== 'string') return null;
const command = text.trim();
if (SESSION_BIND_PREFIX.test(command)) {
return runSessionBindCommand(command, harness, conversationKey);
}
const sessionListMatch = SESSION_LIST_COMMAND.exec(command);
if (sessionListMatch) return runSessionListCommand(sessionListMatch, harness);
const listMatch = WORKSPACE_LIST_COMMAND.exec(command);

View file

@ -1,9 +1,20 @@
export const WORKSPACE_SESSION_STALE = 'workspace-session-stale';
async function sessionExists(harness, sessionId, options) {
function workspaceSession(harness, sessionId) {
if (typeof harness.workspaceSession === 'function') {
return harness.workspaceSession(sessionId);
}
return Object.freeze({
sessionId,
sessionExists: (...args) => harness.sessionExists(sessionId, ...args),
ask: (...args) => harness.ask(sessionId, ...args),
});
}
async function sessionExists(session, options) {
return options === undefined
? harness.sessionExists(sessionId)
: harness.sessionExists(sessionId, options);
? session.sessionExists()
: session.sessionExists(options);
}
async function createSession(harness, options) {
@ -27,15 +38,17 @@ export async function askInWorkspaceSession({
askOptions,
}) {
while (true) {
let sessionId = state.sessionFor(key);
if (!sessionId || !(await sessionExists(harness, sessionId, existsOptions))) {
sessionId = await createSession(harness, createOptions);
if (await state.setSession(key, sessionId) === false) continue;
}
try {
let sessionId = state.sessionFor(key);
let session = sessionId ? workspaceSession(harness, sessionId) : null;
if (!session || !(await sessionExists(session, existsOptions))) {
sessionId = await createSession(harness, createOptions);
if (await state.setSession(key, sessionId) === false) continue;
session = workspaceSession(harness, sessionId);
}
return {
sessionId,
answer: await harness.ask(sessionId, text, askOptions),
answer: await session.ask(text, askOptions),
};
} catch (error) {
if (error?.code !== WORKSPACE_SESSION_STALE) throw error;

View file

@ -10,6 +10,7 @@ const HELP_TEXT = [
'/workspace 工作区绝对路径 切换工作区',
'/workspacelist 列出工作区绝对路径',
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
'/session Session ID 将当前聊天绑定到指定会话',
'/status 检查连接状态',
'/help 显示本帮助',
].join('\n');
@ -187,7 +188,7 @@ export class WecomHarnessBridge {
await this.#state.markSeen(messageId);
return;
}
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
const workspaceCommand = await runWorkspaceCommand(text, this.#harness, key);
if (workspaceCommand) {
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
await this.#sendImmediate(frame, chatId, reply);

View file

@ -2,6 +2,8 @@ import { spawn } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { isAbsolute } from 'node:path';
import { adoptRegisteredWorkspaceSession } from '../shared/harness-session-binding.mjs';
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function workspacePaths(value) {
@ -250,6 +252,10 @@ export class HarnessClient {
return workspaceSessions(workspace, workspaceList.archivedSessionIds, sessionList);
}
async adoptWorkspaceSession(value, options = {}) {
return adoptRegisteredWorkspaceSession(this, value, options);
}
async workspaceId(options = {}) {
const workspace = options.workspace ?? this.#workspace;
const { items } = await this.rpc('workspace.list', {});

View file

@ -14,6 +14,7 @@ const HELP_TEXT = [
'/workspace 工作区绝对路径 切换工作区',
'/workspacelist 列出工作区绝对路径',
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
'/session Session ID 将当前聊天绑定到指定会话',
'/status 检查连接状态',
'/help 显示本帮助',
].join('\n');
@ -121,6 +122,7 @@ export class WeixinHarnessBridge {
}
const command = text.trim().toLowerCase();
const key = conversationKey(sender);
if (command === '/help') {
await this.#send(sender, HELP_TEXT, contextToken, runId);
await this.#state.markSeen(messageId);
@ -133,12 +135,12 @@ export class WeixinHarnessBridge {
return;
}
if (command === '/new') {
await this.#state.clearSession(conversationKey(sender));
await this.#state.clearSession(key);
await this.#send(sender, '已开启新会话。请发送你的问题。', contextToken, runId);
await this.#state.markSeen(messageId);
return;
}
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
const workspaceCommand = await runWorkspaceCommand(text, this.#harness, key);
if (workspaceCommand) {
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
await this.#send(sender, reply, contextToken, runId);
@ -147,7 +149,6 @@ export class WeixinHarnessBridge {
return;
}
const key = conversationKey(sender);
const { answer } = await askInWorkspaceSession({
harness: this.#harness,
state: this.#state,