Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2adf5aa
feat(i18n): session 面向用户改称「任务」,并厘清任务/对话/消息的边界
dashhuang Jul 31, 2026
66b29fc
fix(i18n): /new 类命令改回「新对话」,并把四个 IM 渠道文案包一起收敛
dashhuang Jul 31, 2026
b0cb7ab
fix(i18n): 同步注释与测试常量里已过时的「未命名对话」等文案陈述
dashhuang Jul 31, 2026
ec8dc30
fix(i18n): 补上「原样展示给用户」的三条路径,并修正过宽的不改规则
dashhuang Jul 31, 2026
be61590
fix(i18n): 枚举 main 侧全部"直送用户"出口,补齐通知与 device-link 文案
dashhuang Jul 31, 2026
e3ac86b
docs(naming): 按当前 HEAD 实测重写英文侧与 ja/ko 盘点数字,并标注口径
dashhuang Jul 31, 2026
ba04c97
fix(mobile): 消息操作菜单文案改走 i18n,并修 ja/ko 的「复制对话链接」误译
dashhuang Jul 31, 2026
aeb16ad
fix: 修 i18n 化引入的语言切换回归,并补齐「对话」侧的漏改
dashhuang Jul 31, 2026
0059b79
fix(i18n): rebase 后按命名规则处理新带进的文案(侧栏快捷键 / 钉钉渠道)
dashhuang Jul 31, 2026
8d89e33
fix(i18n): 修同句内换概念的运行时提示,并绕开 memory 描述里两难的名词
dashhuang Jul 31, 2026
f13391a
fix(i18n): MCP 生效范围改回 Agent 会话口径,并扫全 24 篇帮助文档
dashhuang Jul 31, 2026
c23256f
fix: /clear 描述订正为实际行为,mobile fork 弹窗走 i18n,并处理 rebase 新文案
dashhuang Jul 31, 2026
4e5896a
fix(desktop): 同步 Agent Island 两处影子 catalog 的回退文案
dashhuang Jul 31, 2026
cffb3d5
fix(i18n): fork / rewind 的运行时失败改回 Agent 会话口径
dashhuang Jul 31, 2026
c7f2a03
feat(i18n): 新增 agent-session 术语,把 session 的五个语义拆开并加门禁
dashhuang Jul 31, 2026
df38d99
fix(i18n): 任务导入的「内部任务」改回「内部 Agent 会话」
dashhuang Jul 31, 2026
d250996
fix(i18n): 修正项目插件开关的生效时机
dashhuang Jul 31, 2026
2b18c16
fix(i18n): 同步任务改名的影子契约
dashhuang Jul 31, 2026
60b56d8
fix(i18n): 内置工具默认值改用 Agent 会话
dashhuang Jul 31, 2026
0eeca1c
fix(mobile): 协作任务文案统一使用任务
dashhuang Jul 31, 2026
97c4818
fix(i18n): 清理 rebase 冲突标记
dashhuang Jul 31, 2026
e0183f2
fix(i18n): 同步 rebase 新增任务文案
dashhuang Jul 31, 2026
a006bb2
docs: 修正 Orca 协作任务命名指引
dashhuang Jul 31, 2026
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
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
的术语照用,不自造译法;表里没有或拿不准的,在 `i18n/glossary.json` 加
`status: "proposed"` 条目再讨论。门禁为 `pnpm check:i18n-glossary`,规则见
`docs/dev-rules/engineering-conventions.md` §5.1。
- 文案里出现**任务 / 对话 / 消息**这几个词时,必须先读
`docs/product-rules/task-and-conversation-naming.md`:`session` 面向用户叫「任务」,
「对话」只用于任务内的交流过程与内容,单条往来叫「消息」;**「任务」与 `task` 同句出现
时必须消解歧义**。这三个词的边界拿不准会直接做出用户能看见的不一致。
- 所有新增或修改的 UI 必须同时**实现** Light 与 Dark 两种模式(颜色一律走语义 token,
禁止只适配一种模式的硬编码或条件补丁);只实现一种模式视为未完成。**两种模式的实机
目检不是硬性门槛**——能目检更好,做不到时如实写明哪种模式未验证,不得把「复用了 themed
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/help-knowledge/collaboration.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Collaboration ("协同" / Collab) lets one session — the **lead** — bring in
**While collaborating:**

