Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions src/bot-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2160,9 +2160,10 @@ export function parseBotConfigsFromText(jsonText: string): BotConfig[] {
? entry.receivedReactionEmoji.trim() : undefined,
doneReactionEmoji: typeof entry.doneReactionEmoji === 'string' && entry.doneReactionEmoji.trim()
? entry.doneReactionEmoji.trim() : undefined,
// Only 'chat' is meaningful; 'thread' (and anything else) normalizes to
// undefined — the legacy thread-per-message default. Keeps bots.json clean.
p2pMode: entry.p2pMode === 'chat' ? 'chat' : undefined,
// Default is now 'chat' (flat continuous DM session). Only 'thread' is
// meaningful and persists; 'chat' (and anything else) normalizes to
// undefined so bots.json stays clean.
p2pMode: entry.p2pMode === 'thread' ? 'thread' : undefined,
noCardChats: Array.isArray(entry.noCardChats)
? entry.noCardChats.filter((x: any): x is string => typeof x === 'string' && x.trim().length > 0).map((x: string) => x.trim())
: undefined,
Expand All @@ -2180,12 +2181,13 @@ export function parseBotConfigsFromText(jsonText: string): BotConfig[] {
: undefined,
autoStartOnNewTopic: entry.autoStartOnNewTopic === true || undefined,
worktreeMultiPicker: entry.worktreeMultiPicker === true || undefined,
// Per-bot regular-group default mode. Only the non-default modes
// ('chat-topic' | 'new-topic' | 'shared') are meaningful; 'chat' (the flat
// default) and anything else normalize to undefined so bots.json stays clean.
// Per-bot regular-group default mode. Default is 'chat-topic' (顶层平铺
// 连续会话;群内原生话题各自独立会话), so only the NON-default modes
// ('chat' | 'new-topic' | 'shared') are meaningful and persist; 'chat-topic'
// and anything else normalize to undefined so bots.json stays clean.
regularGroupReplyMode: (() => {
const mode = normalizeChatReplyModeConfig(entry.regularGroupReplyMode);
return mode === 'new-topic' || mode === 'shared' || mode === 'chat-topic' ? mode : undefined;
return mode === 'chat' || mode === 'new-topic' || mode === 'shared' ? mode : undefined;
})(),
// 4-tier @ policy. Only 'topic' | 'never' | 'ambient' are meaningful;
// 'always' (the default) and anything else normalize to undefined so
Expand Down
14 changes: 7 additions & 7 deletions src/core/dashboard-ipc-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2307,8 +2307,8 @@ ipcRoute('GET', '/api/bot-default-oncall', async (_req, res) => {
const { defaultOncall, autoboundChats } = oncallStore.getBotDefaultOncall(cachedLarkAppId);
const cardPrefs = cardPrefsStore.getBotCardPrefs(cachedLarkAppId);
const grantPrefs = grantPrefsStore.getBotGrantPrefs(cachedLarkAppId);
let p2pMode: 'thread' | 'chat' = 'thread';
try { if (getBot(cachedLarkAppId).config.p2pMode === 'chat') p2pMode = 'chat'; } catch { /* default thread */ }
let p2pMode: 'thread' | 'chat' = 'chat';
try { if (getBot(cachedLarkAppId).config.p2pMode === 'thread') p2pMode = 'thread'; } catch { /* default chat */ }
let skillInjection: 'global' | 'prompt' | 'off' | null = null;
// How this bot's CLI delivers botmux skills, so the dashboard can render the
// control correctly: 'dynamic' = per-session --plugin-dir (claude-family, not
Expand Down Expand Up @@ -2798,8 +2798,8 @@ ipcRoute('PUT', '/api/bot-agent', async (req, res) => {
});

// Per-bot 私聊单聊模式 p2pMode。Body `{ p2pMode: 'chat' | 'thread' }`:
// • 'chat' → 私聊走扁平连续 chat-scope 会话
// • 'thread'(默认) → 清回每条 DM 独立 thread-scope 会话
// • 'chat'(默认) → 私聊走扁平连续 chat-scope 会话
// • 'thread' → 显式回到每条 DM 独立 thread-scope 会话
// 走 applyConfigField(与 /botconfig 同一写盘 + 热更新路径),保证一致。
ipcRoute('PUT', '/api/bot-p2p-mode', async (req, res) => {
if (!cachedLarkAppId) return jsonRes(res, 503, { error: 'larkAppId_not_set' });
Expand All @@ -2809,11 +2809,11 @@ ipcRoute('PUT', '/api/bot-p2p-mode', async (req, res) => {

const spec = findConfigField('p2pMode');
if (!spec) return jsonRes(res, 500, { ok: false, error: 'spec_missing' });
// 只有 'chat' 有意义;其它(含 'thread')一律清回默认,bots.json 保持干净。
const value = body.p2pMode === 'chat' ? 'chat' : null;
// 只有 'thread' 有意义;其它(含 'chat',新默认)一律清回默认,bots.json 保持干净。
const value = body.p2pMode === 'thread' ? 'thread' : null;
const r = await applyConfigField(cachedLarkAppId, spec, value);
if (!r.ok) return jsonRes(res, 400, { ok: false, error: r.reason });
jsonRes(res, 200, { ok: true, p2pMode: value ?? 'thread' });
jsonRes(res, 200, { ok: true, p2pMode: value ?? 'chat' });
});

// Per-bot 内置技能注入模式 skillInjection。Body `{ skillInjection: 'global'|'prompt'|'off'|'' }`:
Expand Down
2 changes: 1 addition & 1 deletion src/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4081,7 +4081,7 @@ async function adoptCodexNotifierEvent(
signal.throwIfAborted();

const botCfg = getBot(larkAppId).config;
const scope: 'thread' | 'chat' = botCfg.p2pMode === 'chat' ? 'chat' : 'thread';
const scope: 'thread' | 'chat' = botCfg.p2pMode === 'thread' ? 'thread' : 'chat';
const anchor = scope === 'chat' ? chatId : cardMessageId;
const activeKey = sessionKey(anchor, larkAppId);
let ds = activeSessions.get(activeKey);
Expand Down
4 changes: 2 additions & 2 deletions src/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4615,8 +4615,8 @@ const server = createServer(async (req, res) => {
}

// PUT /api/bots/:appId/p2p-mode — proxy to that bot's daemon. Body
// `{ p2pMode: 'chat' | 'thread' }` ('chat' = flat continuous DM session;
// anything else clears back to the per-message thread default).
// `{ p2pMode: 'chat' | 'thread' }` ('thread' = per-message DM session;
// anything else clears back to the flat continuous chat default).
let mBotP2pMode: RegExpMatchArray | null;
if (req.method === 'PUT' && (mBotP2pMode = url.pathname.match(/^\/api\/bots\/([^/]+)\/p2p-mode$/))) {
const appId = decodeURIComponent(mBotP2pMode[1]);
Expand Down
6 changes: 3 additions & 3 deletions src/dashboard/bot-payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,17 @@ export function botDefaultsPayload(bot: DashboardBotDescriptor, j?: any, error?:
summaryRange: j?.summaryRange
?? summaryRangeFromLegacyContentTriggers(j?.contentTriggers)
?? defaultSummaryRangePrefs(),
regularGroupReplyMode: (j?.regularGroupReplyMode === 'new-topic' || j?.regularGroupReplyMode === 'shared' || j?.regularGroupReplyMode === 'chat-topic')
regularGroupReplyMode: (j?.regularGroupReplyMode === 'chat' || j?.regularGroupReplyMode === 'new-topic' || j?.regularGroupReplyMode === 'shared')
? j.regularGroupReplyMode
: 'chat',
: 'chat-topic',
regularGroupMentionMode: (j?.regularGroupMentionMode === 'topic' || j?.regularGroupMentionMode === 'never' || j?.regularGroupMentionMode === 'ambient')
? j.regularGroupMentionMode
: 'always',
substituteMode: j?.substituteMode && typeof j.substituteMode === 'object' ? j.substituteMode : null,
restrictGrantCommands: j?.restrictGrantCommands === true,
autoGrantRequestCards: j?.autoGrantRequestCards !== false,
messageQuotaDefaultLimit: typeof j?.messageQuotaDefaultLimit === 'number' ? j.messageQuotaDefaultLimit : null,
p2pMode: j?.p2pMode === 'chat' ? 'chat' : 'thread',
p2pMode: j?.p2pMode === 'thread' ? 'thread' : 'chat',
skillInjection: (j?.skillInjection === 'global' || j?.skillInjection === 'prompt' || j?.skillInjection === 'off') ? j.skillInjection : null,
skillInjectionDefault: (j?.skillInjectionDefault === 'global' || j?.skillInjectionDefault === 'off') ? j.skillInjectionDefault : 'prompt',
skillInjectionSupport: (j?.skillInjectionSupport === 'dynamic' || j?.skillInjectionSupport === 'global') ? j.skillInjectionSupport : 'none',
Expand Down
10 changes: 5 additions & 5 deletions src/dashboard/web/bot-defaults-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2157,7 +2157,7 @@ function SessionModeSection(props: {
putCardPref(patch: CardPrefPatch): Promise<JsonResponse>;
}) {
const tr = useT();
const [p2p, setP2p] = useState(props.bot.p2pMode === 'chat' ? 'chat' : 'thread');
const [p2p, setP2p] = useState(props.bot.p2pMode === 'thread' ? 'thread' : 'chat');
const [regular, setRegular] = useState(regularGroupMode(props.bot));
const [mention, setMention] = useState(mentionMode(props.bot));
const [docMode, setDocMode] = useState(props.bot.docSubscribeDefaultMode === 'all' ? 'all' : 'mention-only');
Expand All @@ -2168,7 +2168,7 @@ function SessionModeSection(props: {
const [docStatus, setDocStatus] = useState<StatusMessage>(null);

useEffect(() => {
setP2p(props.bot.p2pMode === 'chat' ? 'chat' : 'thread');
setP2p(props.bot.p2pMode === 'thread' ? 'thread' : 'chat');
setRegular(regularGroupMode(props.bot));
setMention(mentionMode(props.bot));
setDocMode(props.bot.docSubscribeDefaultMode === 'all' ? 'all' : 'mention-only');
Expand All @@ -2187,7 +2187,7 @@ function SessionModeSection(props: {
try {
const res = await sendJson('PUT', `/api/bots/${encodeURIComponent(props.bot.larkAppId)}/p2p-mode`, { p2pMode: mode });
if (res.ok && res.body.ok) {
props.patchBot(props.bot.larkAppId, { p2pMode: res.body.p2pMode === 'chat' ? 'chat' : 'thread' });
props.patchBot(props.bot.larkAppId, { p2pMode: res.body.p2pMode === 'thread' ? 'thread' : 'chat' });
setP2pStatus({ text: `✓ ${tr('botDefaults.cardPrefSaved')}`, ok: true });
} else {
setP2pStatus({ text: `✗ ${responseErrorText(res)}` });
Expand Down Expand Up @@ -2720,9 +2720,9 @@ function SubstituteModeSection(props: { bot: BotDefaultsRow; patchBot: PatchBot
}

function regularGroupMode(bot: BotDefaultsRow): string {
return bot.regularGroupReplyMode === 'new-topic' || bot.regularGroupReplyMode === 'shared' || bot.regularGroupReplyMode === 'chat-topic'
return bot.regularGroupReplyMode === 'chat' || bot.regularGroupReplyMode === 'new-topic' || bot.regularGroupReplyMode === 'shared'
? bot.regularGroupReplyMode
: 'chat';
: 'chat-topic';
}

function mentionMode(bot: BotDefaultsRow): string {
Expand Down
20 changes: 10 additions & 10 deletions src/dashboard/web/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1803,14 +1803,14 @@ const zh: DashboardMessages = {
'botDefaults.sectionSessionMode': '会话模式',
'botDefaults.p2pMode': '私聊会话模式',
'botDefaults.p2pThread': 'thread(每条 DM 独立会话)',
'botDefaults.p2pChat': 'chat(扁平连续单聊会话)',
'botDefaults.p2pHelp': '私聊会话方式:thread=每条顶层消息独立会话;chat=整段私聊共享一个连续会话。改动立即生效。',
'botDefaults.p2pChat': 'chat(扁平连续单聊会话,默认)',
'botDefaults.p2pHelp': '私聊会话方式:chat(默认)=整段私聊共享一个连续会话;thread=每条顶层消息独立会话。改动立即生效。',
'botDefaults.regularGroupMode': '普通群会话模式',
'botDefaults.regularGroupModeChat': 'chat(扁平连续单聊会话,默认)',
'botDefaults.regularGroupModeChatTopic': 'chat-topic(顶层平铺连续会话,原生话题各自独立会话)',
'botDefaults.regularGroupModeChat': 'chat(扁平连续单聊会话)',
'botDefaults.regularGroupModeChatTopic': 'chat-topic(顶层平铺连续会话,原生话题各自独立会话,默认)',
'botDefaults.regularGroupModeNewTopic': 'new-topic(每条顶层 @ 各开独立话题与会话)',
'botDefaults.regularGroupModeShared': 'shared(话题模式但复用同一个 session)',
'botDefaults.regularGroupModeHelp': '普通群顶层 @ 的开会话方式:chat=整群连续会话;chat-topic=顶层连续、原生话题独立;new-topic=每次 @ 新建话题与会话;shared=话题展示但共享会话。可被群级 /reply-mode 覆盖。',
'botDefaults.regularGroupModeHelp': '普通群顶层 @ 的开会话方式:chat=整群连续会话(原生话题也并进群会话);chat-topic=顶层连续、群内原生话题各自独立会话(默认);new-topic=每次 @ 新建话题与会话;shared=顶层话题展示但共享会话(原生话题也并进)。原生话题在 chat-topic 与 new-topic 下各自独立、在 chat / shared 下并进群会话。可被群级 /reply-mode 覆盖。',
'botDefaults.mentionMode': '群聊 @ 策略',
'botDefaults.mentionModeAlways': '都需要 @(默认)',
'botDefaults.mentionModeTopic': '仅话题内不需要 @',
Expand Down Expand Up @@ -3801,14 +3801,14 @@ const en: DashboardMessages = {
'botDefaults.sectionSessionMode': 'Session mode',
'botDefaults.p2pMode': 'Private chat session mode',
'botDefaults.p2pThread': 'thread (separate session per DM)',
'botDefaults.p2pChat': 'chat (flat continuous session)',
'botDefaults.p2pHelp': 'Private chat session mode: thread creates one session per top-level message; chat shares one continuous session. Applies immediately.',
'botDefaults.p2pChat': 'chat (flat continuous session, default)',
'botDefaults.p2pHelp': 'Private chat session mode: chat (default) shares one continuous session; thread creates one session per top-level message. Applies immediately.',
'botDefaults.regularGroupMode': 'Regular group session mode',
'botDefaults.regularGroupModeChat': 'chat (flat continuous session, default)',
'botDefaults.regularGroupModeChatTopic': 'chat-topic (flat continuous at top level, native topics each get their own session)',
'botDefaults.regularGroupModeChat': 'chat (flat continuous session)',
'botDefaults.regularGroupModeChatTopic': 'chat-topic (flat continuous at top level, native topics each get their own session, default)',
'botDefaults.regularGroupModeNewTopic': 'new-topic (each top-level @ opens its own topic & session)',
'botDefaults.regularGroupModeShared': 'shared (topic display, reusing one session)',
'botDefaults.regularGroupModeHelp': 'Regular group mode for top-level @mentions: chat = one continuous group session; chat-topic = native topics get separate sessions; new-topic = each @ opens a topic and session; shared = topic UI with shared session. Chat-level /reply-mode can override it.',
'botDefaults.regularGroupModeHelp': 'Regular group mode for top-level @mentions: chat = one continuous group session (native topics fold in too); chat-topic = flat at top level, native topics each get their own session (default); new-topic = each @ opens a topic and session; shared = top-level topic UI with a shared session (native topics fold in too). Native topics run their own session under chat-topic and new-topic, and fold into the group session under chat / shared. Chat-level /reply-mode can override it.',
'botDefaults.mentionMode': 'Group @ policy',
'botDefaults.mentionModeAlways': 'Always require @ (default)',
'botDefaults.mentionModeTopic': 'No @ needed inside topics',
Expand Down
6 changes: 3 additions & 3 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,11 @@ export const messages: Record<string, string> = {
'cmd.reply_mode.updated': '✅ Group reply mode updated to: {mode}',
'cmd.reply_mode.unsupported': '⚠️ /reply-mode only works in regular groups and DMs; topic groups need no setting (they are already threaded).',
'cmd.reply_mode.owner_only': '⚠️ Only owner/allowedUsers can change the session mode.',
'cmd.reply_mode.usage': 'Usage: /reply-mode chat | chat-topic | topic | new-topic\nchat = flat replies reusing the group session (even native topics fold into it); chat-topic = flat at top level reusing the group session, but each native topic runs its own independent session; topic = topic display while reusing the same group session (shared is a compatibility alias); new-topic = each @ opens a fresh topic + its own session.',
'cmd.reply_mode.usage': 'Usage: /reply-mode chat | chat-topic | topic | new-topic\nchat = flat replies reusing the group session (native topics fold in too); chat-topic = flat at top level, but each native topic runs its own independent session (default); topic = topic display while reusing the same group session (shared is a compatibility alias); new-topic = each @ opens a fresh topic + its own session. Native topics run their own session under chat-topic and new-topic; they fold into the group session under chat / shared.',
'cmd.reply_mode.failed': '⚠️ Failed to update reply mode: {reason}',
'cmd.reply_mode.dm_status': 'Current DM session mode: {mode}\nCommands: /reply-mode chat | topic',
'cmd.reply_mode.dm_updated': '✅ DM session mode updated to: {mode}',
'cmd.reply_mode.dm_usage': 'Usage (DM): /reply-mode chat | topic\nchat = the whole DM shares one continuous session; topic = each message starts its own session.',
'cmd.reply_mode.dm_usage': 'Usage (DM): /reply-mode chat | topic\nchat = the whole DM shares one continuous session (default); topic = each message starts its own session.',
'cmd.reply_mode.dm_shared_unsupported': '⚠️ shared / chat-topic only make sense in regular groups; DMs support chat | topic only.',
'cmd.substitute.status_on': 'Current substitute mode for this group: ON (default). When a configured substitute target is @mentioned, I will answer on their behalf.',
'cmd.substitute.status_off': 'Current substitute mode for this group: OFF. Use @me /substitute on to enable it again.',
Expand Down Expand Up @@ -392,7 +392,7 @@ export const messages: Record<string, string> = {
'card.config.quota_off': 'Unlimited',
'card.config.p2p.placeholder': 'DM mode',
'card.config.p2p.thread': '🧵 thread (separate session/DM)',
'card.config.p2p.chat': '💬 chat (continuous session)',
'card.config.p2p.chat': '💬 chat (continuous session, default)',
'config.label.disableStreamingCard': 'Disable live card',
'config.label.silentTurnReactions': 'Disable status reactions',
'config.label.writableTerminalLinkInCard': 'Writable terminal in card',
Expand Down
6 changes: 3 additions & 3 deletions src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,11 @@ export const messages: Record<string, string> = {
'cmd.reply_mode.updated': '✅ 已切换当前群回复模式为:{mode}',
'cmd.reply_mode.unsupported': '⚠️ /reply-mode 仅支持普通群与私聊;话题群无需设置(本就是话题)。',
'cmd.reply_mode.owner_only': '⚠️ 只有 owner/allowedUsers 可以修改会话模式。',
'cmd.reply_mode.usage': '用法:/reply-mode chat|chat-topic|topic|new-topic\nchat=群内平铺回复且复用群会话(连原生话题也并进这个会话);chat-topic=顶层平铺复用群会话,但群里原生话题各自独立会话;topic=回复落在话题里但复用同一个群 session(shared 是兼容别名);new-topic=每次 @ 都开一个独立话题+独立会话。',
'cmd.reply_mode.usage': '用法:/reply-mode chat|chat-topic|topic|new-topic\nchat=群内平铺回复且复用群会话(连原生话题也并进这个会话);chat-topic=顶层平铺复用群会话,但群里原生话题各自独立会话(默认);topic=回复落在话题里但复用同一个群 session(shared 是兼容别名);new-topic=每次 @ 都开一个独立话题+独立会话。原生话题在 chat-topic 与 new-topic 下各自独立,在 chat / shared 下并进群会话。',
'cmd.reply_mode.failed': '⚠️ 切换失败:{reason}',
'cmd.reply_mode.dm_status': '当前私聊会话模式:{mode}\n可用命令:/reply-mode chat|topic',
'cmd.reply_mode.dm_updated': '✅ 已切换私聊会话模式为:{mode}',
'cmd.reply_mode.dm_usage': '用法(私聊):/reply-mode chat|topic\nchat=整段 DM 共用一个连续会话;topic=每条消息各自起独立会话。',
'cmd.reply_mode.dm_usage': '用法(私聊):/reply-mode chat|topic\nchat=整段 DM 共用一个连续会话(默认);topic=每条消息各自起独立会话。',
'cmd.reply_mode.dm_shared_unsupported': '⚠️ shared / chat-topic 仅对普通群有意义;私聊只支持 chat|topic。',
'cmd.substitute.status_on': '当前群替身模式:已开启(默认)。群里 @ 到配置的替身对象时,我会代答。',
'cmd.substitute.status_off': '当前群替身模式:已关闭。可用 @我 /substitute on 重新开启。',
Expand Down Expand Up @@ -395,7 +395,7 @@ export const messages: Record<string, string> = {
'card.config.quota_off': '不限',
'card.config.p2p.placeholder': '私聊模式',
'card.config.p2p.thread': '🧵 thread(每条 DM 独立会话)',
'card.config.p2p.chat': '💬 chat(连续单聊会话)',
'card.config.p2p.chat': '💬 chat(连续单聊会话,默认)',
'config.label.disableStreamingCard': '关闭实时卡片',
'config.label.silentTurnReactions': '关闭状态 reaction',
'config.label.writableTerminalLinkInCard': '卡内嵌可写终端',
Expand Down
Loading