feat: add session and preset list aliases

This commit is contained in:
MikhailB 2026-08-30 02:34:40 +03:00
parent 92ec91b7bd
commit 98c1e48271
21 changed files with 112 additions and 58 deletions

View file

@ -3768,7 +3768,7 @@ test('preset card selection does not expose internal update errors', async () =>
function cards(messages) { return messages.filter((m) => m.msgType === 'interactive'); }
test('session list paginates by page number across 25 sessions', async () => {
test('/sessions alias uses the interactive session list and paginates across 25 sessions', async () => {
const fixture = stateFixture();
const sent = [];
const bridge = new FeishuHarnessBridge({
@ -3782,7 +3782,7 @@ test('session list paginates by page number across 25 sessions', async () => {
allowedSenderOpenIds: new Set(['ou_owner']),
});
await bridge.accept(event('sessions-open', '/sessionlist', { senderOpenId: 'ou_owner' }));
await bridge.accept(event('sessions-open', ' /SESSIONS ', { senderOpenId: 'ou_owner' }));
await bridge.waitForIdle();
assert.equal(cards(sent).length, 1);
const page0 = cards(sent).at(-1).content;
@ -3870,7 +3870,7 @@ test('session pagination preserves an explicitly selected workspace', async () =
allowedSenderOpenIds: new Set(['ou_owner']),
});
await bridge.accept(event('selected-workspace-open', '/sessionlist 2', { senderOpenId: 'ou_owner' }));
await bridge.accept(event('selected-workspace-open', '/sessions 2', { senderOpenId: 'ou_owner' }));
await bridge.waitForIdle();
assert.equal(useActionsFromCard(cards(sent).at(-1).content)[0], 'selected-01');

View file

@ -67,6 +67,8 @@ test('menu exposes the increased command set and keeps permission completion num
test('menu and card help advertise Agent Preset, reasoning, and batch commands', () => {
const help = menuHelpText();
assert.match(help, /\/presetlist/);
assert.match(help, /\/presets/);
assert.match(help, /\/sessions/);
assert.match(help, /\/preset \[序号或完整ID\]/);
assert.match(help, /\/preset id:<ID>/);
assert.match(help, /\/preset --default/);
@ -79,6 +81,8 @@ test('menu and card help advertise Agent Preset, reasoning, and batch commands',
assert.match(help, /\/version/);
const card = helpCard();
assert.match(card, /\/presets/);
assert.match(card, /\/sessions/);
assert.match(card, /\/reasoninglist/);
assert.match(card, /\/reasonings/);
assert.match(card, /\/reasoning \[序号、等级ID或 --default\]/);

View file

@ -218,9 +218,10 @@ test('Telegram API registers the command menu and commands-type menu button', as
await api.setChatMenuButton();
assert.equal(calls.length, 2);
assert.deepEqual(
TELEGRAM_COMMAND_MENU.filter(({ command }) => command === 'presetlist' || command === 'preset'),
TELEGRAM_COMMAND_MENU.filter(({ command }) => ['presetlist', 'presets', 'preset'].includes(command)),
[
{ command: 'presetlist', description: '列出可用 Agent Preset' },
{ command: 'presets', description: '列出可用 Agent Preset' },
{ command: 'preset', description: '查看或设置新会话 Agent Preset' },
],
);

View file

@ -46,20 +46,35 @@ function fixture({
return { calls, harness, state: {}, selected: () => selected };
}
test('isPresetCommand recognizes only /presetlist and /preset command prefixes', () => {
test('isPresetCommand recognizes /presets, /presetlist, and /preset command prefixes', () => {
for (const command of [
'/presetlist', ' /PRESETLIST ', '/presetlist ignored',
'/presetlist', ' /PRESETLIST ', '/presetlist ignored', '/presets', ' /PRESETS ',
'/preset', '/PrEsEt coding',
]) {
assert.equal(isPresetCommand(command), true, command);
}
for (const value of [
null, '', 'preset', '/presetx', '/presetlisting', 'hello /preset',
null, '', 'preset', '/presetx', '/presetlisting', '/presetss', 'hello /preset',
]) {
assert.equal(isPresetCommand(value), false, String(value));
}
});
test('/presets reuses /presetlist validation, listing, and snapshot behavior', async () => {
const { calls, harness, state } = fixture({ agentPreset: 'coding' });
const listed = await runPresetCommand(' /PRESETS ', harness, state, 'direct:alias');
assert.match(listed.message, /2\. Coding(coding)(当前选择)/);
assert.deepEqual(calls, [['agentPresetSettings', {}]]);
const selected = await runPresetCommand('/preset 1', harness, state, 'direct:alias');
assert.match(selected.message, /Standard(standard)/);
assert.deepEqual(calls.at(-1), ['updateAgentPreset', 'standard', {}]);
const invalid = await runPresetCommand('/presets unexpected', harness, state, 'direct:alias');
assert.match(invalid.message, /用法:\/presetlist/);
});
test('/presetlist dynamically lists, annotates, and snapshots presets without updating', async () => {
const { calls, harness, state } = fixture({ agentPreset: 'coding' });
const signal = new AbortController().signal;

View file

@ -1068,6 +1068,30 @@ test('/sessionlist supports the current workspace, list numbers, and absolute pa
assert.equal(workspaceListCalls, 1, 'only numeric selection needs the workspace registry order');
});
test('/sessions reuses /sessionlist parsing for current, numbered, and absolute workspaces', async (t) => {
const { defaultWorkspace, alternateWorkspace } = await fixture(t);
const calls = [];
const harness = {
currentWorkspace() { return defaultWorkspace; },
async listWorkspaces() { return [alternateWorkspace]; },
async listWorkspaceSessions(workspace) {
calls.push(workspace);
return { workspace, sessions: [] };
},
};
await runWorkspaceCommand(' /SESSIONS ', harness);
await runWorkspaceCommand('/sessions 2', harness);
await runWorkspaceCommand(`/sessions ${alternateWorkspace}`, harness);
assert.deepEqual(calls, [defaultWorkspace, alternateWorkspace, alternateWorkspace]);
assert.equal(await runWorkspaceCommand('/sessionsx', harness), null);
assert.match(
(await runWorkspaceCommand('/sessions relative/path', harness)).message,
/工作区必须是绝对路径/,
);
});
test('/sessionlist returns actionable and safe errors', async (t) => {
const { root, defaultWorkspace } = await fixture(t);
const file = join(root, 'not-a-workspace.txt');
@ -1226,7 +1250,7 @@ test('all nine channel bridge families advertise and fan out workspace command r
for (const file of bridgeFiles) {
const source = await readFile(new URL(file, import.meta.url), 'utf8');
assert.match(source, /\/workspacelist 列出工作区绝对路径/);
assert.match(source, /\/sessionlist \[工作区序号或绝对路径\] 列出会话 ID 和标题/);
assert.match(source, /\/sessionlist 或 \/sessions \[工作区序号或绝对路径\] 列出会话 ID 和标题/);
assert.match(source, /workspaceCommand\.messages \?\? \[workspaceCommand\.message\]/);
}
});