- Each worker is a full session with its own history, tools, working directory, and model, and runs with permissions bypassed so it doesn't stop to ask. It inherits the lead's working directory by default (or shares the worktree when the lead uses one).
- A worker's model defaults to your last "New Chat" choice for that vendor, falling back to the lead's model.
- A worker's model defaults to your last new-session choice for that vendor, falling back to the lead's model.
- Only one worker is **focused** (shown) at a time — switch between them with the worker tabs / dropdown. You can intervene in any worker directly, and archive workers individually.

**Turning it off:**
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/help-knowledge/import-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ If you already have Codex or Claude Code conversations on disk (from the CLIs),
- Click **Scan** to discover sessions in the standard Codex / Claude Code locations on your machine.
- Filter the results:
- by **source** — Codex or Claude Code;
- by **placement** — by project (sessions inside a specific working directory) or by dialogue (free-form conversations not tied to a project).
- by **placement** — by project (sessions inside a specific working directory) or by chat (free-form sessions not tied to a project).
- Tick the sessions or projects you want, then click **Import**.
- They appear in the left sidebar.

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/help-knowledge/sessions-and-chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Each **session** is a persistent chat thread driven by **one agent** (Claude Cod
- While the agent is replying, **Send** becomes **Stop** — click it to interrupt the current turn cleanly.
- You can keep typing while the agent is busy — follow-ups are **queued** in order and sent one after another when the previous turn finishes.

**Conversation controls:**
**Chat controls:**

- **`/clear`** — clears the current session's conversation context in place (resets messages and state without creating or switching to a new session). The session itself stays in the sidebar; only its content is wiped.
- **`/compact`** — ask the agent to summarize and compress earlier turns into a shorter context (Claude Code).
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/help-knowledge/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ The left sidebar lists your sessions. It's the main way to navigate and organize

**Right-click actions:**

- On a regular session: **Pin / Unpin**, **Rename**, **Move to project** (submenu), **Copy conversation link** (a `cindy://session/<id>` deep link), **Open in new window**, **Archive**, **Delete**.
- On an archived session: **Rename**, **Unarchive**, **Copy conversation link**, **Delete**.
- On a regular session: **Pin / Unpin**, **Rename**, **Move to project** (submenu), **Copy session link** (a `cindy://session/<id>` deep link), **Open in new window**, **Archive**, **Delete**.
- On an archived session: **Rename**, **Unarchive**, **Copy session link**, **Delete**.

**Session statuses:**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1441,8 +1441,8 @@ struct AgentIslandStrings: Codable, Equatable {

static let fallback = AgentIslandStrings(
appName: "Cindy",
newConversationTitle: "New Maker",
newConversationHint: "Start a new conversation",
newConversationTitle: "New Session",
newConversationHint: "Start a new session",
muteSound: "Mute Agent Island",
enableSound: "Enable Agent Island sound",
settings: "Agent Island settings",
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/main/__tests__/notificationService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ describe('notificationService — channels 分发', () => {
// 文案断言 — 锁住对外可见的飞书消息格式,后续如要改文案需主动调整测试。
expect(feishuIm.sendMarkdownText).toHaveBeenCalledWith(
'ou_owner',
'Cindy · 会话「Hello」需要你回复',
'Cindy · 任务「Hello」需要你回复',
);
});

Expand All @@ -415,7 +415,7 @@ describe('notificationService — channels 分发', () => {
expect(feishuIm.sendMarkdownText).toHaveBeenCalledTimes(1);
expect(feishuIm.sendMarkdownText).toHaveBeenCalledWith(
'ou_owner',
'Cindy · 会话「Hello」已完成 ✓',
'Cindy · 任务「Hello」已完成 ✓',
);
});

Expand All @@ -439,7 +439,7 @@ describe('notificationService — channels 分发', () => {
);
expect(feishuIm.sendMarkdownText).toHaveBeenCalledWith(
'ou_owner',
'Cindy · 会话「Broken model」执行失败',
'Cindy · 任务「Broken model」执行失败',
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1282,7 +1282,7 @@ describe('AgentIslandService native publishing', () => {
setMainLocale('zh-CN');
service.refreshLocalization();
await vi.waitFor(() => expect(publish.mock.calls.at(-1)?.[0].sessions[0]).toMatchObject({
title: '未命名对话',
title: '未命名任务',
}));
setMainLocale('en');
service.refreshLocalization();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ describe('Agent Island display state', () => {
// review 曾担心 priorityCompactTitle 会把原始哨兵带给 native(Swift 侧的 compactTitle
// 优先读这个字段)。事实是取值链第一步 meaningfulSessionTitle 就按大小写无关过滤掉
// 'new maker' / 'untitled' / 'codex' 等泛化串,pill 因此回落到项目名 —— 小尺寸胶囊里
// 项目名本来也比「未命名对话」有信息量。这条断言把该事实钉住,免得日后有人放宽过滤
// 项目名本来也比「未命名任务」有信息量。这条断言把该事实钉住,免得日后有人放宽过滤
// 又把哨兵漏给 native(PR #1031 review 第 13 轮)。
const state = createAgentIslandState();

Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/main/applicationMenuLabels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const APPLICATION_MENU_LABELS: Record<ApplicationMenuLocale, ApplicationM
settings: '设置…',
checkForUpdates: '检查更新…',
fileMenu: '文件',
newMaker: '新建对话',
newMaker: '新建任务',
viewMenu: '显示',
toggleSidebar: '切换侧边栏',
windowMenu: '窗口',
Expand All @@ -56,7 +56,7 @@ export const APPLICATION_MENU_LABELS: Record<ApplicationMenuLocale, ApplicationM
settings: 'Settings…',
checkForUpdates: 'Check for Updates…',
fileMenu: 'File',
newMaker: 'New Chat',
newMaker: 'New Session',
viewMenu: 'View',
toggleSidebar: 'Toggle Sidebar',
windowMenu: 'Window',
Expand All @@ -72,7 +72,7 @@ export const APPLICATION_MENU_LABELS: Record<ApplicationMenuLocale, ApplicationM
settings: '設定…',
checkForUpdates: 'アップデートを確認…',
fileMenu: 'ファイル',
newMaker: '新しい会話',
newMaker: '新規セッション',
viewMenu: '表示',
toggleSidebar: 'サイドバーを切り替え',
windowMenu: 'ウインドウ',
Expand All @@ -88,7 +88,7 @@ export const APPLICATION_MENU_LABELS: Record<ApplicationMenuLocale, ApplicationM
settings: '설정…',
checkForUpdates: '업데이트 확인…',
fileMenu: '파일',
newMaker: '새 대화',
newMaker: '새 세션',
viewMenu: '보기',
toggleSidebar: '사이드바 토글',
windowMenu: '윈도우',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe('commitChatImageUrls(发送时挂引用)', () => {
expect(again).toEqual({ committed: 0, skipped: 1, failed: 0 });
expect(db.select().from(schema.mediaRefs).all()).toHaveLength(1);

// 同图发到另一个会话 → 各挂各的引用
// 同图发到另一个任务 → 各挂各的引用
const other = await chatAttachments.commitChatImageUrls(
{ sessionId: 'sess-2', urls: [PNG_URL] },
db,
Expand Down
7 changes: 5 additions & 2 deletions apps/desktop/src/main/commands/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,11 @@ export function registerBuiltinDesktopCommands(

registry.register({
name: 'clear',
// 实现是 renderer 收到 DESKTOP_COMMAND_TRIGGERED 后调 clearSession() →
// clearSessionAfterGuard:**原地**清空当前任务的对话上下文,不新建、也不切走
// (help-knowledge/{commands,sessions-and-chat}.md 写的才是对的)。
description:
'Equivalent of the sidebar\'s "New Chat" button — opens a fresh empty draft and switches to it. Discards the current conversation context.',
'Clears the current session context in place — wipes its messages and state without creating or switching to a new session. The session stays in the sidebar.',
execute: (ctx) => broadcastDesktopCommand(buildPayload('clear', ctx)),
});

Expand Down Expand Up @@ -573,7 +576,7 @@ export function registerBuiltinDesktopCommands(

registry.register({
name: 'jump-session',
description: '输入 sessionId 后直接跳转到该会话。',
description: '输入 sessionId 后直接跳转到该任务。',
// 实际执行在 renderer 本地拦截(navigationCommands.ts);这里仅负责让命令出现在
// `/` 菜单并提供描述,不走 executeDesktopCommand broadcast。
execute: () => {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ describe('文件级条目(clearDevice 删不掉时用)', () => {
const pending = path.join(`${root}.control`, 'pending');
await fsp.mkdir(cleared, { recursive: true });
await fsp.mkdir(pending, { recursive: true });
// 20 个会话计数 + 20 个墓碑,远超单条目的 key 上限(16)。
// 20 个任务计数 + 20 个墓碑,远超单条目的 key 上限(16)。
for (let i = 0; i < 20; i += 1) {
await fsp.writeFile(path.join(cleared, `sess-${i}`), '1', 'utf8');
await fsp.writeFile(path.join(pending, `sess-${i}`), '1', 'utf8');
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/main/device-link/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,13 +458,13 @@ export async function handleInvoke(
if (capability.error.code === 'CHANNEL_NOT_ALLOWED') {
throwIpcError(
'SESSION_REFERENCE_UNSUPPORTED',
'目标设备版本不支持会话引用,请升级目标设备后重试',
'目标设备版本不支持任务引用,请升级目标设备后重试',
);
}
if (capability.error.code === 'IPC_ERROR') {
throwIpcError(
'SESSION_REFERENCE_UNAVAILABLE',
'目标设备仍在启动,会话引用暂不可用,请稍后重试',
'目标设备仍在启动,任务引用暂不可用,请稍后重试',
);
}
throwIpcError(
Expand All @@ -489,7 +489,7 @@ export async function handleInvoke(
) {
throwIpcError(
'SESSION_REFERENCE_UNSUPPORTED',
'目标设备版本不支持会话引用,请升级目标设备后重试',
'目标设备版本不支持任务引用,请升级目标设备后重试',
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ describe('dispatcher 核心语义', () => {
fr.finish({ finalText: '新对话的回答' });
await tick();
const finalText = c.last('turn.end')!.payload.finalText;
expect(finalText).toContain('原对话已不在可用的工作目录里');
expect(finalText).toContain('原任务已不在可用的工作目录里');
expect(finalText).toContain('把它所在的目录加进来');
expect(finalText).toContain('新对话的回答');
});
Expand Down Expand Up @@ -818,7 +818,7 @@ describe('dispatcher 核心语义', () => {
fr.finish({ finalText: '新会话的回答' });
await tick();
const finalText = c.last('turn.end')!.payload.finalText;
expect(finalText).toContain('原对话已不在可用的工作目录里');
expect(finalText).toContain('原任务已不在可用的工作目录里');
expect(finalText).toContain('新会话的回答');
});

Expand Down Expand Up @@ -867,7 +867,7 @@ describe('dispatcher 核心语义', () => {
fr.finish({ finalText: '新的回答' });
await tick();
// 措辞留余地: inspect 的 null 也可能是读库瞬时失败, 不能一口咬定会话没了
expect(c.last('turn.end')!.payload.finalText).toContain('原对话现在读不到');
expect(c.last('turn.end')!.payload.finalText).toContain('原任务现在读不到');
});

it('切账号期间异步定位失败也不回写旧代 rejected ack', async () => {
Expand Down Expand Up @@ -2004,7 +2004,7 @@ describe('turn.reopen: 失败任务在桌面端被续跑后接回原消息', ()
undispatchListeners.add(listener);
return () => undispatchListeners.delete(listener);
}) as NonNullable<HookDispatcherDeps['subscribeUiTurnUndispatched']>,
/** 模拟"桌面端在这个会话里做了与续跑无关的事"。 */
/** 模拟"桌面端在这个任务里做了与续跑无关的事"。 */
intervene: (sessionId: string) => {
for (const l of [...interveners]) l(sessionId);
},
Expand Down Expand Up @@ -2139,7 +2139,7 @@ describe('turn.reopen: 失败任务在桌面端被续跑后接回原消息', ()

it('同 session 又来了新的 hook 任务 -> 撤销在观察的续跑并作废记账', async () => {
const { cr, sig, c, d, sessionId } = await failOneTask();
// 让后续 dispatch 沿 binding 落回同一个会话(否则 inspect 查不到会重建)。
// 让后续 dispatch 沿 binding 落回同一个任务(否则 inspect 查不到会重建)。
cr.sessions[sessionId] = { workingDir: WS_DIR, usable: true };
sig.retry(sessionId);
await tick();
Expand Down Expand Up @@ -2358,7 +2358,7 @@ describe('turn.reopen: 失败任务在桌面端被续跑后接回原消息', ()
expect(cr.watches).toHaveLength(0);
expect(c.ofType('turn.reopen')).toHaveLength(0);

// 而且它同时作废了意图与记账: 与 enqueue 侧同一条规则 —— 这个会话被无关内容推进过,
// 而且它同时作废了意图与记账: 与 enqueue 侧同一条规则 —— 这个任务被无关内容推进过,
// 就不再把任何结果接回那条旧消息。哪怕目标那条随后真的 dispatch 也不接。
// 取舍是明确的: 这里判错的代价是"渠道消息停在失败上"(本能力之前的状态), 反过来
// 放行则是"把无关输出写进用户那条消息", 后者是真的错。
Expand Down Expand Up @@ -2667,7 +2667,7 @@ describe('turn.reopen: 失败任务在桌面端被续跑后接回原消息', ()
await tick();

// 同一 externalKey 的新 hook 任务跑起来, 并同样以失败收口 -> 记账换成它的 requestId。
// (让 inspect 看见这个 session, 第二条派发才会落到同一个会话上。)
// (让 inspect 看见这个 session, 第二条派发才会落到同一个任务上。)
cr.sessions[sessionId] = { workingDir: WS_DIR, usable: true };
d.handleDispatch('conn-1', dispatch({ requestId: 'req-take-over', prompt: '新任务' }), c.send);
await tick();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('composeInteractionCard', () => {
});
});

it('permission: 三按钮卡(允许一次/本对话总是允许/拒绝), 超时默认拒绝', () => {
it('permission: 三按钮卡(允许一次/本任务总是允许/拒绝), 超时默认拒绝', () => {
const composed = composeInteractionCard({
kind: 'permission',
requestId: 'int-6',
Expand All @@ -99,12 +99,12 @@ describe('composeInteractionCard', () => {
expect(composed.card.body).toContain('rm -rf dist');
expect(composed.card.buttons).toEqual([
{ id: 'perm:allow', label: '允许一次', style: 'primary' },
{ id: 'perm:always', label: '本对话总是允许', style: 'default' },
{ id: 'perm:always', label: '本任务总是允许', style: 'default' },
{ id: 'perm:deny', label: '拒绝', style: 'danger' },
]);
expect(composed.decisions.get('perm:allow')).toEqual({ kind: 'permission', behavior: 'allow' });
// 「本对话总是允许」带会话级 addRules(claude 直接消费, codex 非空即会话放行)
// 注:按钮文案用「对话」(产品术语,与应用内 permissions.alwaysAllowForSession 一致),
// 「本任务总是允许」带会话级 addRules(claude 直接消费, codex 非空即会话放行)
// 注:按钮文案用「任务」(产品术语,与应用内 permissions.alwaysAllowForSession 一致),
// 但 addRules 的作用域仍是技术意义上的 agent session,两者刻意不同名。
expect(composed.decisions.get('perm:always')).toEqual({
kind: 'permission',
Expand Down
10 changes: 5 additions & 5 deletions apps/desktop/src/main/hook-control/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,16 +372,16 @@ const MAX_PENDING_REOPENS = 200;
* 对话选择重新指定原对话才接得回来 —— 两步缺一不可。
*/
const NOTICE_SESSION_RECREATED =
'ℹ️ 原对话已不在可用的工作目录里,这条消息起换用了新对话,原对话的上下文不会带过来。' +
'想接回原对话:先到 Cindy 的 设置 → 远程连接 → 工作目录映射 把它所在的目录加进来,' +
'再在这里用对话选择重新指定它。';
'ℹ️ 原任务已不在可用的工作目录里,这条消息起换用了新任务,原任务的上下文不会带过来。' +
'想接回原任务:先到 Cindy 的 设置 → 远程连接 → 工作目录映射 把它所在的目录加进来,' +
'再在这里选择任务重新指定它。';
Comment thread
dashhuang marked this conversation as resolved.
/**
* 查不到原对话时的说明。措辞刻意留了余地: inspect 返回 null 是多义的 ——
* 会话真的没了是 null, meta / DB 读取瞬时失败也被吞成 null(session-runner
* 两路都 catch)。一口咬定"已被归档或删除"会在读库抖动时误导用户
* (PR #733 review 指出)。
*/
const NOTICE_SESSION_GONE = 'ℹ️ 原对话现在读不到(可能已被归档或删除),这条消息起换用了新对话。';
const NOTICE_SESSION_GONE = 'ℹ️ 原任务现在读不到(可能已被归档或删除),这条消息起换用了新任务。';

/** 标题里消息摘要的最大长度(字符), 超出截断加省略号。 */
const TITLE_SNIPPET_MAX = 24;
Expand Down Expand Up @@ -1139,7 +1139,7 @@ export function createHookDispatcher(deps: HookDispatcherDeps): HookDispatcher {
status: 'error',
finalText: '',
errorMessage:
'这个对话所在的目录已不在工作目录映射里,本条消息没有执行。把它所在的目录加进 设置 → 远程连接 → 工作目录映射 后再发一次。',
'这个任务所在的目录已不在工作目录映射里,本条消息没有执行。把它所在的目录加进 设置 → 远程连接 → 工作目录映射 后再发一次。',
durationMs: 0,
};
} else {
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/main/hook-control/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export interface ComposedInteractionCard {
* InteractionRequest -> 卡片 + 决策映射。
* 返回 null 表示该 kind 不出卡(未来未知 kind), 由调用方按 kind 安全默认
* 就地自决。permission 自 Slack 按目录权限偏好上线起出卡: 非 bypass 会话的
* 权限请求渲染成「允许一次 / 本对话总是允许 / 拒绝」三按钮卡, 超时安全默认
* 权限请求渲染成「允许一次 / 本任务总是允许 / 拒绝」三按钮卡, 超时安全默认
* **拒绝**(用户显式选了收紧档, 放行才是意外)。
*/
export function composeInteractionCard(req: InteractionRequest): ComposedInteractionCard | null {
Expand Down Expand Up @@ -180,7 +180,7 @@ export function composeInteractionCard(req: InteractionRequest): ComposedInterac
body,
buttons: [
{ id: 'perm:allow', label: '允许一次', style: 'primary' },
{ id: 'perm:always', label: '本对话总是允许', style: 'default' },
{ id: 'perm:always', label: '本任务总是允许', style: 'default' },
{ id: 'perm:deny', label: '拒绝', style: 'danger' },
],
},
Expand Down
Loading