feat: add agent preset chat commands

This commit is contained in:
xmanrui 2026-08-22 04:04:44 +08:00
parent 2ad6f1a44e
commit eea29fe296
33 changed files with 1519 additions and 234 deletions

View file

@ -0,0 +1,305 @@
import {
normalizeAgentPresetCatalog,
normalizeAgentPresetId,
} from './agent-preset.mjs';
import { withSessionBindingLock } from './session-binding-lock.mjs';
import { splitWorkspaceCommandMessage } from './workspace-command.mjs';
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
const PRESET_COMMAND = /^\/preset(?=$|\s)/iu;
const PRESET_LIST_COMMAND = /^\/presetlist(?=$|\s)/iu;
const PRESET_LIST_USAGE = '用法:/presetlist(不带参数)';
const PRESET_USAGE = [
'用法:',
'/preset 查看当前设置',
'/preset <序号> 按最近一次 /presetlist 的序号选择',
'/preset <ID> 按 Agent Preset ID 选择',
'/preset id:<纯数字 ID> 选择纯数字 ID',
'/preset --default 跟随 Host 默认',
].join('\n');
const UNSAFE_DISPLAY_TEXT_GLOBAL = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]+/gu;
const LIST_SNAPSHOTS = new WeakMap();
export const PRESET_LIST_SNAPSHOT_TTL_MS = 15 * 60_000;
export const PRESET_LIST_SNAPSHOT_MAX_ENTRIES = 256;
function commandResult(message) {
return {
handled: true,
message,
messages: splitWorkspaceCommandMessage(message),
};
}
function safeDisplayText(value) {
if (typeof value !== 'string') return '';
return value.replace(UNSAFE_DISPLAY_TEXT_GLOBAL, ' ').replace(/\s+/gu, ' ').trim();
}
function rpcOptions(signal) {
return signal ? { signal } : {};
}
function normalizeSettings(value) {
if (!value || typeof value !== 'object' || Array.isArray(value)
|| !value.agentPresetCatalog || typeof value.agentPresetCatalog !== 'object'
|| !Array.isArray(value.agentPresetCatalog.items)) {
throw new TypeError('Harness returned invalid Agent Preset settings');
}
const agentPreset = value.agentPreset === null
? null
: normalizeAgentPresetId(value.agentPreset);
if (value.agentPreset !== null && agentPreset === null) {
throw new TypeError('Harness returned an invalid current Agent Preset');
}
return {
agentPreset,
agentPresetCatalog: normalizeAgentPresetCatalog(value.agentPresetCatalog),
};
}
function presetItemText(item) {
const label = safeDisplayText(item.label) || item.id;
return `${label}(${item.id})`;
}
function itemFor(catalog, id) {
return catalog.items.find((item) => item.id === id) ?? null;
}
function defaultDescription(catalog) {
if (!catalog.defaultId) return '未设置或当前不可用';
const item = itemFor(catalog, catalog.defaultId);
return item
? presetItemText(item)
: `${catalog.defaultId}(当前不可用)`;
}
function currentDescription(settings) {
const { agentPreset, agentPresetCatalog: catalog } = settings;
if (agentPreset === null) {
const item = itemFor(catalog, catalog.defaultId);
return item
? `跟随 Host 默认:${presetItemText(item)}`
: '跟随 Host 默认(Host 默认当前不可用)';
}
const item = itemFor(catalog, agentPreset);
return item
? presetItemText(item)
: `${agentPreset}(已不可用)`;
}
function formatCurrent(settings) {
return [
'当前机器人用于新会话的 Agent Preset:',
currentDescription(settings),
'',
'已有会话不会受此设置影响。',
'查看可用项:/presetlist',
'恢复跟随 Host 默认:/preset --default',
].join('\n');
}
function formatList(settings) {
const { agentPreset, agentPresetCatalog: catalog } = settings;
const lines = [
'当前机器人用于新会话的 Agent Preset:',
currentDescription(settings),
'',
`Host 默认:${defaultDescription(catalog)}`,
'',
`可用 Agent Preset(${catalog.items.length}):`,
];
if (catalog.items.length === 0) {
lines.push('当前没有可用 Agent Preset。');
} else {
catalog.items.forEach((item, index) => {
const markers = [];
if (item.id === catalog.defaultId) markers.push('Host 默认');
if (item.id === agentPreset) markers.push('当前选择');
if (agentPreset === null && item.id === catalog.defaultId) markers.push('当前生效');
const annotation = markers.length > 0 ? `(${markers.join(',')})` : '';
lines.push(`${index + 1}. ${presetItemText(item)}${annotation}`);
});
}
lines.push(
'',
'选择:/preset <序号或 ID>',
'纯数字 ID:/preset id:<ID>',
'恢复跟随 Host 默认:/preset --default',
);
return lines.join('\n');
}
function formatUpdated(settings) {
return [
'当前机器人用于新会话的 Agent Preset 已设置为:',
currentDescription(settings),
'',
'已有会话不变。若当前聊天已有会话,请先发送 /new,再发送普通消息,才会使用新设置创建会话。',
].join('\n');
}
function stateSnapshots(state, { create = false } = {}) {
if ((typeof state !== 'object' || state === null) && typeof state !== 'function') return null;
let snapshots = LIST_SNAPSHOTS.get(state);
if (!snapshots && create) {
snapshots = new Map();
LIST_SNAPSHOTS.set(state, snapshots);
}
return snapshots ?? null;
}
function pruneExpiredSnapshots(snapshots, now) {
for (const [snapshotKey, snapshot] of snapshots) {
if (snapshot.expiresAt <= now) snapshots.delete(snapshotKey);
}
}
function saveSnapshot(state, key, items) {
const snapshots = stateSnapshots(state, { create: true });
if (!snapshots) return;
const now = Date.now();
pruneExpiredSnapshots(snapshots, now);
snapshots.delete(key);
snapshots.set(key, {
expiresAt: now + PRESET_LIST_SNAPSHOT_TTL_MS,
ids: items.map((item) => item.id),
});
while (snapshots.size > PRESET_LIST_SNAPSHOT_MAX_ENTRIES) {
const oldest = snapshots.keys().next();
if (oldest.done) break;
snapshots.delete(oldest.value);
}
}
function loadSnapshot(state, key) {
const snapshots = stateSnapshots(state);
const snapshot = snapshots?.get(key);
if (!snapshots || !snapshot) return null;
if (snapshot.expiresAt <= Date.now()) {
snapshots.delete(key);
return null;
}
snapshots.delete(key);
snapshots.set(key, snapshot);
return snapshot.ids;
}
function presetFromSnapshot(state, key, requested) {
if (!/^\d+$/u.test(requested)) return { numeric: false, id: null };
const index = Number(requested);
if (!Number.isSafeInteger(index) || index < 1) {
return { numeric: true, error: 'Agent Preset 序号无效,请先执行 /presetlist。' };
}
const snapshot = loadSnapshot(state, key);
if (!snapshot) {
return { numeric: true, error: '请先执行 /presetlist,再按列表序号选择 Agent Preset。' };
}
const id = snapshot[index - 1];
return id
? { numeric: true, id }
: { numeric: true, error: 'Agent Preset 序号不存在,请重新执行 /presetlist。' };
}
function errorCode(error) {
return error?.code ?? error?.failure?.code;
}
function presetErrorMessage(error, action) {
const code = errorCode(error);
if (code === 'agent-preset-invalid') {
return `Agent Preset ID 格式无效。\n${PRESET_USAGE}`;
}
if (code === 'agent-preset-unavailable') {
return 'Agent Preset 不存在或当前不可用,请重新执行 /presetlist。';
}
if (code === WORKSPACE_SESSION_STALE || code === 'workspace-bot-not-found') {
return '工作区或机器人状态已发生变化,请重试。';
}
if (code === 'cancelled' || error?.name === 'AbortError') {
if (action === 'list') return '获取 Agent Preset 列表已取消。';
if (action === 'current') return '获取 Agent Preset 设置已取消。';
return 'Agent Preset 修改已取消。';
}
if (action === 'list') return '暂时无法获取 Agent Preset 列表,请稍后重试。';
if (action === 'current') return '暂时无法获取 Agent Preset 设置,请稍后重试。';
return 'Agent Preset 修改失败,请稍后重试。';
}
async function settings(harness, options) {
if (typeof harness?.agentPresetSettings !== 'function') {
throw new TypeError('Harness does not support Agent Preset settings');
}
return normalizeSettings(await harness.agentPresetSettings(options));
}
async function update(harness, value, options) {
if (typeof harness?.updateAgentPreset !== 'function') {
throw new TypeError('Harness does not support updating Agent Preset settings');
}
return normalizeSettings(await harness.updateAgentPreset(value, options));
}
export function isPresetCommand(text) {
if (typeof text !== 'string') return false;
const command = text.trim();
return PRESET_LIST_COMMAND.test(command) || PRESET_COMMAND.test(command);
}
export async function runPresetCommand(text, harness, state, key, options = {}) {
if (!isPresetCommand(text)) return null;
const command = text.trim();
if (options.hasImages) {
return commandResult('Agent Preset 命令仅支持纯文字,请移除图片后重试。');
}
const requestOptions = rpcOptions(options.signal);
if (PRESET_LIST_COMMAND.test(command)) {
if (!/^\/presetlist[ \t]*$/iu.test(command)) return commandResult(PRESET_LIST_USAGE);
try {
const current = await settings(harness, requestOptions);
saveSnapshot(state, key, current.agentPresetCatalog.items);
return commandResult(formatList(current));
} catch (error) {
return commandResult(presetErrorMessage(error, 'list'));
}
}
const match = /^\/preset(?:[ \t]+([^\s]+))?[ \t]*$/iu.exec(command);
if (!match) return commandResult(PRESET_USAGE);
const requested = match[1];
if (!requested) {
try {
return commandResult(formatCurrent(await settings(harness, requestOptions)));
} catch (error) {
return commandResult(presetErrorMessage(error, 'current'));
}
}
let selected;
if (requested.toLowerCase() === '--default') {
selected = null;
} else {
const explicitNumericId = /^id:(\d+)$/iu.exec(requested);
if (explicitNumericId) {
selected = explicitNumericId[1];
} else {
const fromSnapshot = presetFromSnapshot(state, key, requested);
if (fromSnapshot.numeric) {
if (fromSnapshot.error) return commandResult(fromSnapshot.error);
selected = fromSnapshot.id;
} else {
selected = normalizeAgentPresetId(requested);
if (!selected) return commandResult(`Agent Preset ID 格式无效。\n${PRESET_USAGE}`);
}
}
}
try {
return await withSessionBindingLock(state, key, async () => (
commandResult(formatUpdated(await update(harness, selected, requestOptions)))
));
} catch (error) {
return commandResult(presetErrorMessage(error, 'update'));
}
}