mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 12:53:17 +08:00
feat: support limiting session list results
This commit is contained in:
parent
52deb872aa
commit
37ec6abb06
16 changed files with 373 additions and 225 deletions
|
|
@ -82,6 +82,7 @@ const HELP_TEXT_LINES = [
|
|||
'/workspace 工作区序号或绝对路径 切换工作区',
|
||||
'/workspacelist 列出工作区绝对路径',
|
||||
'/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题',
|
||||
'/sessionlist --limit N 仅列出当前工作区前 N 个会话',
|
||||
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
||||
'/models 按序号列出所有可用模型',
|
||||
'/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级',
|
||||
|
|
|
|||
|
|
@ -47,7 +47,12 @@ import {
|
|||
isPresetCommand,
|
||||
runPresetCommand,
|
||||
} from '../shared/preset-command.mjs';
|
||||
import { runWorkspaceCommand, resolveSessionListWorkspace, workspacePathSnapshot } from '../shared/workspace-command.mjs';
|
||||
import {
|
||||
parseSessionListArgument,
|
||||
resolveSessionListWorkspace,
|
||||
runWorkspaceCommand,
|
||||
workspacePathSnapshot,
|
||||
} from '../shared/workspace-command.mjs';
|
||||
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
||||
import { captureContextEnhancement, enhanceContextContent } from '../shared/context-enhancement.mjs';
|
||||
import { deliverOutboundArtifacts } from '../shared/semantic/artifact-delivery.mjs';
|
||||
|
|
@ -160,6 +165,7 @@ const WORKSPACE_HELP_LINES = [
|
|||
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
||||
'/workspacelist 列出工作区绝对路径',
|
||||
'/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题',
|
||||
'/sessionlist --limit N 仅列出当前工作区前 N 个会话',
|
||||
];
|
||||
|
||||
/** Safe user-facing text for bind/workspace failures (no raw messages). */
|
||||
|
|
@ -1085,8 +1091,18 @@ export class FeishuHarnessBridge {
|
|||
return;
|
||||
}
|
||||
if (SESSION_LIST_PREFIX.test(commandText)) {
|
||||
const selector = commandText.replace(/^\/(?:sessionlist|sessions)/i, '').trim() || null;
|
||||
await this.#showSessions({ chatId: event.message.chat_id, key, replyTo: event.message.message_id }, selector, 0);
|
||||
const argument = commandText.replace(/^\/(?:sessionlist|sessions)/i, '').trim();
|
||||
const request = parseSessionListArgument(argument);
|
||||
if (request.error) {
|
||||
await this.#send(event.message.chat_id, request.error, { replyTo: event.message.message_id });
|
||||
return;
|
||||
}
|
||||
await this.#showSessions(
|
||||
{ chatId: event.message.chat_id, key, replyTo: event.message.message_id },
|
||||
request.selector || null,
|
||||
0,
|
||||
{ limit: request.limit },
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (WORKSPACE_LIST_COMMAND.test(commandText)) {
|
||||
|
|
@ -1850,6 +1866,7 @@ export class FeishuHarnessBridge {
|
|||
messageId = null,
|
||||
sessionWorkspace = null,
|
||||
sessionPage = 0,
|
||||
sessionLimit = null,
|
||||
selections = [],
|
||||
}) {
|
||||
// Confirmations triggered by a card interaction stay anchored to the
|
||||
|
|
@ -1861,7 +1878,7 @@ export class FeishuHarnessBridge {
|
|||
{ chatId, key, replyTo: messageId },
|
||||
sessionWorkspace,
|
||||
page,
|
||||
{ updateMessageId: messageId },
|
||||
{ updateMessageId: messageId, limit: sessionLimit },
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -2040,7 +2057,7 @@ export class FeishuHarnessBridge {
|
|||
{ chatId, key, replyTo: messageId },
|
||||
sessionWorkspace,
|
||||
sessionPage,
|
||||
{ updateMessageId: messageId },
|
||||
{ updateMessageId: messageId, limit: sessionLimit },
|
||||
);
|
||||
}
|
||||
return;
|
||||
|
|
@ -2056,7 +2073,7 @@ export class FeishuHarnessBridge {
|
|||
{ chatId, key, replyTo: messageId },
|
||||
sessionWorkspace,
|
||||
sessionPage,
|
||||
{ updateMessageId: messageId },
|
||||
{ updateMessageId: messageId, limit: sessionLimit },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2141,7 +2158,7 @@ export class FeishuHarnessBridge {
|
|||
{ chatId, key, replyTo = null },
|
||||
selector,
|
||||
page = 0,
|
||||
{ updateMessageId = null } = {},
|
||||
{ updateMessageId = null, limit = null } = {},
|
||||
) {
|
||||
try {
|
||||
const signal = this.#cardDataSignal();
|
||||
|
|
@ -2151,7 +2168,11 @@ export class FeishuHarnessBridge {
|
|||
return;
|
||||
}
|
||||
const listed = await this.#harness.listWorkspaceSessions(resolved.workspace, { signal });
|
||||
const sessions = this.#visibleSessions(Array.isArray(listed?.sessions) ? listed.sessions : []);
|
||||
const visibleSessions = this.#visibleSessions(Array.isArray(listed?.sessions) ? listed.sessions : []);
|
||||
const sessionLimit = Number.isSafeInteger(limit) && limit > 0 ? limit : null;
|
||||
const sessions = sessionLimit === null
|
||||
? visibleSessions
|
||||
: visibleSessions.slice(0, sessionLimit);
|
||||
const workspace = listed?.workspace ?? resolved.workspace;
|
||||
if (sessions.length === 0) {
|
||||
await this.#send(chatId, t('工作区:{workspace}\n该工作区暂无会话。', { workspace }), { replyTo });
|
||||
|
|
@ -2178,6 +2199,7 @@ export class FeishuHarnessBridge {
|
|||
// list response's workspace is display data and is not authoritative.
|
||||
sessionWorkspace: resolved.workspace,
|
||||
sessionPage: safePage,
|
||||
sessionLimit,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
@ -2246,6 +2268,9 @@ export class FeishuHarnessBridge {
|
|||
sessionPage: Number.isSafeInteger(options.sessionPage) && options.sessionPage >= 0
|
||||
? options.sessionPage
|
||||
: 0,
|
||||
sessionLimit: Number.isSafeInteger(options.sessionLimit) && options.sessionLimit > 0
|
||||
? options.sessionLimit
|
||||
: null,
|
||||
});
|
||||
if (this.#cardKeys.size > 200) {
|
||||
const oldest = this.#cardKeys.keys().next().value;
|
||||
|
|
|
|||
|
|
@ -488,6 +488,7 @@ export function menuHelpText() {
|
|||
'',
|
||||
'📋 会话 / 工作区',
|
||||
'/sessionlist 或 /sessions 列出工作区会话',
|
||||
'/sessionlist --limit N 仅列出当前工作区前 N 个会话',
|
||||
'/session ID 绑定已有会话',
|
||||
'/workspacelist 列出工作区',
|
||||
'/workspace 工作区序号或绝对路径 切换工作区',
|
||||
|
|
@ -554,6 +555,7 @@ const HELP_TEXT_COMMANDS = [
|
|||
'`/new` — 开启全新会话',
|
||||
'`/session ID` — 绑定已有会话',
|
||||
'`/sessionlist [工作区]` 或 `/sessions [工作区]` — 列出会话',
|
||||
'`/sessionlist --limit N` 或 `/sessions --limit N` — 仅列出当前工作区前 N 个会话',
|
||||
'`/workspace 工作区序号或绝对路径` — 切换工作区',
|
||||
'`/workspacelist` — 列出工作区',
|
||||
'`/status` — 查看连接状态',
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ function helpText() {
|
|||
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
||||
t('/workspacelist 列出工作区绝对路径'),
|
||||
t('/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
||||
t('/sessionlist --limit N 仅列出当前工作区前 N 个会话'),
|
||||
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
||||
t('/models 按序号列出所有可用模型'),
|
||||
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
||||
|
|
|
|||
|
|
@ -246,8 +246,8 @@ export default {
|
|||
'/steer 指令 给 Agent 补充指令': '/steer INSTRUCTION Steer the Agent',
|
||||
'**📋 卡片功能**\n\n1. 会话下拉 — 切换当前绑定会话\n2. 工作区下拉 — 切换工作区\n3. 🤖 预设下拉 — 切换 Agent 预设\n4. 🧠 模型下拉 — 切换模型\n5. 🆕 新会话 — 开启全新会话\n6. 📋 会话/关注 — 查看/绑定会话,管理关注\n7. ⏹ 停止 — 停止当前任务\n8. 📐 压缩 — 压缩当前会话上下文\n9. 补充指令 — 给 Agent 发送指令\n10. 🗄 归档切换 — 显示/隐藏归档会话\n11. 📊 状态 — 查看系统连接状态\n12. 📖 帮助 — 查看本帮助':
|
||||
'**📋 Card features**\n\n1. Session dropdown — switch the bound session\n2. Workspace dropdown — switch workspace\n3. 🤖 Preset dropdown — switch Agent Preset\n4. 🧠 Model dropdown — switch model\n5. 🆕 New session — start fresh\n6. 📋 Sessions/watches — view or bind sessions and manage watches\n7. ⏹ Stop — stop the current task\n8. 📐 Compact — compact the current session context\n9. Steer task — send an instruction to the Agent\n10. 🗄 Archived toggle — show or hide archived sessions\n11. 📊 Status — view connection status\n12. 📖 Help — view this help',
|
||||
'**⌨️ 文本命令**\n\n`/m` — 打开菜单卡片\n`/new` — 开启全新会话\n`/session ID` — 绑定已有会话\n`/sessionlist [工作区]` 或 `/sessions [工作区]` — 列出会话\n`/workspace 工作区序号或绝对路径` — 切换工作区\n`/workspacelist` — 列出工作区\n`/status` — 查看连接状态\n`/compact` — 压缩上下文\n`/stop` — 停止当前任务\n`/steer 指令` — 补充指令\n`/watch ID` — 关注会话\n`/watchlist` — 关注列表\n`/unwatch ID` — 取消关注\n`/archived on/off` — 归档显隐\n`/presetlist` 或 `/presets` — 列出预设\n`/preset [序号/ID]` — 切换预设\n`/preset --default` — 跟随默认\n`/models` — 列出模型\n`/reasoninglist` 或 `/reasonings` — 按序号列出当前模型可用推理等级\n`/reasoning [序号、等级ID或 --default]` — 查看或切换当前推理等级\n`/model [序号或完整模型ID] [推理等级ID]` — 查看或切换当前会话模型\n`/batch` — 开启批量输入(仅私聊,最多 10 条文字)\n`/send` — 提交当前批次\n`/cancel` — 取消当前批次\n`/repair` — 补全飞书权限与卡片回调':
|
||||
'**⌨️ Text commands**\n\n`/m` — open the menu card\n`/new` — start a new session\n`/session ID` — bind an existing session\n`/sessionlist [workspace]` or `/sessions [workspace]` — list sessions\n`/workspace <workspace index or absolute path>` — switch workspace\n`/workspacelist` — list workspaces\n`/status` — view connection status\n`/compact` — compact context\n`/stop` — stop the current task\n`/steer INSTRUCTION` — steer the task\n`/watch ID` — watch a session\n`/watchlist` — list watched sessions\n`/unwatch ID` — stop watching\n`/archived on/off` — show or hide archived sessions\n`/presetlist` or `/presets` — list presets\n`/preset [index/ID]` — switch preset\n`/preset --default` — follow default\n`/models` — list models\n`/reasoninglist` or `/reasonings` — list reasoning efforts for the current model\n`/reasoning [index, effort ID, or --default]` — show or switch reasoning effort\n`/model [index or full model ID] [reasoning effort ID]` — show or switch the current Session model\n`/batch` — start batch input (direct messages only, up to 10 text messages)\n`/send` — submit the current batch\n`/cancel` — cancel the current batch\n`/repair` — complete Feishu permissions and the card callback',
|
||||
'**⌨️ 文本命令**\n\n`/m` — 打开菜单卡片\n`/new` — 开启全新会话\n`/session ID` — 绑定已有会话\n`/sessionlist [工作区]` 或 `/sessions [工作区]` — 列出会话\n`/sessionlist --limit N` 或 `/sessions --limit N` — 仅列出当前工作区前 N 个会话\n`/workspace 工作区序号或绝对路径` — 切换工作区\n`/workspacelist` — 列出工作区\n`/status` — 查看连接状态\n`/compact` — 压缩上下文\n`/stop` — 停止当前任务\n`/steer 指令` — 补充指令\n`/watch ID` — 关注会话\n`/watchlist` — 关注列表\n`/unwatch ID` — 取消关注\n`/archived on/off` — 归档显隐\n`/presetlist` 或 `/presets` — 列出预设\n`/preset [序号/ID]` — 切换预设\n`/preset --default` — 跟随默认\n`/models` — 列出模型\n`/reasoninglist` 或 `/reasonings` — 按序号列出当前模型可用推理等级\n`/reasoning [序号、等级ID或 --default]` — 查看或切换当前推理等级\n`/model [序号或完整模型ID] [推理等级ID]` — 查看或切换当前会话模型\n`/batch` — 开启批量输入(仅私聊,最多 10 条文字)\n`/send` — 提交当前批次\n`/cancel` — 取消当前批次\n`/repair` — 补全飞书权限与卡片回调':
|
||||
'**⌨️ Text commands**\n\n`/m` — open the menu card\n`/new` — start a new session\n`/session ID` — bind an existing session\n`/sessionlist [workspace]` or `/sessions [workspace]` — list sessions\n`/sessionlist --limit N` or `/sessions --limit N` — list only the first N sessions in the current workspace\n`/workspace <workspace index or absolute path>` — switch workspace\n`/workspacelist` — list workspaces\n`/status` — view connection status\n`/compact` — compact context\n`/stop` — stop the current task\n`/steer INSTRUCTION` — steer the task\n`/watch ID` — watch a session\n`/watchlist` — list watched sessions\n`/unwatch ID` — stop watching\n`/archived on/off` — show or hide archived sessions\n`/presetlist` or `/presets` — list presets\n`/preset [index/ID]` — switch preset\n`/preset --default` — follow default\n`/models` — list models\n`/reasoninglist` or `/reasonings` — list reasoning efforts for the current model\n`/reasoning [index, effort ID, or --default]` — show or switch reasoning effort\n`/model [index or full model ID] [reasoning effort ID]` — show or switch the current Session model\n`/batch` — start batch input (direct messages only, up to 10 text messages)\n`/send` — submit the current batch\n`/cancel` — cancel the current batch\n`/repair` — complete Feishu permissions and the card callback',
|
||||
'**💡 数字兜底**\n回复数字快速操作:\n**1**工作区列表 · **2**新会话 · **3**会话/关注\n**4**状态 · **5**补全权限 · **6**帮助':
|
||||
'**💡 Number fallback**\nReply with a number for a quick action:\n**1** Workspace list · **2** New session · **3** Sessions/watches\n**4** Status · **5** Complete permissions · **6** Help',
|
||||
'从下方下拉选择补充指令;最后一项可自定义输入。':
|
||||
|
|
|
|||
|
|
@ -127,6 +127,8 @@ export default {
|
|||
'/sessionlist [workspace index or absolute path] List session IDs and titles',
|
||||
'/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题':
|
||||
'/sessionlist or /sessions [workspace index or absolute path] List session IDs and titles',
|
||||
'/sessionlist --limit N 仅列出当前工作区前 N 个会话':
|
||||
'/sessionlist --limit N List only the first N sessions in the current workspace',
|
||||
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话':
|
||||
'/session <Session ID or workspace index> Bind this chat to the specified session',
|
||||
'/models 按序号列出所有可用模型': '/models List all available models by index',
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ export default {
|
|||
// workspace-command.mjs
|
||||
'用法:/session Session ID 或当前工作区序号(/session N)':
|
||||
'Usage: /session Session ID or the session index in the current Workspace (/session N)',
|
||||
'用法:\n/sessionlist 列出当前工作区会话\n/sessionlist 工作区序号 按 /workspacelist 序号列出会话\n/sessionlist 工作区绝对路径 列出指定工作区会话':
|
||||
'Usage:\n/sessionlist List Sessions in the current Workspace\n/sessionlist Workspace index List Sessions by /workspacelist index\n/sessionlist Workspace absolute path List Sessions in the given Workspace',
|
||||
'用法:\n/sessionlist 列出当前工作区会话\n/sessionlist --limit N 列出当前工作区前 N 个会话(N 为正整数)\n/sessionlist 工作区序号 按 /workspacelist 序号列出会话\n/sessionlist 工作区绝对路径 列出指定工作区会话':
|
||||
'Usage:\n/sessionlist List Sessions in the current Workspace\n/sessionlist --limit N List the first N Sessions in the current Workspace (N must be a positive integer)\n/sessionlist Workspace index List Sessions by /workspacelist index\n/sessionlist Workspace absolute path List Sessions in the given Workspace',
|
||||
'工作区必须是绝对路径。\n{usage}': 'The Workspace must be an absolute path.\n{usage}',
|
||||
'工作区路径包含不支持的字符或长度超过限制。\n{usage}':
|
||||
'The Workspace path contains unsupported characters or exceeds the length limit.\n{usage}',
|
||||
|
|
|
|||
|
|
@ -597,6 +597,7 @@ export class TextHarnessBridge {
|
|||
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
||||
t('/workspacelist 列出工作区绝对路径'),
|
||||
t('/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
||||
t('/sessionlist --limit N 仅列出当前工作区前 N 个会话'),
|
||||
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
||||
t('/models 按序号列出所有可用模型'),
|
||||
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const SESSION_BIND_USAGE = '用法:/session Session ID 或当前工作区序
|
|||
const SESSION_LIST_USAGE = [
|
||||
'用法:',
|
||||
'/sessionlist 列出当前工作区会话',
|
||||
'/sessionlist --limit N 列出当前工作区前 N 个会话(N 为正整数)',
|
||||
'/sessionlist 工作区序号 按 /workspacelist 序号列出会话',
|
||||
'/sessionlist 工作区绝对路径 列出指定工作区会话',
|
||||
].join('\n');
|
||||
|
|
@ -45,6 +46,20 @@ function validSessionId(value) {
|
|||
&& !UNSAFE_DISPLAY_TEXT.test(value);
|
||||
}
|
||||
|
||||
export function parseSessionListArgument(value) {
|
||||
const argument = typeof value === 'string' ? value.trim() : '';
|
||||
if (!argument) return { selector: '', limit: null };
|
||||
if (!argument.toLowerCase().startsWith('--limit')) {
|
||||
return { selector: argument, limit: null };
|
||||
}
|
||||
const match = /^--limit[ \t]+(\d+)$/iu.exec(argument);
|
||||
const limit = match ? Number(match[1]) : null;
|
||||
if (!Number.isSafeInteger(limit) || limit < 1) {
|
||||
return { error: t(SESSION_LIST_USAGE) };
|
||||
}
|
||||
return { selector: '', limit };
|
||||
}
|
||||
|
||||
async function existingWorkspacePaths(values) {
|
||||
const checked = await Promise.all(values.map(async (value) => {
|
||||
const workspace = normalizedWorkspacePath(value);
|
||||
|
|
@ -235,12 +250,13 @@ async function currentSessionListWorkspace(harness) {
|
|||
}
|
||||
|
||||
async function runSessionListCommand(match, harness) {
|
||||
const request = parseSessionListArgument(match[1]);
|
||||
if (request.error) return commandResult(request.error);
|
||||
if (typeof harness?.listWorkspaceSessions !== 'function') {
|
||||
return commandResult(t('当前机器人暂不支持列出工作区会话。'));
|
||||
}
|
||||
const selector = match[1]?.trim() ?? '';
|
||||
try {
|
||||
const resolved = await resolveSessionListWorkspace(selector, harness);
|
||||
const resolved = await resolveSessionListWorkspace(request.selector, harness);
|
||||
if (resolved.error) return commandResult(resolved.error);
|
||||
const listed = await harness.listWorkspaceSessions(resolved.workspace);
|
||||
if (!listed || !Array.isArray(listed.sessions)) {
|
||||
|
|
@ -249,7 +265,10 @@ async function runSessionListCommand(match, harness) {
|
|||
harness.assertWorkspaceScope?.();
|
||||
const workspace = normalizedWorkspacePath(listed.workspace) ?? resolved.workspace;
|
||||
const currentWorkspace = await currentSessionListWorkspace(harness);
|
||||
const message = sessionListMessage(workspace, listed.sessions, {
|
||||
const sessions = request.limit === null
|
||||
? listed.sessions
|
||||
: listed.sessions.slice(0, request.limit);
|
||||
const message = sessionListMessage(workspace, sessions, {
|
||||
currentWorkspace: workspace === currentWorkspace,
|
||||
});
|
||||
return commandResult(message, splitWorkspaceCommandMessage(message));
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ function helpText() {
|
|||
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
||||
t('/workspacelist 列出工作区绝对路径'),
|
||||
t('/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
||||
t('/sessionlist --limit N 仅列出当前工作区前 N 个会话'),
|
||||
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
||||
t('/models 按序号列出所有可用模型'),
|
||||
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ const HELP_TEXT = () => [
|
|||
t('/workspace 工作区序号或绝对路径 切换工作区'),
|
||||
t('/workspacelist 列出工作区绝对路径'),
|
||||
t('/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
||||
t('/sessionlist --limit N 仅列出当前工作区前 N 个会话'),
|
||||
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
||||
t('/models 按序号列出所有可用模型'),
|
||||
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue