模型
-
- 快速找到、配置并维护聊天使用的模型连接。Provider 决定可用字段,预设保存后才会出现在会话模型选择器中。
-
{catalogMeta && (
{`目录状态:${catalogMeta}`}
)}
diff --git a/web/src/i18n/sourceCatalog.generated.js b/web/src/i18n/sourceCatalog.generated.js
index a36fca9a..89e8890e 100644
--- a/web/src/i18n/sourceCatalog.generated.js
+++ b/web/src/i18n/sourceCatalog.generated.js
@@ -38,7 +38,9 @@ export const sourceCatalogs = {
"s_02bcdbc5a1453cb0": "复制相对路径",
"s_02c9e52a00e2b85a": "每个模型的输入 / 输出 token 与用量记录",
"s_02d9879748ba0ee6": "主题包已更新,请重新确认下载大小",
+ "s_030fdb9a62594f11": "写作",
"s_03189d028a4d04fd": "已导出 {{p0}}",
+ "s_035b616cb7fbea92": "自动更新失败,点击重试",
"s_037f4fb77771bd21": "暂无可配置的本地工具",
"s_03b4c3a9f038e956": "最近 1 小时",
"s_03ea92a14ed4272e": "保存电脑操控配置失败,请重试确认状态",
@@ -60,7 +62,6 @@ export const sourceCatalogs = {
"s_05a531a083e68807": "复制 {{p0}} 验证码 {{p1}}",
"s_05abefa30a624a8a": "派发任务",
"s_05ce3aa87cd46703": "使用发现到的最合适的 Xcode",
- "s_05df17e0e401d645": "让 Agent 帮你重构、修 bug、加测试",
"s_05efef8465e0c666": "git 响应超时(仓库可能很大)",
"s_05fdf30411d98d32": "沙箱",
"s_0604cd82b64752a0": "无确认通道",
@@ -104,7 +105,6 @@ export const sourceCatalogs = {
"s_08d43bdc4dcb6253": "可更新",
"s_08e5a3f74d4c6894": "用手机 QQ 扫一扫,按提示创建并授权机器人",
"s_08e9d9ef7d382947": "仅拦截写入",
- "s_08f10fb5ebba9e65": "使用深色背景,关闭后使用浅色背景",
"s_08f933805d72af91": "正在读取模拟器列表",
"s_092d1bb8c3f5739e": "输入框为空时向上浏览,历史浏览中可向下返回",
"s_0970eb5b921e82c5": "{{p0}} 行",
@@ -178,6 +178,7 @@ export const sourceCatalogs = {
"s_0e0163080d2e4ad4": "无法获取文件路径",
"s_0e0c2d7597ef508d": "已缺失",
"s_0e1a7040390fb03e": "新建项目",
+ "s_0e29fc5caee98f07": "仅影响新启动的实例。",
"s_0e700e0f522f37ff": "比较基线:{{p0}}(仅切换查看对象,不会 checkout)",
"s_0e993148f8f5d3d2": "Grok 登录已失效,请重新连接",
"s_0eab58baf452449c": "请输入完整的绝对路径,例如 C:\\Users 或 /home",
@@ -243,7 +244,6 @@ export const sourceCatalogs = {
"s_13835a85b09dc397": "工作区相对",
"s_13a46616e16fe5e6": "未知错误",
"s_13df8c848dbeb590": "上下文消息",
- "s_13e0991638226a6d": "开启后,Agent 可查看窗口并操作鼠标、键盘和应用控件",
"s_13f6a415ac664be2": "从当前 Provider 探测真实模型列表",
"s_140b07fe3f9f7d48": "需要下载",
"s_1414769bed7c6551": "采样参数(可选)",
@@ -330,6 +330,7 @@ export const sourceCatalogs = {
"s_1a85c9ae73a7f70e": "替换 {{p0}} 行 → {{p1}} 行",
"s_1a9d20be0eb09509": "单次 · {{p0}}",
"s_1aa65c55c6cc6163": "设备验证码",
+ "s_1ac8f10196da4e5e": "主页提示刷新失败,请检查模型配置后按 Ctrl+Y 重试",
"s_1ae4451e51587ed6": "主 checkout 在运行期间出现 {{p0}} 处 worktree 之外的改动",
"s_1ae6b0a0f8266382": "关闭窗口",
"s_1ae7a34c1452785e": "Markdown 所见即所得编辑器",
@@ -500,6 +501,7 @@ export const sourceCatalogs = {
"s_28aacfb2e3d8d395": "专家团名称",
"s_28b41a678b1e4c3e": "当前已是最新版本,无需升级。",
"s_28bbfc2adebfe4c2": "JSON 语法错误",
+ "s_28db50cbd80be382": "AI 创建主题",
"s_28e799b249e6bfa7": "彻底删除选中的会话",
"s_28ec083a352028c9": "无变更",
"s_28ed6de0ee4b4501": "等待用户",
@@ -563,6 +565,7 @@ export const sourceCatalogs = {
"s_2f067f4185b25551": "每周{{p0}} {{p1}}",
"s_2f53c8bcb2808922": "执行规则要求确认这条命令",
"s_2f5f1d6fbfb061ed": "未设置",
+ "s_2f7836ee07547f93": "请帮我起草一份文档。先结合当前上下文梳理目标、读者和关键内容;缺少必要信息时向我提问,再给出结构清晰、可以继续修改的初稿。",
"s_2f80fc3f9ffba0a9": "模型列表已发生变化,请刷新后重新排序",
"s_2f9381fe3414f405": "指定 Xcode 路径",
"s_2f9daa828907b93f": "删除",
@@ -600,6 +603,7 @@ export const sourceCatalogs = {
"s_326d1d537e72f82d": "正在启动或停止反向代理",
"s_327f00da9fc2ec36": "隐藏详情面板",
"s_32a202d6fed3a1da": "在此工作区新建任务",
+ "s_32c8d373afe08328": "执行",
"s_33281591669db79f": "产品交付专家团",
"s_33360315a592f985": "间隔单位",
"s_333eda18f13dbaad": "语音识别启用后生效,停止后回填文字",
@@ -615,10 +619,9 @@ export const sourceCatalogs = {
"s_342292808d6c0954": "切换文件及预览面板的显示",
"s_34486c598efd2582": "Provider 探测失败",
"s_34492b04ea0ccbcc": "请在系统设置中允许本次 ACECode 启动所对应的应用,完成后返回并刷新授权状态",
- "s_346114238816edc2": "全局生效。开启后,新启动的桌面实例可与已有实例同时运行;关闭后不影响已打开的实例。",
"s_34615a3c7580c3e4": "搜索任务加载失败",
"s_348f1cf1243e74bc": "我知道了",
- "s_349424f8eda35375": "配置 Agent 可以使用的内置工具。",
+ "s_3495b529912ceb00": "频繁修改会降低提示词缓存命中率。",
"s_349706d62cafa56d": "退出 ACECode",
"s_34a8a93ff639915f": "暂停目标",
"s_34b3cac2a496d263": "模型目录",
@@ -678,7 +681,6 @@ export const sourceCatalogs = {
"s_39b068ea492a4f9a": "模型能力标签无效",
"s_39b1c2253dc352a0": "上一题",
"s_39c737c10145eb96": "当前页面还没有控制台日志",
- "s_3a020b58cfeb13b3": "让 ACECode 在会话之间记住你的偏好、反馈和项目背景。会话中也可以用 /memory 查看和整理记忆。",
"s_3a045c322efabe1d": "{{p0}}暂无生效头像",
"s_3a1abf3422d22d7d": "检测中…",
"s_3a3b44c38e795b98": "加载记忆设置失败",
@@ -689,13 +691,11 @@ export const sourceCatalogs = {
"s_3ac23fa27c7b0894": "留空的参数继承模型设置;发起子任务时可单独覆盖。",
"s_3ae030dd220465c9": "工作区用量",
"s_3afa87e94673b0d1": "派遣失败:{{p0}}",
- "s_3b002388661e94cb": "切换当前主题的配色风格",
"s_3b16fa8c3a8623e9": "关闭主题音效",
"s_3b6fefc50febace4": "小时",
"s_3b7e27cb4379da3c": "正在处理",
"s_3b8f1538e0895e4d": "简要说明什么时候适合派遣这个组件",
"s_3badb01486cd5de8": "迁移会复制现有数据,完成后需重启 ACECode",
- "s_3bc19f21617f0a86": "根据描述生成图片,或编辑已有图片",
"s_3bceaa431f3153d5": "正在读取状态",
"s_3be2c9604a690f97": "旁路聊天连接超时,请重试。",
"s_3c2d4d2fcc58fb6d": "向下滚动最近文件",
@@ -753,9 +753,7 @@ export const sourceCatalogs = {
"s_402c881d6d0b5320": "附件消息",
"s_4036cb031bb7397e": "worktree 基线分支",
"s_4048d8cd52d1ca52": "模型 {{p0}} 正在被会话使用,暂时不能删除",
- "s_40540dfc29cbcd2d": "用于同时运行多个 ACECode 桌面实例,方便开发和调试 ACECode。",
"s_4058befef3a6363e": "等待用户处理权限请求",
- "s_4088127b773ce83c": "编辑代码",
"s_40ab0013082f8076": "高清",
"s_40cc8bac959395ae": "(可多选)",
"s_40cfdadfe78e6f05": "正在读取记忆摘要状态…",
@@ -766,7 +764,6 @@ export const sourceCatalogs = {
"s_410a8e8a6bf253ac": "剪切",
"s_4115d63bd5497d26": "集成",
"s_412c8bdb63028865": "点击“工作区”栏右侧的添加按钮,选择已有的本地代码目录,它就会出现在左侧工作区列表中。引导期间只做说明,不会真的打开目录选择器。",
- "s_4136ed8160d39565": "使用 Skills",
"s_41623c6b19ff5c83": "升级服务",
"s_416deb8c2fd2287e": "拖入的会话已引用过,或是当前会话",
"s_41abc174fa444bb5": "已保存。需重启 daemon 才能生效",
@@ -778,7 +775,6 @@ export const sourceCatalogs = {
"s_426c18d792670d65": "覆盖已有预设",
"s_4271f3813edcc2d3": "此主题版本已存在且内容不同,请更新版本后重新导出",
"s_4275e28d460a45af": "创建项目失败,请稍后重试",
- "s_42781f603fa4416c": "ACECode 桌面 / TUI / Daemon 同版本号",
"s_4296d4f340dadc15": "审计日志已清空",
"s_42e1b73173a3b1d4": "确认专家切换状态失败:{{p0}}",
"s_432a6c3328243eee": "无法连接 xAI 设备授权服务",
@@ -802,7 +798,6 @@ export const sourceCatalogs = {
"s_43fcef33a825715a": "把 Token 粘贴到下面;验证通过后用邀请链接把机器人拉进你的服务器。",
"s_43fd0fc1e9ae1edb": "名称冲突已处理",
"s_441babd52ac35c3b": "Agent 只能操作已允许的模拟器;未允许的设备在首次使用时会请求确认",
- "s_4430768f253a5d6d": "开启工具重写,并对之后的模型请求生效",
"s_443cb29217829285": "查看擅长领域后派遣到新任务,或维护自己的专家和专家团。",
"s_44455611b9108b91": "高级设置",
"s_445fad5b93c9ad4f": "保存{{p0}}失败",
@@ -822,7 +817,6 @@ export const sourceCatalogs = {
"s_45c05be574029a4a": "macOS 已阻止此连接",
"s_45da2d9c85d15628": "添加文件夹",
"s_45e03e436ed0f697": "执行时间",
- "s_460429decdefd286": "仅在 ACECode 窗口失去焦点且主任务完成时发送系统通知",
"s_460b3574e4bdb8ca": "不可用",
"s_460d648dec104c7e": "终端类型",
"s_4647d9a5b4eea173": "当前工作区的记忆不可用",
@@ -840,13 +834,13 @@ export const sourceCatalogs = {
"s_478211dd029aa604": "网站的安全证书无效或与当前地址不匹配。",
"s_47b50c43e14ca6ba": "扫码成功",
"s_47c99fb0ddf2cffa": "{{p0}} – {{p1}} 累计使用了 {{p2}} 个 Token",
+ "s_47ceaf502c5a05d6": "新版本",
"s_47f22728ee1600ed": "状态消息:",
"s_481acc2c1413db38": "图像生成",
"s_482521a81877f106": "无法建立安全连接",
"s_4835a3fdbd55cde1": "搜索名称、Tag 或擅长领域",
"s_483e7e8d6cf19c2c": "加载失败:{{p0}}",
"s_4869fd312a2bf2cf": "关闭记忆编辑框",
- "s_4871613a44be8d7a": "默认打开文件和文件夹的位置",
"s_487422ef0c4e25b6": "首页标题",
"s_48807d75530c1b03": "找不到这条排队消息",
"s_4888c0a26e905c1f": "用户 {{p0}}",
@@ -857,6 +851,7 @@ export const sourceCatalogs = {
"s_4927a53bcc886afb": "加载中…",
"s_492edaa646797862": "使用 ACECode 现有的 GitHub 设备登录与受管端点。",
"s_49504fdfc1b5a604": "搜索快捷键",
+ "s_495a028100eaed83": "请帮我完成接下来要做的任务。先确认目标和当前状态,给出简短步骤,然后实施必要的修改并验证结果;保留无关的已有改动。",
"s_496124e1bfae76ff": "确定删除“{{p0}}”吗?此操作只删除预设,不会删除会话记录。",
"s_497c1a65b2f98a63": "演示画面,开启后将跟随您的会话。",
"s_499c729eb09aa2a6": "上下文窗口 Token",
@@ -904,7 +899,6 @@ export const sourceCatalogs = {
"s_4d2b19065afe4b07": "LSP 已开启:{{p0}}",
"s_4d3f6a081ea86097": "彻底删除失败:",
"s_4d5193ca941bf930": "下载中 {{p0}}%",
- "s_4d5262c1b9621225": "播放开屏扫描、风格切换过场和霓虹闪烁等主题动画",
"s_4d6a4bc105dd3f07": "共享",
"s_4d6b5440965402ae": "通过右上角操作开始创建。",
"s_4d7611fc1530dbaa": "不可用警告",
@@ -1010,7 +1004,6 @@ export const sourceCatalogs = {
"s_561817f0252936a9": "放行",
"s_562834f547655a37": "搜索代码",
"s_56410fc65314dfb5": "启动",
- "s_5653d97cdf569cb9": "系统防火墙、路由器或云安全组可能仍需放行代理端口。公网访问仍建议使用可信 VPN,并在上游配置 HTTPS。",
"s_56627c94a9decee6": "最大输出 Token",
"s_566e5f182a63185d": "正在压缩主题资源…",
"s_5670da45d1d6e526": "App ID(cli_ 开头)",
@@ -1034,7 +1027,6 @@ export const sourceCatalogs = {
"s_57cb3a025f981aca": "全局技能分页",
"s_57d2858750d28004": "调色板",
"s_57e4fcb2dad50b5c": "远程 Web 模式已开启",
- "s_57eb46dacc97defb": "在任务列表每一行右侧显示最近活动时间,关闭后仍可在悬停卡片里查看",
"s_5804b9cca84304ff": "清空侧边聊天",
"s_58210afb340c2c5e": "链接 10 分钟内有效,只能用一次。",
"s_5835048333dad1d6": "选择自定义答案",
@@ -1086,7 +1078,6 @@ export const sourceCatalogs = {
"s_5d31027802e2d944": "当前内容未更改",
"s_5d344e9b4013d978": "进入快捷键并聚焦搜索栏",
"s_5d3f86d033dabffe": "钩子",
- "s_5d53a39c33ae87b8": "已关闭:新会话不会注入记忆上下文,也不会提供记忆工具;已有记忆仍保留在磁盘上",
"s_5d5cbbbef4de8fdc": "可直接输入一个或多个模型 ID(使用逗号分隔),也可探测后多选。",
"s_5d682392c70fb245": "专家团",
"s_5d6e4b9d0beac865": "代理服务器需要身份验证",
@@ -1121,6 +1112,7 @@ export const sourceCatalogs = {
"s_5ffdb67a4142cc22": "当前环境无法保存截图",
"s_605a534da98e42ec": "Token 活动视图",
"s_60b83f21d9d5d703": "群 {{p0}}",
+ "s_6106f0a12583a334": "刷新失败",
"s_610a30de692b91b0": "请把你的公网地址转发到本机 http://127.0.0.1:{{p0}},连通后会自动继续。",
"s_610e4e820d6881ea": "彻底删除会话“{{p0}}”?此操作不可撤销。",
"s_6135d4159e892541": "移除",
@@ -1134,6 +1126,7 @@ export const sourceCatalogs = {
"s_622de9749cb55343": "加载开发者配置失败",
"s_624b5799ebd036b0": "无工作区",
"s_6258a2c6f9c89143": "复制全文",
+ "s_625cea954ac1e717": "探索一个问题",
"s_62711127c9ee73df": "大于 0,最多 1",
"s_62731d95bc805803": "正在导出…",
"s_6278c2f645ab3982": "公网地址",
@@ -1155,7 +1148,6 @@ export const sourceCatalogs = {
"s_641778357a5e8a41": "查看配置 Schema",
"s_643a1b94e9498780": "已信任钩子",
"s_64568afad9efa616": "每页条目数",
- "s_6492494fbe805292": "描述灵感,生成专属主题",
"s_64ded447c5bdd6ea": "验证并继续",
"s_64e9f3e72b149b27": "此页面意外退出,请重新加载。",
"s_64fd6a54381b531c": "获取二维码失败,请重试",
@@ -1176,20 +1168,17 @@ export const sourceCatalogs = {
"s_667b75d63a19b6b8": "上传失败:",
"s_66809c2ae51ddf06": "移除 Tag {{p0}}",
"s_66b5df56cbaed245": "另存为预设名称",
- "s_66d1628f97183e45": "预定义工作流,从侧边栏开启",
"s_66f054d7bc356104": "Agent 请求进入 Plan 模式,先探索并写计划,批准后再改代码。",
"s_67035e6b7f82acdb": "蓝色",
"s_670ec25af8419f48": "保留",
"s_670fd17c931a0170": "旁路聊天记录过长,无法继续发送。",
"s_6714ad665cd9dce7": "Top P 需大于 0 且不超过 1",
- "s_6730e89aac61dfb7": "会话闲置后自动提炼经验并整合成记忆;开启后会额外调用模型",
"s_67330e9d7654744c": "已错过",
"s_673bfd286541c87b": "请输入提示词",
"s_6744b4c6a9aa0038": "已关闭",
"s_674f9ac747ebfd69": "升级已安装,点击查看重启选项",
"s_67a1de4cea20e015": "打开模型设置",
"s_67ee0d22e90a49f5": "已保存,技能扫描路径已更新",
- "s_68029f7769600f70": "从最后一次侧聊结束开始计时,到期后切回该会话时清空;正在查看时不会自动消失。",
"s_681139c14b0eea8e": "{{p0}} 等 {{p1}} 个",
"s_6820f44987bb2ae6": "重试发送",
"s_682d211b7142758a": "已修改",
@@ -1269,16 +1258,17 @@ export const sourceCatalogs = {
"s_6dd39868b7d587c7": "当前后台服务不支持安全中心,请更新 ACECode 并重启后重试",
"s_6e14496b435e5f7b": "加载 iOS 模拟器配置失败",
"s_6e1ba4bf187327e9": "终端程序路径",
+ "s_6e2295a41a1e0250": "写一份清晰的文档",
"s_6e382d9250f8dc10": "手动填写",
"s_6e392a597af4abab": "memory summary model 记忆摘要模型",
"s_6e55e0f207bfd2e0": "运行记录",
"s_6e6aba42eba94da0": "正在与智能体共享",
"s_6e8017799e19b5a0": "搜索探测到的模型",
"s_6ec00e356283c830": "确定删除“",
+ "s_6ed0bd0cf012e98b": "一起推敲方案",
"s_6f35c88ed171bc46": "模型回复超时,请重试",
"s_6f4b75ff28679ce4": "扁平列表",
"s_6f5994e138557f90": "% 已用",
- "s_6f87682bc02532c6": "每个工作区可拥有独立的 Skill,仅在该工作区的会话中生效。",
"s_6f98bb98abfcd0c9": "打开飞书开放平台,创建「企业自建应用」,在「添加应用能力」里添加「机器人」。",
"s_6f9c11640850a30c": "清空审计日志失败",
"s_6fb9dd3555d1b3b0": "确认迁移",
@@ -1289,7 +1279,6 @@ export const sourceCatalogs = {
"s_6ff4b7dec15fff71": "折叠显示",
"s_700b6c80d9543ebe": "立即重启",
"s_701aa5a8c70f1da6": "拖动调整预览面板宽度",
- "s_702abbca55759b5c": "当前桌面 WebView / 浏览器渲染核心",
"s_703ed122eea2bf76": "连接已取消",
"s_704b1115408edd65": "无法连接语音识别服务,请检查地址、端口和网络。",
"s_705dc58bfaf7471b": "自动判定",
@@ -1323,6 +1312,7 @@ export const sourceCatalogs = {
"s_72e9b59aef42c6bd": "定位当前工作区内的活动文件并高亮",
"s_72ec61d7855c86a1": "请求超时(秒)",
"s_7310f2c21a738bd0": "(上一条)",
+ "s_736cff237d7d9255": "自动更新",
"s_737c725c1321d260": "已保存 {{p0}},留空则不修改",
"s_73859d3cbb42c1c7": "语音请求超时秒数",
"s_73952aa91a9366b5": "已切换至 ",
@@ -1346,7 +1336,6 @@ export const sourceCatalogs = {
"s_749daf601249f185": "粘贴的文本上传失败:",
"s_74a793aa497a96d8": "Grok 设备授权信息已失效,请重新连接",
"s_74d0a87b32e08fcc": "试试这样问我",
- "s_74dfd40904def5a9": "为你的项目向 ACECode 提供额外说明和上下文",
"s_74e8c8c3682706c4": "还原预览面板",
"s_74f5aee6f7a56856": "工作空间路径",
"s_75248c2474f6ff5d": "排队中的待发送消息",
@@ -1389,7 +1378,6 @@ export const sourceCatalogs = {
"s_78161046757aa1f3": "请输入批注内容",
"s_784c2d76956e7faf": "语音识别正在处理其他录音,请稍后重试。",
"s_785e930b8f0e0dba": "请选择空文件夹",
- "s_7875c0f22b559b5e": "提炼和整合记忆时调用的模型;「当前模型」沿用会话正在使用的模型",
"s_788db1cfec2a3db5": "主题",
"s_789eabc8eb6e9d9f": "编辑 {{p0}}",
"s_789f61dbfcc7a2d2": "红色",
@@ -1418,7 +1406,6 @@ export const sourceCatalogs = {
"s_7acf2ee49b4924ef": "位置:{{p0}}",
"s_7ad6bbf3f8508928": "忽略建议",
"s_7adc856731f31518": "尚未运行",
- "s_7af9ed362c1ed48d": "提示:自定义指令会参与每次请求的提示词上下文,频繁修改可能降低提示词缓存命中率。",
"s_7afb72a4b5b21193": "添加工作区",
"s_7b00c3d69a0c48a5": "个模型",
"s_7b10b3966368f243": "没有匹配的",
@@ -1493,7 +1480,6 @@ export const sourceCatalogs = {
"s_8077a5279e43effd": "暂无全局技能",
"s_8078f96cff000853": "已派遣{{p0}}:{{p1}}",
"s_80891538e688b066": "正在处理请求",
- "s_80964c285e90e139": "单次 agent loop 的最大迭代数",
"s_80abe59040677140": "审查当前改动\n帮我设计这项功能",
"s_80e015bfff3865e5": "清空记录",
"s_80f8fbcfa0117633": "放大",
@@ -1538,7 +1524,6 @@ export const sourceCatalogs = {
"s_843e5a96794f9e8c": "测试会使用标准画质生成一张图片并消耗额度。",
"s_844b8cc8dff7c1d8": "默认",
"s_84659f76de148bb0": "转到文件",
- "s_8472a7daca875078": "直接编辑 JSON 配置 MCP 服务器连接(stdio / sse / http)",
"s_84941ac6e844e7af": "显示全部",
"s_84a07f292f1f78cc": "折叠",
"s_84a5571a1cc78c74": "网络访问",
@@ -1596,7 +1581,6 @@ export const sourceCatalogs = {
"s_8949d6721460a7f8": "聊天输入区的模型选择器使用这里的预设。",
"s_894e891e819e220c": "没有可用的模拟器",
"s_8968ac45a626bd2a": "会话菜单",
- "s_8974084a4afd2b6e": "选择界面的主色风格",
"s_8981949fd4a7d012": "当前 Provider 没有返回可用模型。",
"s_8984825b34bd44e8": "拖动排序,也可按上下方向键",
"s_898aa605dcbf3ec1": "排队下一条 (Enter)",
@@ -1630,7 +1614,6 @@ export const sourceCatalogs = {
"s_8b96b96448b4343c": "麦克风配置",
"s_8bcc3e177f5f3b10": "不保存",
"s_8be0ea36a9bba286": "正在连接…",
- "s_8c1ae715559b78bd": "无法获取升级进度",
"s_8c5c96ae7d403826": "已放行指定目录",
"s_8c803ec4ccd237fb": "此网站暂时无法正确响应,请稍后重试。",
"s_8ca30ef95071173d": "正在分叉到新会话",
@@ -1688,6 +1671,7 @@ export const sourceCatalogs = {
"s_9003b29bcd11bb17": "批量操作:成功 {{success}} 项,失败 {{failed}} 项",
"s_901a6d728b8cb75b": "尚未配置机器人",
"s_90253fc5741dc402": "配置 JSON(直接编辑 mcp_servers 部分)",
+ "s_90550acef25a9ace": "读取更新设置失败",
"s_906502a6a28e4eaa": "导入后立即应用",
"s_907fdb0785b5a284": "找到 {{p0}} 项设置",
"s_90835dd8d457ba5a": "继续发送排队的消息 (Enter)",
@@ -1733,6 +1717,7 @@ export const sourceCatalogs = {
"s_93b2b434d6e7eb40": "模拟器屏幕",
"s_93b93a43e96a3556": "目录名:",
"s_93c78cfadc951d31": "撤销中",
+ "s_93fd8d2100096abe": "新会话生效;关闭不会删除已有记忆。",
"s_94078a28716517ee": "记忆已删除",
"s_940c5ebd7fe94a7a": "升级服务 URL",
"s_94222dcc2d741467": "删除主题时恢复文件失败,请检查主题资源目录",
@@ -1743,7 +1728,6 @@ export const sourceCatalogs = {
"s_946fb32dc6fbb463": "变更文件展示方式",
"s_9477f35309b3d9f3": "该专家由当前项目提供,只能查看和派遣。",
"s_947821324c97b7e5": "命令未记录",
- "s_949cf55bb66e100d": "config.json 中配置的插件",
"s_94ab83f1fcb5b7b9": "修改快捷键:{{p0}}{{p1}}",
"s_94c37ae7b384c7d9": "发现诊断",
"s_94f60d2252d6b686": "页面暂时不可用",
@@ -1792,9 +1776,11 @@ export const sourceCatalogs = {
"s_988f77b34abbcca2": "Cookie 已改为「{{p0}}」,已保存的 Cookie 已清除",
"s_98c4b439f2506ab2": "在消息输入框中生效",
"s_98cbc356f0e61a08": "新增一个 Provider 配置后即可在这里管理。",
+ "s_98ceb57fd8c4192a": "探索",
"s_98fc221b10e87c21": "创建应用",
"s_991bb7cfe5a81550": "保存修改",
"s_992b4416c9d23e96": "正在读取模型…",
+ "s_9952001846c63208": "更新设置连接已变更",
"s_99587cf2ca773409": "按住录音",
"s_9966d6b375133f89": "路径必须是绝对路径:{{p0}}",
"s_99731fe916c6b3bf": "现在可以在钉钉里私聊机器人了。把机器人拉进群后 @ 它,第一次需要在设置页批准。",
@@ -1813,6 +1799,7 @@ export const sourceCatalogs = {
"s_9ad0aac32ea304be": "停止生成",
"s_9ae14f5b00594c4c": "可读白名单",
"s_9afc6a4c62aea23f": "申请额外权限",
+ "s_9b48b5cf2345abd5": "侧聊结束后计时;到期切回时清空,浏览期间保留。",
"s_9b7824cefa1e8b16": "放弃修改",
"s_9b7cbee7afe5aee4": "已处理 {{count}} 个会话",
"s_9b8cf7af48da71e0": "第 {{p0}} / {{p1}} 步",
@@ -1864,7 +1851,6 @@ export const sourceCatalogs = {
"s_9f9a95299a7588a3": "地球仪",
"s_9fa08e87505a230d": "选择会话 {{p0}}",
"s_9fa433be4c58ee49": "Agent 浏览器",
- "s_9fa9840d7aa93802": "主题色箭头,右下角显示 ACE",
"s_9fc9368cc2c409a3": "暂无已配置插件",
"s_9fcad048086de1f8": "${ACECODE_DATA_DIR} 为数据目录;${XDG_CONFIG_HOME}、${XDG_CACHE_HOME} 默认分别为 ~/.config、~/.cache。未设置的其他变量会跳过。",
"s_9fd56f7aae1ca61d": "显示隐藏项目",
@@ -1941,7 +1927,6 @@ export const sourceCatalogs = {
"s_a67fa4bcb68094b4": "没有符合筛选条件的专家组件",
"s_a686e64364525272": "最近文件",
"s_a6a4761cfcca4484": "当前路径",
- "s_a6a4d8fa7f9120be": "开启后,真正退出桌面应用时,后台任务和待处理交互仍会继续运行",
"s_a6dfb80406396953": "当前平台不拦截读取,此清单不生效(仅 macOS / Linux)",
"s_a6e1beaceb5bbafa": "暂无任务或项目",
"s_a7310a28aa28faf4": "取消升级失败:",
@@ -1985,7 +1970,6 @@ export const sourceCatalogs = {
"s_aa6d8879058e32fb": "本会话暂无文件变更",
"s_aa946ad4ec4c58f9": "API 协议",
"s_aa9e366f68d3d097": "低",
- "s_aae110840475eea7": "管理 ACECode 可调用的技能模块。启用后 Agent 在任务中可自动使用。",
"s_aae17129468e10f4": "拦截 {{p0}} 次 · {{p1}}",
"s_aae897a12157cc7c": "ACECode 本地工具",
"s_ab2b8bcfda883522": "放行 {{p0}}",
@@ -2010,9 +1994,9 @@ export const sourceCatalogs = {
"s_ac7246e3c8488218": "{{p0}} 个请求待批准",
"s_ac7646d3e0e17e38": "待导入主题预览",
"s_ac77953a1e064ed6": "发送失败",
+ "s_ac84afc335ce8f26": "完成一项任务",
"s_ac8e7ffd9e3458ab": "本平台没有可用沙盒,未知命令需要确认",
"s_acae259f56e1fc3e": "浏览器页面已关闭",
- "s_acb60e5e17fe5e0a": "当前安装的 ACECode 已是最新版本。",
"s_acf6e8244ae05b33": "第 {{p0}} / {{p1}} 页,共 {{p2}} 条",
"s_acfc4e74a650e7df": "下一步",
"s_acfc92e72a99cc1a": "危险模式下不能开启远程 Web 模式",
@@ -2022,7 +2006,6 @@ export const sourceCatalogs = {
"s_ad4c46728555aeb6": "找不到此网站",
"s_ad5aaf33d6ce9b8a": "正在获取二维码…",
"s_ae115efd1b2c2524": "任务已中止",
- "s_ae13d96782b88e57": "探索代码库",
"s_ae30451fc65b75de": "没有匹配的 Provider",
"s_ae5090199fb03c14": "缓存读",
"s_ae5358b57d3a7ea5": "找不到路径:{{p0}}",
@@ -2040,7 +2023,6 @@ export const sourceCatalogs = {
"s_aedbf507f1cf0b1d": "删除失败:{{p0}}",
"s_aee88743413144a2": "刷新",
"s_af525b682a4bd1c5": "使用 ACECode 管理的登录和端点",
- "s_af654152fe716012": "当前安装没有被修改,可以稍后重新升级。",
"s_af731f5b91782a5d": "可选思考深度",
"s_af8043aec70ffeb2": "展开智能体",
"s_af8f7e7c5bb42c09": "下一题 (Tab)",
@@ -2091,7 +2073,6 @@ export const sourceCatalogs = {
"s_b52786ac0ce72cdb": "已选 {{p0}} / {{p1}}",
"s_b52b7d39abcf20e5": "搜索当前对话内容",
"s_b570c915f747718f": "暂无文件变更",
- "s_b5bb09526db21805": "快速找到、配置并维护聊天使用的模型连接。Provider 决定可用字段,预设保存后才会出现在会话模型选择器中。",
"s_b5e60c3f81558ada": "沙箱内命令是否可以访问网络",
"s_b5e6b5180f25e0d7": "工作区列表已变化,请重新拖动排序",
"s_b5f241af1539b4b8": "未作答",
@@ -2221,7 +2202,6 @@ export const sourceCatalogs = {
"s_c02fdcf208b3e472": "删除规则",
"s_c03b9deb55408bc4": "页面进程已停止",
"s_c043eb82f9332c31": "正在打开连接…",
- "s_c044ecbe7fe9c678": "仅显示主题色箭头",
"s_c051216ed509001e": "在 Telegram 里搜索 @BotFather(认准蓝色认证标记),发送 /newbot。",
"s_c05f7cd6ed3587a2": "飞机",
"s_c0866a5a020be7a3": "彻底删除会话",
@@ -2232,7 +2212,6 @@ export const sourceCatalogs = {
"s_c0f199171e53c7b1": "查看探测结果",
"s_c1d514e0ffe641b8": "定时任务,归档",
"s_c1dc0536f72cd0e1": "尚未安装模拟器组件:请先打开一次 Xcode,或运行 xcodebuild -runFirstLaunch",
- "s_c1e371336945b78e": "bash / npm / git 等 Agent 会逐步确认",
"s_c1e98d265c56af6b": "暂时无法确认系统授权,请完成授权后刷新状态;必要时重新启动 ACECode",
"s_c1f563ebdb29c100": "已创建分支会话",
"s_c2144b0b3814435e": "个可用",
@@ -2260,12 +2239,14 @@ export const sourceCatalogs = {
"s_c3b8f744ed2c55bf": "JSON 格式错误:",
"s_c3e1b26c451e8221": "添加事件「接收消息 v2.0」(im.message.receive_v1)。",
"s_c3e5589604083870": "暂无插件",
+ "s_c3ea992142a430ee": "请帮我探索当前项目或问题。先阅读相关资料,梳理关键结构、调用关系和已有结论,用具体证据解释你的发现,并列出最值得继续调查的问题。",
"s_c3f989b790daab98": "后台服务暂不可用,请稍后重试",
"s_c43f153cb1be5ca5": "等成员回报",
"s_c4ae8fa5a16b5a75": "跳过所有工具权限确认",
"s_c4c5b649cbf25fbb": "记忆与上下文",
"s_c52c7fbed621e8ff": "批注 {{p0}}",
"s_c52e712d492eab77": "添加专家",
+ "s_c55c2446bebb1524": "连接失败时请检查端口放行;公网访问请使用 VPN 和 HTTPS。",
"s_c56a5bb657de8f96": "更早",
"s_c58129c7de6206bc": "拖动排序,也可按 Alt+上/下方向键",
"s_c58140e6cf837851": "估算",
@@ -2282,14 +2263,12 @@ export const sourceCatalogs = {
"s_c6b0f88f90bc06b5": "模型服务返回错误,请检查密钥和模型配置",
"s_c6b2b6e8c8d95676": "选择工作区",
"s_c6f305e9b180323b": "参考",
- "s_c6f66a03ad3b9890": "升级期间可以继续查看当前页面,请勿重复启动升级。",
"s_c705d4ec7b975bf7": "ACECode 无法自动重启",
"s_c71200b5952e3781": "文件路径",
"s_c7145f1337d8e781": "最大轮次",
"s_c71fd5f1e6e2f51e": "保存工作区顺序失败,请重试",
"s_c7266ddd7b28d9d6": "附件超过 25MiB,且无法压缩到限制内",
"s_c726f6815a8fe56d": "文件与代码",
- "s_c729477a05a753e2": "用于 Agent 命令和控制台",
"s_c731603dc0d88843": "从opencode导入会话",
"s_c75aa06133eec97a": "Copilot 已退出",
"s_c771248e511fbf93": "打开",
@@ -2322,7 +2301,6 @@ export const sourceCatalogs = {
"s_ca3b53e14e9b3c6f": "此消息不可分叉(无 ID)",
"s_ca4353258c1aa8b6": "智能添加定时任务",
"s_ca478c91d3839a44": "输入 API Key",
- "s_ca48ad24f64e90a1": "协助用户总结当前会话的摘要",
"s_ca4d973c0b006b75": "停止",
"s_ca6343cddb75f289": "生成测试图片失败,请检查连接和模型名称",
"s_ca65e0b0c05810ab": "正在连接模拟器",
@@ -2372,7 +2350,6 @@ export const sourceCatalogs = {
"s_ce70cddf9261401d": "回到当前输入框,保留草稿",
"s_ce79d2e77461ae05": "使用默认路径",
"s_ce99adb25bdca08b": "下次退出时将停止后台进程",
- "s_cea5fddb03aa0275": "来自升级服务的历次版本说明",
"s_ceb33e0328a6f776": "保存定时任务失败",
"s_cec67948278c164e": "语音服务返回的内容无法识别,请检查接口协议。",
"s_cec844f175819030": "读取审计日志失败",
@@ -2427,7 +2404,6 @@ export const sourceCatalogs = {
"s_d33995ad669c80cf": "创建频道",
"s_d33e249955c846e3": "正在安全停止升级,请稍候。",
"s_d35ab580f555345e": "停止请求发送失败,连接恢复后请重试",
- "s_d381ac2d678d3f8e": "点击窗口右上角关闭按钮时执行的操作",
"s_d3948715580cffe1": "撤销本轮文件改动",
"s_d3d0230eabdd5d3b": "下载并应用",
"s_d3d2646b806c3889": "用户确认",
@@ -2465,6 +2441,7 @@ export const sourceCatalogs = {
"s_d63dfe4f7dad7694": "当前浏览器无法下载主题包",
"s_d6602c9dcd1af98c": "自动刷新运行状态",
"s_d681c6e2947ae79b": "前进",
+ "s_d68772c02ccca226": "请和我一起推敲一个方案。先明确问题、目标与约束,比较可行选择的收益、代价和风险,指出关键假设,再给出建议与下一步行动。",
"s_d68bb14bb86a551b": "新建专家选项",
"s_d696e50022d12267": "消息自动折叠",
"s_d69766c856685234": "新手指引",
@@ -2472,6 +2449,7 @@ export const sourceCatalogs = {
"s_d6b75ab7a55790a6": "语音识别服务返回错误,请稍后重试。",
"s_d6cd7e5f2a8a82cf": "这条记忆已不存在,请刷新列表",
"s_d6d20f05ed0f96e0": "单次执行时间",
+ "s_d6e2571c9f9c18e5": "刷新超时,请按 Ctrl+Y 重试",
"s_d6e882143a3c3b83": "扫码创建机器人",
"s_d6f766f2adf53227": "排队中",
"s_d6feded758aed3eb": "取消升级",
@@ -2508,7 +2486,6 @@ export const sourceCatalogs = {
"s_d9c35f6c3c4deeaa": "(空目录)",
"s_d9f02c7caa8a847d": "保存自定义指令失败:",
"s_da336fdc0dbd1818": "上一步",
- "s_da3749e80938db7f": "指针颜色跟随当前主题色",
"s_da721af2e6cd06db": "在独立的 Git worktree 中运行此定时任务",
"s_da83883e170a2865": "其他工具",
"s_da8649278bb4f113": "选择 shell",
@@ -2519,12 +2496,10 @@ export const sourceCatalogs = {
"s_dae661d17c2dd8f0": "空闲",
"s_daed57d0aa9c2d71": "批准计划",
"s_daf5103f90cc7d3a": "重新扫描",
- "s_dafba6cd167364a4": "新会话会读取已保存的记忆,并可以用记忆工具记录偏好和经验",
"s_dafe124c3fa3fb9b": "侧边聊天问题",
"s_db00f2c9bb3f3dc3": "已中止",
"s_db081fb208a547ef": "复制可见输出",
"s_db0fb23d463045a2": "请求超时,请重试",
- "s_db198551654d049e": "Agent 记下的记忆与记忆摘要整合出的记忆,按最近更新排列;点击条目可以查看和编辑。",
"s_db4740e9d8c8e52b": "移除浏览器上下文",
"s_db658bd2e4597c10": "移除工作区失败:",
"s_db8630211fe3934a": "原生设置不可用",
@@ -2574,7 +2549,6 @@ export const sourceCatalogs = {
"s_deeceedbed503b78": "每日代码健康巡检",
"s_df0ee0cbc554323b": "相对路径从当前工作区逐级向上查找;绝对路径和 ~ 路径作为全局目录。支持 ${ENV} 环境变量。",
"s_df1af41085bbfa2c": "模拟器设备",
- "s_df359a6a54eab051": "通过配置和已启用的插件管理生命周期钩子。",
"s_df3d58c7d84b85f2": "设置",
"s_df4c0ce7af6b33d1": "请选择要导入的 opencode 会话",
"s_df6aca03747ecd4b": "爪印",
@@ -2618,6 +2592,7 @@ export const sourceCatalogs = {
"s_e2469247115761e9": "默认画质",
"s_e26740162fbcb387": "将排队消息插入当前回合",
"s_e2780ee23078ea51": "{{p0}}{{p1}}请求使用 ACECode,到设置 > 消息通道处理",
+ "s_e283ee273f6e29f4": "仅在对应工作区生效。",
"s_e2a17a075548001b": "主题风格",
"s_e2a76ef1f12e147f": "终端",
"s_e2ad5101dbde2f62": "展开/收起左侧项目栏",
@@ -2719,6 +2694,7 @@ export const sourceCatalogs = {
"s_eb4c406398df4d73": "暂无工作区用量",
"s_eb557068ab4833dd": "无法连接 Copilot 模型服务",
"s_eb55f0980fa90bac": "等待你回答",
+ "s_eb7db838d3b73653": "仅在窗口失焦时通知",
"s_eb92486a46895884": "现在可以私信机器人,或在服务器频道里 @ 它。第一次在频道里使用需要在设置页批准。",
"s_eb93045599c0e25c": "进入 Plan 模式",
"s_ebb240a1930e8bfb": "搜索图标",
@@ -2726,6 +2702,7 @@ export const sourceCatalogs = {
"s_ebd8027daae319aa": "搜索会话、工作区和设置",
"s_ec7a11c7061bc164": "正在迁移工作空间…",
"s_ec869826ec67f839": "Agent Browser 的部分浏览器数据未能复制,重启后可能需要重新登录",
+ "s_ec99e5c45d648fd6": "更新失败",
"s_ecdf5ea03d83b916": "line messaging api channel secret webhook cloudflared 机器人 im channel 消息通道 连接 取消连接",
"s_ece45f7651c8c206": "Agent Browser 桌面桥不可用",
"s_ed100f18801746c9": "命令规则已保存",
@@ -2754,7 +2731,6 @@ export const sourceCatalogs = {
"s_eeef861b87676f3f": "地球",
"s_eef926034e070daa": "读取记忆失败",
"s_ef01449d86012a4f": "分叉到新会话",
- "s_ef1ba75237bde278": "开启后会启动独立的反向代理进程,daemon 仍仅监听 127.0.0.1",
"s_ef2e03c779d7a74c": "看图",
"s_ef4145d8a3012ad3": "写入规则文件:{{p0}}",
"s_ef4567cabc110c5b": "加载钩子失败:",
@@ -2797,13 +2773,13 @@ export const sourceCatalogs = {
"s_f2aac0ab859ff5be": "主题包",
"s_f2b4237b35f0b3b2": "查看会话",
"s_f2bdc88464c51c2e": "显示 API Key",
+ "s_f2d284240c13599a": "保存更新设置失败",
"s_f2d964d3dc5862d1": "超时:",
"s_f2df9f7ce435d9d6": "通道数据文件异常,详情见日志。",
"s_f2f491398295b1b9": "莲花",
"s_f323fc507d585565": "最小化到托盘",
"s_f335420fcda22e54": "该文件格式暂不支持预览",
"s_f33c31866a8c335e": "已拒绝 {{p0}}",
- "s_f34ed7e8755e9bbf": "选择一位桌面伙伴,跟随您的会话一起工作。",
"s_f36f1a90b64f69e5": "Node.js 工具",
"s_f37b8e7538b116da": "展开{{p0}}",
"s_f39f410e0b493a67": "跳过指引",
@@ -2815,6 +2791,7 @@ export const sourceCatalogs = {
"s_f4208226371456cc": "工作中",
"s_f44fb4a283b6bfac": "当前工作区 {{p0}}",
"s_f45f45184414bd14": "本次会话授权额外权限",
+ "s_f46aa7f4bceca841": "更新已完成,重启后生效",
"s_f48d334495f6e4f4": "桌面",
"s_f4ad2bb421a3f324": "权限模式",
"s_f4b31e37ac94ca55": "新路径不能包含当前工作空间",
@@ -2885,10 +2862,8 @@ export const sourceCatalogs = {
"s_f944229395fb16af": "操作不可用",
"s_f956a251fa3eee8d": "无法保存主题包,请检查目标位置和写入权限",
"s_f9870fade6c7948e": "刷新授权状态",
- "s_f988a671d6f2be8c": "AI主题",
"s_f99be5ad73ad618a": "删除定时任务“{{p0}}”?运行记录也会一并删除。",
"s_f9c08b25e6d154cb": "正在取消升级",
- "s_f9c4971818b3037a": "会话、记忆、技能等数据的存储位置",
"s_f9d19345a067cbe1": "中断",
"s_fa153621ba3498cf": "停止当前会话执行",
"s_fa2ff3c379a7c988": "查看全部",
@@ -2918,7 +2893,6 @@ export const sourceCatalogs = {
"s_fba11e3e139dcf31": "完整端点 URL",
"s_fbb6b7889fcc82f5": "配置文件无法解析,已按默认值显示:",
"s_fbb94be3d76b4721": "时间:{{p0}}",
- "s_fbcff96ec20552d1": "问“这个函数在哪里被调用”",
"s_fbd1ecdc5137fe6f": "命令安全",
"s_fbd5fa82e67b1a8b": "{{p0}} 这类前缀不能放行(解释器 / shell / rm / sudo 可以执行任意内容)",
"s_fbfa7054903fbd81": "当前会话不可用",
@@ -2927,6 +2901,7 @@ export const sourceCatalogs = {
"s_fc340cb578c2c3ec": "条目不能为空",
"s_fc455d618fecf57f": "Provider 文档",
"s_fc50a99caae0cddc": "跳过",
+ "s_fc746bfacfbf65ac": "闲置时自动整理记忆,会额外调用模型。",
"s_fc9cb8bc5414d5b3": "网站关闭了当前连接,请重新加载页面。",
"s_fca3398d38668a75": "· 模型不可用",
"s_fcac6c9df3a773c4": "Copilot 登录已开始,验证码已复制",
@@ -2946,7 +2921,6 @@ export const sourceCatalogs = {
"s_fdd200f5dbdb26fc": "选择 Agent 显示多少技术细节",
"s_fddeb12861d6d200": "按服务商支持的接口选择;需要 Responses 的模型请在此切换。",
"s_fdfc8be6c3e9fa53": "问一个不打断当前任务的问题…",
- "s_fe0ba53d223b301c": "更新服务返回状态 {{p0}}",
"s_fe6d0b07c1c680f6": "主理人:",
"s_fe7bcf6cde7c28f4": "在 worktree 中开始",
"s_fe83e46159226601": "展开命令详情",
@@ -3005,7 +2979,9 @@ export const sourceCatalogs = {
"s_02bcdbc5a1453cb0": "copy relative path",
"s_02c9e52a00e2b85a": "Input/output token and usage records of each model",
"s_02d9879748ba0ee6": "The theme package has changed. Confirm the new download size.",
+ "s_030fdb9a62594f11": "Writing",
"s_03189d028a4d04fd": "Exported {{p0}}",
+ "s_035b616cb7fbea92": "Automatic update failed. Click to retry",
"s_037f4fb77771bd21": "No configurable local tools",
"s_03b4c3a9f038e956": "Last hour",
"s_03ea92a14ed4272e": "Could not confirm Computer Use settings. Retry to check the current state.",
@@ -3027,7 +3003,6 @@ export const sourceCatalogs = {
"s_05a531a083e68807": "Copy {{p0}} verification code {{p1}}",
"s_05abefa30a624a8a": "Dispatching a task",
"s_05ce3aa87cd46703": "Use the most suitable Xcode that was found",
- "s_05df17e0e401d645": "Ask Agent to refactor, fix bugs, and add tests",
"s_05efef8465e0c666": "git response timeout (the warehouse may be large)",
"s_05fdf30411d98d32": "Sandbox",
"s_0604cd82b64752a0": "No confirmation channel",
@@ -3071,7 +3046,6 @@ export const sourceCatalogs = {
"s_08d43bdc4dcb6253": "Update available",
"s_08e5a3f74d4c6894": "Scan with QQ on your phone and follow the prompts to create and authorize the bot",
"s_08e9d9ef7d382947": "writes only",
- "s_08f10fb5ebba9e65": "Use dark backgrounds; turn it off to use light backgrounds",
"s_08f933805d72af91": "Loading simulators",
"s_092d1bb8c3f5739e": "Browse up from an empty input; move down while browsing history",
"s_0970eb5b921e82c5": "{{p0}} line",
@@ -3145,6 +3119,7 @@ export const sourceCatalogs = {
"s_0e0163080d2e4ad4": "Unable to get file path",
"s_0e0c2d7597ef508d": "Missing",
"s_0e1a7040390fb03e": "New project",
+ "s_0e29fc5caee98f07": "Applies to newly started instances only.",
"s_0e700e0f522f37ff": "Comparison baseline:{{p0}}(only switches the viewing object, not checkout)",
"s_0e993148f8f5d3d2": "The Grok sign-in is invalid. Connect again.",
"s_0eab58baf452449c": "Enter a full absolute path, such as C:\\Users or /home",
@@ -3157,7 +3132,7 @@ export const sourceCatalogs = {
"s_0f673a156d4bf981": "On · {{p0}}",
"s_0f7f6afb91e8922f": "Remove reference",
"s_0fbaf5c44a3d5a28": "Run this session in a separate git worktree (created when the first message is sent)",
- "s_0fc55068f541c2cb": "The upgrade installation is complete, please exit completely and restart ACECode",
+ "s_0fc55068f541c2cb": "Update installed. Fully exit and restart ACECode",
"s_0fc80b1f2c00b1cd": "File change denied",
"s_0fdada0fc86aed7f": "Copy failed:",
"s_0fdadddc66f59cee": "This page cannot access the microphone. Use ACECode Desktop, localhost, or HTTPS.",
@@ -3210,7 +3185,6 @@ export const sourceCatalogs = {
"s_13835a85b09dc397": "relative to workspace",
"s_13a46616e16fe5e6": "unknown error",
"s_13df8c848dbeb590": "context message",
- "s_13e0991638226a6d": "Allow the agent to view windows and use the mouse, keyboard, and app controls",
"s_13f6a415ac664be2": "Detect the actual model list from this provider",
"s_140b07fe3f9f7d48": "Download size:",
"s_1414769bed7c6551": "Sampling parameters (optional)",
@@ -3297,6 +3271,7 @@ export const sourceCatalogs = {
"s_1a85c9ae73a7f70e": "Replace {{p0}} line →{{p1}} line",
"s_1a9d20be0eb09509": "Single ·{{p0}}",
"s_1aa65c55c6cc6163": "Device verification code",
+ "s_1ac8f10196da4e5e": "Could not refresh home prompts. Check your model settings, then press Ctrl+Y to retry.",
"s_1ae4451e51587ed6": "The main checkout gained {{p0}} change(s) outside the worktree while this run executed",
"s_1ae6b0a0f8266382": "Close window",
"s_1ae7a34c1452785e": "Markdown WYSIWYG editor",
@@ -3467,6 +3442,7 @@ export const sourceCatalogs = {
"s_28aacfb2e3d8d395": "Expert group name",
"s_28b41a678b1e4c3e": "It is currently the latest version and does not need to be upgraded.",
"s_28bbfc2adebfe4c2": "JSON syntax error",
+ "s_28db50cbd80be382": "Create theme with AI",
"s_28e799b249e6bfa7": "Completely delete selected conversations",
"s_28ec083a352028c9": "No changes",
"s_28ed6de0ee4b4501": "Waiting for user",
@@ -3530,6 +3506,7 @@ export const sourceCatalogs = {
"s_2f067f4185b25551": "Weekly {{p0}}{{p1}}",
"s_2f53c8bcb2808922": "An execution rule requires approval for this command",
"s_2f5f1d6fbfb061ed": "Not set",
+ "s_2f7836ee07547f93": "Help me draft a document. Use the current context to identify its purpose, audience and key content. Ask for any essential missing information, then write a clear first draft that I can revise.",
"s_2f80fc3f9ffba0a9": "The model list has changed. Refresh it and reorder again.",
"s_2f9381fe3414f405": "Xcode path",
"s_2f9daa828907b93f": "delete",
@@ -3567,6 +3544,7 @@ export const sourceCatalogs = {
"s_326d1d537e72f82d": "Starting or stopping reverse proxy",
"s_327f00da9fc2ec36": "Hide details panel",
"s_32a202d6fed3a1da": "Create a new task in this workspace",
+ "s_32c8d373afe08328": "Execution",
"s_33281591669db79f": "Product delivery expert team",
"s_33360315a592f985": "interval unit",
"s_333eda18f13dbaad": "Available when speech recognition is enabled; stopping inserts the transcript",
@@ -3582,10 +3560,9 @@ export const sourceCatalogs = {
"s_342292808d6c0954": "Show or hide the file and preview panels",
"s_34486c598efd2582": "Provider detection failed",
"s_34492b04ea0ccbcc": "Allow the application shown by macOS for this ACECode launch, then return and refresh permissions.",
- "s_346114238816edc2": "Applies globally. New desktop instances can run alongside existing ones when enabled. Turning this off leaves open instances running.",
"s_34615a3c7580c3e4": "Search task loading failed",
"s_348f1cf1243e74bc": "I see",
- "s_349424f8eda35375": "Configure the built-in tools available to the agent.",
+ "s_3495b529912ceb00": "Frequent changes reduce prompt cache hits.",
"s_349706d62cafa56d": "Exit ACECode",
"s_34a8a93ff639915f": "Pause goal",
"s_34b3cac2a496d263": "Model catalog",
@@ -3645,7 +3622,6 @@ export const sourceCatalogs = {
"s_39b068ea492a4f9a": "Model capability label is invalid",
"s_39b1c2253dc352a0": "Previous question",
"s_39c737c10145eb96": "The current page has no console logs yet",
- "s_3a020b58cfeb13b3": "Let ACECode remember your preferences, feedback and project context across sessions. You can also use /memory in a session to view and manage memories.",
"s_3a045c322efabe1d": "No effective {{p0}} avatar",
"s_3a1abf3422d22d7d": "Detecting…",
"s_3a3b44c38e795b98": "Could not load memory settings",
@@ -3656,13 +3632,11 @@ export const sourceCatalogs = {
"s_3ac23fa27c7b0894": "Blank fields inherit model settings. Each subtask can override them when started.",
"s_3ae030dd220465c9": "Work area usage",
"s_3afa87e94673b0d1": "Dispatch failed: {{p0}}",
- "s_3b002388661e94cb": "Switch the color style of the current theme",
"s_3b16fa8c3a8623e9": "Mute theme sounds",
"s_3b6fefc50febace4": "Hour",
"s_3b7e27cb4379da3c": "Working",
"s_3b8f1538e0895e4d": "Briefly describe when it is appropriate to dispatch this component",
"s_3badb01486cd5de8": "Migration copies your existing data. Restart ACECode when finished.",
- "s_3bc19f21617f0a86": "Generate images from descriptions or edit existing images",
"s_3bceaa431f3153d5": "Loading status",
"s_3be2c9604a690f97": "The side chat connection timed out. Try again.",
"s_3c2d4d2fcc58fb6d": "Scroll recents down",
@@ -3720,9 +3694,7 @@ export const sourceCatalogs = {
"s_402c881d6d0b5320": "Attachment message",
"s_4036cb031bb7397e": "worktree baseline branch",
"s_4048d8cd52d1ca52": "Model {{p0}} is in use by a session and cannot be deleted yet",
- "s_40540dfc29cbcd2d": "Run multiple ACECode desktop instances to develop and debug ACECode.",
"s_4058befef3a6363e": "Waiting for user to process permission request",
- "s_4088127b773ce83c": "Edit code",
"s_40ab0013082f8076": "High",
"s_40cc8bac959395ae": "(Multi-select)",
"s_40cfdadfe78e6f05": "Loading memory summarization status…",
@@ -3733,7 +3705,6 @@ export const sourceCatalogs = {
"s_410a8e8a6bf253ac": "cut",
"s_4115d63bd5497d26": "Integrations",
"s_412c8bdb63028865": "Click the Add button on the right of the Workspaces heading and select an existing local code folder to add it to the sidebar. The tour only explains this action; it does not open the folder picker.",
- "s_4136ed8160d39565": "Use Skills",
"s_41623c6b19ff5c83": "Upgrade service",
"s_416deb8c2fd2287e": "These chats are already referenced or are the current chat",
"s_41abc174fa444bb5": "Saved. The daemon needs to be restarted to take effect.",
@@ -3745,7 +3716,6 @@ export const sourceCatalogs = {
"s_426c18d792670d65": "Overwrite existing profile",
"s_4271f3813edcc2d3": "This theme version already exists with different content. Update the version and export again",
"s_4275e28d460a45af": "Failed to create project, please try again later",
- "s_42781f603fa4416c": "ACECode desktop / TUI / Daemon same version number",
"s_4296d4f340dadc15": "Audit log cleared",
"s_42e1b73173a3b1d4": "Could not confirm the expert switch status: {{p0}}",
"s_432a6c3328243eee": "Could not reach the xAI device authorization service",
@@ -3769,7 +3739,6 @@ export const sourceCatalogs = {
"s_43fcef33a825715a": "Paste the token below. Once it is verified, use the invite link to add the bot to your server.",
"s_43fd0fc1e9ae1edb": "Name conflict resolved",
"s_441babd52ac35c3b": "The agent can only operate allowed simulators. Other devices ask for confirmation the first time they are used.",
- "s_4430768f253a5d6d": "Enable tool rewrite for subsequent model requests",
"s_443cb29217829285": "Review specialties and dispatch to a new task, or manage your own experts and expert teams.",
"s_44455611b9108b91": "Advanced settings",
"s_445fad5b93c9ad4f": "Failed to save {{p0}}",
@@ -3789,7 +3758,6 @@ export const sourceCatalogs = {
"s_45c05be574029a4a": "macOS blocked this connection",
"s_45da2d9c85d15628": "Add folder",
"s_45e03e436ed0f697": "Execution time",
- "s_460429decdefd286": "Notify only when a main task finishes while the ACECode window is unfocused",
"s_460b3574e4bdb8ca": "Not available",
"s_460d648dec104c7e": "Terminal type",
"s_4647d9a5b4eea173": "Memory is unavailable for the current workspace",
@@ -3807,13 +3775,13 @@ export const sourceCatalogs = {
"s_478211dd029aa604": "The site security certificate is invalid or does not match this address.",
"s_47b50c43e14ca6ba": "Scanned",
"s_47c99fb0ddf2cffa": "{{p0}} – {{p1}}: {{p2}} tokens used in total",
+ "s_47ceaf502c5a05d6": "New version",
"s_47f22728ee1600ed": "Status message:",
"s_481acc2c1413db38": "Image generation",
"s_482521a81877f106": "Could not establish a secure connection",
"s_4835a3fdbd55cde1": "Search by name, Tag, or expertise",
"s_483e7e8d6cf19c2c": "Loading failed:{{p0}}",
"s_4869fd312a2bf2cf": "Close memory editor",
- "s_4871613a44be8d7a": "Where to open files and folders by default",
"s_487422ef0c4e25b6": "Home title",
"s_48807d75530c1b03": "This queued message cannot be found",
"s_4888c0a26e905c1f": "User {{p0}}",
@@ -3824,6 +3792,7 @@ export const sourceCatalogs = {
"s_4927a53bcc886afb": "Loading…",
"s_492edaa646797862": "Uses the existing ACECode GitHub device login and managed endpoint.",
"s_49504fdfc1b5a604": "Search shortcuts",
+ "s_495a028100eaed83": "Help me complete the next task. Confirm the goal and current state, outline a few steps, then make the necessary changes and verify the result. Preserve unrelated existing changes.",
"s_496124e1bfae76ff": "Delete “{{p0}}”? This removes only the profile and keeps all conversation history.",
"s_497c1a65b2f98a63": "Demo preview. Your office will follow your conversations.",
"s_499c729eb09aa2a6": "Context window tokens",
@@ -3871,7 +3840,6 @@ export const sourceCatalogs = {
"s_4d2b19065afe4b07": "LSP is on:{{p0}}",
"s_4d3f6a081ea86097": "Complete deletion failed:",
"s_4d5193ca941bf930": "Downloading {{p0}}%",
- "s_4d5262c1b9621225": "Play theme animations such as the startup scan, style-switch transition and neon flicker",
"s_4d6a4bc105dd3f07": "Shared",
"s_4d6b5440965402ae": "Use the actions in the upper-right corner to get started.",
"s_4d7611fc1530dbaa": "Availability warning",
@@ -3977,7 +3945,6 @@ export const sourceCatalogs = {
"s_561817f0252936a9": "Allowed",
"s_562834f547655a37": "Searching code",
"s_56410fc65314dfb5": "Boot",
- "s_5653d97cdf569cb9": "Your system firewall, router, or cloud security group may still need to allow the proxy port. For public Internet access, continue to use a trusted VPN and configure HTTPS upstream.",
"s_56627c94a9decee6": "Maximum output tokens",
"s_566e5f182a63185d": "Compressing theme files…",
"s_5670da45d1d6e526": "App ID (starts with cli_)",
@@ -4001,7 +3968,6 @@ export const sourceCatalogs = {
"s_57cb3a025f981aca": "Global skills pagination",
"s_57d2858750d28004": "Palette",
"s_57e4fcb2dad50b5c": "Remote Web mode enabled",
- "s_57eb46dacc97defb": "Show the last active time on the right of each task row; when off, it is still available in the hover card",
"s_5804b9cca84304ff": "Clear side chat",
"s_58210afb340c2c5e": "The link is valid for 10 minutes and works once.",
"s_5835048333dad1d6": "Select custom answer",
@@ -4053,7 +4019,6 @@ export const sourceCatalogs = {
"s_5d31027802e2d944": "The current content has not changed",
"s_5d344e9b4013d978": "Open keyboard shortcuts and focus search",
"s_5d3f86d033dabffe": "Hooks",
- "s_5d53a39c33ae87b8": "Off: new sessions get no memory context and no memory tools; existing memories stay on disk",
"s_5d5cbbbef4de8fdc": "You can directly enter one or more model IDs (separated by commas), or you can select multiple models after detection.",
"s_5d682392c70fb245": "Expert team",
"s_5d6e4b9d0beac865": "The proxy server requires authentication",
@@ -4088,6 +4053,7 @@ export const sourceCatalogs = {
"s_5ffdb67a4142cc22": "Screenshots cannot be saved in this environment",
"s_605a534da98e42ec": "Token activity view",
"s_60b83f21d9d5d703": "Group {{p0}}",
+ "s_6106f0a12583a334": "Refresh failed",
"s_610a30de692b91b0": "Forward your public address to http://127.0.0.1:{{p0}} on this computer. Setup continues automatically once it is reachable.",
"s_610e4e820d6881ea": "Completely delete session \"{{p0}}\"? This action cannot be undone.",
"s_6135d4159e892541": "Remove",
@@ -4101,6 +4067,7 @@ export const sourceCatalogs = {
"s_622de9749cb55343": "Could not load developer settings",
"s_624b5799ebd036b0": "No workspace",
"s_6258a2c6f9c89143": "Copy all",
+ "s_625cea954ac1e717": "Explore a question",
"s_62711127c9ee73df": "Greater than 0, at most 1",
"s_62731d95bc805803": "Exporting…",
"s_6278c2f645ab3982": "Public address",
@@ -4113,7 +4080,7 @@ export const sourceCatalogs = {
"s_6367692be2330eed": "Could not select a web element",
"s_63ba4b731e56c1a3": "Enter to resume the queued messages · Shift+Enter for a new line · Up/Down to browse history",
"s_63bf201cb2da76e7": "Expert can enable",
- "s_63d113a913a24b08": "The upgrade installation is complete, you can restart ACECode immediately",
+ "s_63d113a913a24b08": "Update installed. You can restart ACECode now",
"s_63d6b47116de363c": "Global",
"s_63d90d977348ab1f": "copy",
"s_63dcd2f732d85a92": "Could not load Computer Use settings",
@@ -4122,7 +4089,6 @@ export const sourceCatalogs = {
"s_641778357a5e8a41": "View configuration schema",
"s_643a1b94e9498780": "Trusted hook",
"s_64568afad9efa616": "Items per page",
- "s_6492494fbe805292": "Describe your idea, create your theme",
"s_64ded447c5bdd6ea": "Verify and continue",
"s_64e9f3e72b149b27": "This page exited unexpectedly. Reload it.",
"s_64fd6a54381b531c": "Couldn't get a QR code. Try again",
@@ -4143,20 +4109,17 @@ export const sourceCatalogs = {
"s_667b75d63a19b6b8": "Upload failed:",
"s_66809c2ae51ddf06": "Remove Tag {{p0}}",
"s_66b5df56cbaed245": "Profile name for Save as",
- "s_66d1628f97183e45": "Start predefined workflows from the sidebar",
"s_66f054d7bc356104": "Agent requests to enter Plan mode, first explores and writes the plan, and then changes the code after approval.",
"s_67035e6b7f82acdb": "Blue",
"s_670ec25af8419f48": "Keep",
"s_670fd17c931a0170": "The side chat history is too long to send another question.",
"s_6714ad665cd9dce7": "Top P must be greater than 0 and at most 1",
- "s_6730e89aac61dfb7": "Automatically extracts lessons from idle sessions and consolidates them into memories; uses additional model calls when on",
"s_67330e9d7654744c": "Missed",
"s_673bfd286541c87b": "Please enter the prompt word",
"s_6744b4c6a9aa0038": "Closed",
"s_674f9ac747ebfd69": "The upgrade has been installed, click to view restart options",
"s_67a1de4cea20e015": "Open model settings",
"s_67ee0d22e90a49f5": "Saved. Skill scan paths updated.",
- "s_68029f7769600f70": "Starts when the last side chat ends. After this time, returning to the session clears it. It stays visible while you are viewing it.",
"s_681139c14b0eea8e": "{{p0}} and others ({{p1}} in total)",
"s_6820f44987bb2ae6": "Retry sending",
"s_682d211b7142758a": "Modified",
@@ -4236,16 +4199,17 @@ export const sourceCatalogs = {
"s_6dd39868b7d587c7": "The current background service does not support the Security Center. Update ACECode, restart and try again",
"s_6e14496b435e5f7b": "Could not load iOS Simulator settings",
"s_6e1ba4bf187327e9": "Terminal program path",
+ "s_6e2295a41a1e0250": "Draft a clear document",
"s_6e382d9250f8dc10": "Enter manually",
"s_6e392a597af4abab": "memory summary model",
"s_6e55e0f207bfd2e0": "Run history",
"s_6e6aba42eba94da0": "Shared with agent",
"s_6e8017799e19b5a0": "Search detected models",
"s_6ec00e356283c830": "Delete “",
+ "s_6ed0bd0cf012e98b": "Think through a plan",
"s_6f35c88ed171bc46": "The model response timed out. Please retry",
"s_6f4b75ff28679ce4": "Flat list",
"s_6f5994e138557f90": "% used",
- "s_6f87682bc02532c6": "Each workspace can have an independent Skill, which only takes effect in the session of that workspace.",
"s_6f98bb98abfcd0c9": "Open the Feishu Open Platform, create a custom app, and add the Bot capability under Add features.",
"s_6f9c11640850a30c": "Could not clear the audit log",
"s_6fb9dd3555d1b3b0": "Confirm migration",
@@ -4256,7 +4220,6 @@ export const sourceCatalogs = {
"s_6ff4b7dec15fff71": "Collapse display",
"s_700b6c80d9543ebe": "Restart now",
"s_701aa5a8c70f1da6": "Drag to adjust preview panel width",
- "s_702abbca55759b5c": "Current desktop WebView / browser rendering core",
"s_703ed122eea2bf76": "Connection cancelled",
"s_704b1115408edd65": "Cannot connect to the speech service. Check its URL, port, and network.",
"s_705dc58bfaf7471b": "Automatic",
@@ -4290,6 +4253,7 @@ export const sourceCatalogs = {
"s_72e9b59aef42c6bd": "Locate and highlight the active file in the current workspace",
"s_72ec61d7855c86a1": "Request timeout (seconds)",
"s_7310f2c21a738bd0": "(previous)",
+ "s_736cff237d7d9255": "Automatic updates",
"s_737c725c1321d260": "Saved {{p0}}. Leave blank to keep it",
"s_73859d3cbb42c1c7": "Speech request timeout seconds",
"s_73952aa91a9366b5": "Switched to",
@@ -4313,7 +4277,6 @@ export const sourceCatalogs = {
"s_749daf601249f185": "Pasted text upload failed:",
"s_74a793aa497a96d8": "The Grok device authorization expired. Connect again.",
"s_74d0a87b32e08fcc": "Try asking me this",
- "s_74dfd40904def5a9": "Provide additional instructions and context for your project to ACECode",
"s_74e8c8c3682706c4": "Restore preview panel",
"s_74f5aee6f7a56856": "Workspace path",
"s_75248c2474f6ff5d": "Queued messages to be sent",
@@ -4356,7 +4319,6 @@ export const sourceCatalogs = {
"s_78161046757aa1f3": "Enter an annotation",
"s_784c2d76956e7faf": "The speech service is processing another recording. Try again later.",
"s_785e930b8f0e0dba": "Choose an empty directory",
- "s_7875c0f22b559b5e": "Model used to extract and consolidate memories; \"Current model\" uses the model the session is using",
"s_788db1cfec2a3db5": "Theme",
"s_789eabc8eb6e9d9f": "Edit {{p0}}",
"s_789f61dbfcc7a2d2": "Red",
@@ -4385,7 +4347,6 @@ export const sourceCatalogs = {
"s_7acf2ee49b4924ef": "Location: {{p0}}",
"s_7ad6bbf3f8508928": "Dismiss suggestion",
"s_7adc856731f31518": "Not run yet",
- "s_7af9ed362c1ed48d": "Tip: Custom instructions will participate in the prompt word context of each request, and frequent modifications may reduce the prompt word cache hit rate.",
"s_7afb72a4b5b21193": "Add workspace",
"s_7b00c3d69a0c48a5": "models",
"s_7b10b3966368f243": "no match",
@@ -4460,7 +4421,6 @@ export const sourceCatalogs = {
"s_8077a5279e43effd": "No global skills yet",
"s_8078f96cff000853": "Dispatched {{p0}}:{{p1}}",
"s_80891538e688b066": "Processing request",
- "s_80964c285e90e139": "Maximum iterations in one agent loop",
"s_80abe59040677140": "Review current changes\nHelp me design this feature",
"s_80e015bfff3865e5": "Clear log",
"s_80f8fbcfa0117633": "Zoom in",
@@ -4505,7 +4465,6 @@ export const sourceCatalogs = {
"s_843e5a96794f9e8c": "Testing generates one standard-quality image and consumes credits.",
"s_844b8cc8dff7c1d8": "default",
"s_84659f76de148bb0": "Go to file",
- "s_8472a7daca875078": "Directly edit JSON to configure MCP server connections (stdio/sse/http)",
"s_84941ac6e844e7af": "Show all",
"s_84a07f292f1f78cc": "fold",
"s_84a5571a1cc78c74": "Network access",
@@ -4563,7 +4522,6 @@ export const sourceCatalogs = {
"s_8949d6721460a7f8": "The model picker in the chat composer uses these profiles.",
"s_894e891e819e220c": "No simulators available",
"s_8968ac45a626bd2a": "session menu",
- "s_8974084a4afd2b6e": "Choose the interface color theme",
"s_8981949fd4a7d012": "The current Provider returns no available models.",
"s_8984825b34bd44e8": "Drag to reorder, or use the up and down arrow keys",
"s_898aa605dcbf3ec1": "Queue next item (Enter)",
@@ -4597,7 +4555,6 @@ export const sourceCatalogs = {
"s_8b96b96448b4343c": "Microphone settings",
"s_8bcc3e177f5f3b10": "Don't save",
"s_8be0ea36a9bba286": "Connecting…",
- "s_8c1ae715559b78bd": "Unable to get upgrade progress",
"s_8c5c96ae7d403826": "Allowed for the selected directory",
"s_8c803ec4ccd237fb": "The site cannot respond correctly right now. Try again later.",
"s_8ca30ef95071173d": "Forking to a new session",
@@ -4655,6 +4612,7 @@ export const sourceCatalogs = {
"s_9003b29bcd11bb17": "Batch operation: {{success}} succeeded, {{failed}} failed",
"s_901a6d728b8cb75b": "No bot configured",
"s_90253fc5741dc402": "Configure JSON (edit the mcp_servers section directly)",
+ "s_90550acef25a9ace": "Failed to load update settings",
"s_906502a6a28e4eaa": "Apply after importing",
"s_907fdb0785b5a284": "{{p0}} settings found",
"s_90835dd8d457ba5a": "Resume sending the queued messages (Enter)",
@@ -4700,6 +4658,7 @@ export const sourceCatalogs = {
"s_93b2b434d6e7eb40": "Simulator screen",
"s_93b93a43e96a3556": "Directory name:",
"s_93c78cfadc951d31": "Withdrawing",
+ "s_93fd8d2100096abe": "Applies to new chats. Disabling keeps existing memories.",
"s_94078a28716517ee": "Memory deleted",
"s_940c5ebd7fe94a7a": "Upgrade service URL",
"s_94222dcc2d741467": "Files could not be restored after theme deletion failed. Check the theme directory.",
@@ -4710,7 +4669,6 @@ export const sourceCatalogs = {
"s_946fb32dc6fbb463": "Changed file display",
"s_9477f35309b3d9f3": "This expert is provided by the current project and can only be viewed and dispatched.",
"s_947821324c97b7e5": "Command not recorded",
- "s_949cf55bb66e100d": "Plugins configured in config.json",
"s_94ab83f1fcb5b7b9": "Edit shortcut: {{p0}}{{p1}}",
"s_94c37ae7b384c7d9": "discover diagnosis",
"s_94f60d2252d6b686": "Page temporarily unavailable",
@@ -4759,9 +4717,11 @@ export const sourceCatalogs = {
"s_988f77b34abbcca2": "Cookies set to \"{{p0}}\"; saved cookies were cleared",
"s_98c4b439f2506ab2": "Available in the message input",
"s_98cbc356f0e61a08": "Add a provider configuration to manage it here.",
+ "s_98ceb57fd8c4192a": "Exploration",
"s_98fc221b10e87c21": "Create app",
"s_991bb7cfe5a81550": "Save changes",
"s_992b4416c9d23e96": "Reading models…",
+ "s_9952001846c63208": "Update settings connection changed",
"s_99587cf2ca773409": "Hold to record",
"s_9966d6b375133f89": "Paths must be absolute: {{p0}}",
"s_99731fe916c6b3bf": "You can now message the robot privately in DingTalk. Add it to a group and @mention it there; the first time needs approval in Settings.",
@@ -4780,6 +4740,7 @@ export const sourceCatalogs = {
"s_9ad0aac32ea304be": "Stop generating",
"s_9ae14f5b00594c4c": "Readable allow list",
"s_9afc6a4c62aea23f": "Requested additional permissions",
+ "s_9b48b5cf2345abd5": "Starts when the side chat ends. Clears on return after expiry; stays while viewing.",
"s_9b7824cefa1e8b16": "Discard changes",
"s_9b7cbee7afe5aee4": "Processed {{count}} chats",
"s_9b8cf7af48da71e0": "Step {{p0}} of {{p1}}",
@@ -4831,7 +4792,6 @@ export const sourceCatalogs = {
"s_9f9a95299a7588a3": "Desk globe",
"s_9fa08e87505a230d": "Select session {{p0}}",
"s_9fa433be4c58ee49": "Agent Browser",
- "s_9fa9840d7aa93802": "Theme-colored arrow with ACE at the bottom right",
"s_9fc9368cc2c409a3": "No plugins configured",
"s_9fcad048086de1f8": "${ACECODE_DATA_DIR} is the data directory; ${XDG_CONFIG_HOME} and ${XDG_CACHE_HOME} default to ~/.config and ~/.cache. Paths with other unset variables are skipped.",
"s_9fd56f7aae1ca61d": "Show hidden items",
@@ -4908,7 +4868,6 @@ export const sourceCatalogs = {
"s_a67fa4bcb68094b4": "There are no expert components matching the filter criteria",
"s_a686e64364525272": "Recent files",
"s_a6a4761cfcca4484": "Current path",
- "s_a6a4d8fa7f9120be": "Once enabled, background tasks and pending interactions will continue to run when you actually exit the desktop application.",
"s_a6dfb80406396953": "The current platform does not block reads, so this list has no effect (macOS / Linux only)",
"s_a6e1beaceb5bbafa": "No tasks or projects yet",
"s_a7310a28aa28faf4": "Failed to cancel update:",
@@ -4952,7 +4911,6 @@ export const sourceCatalogs = {
"s_aa6d8879058e32fb": "There are no file changes in this session",
"s_aa946ad4ec4c58f9": "API protocol",
"s_aa9e366f68d3d097": "Low",
- "s_aae110840475eea7": "Manage ACECode callable skill modules. After enabling, Agent can be used automatically in tasks.",
"s_aae17129468e10f4": "Blocked {{p0}} times · {{p1}}",
"s_aae897a12157cc7c": "ACECode native tools",
"s_ab2b8bcfda883522": "Allowed {{p0}}",
@@ -4977,9 +4935,9 @@ export const sourceCatalogs = {
"s_ac7246e3c8488218": "{{p0}} request(s) awaiting approval",
"s_ac7646d3e0e17e38": "Theme import preview",
"s_ac77953a1e064ed6": "Sending failed",
+ "s_ac84afc335ce8f26": "Complete a task",
"s_ac8e7ffd9e3458ab": "No sandbox is available; unknown commands require approval",
"s_acae259f56e1fc3e": "The browser page has been closed",
- "s_acb60e5e17fe5e0a": "The currently installed ACECode is the latest version.",
"s_acf6e8244ae05b33": "Page {{p0}} / {{p1}}, {{p2}} items",
"s_acfc4e74a650e7df": "Next step",
"s_acfc92e72a99cc1a": "Remote Web mode cannot be enabled in dangerous mode",
@@ -4989,7 +4947,6 @@ export const sourceCatalogs = {
"s_ad4c46728555aeb6": "Site not found",
"s_ad5aaf33d6ce9b8a": "Getting a QR code…",
"s_ae115efd1b2c2524": "Task aborted",
- "s_ae13d96782b88e57": "Explore the codebase",
"s_ae30451fc65b75de": "No matching providers",
"s_ae5090199fb03c14": "cache read",
"s_ae5358b57d3a7ea5": "Path not found: {{p0}}",
@@ -5007,7 +4964,6 @@ export const sourceCatalogs = {
"s_aedbf507f1cf0b1d": "Delete failed: {{p0}}",
"s_aee88743413144a2": "refresh",
"s_af525b682a4bd1c5": "Uses ACECode-managed authentication and endpoint",
- "s_af654152fe716012": "The current installation was not changed. You can update again later.",
"s_af731f5b91782a5d": "Available reasoning levels",
"s_af8043aec70ffeb2": "Expand the agent",
"s_af8f7e7c5bb42c09": "Next (Tab)",
@@ -5058,7 +5014,6 @@ export const sourceCatalogs = {
"s_b52786ac0ce72cdb": "Selected {{p0}}/{{p1}}",
"s_b52b7d39abcf20e5": "Search the current conversation",
"s_b570c915f747718f": "No file changes yet",
- "s_b5bb09526db21805": "Find, configure, and maintain chat model connections. The provider determines available fields, and a profile appears in the chat model picker only after it is saved.",
"s_b5e60c3f81558ada": "Whether sandboxed commands may access the network",
"s_b5e6b5180f25e0d7": "The workspace list has changed. Drag to reorder again.",
"s_b5f241af1539b4b8": "Not answered",
@@ -5188,7 +5143,6 @@ export const sourceCatalogs = {
"s_c02fdcf208b3e472": "Delete rule",
"s_c03b9deb55408bc4": "Page process stopped",
"s_c043eb82f9332c31": "Opening the connection…",
- "s_c044ecbe7fe9c678": "Theme-colored arrow only",
"s_c051216ed509001e": "In Telegram, search for @BotFather (look for the blue verified badge) and send /newbot.",
"s_c05f7cd6ed3587a2": "Plane",
"s_c0866a5a020be7a3": "Delete conversation completely",
@@ -5199,7 +5153,6 @@ export const sourceCatalogs = {
"s_c0f199171e53c7b1": "View detection result",
"s_c1d514e0ffe641b8": "Scheduled task, archive",
"s_c1dc0536f72cd0e1": "Simulator components are not installed yet. Open Xcode once, or run xcodebuild -runFirstLaunch.",
- "s_c1e371336945b78e": "Agent will request confirmation for bash, npm, git, and more",
"s_c1e98d265c56af6b": "Unable to confirm system permissions. Grant access and refresh, or restart ACECode if needed.",
"s_c1f563ebdb29c100": "Forked session created",
"s_c2144b0b3814435e": "available",
@@ -5227,12 +5180,14 @@ export const sourceCatalogs = {
"s_c3b8f744ed2c55bf": "JSON format error:",
"s_c3e1b26c451e8221": "Add the event \"Message received v2.0\" (im.message.receive_v1).",
"s_c3e5589604083870": "No plugins",
+ "s_c3ea992142a430ee": "Help me explore this project or question. Read the relevant material, map its key structures, call relationships and existing findings, explain the evidence, and identify the most useful questions to investigate next.",
"s_c3f989b790daab98": "The backend is temporarily unavailable. Try again later.",
"s_c43f153cb1be5ca5": "Waiting for members",
"s_c4ae8fa5a16b5a75": "Skip all tool permission confirmations",
"s_c4c5b649cbf25fbb": "Memory and context",
"s_c52c7fbed621e8ff": "Annotation {{p0}}",
"s_c52e712d492eab77": "Add expert",
+ "s_c55c2446bebb1524": "If the connection fails, check port access. Use a VPN and HTTPS for public access.",
"s_c56a5bb657de8f96": "earlier",
"s_c58129c7de6206bc": "Drag to reorder, or press Alt+Up/Down",
"s_c58140e6cf837851": "Estimate",
@@ -5249,14 +5204,12 @@ export const sourceCatalogs = {
"s_c6b0f88f90bc06b5": "The model service returned an error. Check the key and model settings",
"s_c6b2b6e8c8d95676": "Select workspace",
"s_c6f305e9b180323b": "Reference",
- "s_c6f66a03ad3b9890": "You can continue to view the current page during the upgrade. Please do not start the upgrade repeatedly.",
"s_c705d4ec7b975bf7": "ACECode cannot restart automatically",
"s_c71200b5952e3781": "File path",
"s_c7145f1337d8e781": "Maximum turns",
"s_c71fd5f1e6e2f51e": "Could not save workspace order. Please try again.",
"s_c7266ddd7b28d9d6": "Attachment exceeds 25MiB and cannot be compressed within the limit",
"s_c726f6815a8fe56d": "Files and code",
- "s_c729477a05a753e2": "Used for agent commands and the console",
"s_c731603dc0d88843": "Import session from opencode",
"s_c75aa06133eec97a": "Copilot has exited",
"s_c771248e511fbf93": "Open",
@@ -5289,7 +5242,6 @@ export const sourceCatalogs = {
"s_ca3b53e14e9b3c6f": "This message is not forkable (no ID)",
"s_ca4353258c1aa8b6": "Add scheduled task with AI",
"s_ca478c91d3839a44": "Enter API key",
- "s_ca48ad24f64e90a1": "Help summarize the current conversation",
"s_ca4d973c0b006b75": "Stop",
"s_ca6343cddb75f289": "Could not generate a test image. Check the connection and model name.",
"s_ca65e0b0c05810ab": "Connecting to the simulator",
@@ -5339,7 +5291,6 @@ export const sourceCatalogs = {
"s_ce70cddf9261401d": "Return to the message input and keep your draft",
"s_ce79d2e77461ae05": "Using default paths",
"s_ce99adb25bdca08b": "The background process will be stopped the next time you exit",
- "s_cea5fddb03aa0275": "Previous version notes from the upgrade service",
"s_ceb33e0328a6f776": "Failed to save scheduled task",
"s_cec67948278c164e": "The speech response could not be read. Check the API protocol.",
"s_cec844f175819030": "Could not read the audit log",
@@ -5394,7 +5345,6 @@ export const sourceCatalogs = {
"s_d33995ad669c80cf": "Create channel",
"s_d33e249955c846e3": "Stopping the update safely. Please wait.",
"s_d35ab580f555345e": "Could not send the stop request. Try again after reconnecting",
- "s_d381ac2d678d3f8e": "What to do when the close button in the top-right corner is clicked",
"s_d3948715580cffe1": "Undo this round of file changes",
"s_d3d0230eabdd5d3b": "Download and apply",
"s_d3d2646b806c3889": "User confirmation",
@@ -5432,6 +5382,7 @@ export const sourceCatalogs = {
"s_d63dfe4f7dad7694": "This browser cannot download the theme package",
"s_d6602c9dcd1af98c": "Automatically refresh running status",
"s_d681c6e2947ae79b": "Forward",
+ "s_d68772c02ccca226": "Help me think through a plan. Clarify the problem, goal and constraints, compare the benefits, costs and risks of viable options, identify key assumptions, and recommend next steps.",
"s_d68bb14bb86a551b": "New expert options",
"s_d696e50022d12267": "Automatically collapse messages",
"s_d69766c856685234": "Beginner's Guide",
@@ -5439,6 +5390,7 @@ export const sourceCatalogs = {
"s_d6b75ab7a55790a6": "The speech service returned an error. Try again later.",
"s_d6cd7e5f2a8a82cf": "This memory no longer exists. Refresh the list.",
"s_d6d20f05ed0f96e0": "Single execution time",
+ "s_d6e2571c9f9c18e5": "Refresh timed out. Press Ctrl+Y to retry.",
"s_d6e882143a3c3b83": "Scan to create bot",
"s_d6f766f2adf53227": "Queuing",
"s_d6feded758aed3eb": "Cancel update",
@@ -5475,7 +5427,6 @@ export const sourceCatalogs = {
"s_d9c35f6c3c4deeaa": "(empty directory)",
"s_d9f02c7caa8a847d": "Failed to save custom directive:",
"s_da336fdc0dbd1818": "Previous step",
- "s_da3749e80938db7f": "The pointer follows the current theme color",
"s_da721af2e6cd06db": "Run this scheduled task in an isolated Git worktree",
"s_da83883e170a2865": "Other tools",
"s_da8649278bb4f113": "Select shell",
@@ -5486,12 +5437,10 @@ export const sourceCatalogs = {
"s_dae661d17c2dd8f0": "idle",
"s_daed57d0aa9c2d71": "approve plan",
"s_daf5103f90cc7d3a": "Rescan",
- "s_dafba6cd167364a4": "New sessions read saved memories and can use memory tools to record preferences and lessons",
"s_dafe124c3fa3fb9b": "Side chat question",
"s_db00f2c9bb3f3dc3": "aborted",
"s_db081fb208a547ef": "Copy visible output",
"s_db0fb23d463045a2": "Request timed out. Please try again",
- "s_db198551654d049e": "Memories recorded by the agent or consolidated by memory summarization, most recently updated first. Click an entry to view or edit it.",
"s_db4740e9d8c8e52b": "Remove browser context",
"s_db658bd2e4597c10": "Removing workspace failed:",
"s_db8630211fe3934a": "Native settings are not available",
@@ -5541,7 +5490,6 @@ export const sourceCatalogs = {
"s_deeceedbed503b78": "Daily code health inspection",
"s_df0ee0cbc554323b": "Relative paths are searched from the workspace up through its ancestors; absolute and ~ paths are global. ${ENV} environment variables are supported.",
"s_df1af41085bbfa2c": "Simulator devices",
- "s_df359a6a54eab051": "Manage lifecycle hooks via configuration and enabled plugins.",
"s_df3d58c7d84b85f2": "Settings",
"s_df4c0ce7af6b33d1": "Please select an opencode session to import",
"s_df6aca03747ecd4b": "Paw print",
@@ -5585,6 +5533,7 @@ export const sourceCatalogs = {
"s_e2469247115761e9": "Default quality",
"s_e26740162fbcb387": "Interject queued message into the active turn",
"s_e2780ee23078ea51": "{{p0}}{{p1}} wants to use ACECode. Review it in Settings > Messaging channels",
+ "s_e283ee273f6e29f4": "Applies only to its workspace.",
"s_e2a17a075548001b": "Theme style",
"s_e2a76ef1f12e147f": "Terminal",
"s_e2ad5101dbde2f62": "Toggle project sidebar",
@@ -5686,6 +5635,7 @@ export const sourceCatalogs = {
"s_eb4c406398df4d73": "No workspace usage yet",
"s_eb557068ab4833dd": "Unable to connect to Copilot model service",
"s_eb55f0980fa90bac": "waiting for your answer",
+ "s_eb7db838d3b73653": "Notify only when the window is unfocused",
"s_eb92486a46895884": "You can now DM the bot or @mention it in a server channel. Using it in a channel the first time needs approval in Settings.",
"s_eb93045599c0e25c": "Enter Plan mode",
"s_ebb240a1930e8bfb": "Search icons",
@@ -5693,6 +5643,7 @@ export const sourceCatalogs = {
"s_ebd8027daae319aa": "Search conversations, workspaces and settings",
"s_ec7a11c7061bc164": "Migrating workspace…",
"s_ec869826ec67f839": "Some Agent Browser data could not be copied. You may need to sign in again after restarting.",
+ "s_ec99e5c45d648fd6": "Update failed",
"s_ecdf5ea03d83b916": "line messaging api channel secret webhook cloudflared bot im channel messaging connect disconnect",
"s_ece45f7651c8c206": "The Agent Browser Desktop bridge is unavailable",
"s_ed100f18801746c9": "Command rules saved",
@@ -5721,7 +5672,6 @@ export const sourceCatalogs = {
"s_eeef861b87676f3f": "Globe",
"s_eef926034e070daa": "Could not read memory",
"s_ef01449d86012a4f": "Fork to new session",
- "s_ef1ba75237bde278": "Starts a separate reverse-proxy process while the daemon continues listening only on 127.0.0.1",
"s_ef2e03c779d7a74c": "Look at the picture",
"s_ef4145d8a3012ad3": "Save to rules file: {{p0}}",
"s_ef4567cabc110c5b": "Loading hook failed:",
@@ -5764,13 +5714,13 @@ export const sourceCatalogs = {
"s_f2aac0ab859ff5be": "Theme package",
"s_f2b4237b35f0b3b2": "View session",
"s_f2bdc88464c51c2e": "Show API key",
+ "s_f2d284240c13599a": "Failed to save update settings",
"s_f2d964d3dc5862d1": "time out:",
"s_f2df9f7ce435d9d6": "The channel data file has a problem. See the log for details.",
"s_f2f491398295b1b9": "Lotus",
"s_f323fc507d585565": "Minimize to tray",
"s_f335420fcda22e54": "This file format does not currently support preview",
"s_f33c31866a8c335e": "Declined {{p0}}",
- "s_f34ed7e8755e9bbf": "Choose a desktop companion to work alongside your session.",
"s_f36f1a90b64f69e5": "Node.js Tools",
"s_f37b8e7538b116da": "Expand {{p0}}",
"s_f39f410e0b493a67": "skip guide",
@@ -5782,6 +5732,7 @@ export const sourceCatalogs = {
"s_f4208226371456cc": "Working",
"s_f44fb4a283b6bfac": "Current workspace {{p0}}",
"s_f45f45184414bd14": "Additional permissions granted for this session",
+ "s_f46aa7f4bceca841": "Update complete. Restart to apply",
"s_f48d334495f6e4f4": "Desktop",
"s_f4ad2bb421a3f324": "Permission mode",
"s_f4b31e37ac94ca55": "The new path cannot contain the current workspace",
@@ -5852,10 +5803,8 @@ export const sourceCatalogs = {
"s_f944229395fb16af": "Operation not available",
"s_f956a251fa3eee8d": "Could not save the theme package. Check the destination and write permissions.",
"s_f9870fade6c7948e": "Refresh permissions",
- "s_f988a671d6f2be8c": "AI theme",
"s_f99be5ad73ad618a": "Delete scheduled task “{{p0}}”? Its run history will also be deleted.",
"s_f9c08b25e6d154cb": "Cancelling update",
- "s_f9c4971818b3037a": "Storage for conversations, memory, skills and other data",
"s_f9d19345a067cbe1": "interrupt",
"s_fa153621ba3498cf": "Stop the current conversation",
"s_fa2ff3c379a7c988": "View all",
@@ -5885,7 +5834,6 @@ export const sourceCatalogs = {
"s_fba11e3e139dcf31": "Full endpoint URL",
"s_fbb6b7889fcc82f5": "The settings file could not be parsed; showing defaults instead:",
"s_fbb94be3d76b4721": "Time: {{p0}}",
- "s_fbcff96ec20552d1": "Ask “Where is this function called?”",
"s_fbd1ecdc5137fe6f": "Command security",
"s_fbd5fa82e67b1a8b": "{{p0}} cannot be allowed as a prefix (interpreters, shells, rm and sudo can run anything)",
"s_fbfa7054903fbd81": "Current session is unavailable",
@@ -5894,6 +5842,7 @@ export const sourceCatalogs = {
"s_fc340cb578c2c3ec": "Entries must not be empty",
"s_fc455d618fecf57f": "Provider documentation",
"s_fc50a99caae0cddc": "Skip",
+ "s_fc746bfacfbf65ac": "Organizes memory when idle, using additional model calls.",
"s_fc9cb8bc5414d5b3": "The site closed the connection. Reload the page.",
"s_fca3398d38668a75": "· Model unavailable",
"s_fcac6c9df3a773c4": "Copilot sign-in started and the verification code was copied",
@@ -5913,7 +5862,6 @@ export const sourceCatalogs = {
"s_fdd200f5dbdb26fc": "Choose how much technical detail Agent shows",
"s_fddeb12861d6d200": "Choose the interface supported by your provider. Select Responses for models that require it.",
"s_fdfc8be6c3e9fa53": "Ask a question without interrupting the current task…",
- "s_fe0ba53d223b301c": "Update service returns status {{p0}}",
"s_fe6d0b07c1c680f6": "Lead:",
"s_fe7bcf6cde7c28f4": "Start with worktree",
"s_fe83e46159226601": "Expand command details",
diff --git a/web/src/lib/api.js b/web/src/lib/api.js
index 024856f8..e04eb936 100644
--- a/web/src/lib/api.js
+++ b/web/src/lib/api.js
@@ -748,6 +748,8 @@ export function createApi(base = null) {
iosSimulatorScreenshot: (udid) => request('GET',
`/api/ios-simulator/devices/${encodeURIComponent(udid)}/screenshot`, undefined, base,
{ responseType: 'blob', timeoutMs: 45000 }),
+ getHomeIntentPrompts: (opts = {}) => request('GET', '/api/home/intent-prompts', undefined, base, opts),
+ refreshHomeIntentPrompts: (force = false, opts = {}) => request('POST', '/api/home/intent-prompts', { force }, base, { timeoutMs: 75000, ...opts }),
getSummaryGeneration: () => request('GET', '/api/config/summary-generation', undefined, base),
setSummaryGeneration: (config) => request('PUT', '/api/config/summary-generation', config, base, { keepalive: true }),
setImageGeneration: (config) => request('PUT', '/api/config/image-generation', config, base, { keepalive: true }),
@@ -807,7 +809,7 @@ export function createApi(base = null) {
pickSettingsFile: () => request('POST', '/api/dialog/pick-file', undefined, base, { timeoutMs: NO_TIMEOUT }),
getUpdateStatus: () => request('GET', '/api/update/status', undefined, base,
{ timeoutMs: NO_TIMEOUT }),
- startUpdate: () => request('POST', '/api/update/start', undefined, base,
+ startUpdate: (options) => request('POST', '/api/update/start', options, base,
{ timeoutMs: NO_TIMEOUT }),
getLatestUpdateJob: () => request('GET', '/api/update/job', undefined, base),
getUpdateJob: (jobId) => request('GET', `/api/update/jobs/${encodeURIComponent(jobId)}`, undefined, base),
diff --git a/web/src/lib/automaticUpdates.js b/web/src/lib/automaticUpdates.js
new file mode 100644
index 00000000..5135642f
--- /dev/null
+++ b/web/src/lib/automaticUpdates.js
@@ -0,0 +1,223 @@
+import { desktopUpdateRestartAvailable, updateJobIsActive } from './updateJob.js';
+
+export const MIN_UPDATE_CHECK_DELAY_MS = 10 * 60 * 1000;
+export const MAX_UPDATE_CHECK_DELAY_MS = 2 * 60 * 60 * 1000;
+
+export function randomUpdateCheckDelay(random = Math.random) {
+ const value = Number(random());
+ const fraction = Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0;
+ return MIN_UPDATE_CHECK_DELAY_MS
+ + Math.floor(fraction * (MAX_UPDATE_CHECK_DELAY_MS - MIN_UPDATE_CHECK_DELAY_MS));
+}
+
+export function automaticUpdateFailed(status, job) {
+ return job?.target_version === status?.latest_version && Number(job?.automatic_failures) >= 3;
+}
+
+// One owner for checks, starts and polling. Transport failures never invent a
+// job outcome; the daemon owns installation state and consecutive failures.
+export function createUpdateController({
+ api, notify = () => {}, random = Math.random,
+ isCurrentConnection = () => true,
+ setTimer = setTimeout, clearTimer = clearTimeout,
+}) {
+ let state = { status: null, job: null, checking: false, starting: false, dialogOpen: false };
+ const listeners = new Set();
+ const notified = new Set();
+ let active = false;
+ let generation = 0;
+ let pollGeneration = 0;
+ let checkTimer;
+ let pollTimer;
+ let checking;
+ let starting;
+ let refreshRequested = false;
+ let interactiveJobId = '';
+ const publish = (patch) => {
+ state = { ...state, ...patch };
+ listeners.forEach((listener) => listener());
+ };
+ const live = (id) => active && generation === id && isCurrentConnection();
+ const ready = () => state.job?.state === 'succeeded' && state.job?.restart_required;
+
+ function acceptJob(job, { interactive = false, terminal = false } = {}) {
+ if (!job?.job_id) return;
+ if (interactive) interactiveJobId = job.job_id;
+ publish({ job, ...(interactive ? { dialogOpen: true } : {}) });
+ if (!terminal || !['succeeded', 'failed', 'cancelled'].includes(job.state) || notified.has(job.job_id)) return;
+ notified.add(job.job_id);
+ if (job.automatic) {
+ if (job.state === 'succeeded') notify({ kind: 'ok', text: '更新已完成,重启后生效' });
+ return;
+ }
+ if (interactiveJobId !== job.job_id) return;
+ publish({ dialogOpen: true });
+ if (job.state === 'succeeded') notify({
+ kind: 'ok',
+ text: desktopUpdateRestartAvailable()
+ ? '升级安装完成,可立即重启 ACECode'
+ : '升级安装完成,请完全退出并重新启动 ACECode',
+ });
+ else if (job.state === 'failed') notify({ kind: 'err', text: '升级失败:' + (job.error || '未知错误') });
+ else if (job.state === 'cancelled') notify({ kind: 'info', text: '升级已取消,当前安装未修改' });
+ }
+
+ function poll(jobId) {
+ clearTimer(pollTimer);
+ const pollId = ++pollGeneration;
+ const id = generation;
+ let failures = 0;
+ const valid = () => live(id) && pollGeneration === pollId;
+ const tick = async () => {
+ try {
+ const job = await api.getUpdateJob(jobId);
+ if (!valid()) return;
+ failures = 0;
+ acceptJob(job, { terminal: true });
+ if (updateJobIsActive(job)) pollTimer = setTimer(tick, 350);
+ } catch {
+ if (!valid()) return;
+ failures = Math.min(failures + 1, 7);
+ pollTimer = setTimer(tick, Math.min(60000, 600 * 2 ** failures));
+ }
+ };
+ void tick();
+ }
+
+ async function restore(id, interactive = false) {
+ try {
+ const job = await api.getLatestUpdateJob();
+ if (!live(id)) return;
+ acceptJob(job, { interactive: interactive && !job.automatic && updateJobIsActive(job) });
+ if (updateJobIsActive(job)) poll(job.job_id);
+ } catch { /* No job or an offline daemon: keep the last known state. */ }
+ }
+
+ function startUpdate({ automatic = false } = {}) {
+ if (!live(generation) || starting || updateJobIsActive(state.job) || ready() || !state.status?.update_available) {
+ return starting || Promise.resolve();
+ }
+ const id = generation;
+ const work = Promise.resolve().then(async () => {
+ try {
+ if (automatic) {
+ const config = await api.getUpgradeConfig();
+ if (!live(id) || config?.auto_update !== true) return;
+ }
+ if (!live(id)) return;
+ publish({ starting: true });
+ const job = await api.startUpdate({ automatic });
+ if (!live(id)) return;
+ acceptJob(job, { interactive: !automatic, terminal: true });
+ if (updateJobIsActive(job)) poll(job.job_id);
+ } catch (error) {
+ if (!live(id)) return;
+ if (error?.body?.job) {
+ const job = error.body.job;
+ acceptJob(job, { interactive: !automatic, terminal: true });
+ if (updateJobIsActive(job)) poll(job.job_id);
+ } else if (!automatic) {
+ notify({ kind: 'err', text: '启动升级失败:' + (error?.message || '未知错误') });
+ }
+ } finally {
+ if (live(id)) publish({ starting: false });
+ if (starting === work) starting = null;
+ }
+ });
+ starting = work;
+ return work;
+ }
+
+ function schedule() {
+ clearTimer(checkTimer);
+ if (active) checkTimer = setTimer(() => { void check(); }, randomUpdateCheckDelay(random));
+ }
+
+ function check({ manual = false, recoverInteractive = false } = {}) {
+ if (!live(generation)) return Promise.resolve();
+ if (manual) publish({ checking: true });
+ if (checking) {
+ // A manual request can join a background check without another request.
+ const id = generation;
+ return checking.then((result) => {
+ if (!live(id) || !manual) return result;
+ publish({ checking: false });
+ if (result?.error) notify({ kind: 'err', text: '检查更新失败:' + (result.error?.message || '未知错误') });
+ else if (result?.status) publish({ dialogOpen: true });
+ return result;
+ });
+ }
+ const id = generation;
+ const work = Promise.resolve().then(async () => {
+ try {
+ await restore(id, recoverInteractive);
+ if (!live(id)) return;
+ const status = await api.getUpdateStatus();
+ if (!live(id)) return;
+ if (!['available', 'up_to_date', 'store_managed'].includes(status?.status)) {
+ throw new Error(status?.error || '更新服务返回无效响应');
+ }
+ const keepJob = updateJobIsActive(state.job) || ready();
+ const stale = state.job?.target_version && state.job.target_version !== status.latest_version;
+ publish({ status, ...(!keepJob && (stale || status.status !== 'available') ? { job: null } : {}) });
+ if (manual) publish({ dialogOpen: true });
+ else await startUpdate({ automatic: true });
+ return { status };
+ } catch (error) {
+ if (live(id) && manual) notify({ kind: 'err', text: '检查更新失败:' + (error?.message || '未知错误') });
+ return { error };
+ } finally {
+ if (checking === work) checking = null;
+ if (live(id)) {
+ publish({ checking: false });
+ if (refreshRequested) { refreshRequested = false; void check(); }
+ else schedule();
+ }
+ }
+ });
+ checking = work;
+ return work;
+ }
+
+ return {
+ getSnapshot: () => state,
+ subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
+ check,
+ startUpdate,
+ refreshConfiguration() {
+ if (!live(generation)) return;
+ if (checking) refreshRequested = true;
+ else void check();
+ },
+ setDialogOpen(value) { publish({ dialogOpen: value }); },
+ setJob(job) {
+ if (!live(generation)) return;
+ clearTimer(pollTimer);
+ pollGeneration += 1;
+ acceptJob(job, { terminal: true });
+ if (updateJobIsActive(job)) poll(job.job_id);
+ },
+ openDialog() {
+ const stale = !updateJobIsActive(state.job) && !ready() && state.job?.target_version
+ && state.status?.latest_version && state.job.target_version !== state.status.latest_version;
+ publish({ dialogOpen: true, ...(stale ? { job: null } : {}) });
+ },
+ async start() {
+ if (active) return;
+ active = true;
+ generation += 1;
+ await check({ recoverInteractive: true });
+ },
+ stop() {
+ active = false;
+ generation += 1;
+ pollGeneration += 1;
+ clearTimer(checkTimer);
+ clearTimer(pollTimer);
+ checking = null;
+ starting = null;
+ refreshRequested = false;
+ publish({ checking: false, starting: false });
+ },
+ };
+}
diff --git a/web/src/lib/automaticUpdates.test.js b/web/src/lib/automaticUpdates.test.js
new file mode 100644
index 00000000..57c6551e
--- /dev/null
+++ b/web/src/lib/automaticUpdates.test.js
@@ -0,0 +1,198 @@
+import assert from 'node:assert/strict';
+import { createUpdateController, randomUpdateCheckDelay, automaticUpdateFailed,
+ MIN_UPDATE_CHECK_DELAY_MS, MAX_UPDATE_CHECK_DELAY_MS } from './automaticUpdates.js';
+
+const available = { status: 'available', update_available: true, latest_version: '2.0.0' };
+const flush = async () => { for (let i = 0; i < 20; i += 1) await Promise.resolve(); };
+const deferred = () => { let resolve; const promise = new Promise((r) => { resolve = r; }); return { promise, resolve }; };
+function fixture(overrides = {}, options = {}) {
+ let clock = 0;
+ let seq = 0;
+ const timers = new Map();
+ const notices = [];
+ const calls = { check: 0, start: 0, poll: 0 };
+ const api = {
+ getUpdateStatus: async () => { calls.check += 1; return available; },
+ getLatestUpdateJob: async () => { throw Object.assign(new Error('missing'), { status: 404 }); },
+ getUpgradeConfig: async () => ({ auto_update: false }),
+ startUpdate: async ({ automatic }) => {
+ calls.start += 1;
+ return { job_id: `job-${calls.start}`, state: 'running', target_version: '2.0.0', automatic };
+ },
+ getUpdateJob: async (id) => { calls.poll += 1; return { job_id: id, state: 'running', automatic: true }; },
+ ...overrides,
+ };
+ const controller = createUpdateController({ api, notify: (item) => notices.push(item), random: () => 0,
+ setTimer: (fn, delay) => { const id = ++seq; timers.set(id, { fn, at: clock + delay, delay }); return id; },
+ clearTimer: (id) => timers.delete(id),
+ ...options,
+ });
+ return { api, controller, calls, notices, timers,
+ async advance(ms) {
+ const end = clock + ms;
+ for (;;) {
+ const next = [...timers].sort((a, b) => a[1].at - b[1].at)[0];
+ if (!next || next[1].at > end) break;
+ clock = next[1].at; timers.delete(next[0]); next[1].fn(); await flush();
+ }
+ clock = end; await flush();
+ },
+ };
+}
+async function test(name, fn) { await fn(); console.log(`[pass] ${name}`); }
+
+await test('automatic update interval samples both inclusive boundaries each cycle', async () => {
+ assert.equal(randomUpdateCheckDelay(() => 0), 600000);
+ assert.equal(randomUpdateCheckDelay(() => 1), 7200000);
+ assert.equal(randomUpdateCheckDelay(() => 0.5), 3900000);
+ assert.equal(randomUpdateCheckDelay(() => NaN), MIN_UPDATE_CHECK_DELAY_MS);
+ assert.equal(randomUpdateCheckDelay(() => 2), MAX_UPDATE_CHECK_DELAY_MS);
+ const f = fixture(); await f.controller.start();
+ assert.equal(f.calls.check, 1);
+ await f.advance(MIN_UPDATE_CHECK_DELAY_MS - 1); assert.equal(f.calls.check, 1);
+ await f.advance(1); assert.equal(f.calls.check, 2);
+ await f.advance(MIN_UPDATE_CHECK_DELAY_MS); assert.equal(f.calls.check, 3);
+ assert.equal(f.calls.start, 0); assert.deepEqual(f.notices, []);
+ assert.equal(f.controller.getSnapshot().dialogOpen, false);
+ f.controller.stop(); assert.equal(f.timers.size, 0);
+});
+
+await test('failed automatic checks preserve the last offer and never expose errors', async () => {
+ const f = fixture(); await f.controller.start();
+ f.api.getUpdateStatus = async () => { throw new Error('offline'); };
+ await f.advance(MIN_UPDATE_CHECK_DELAY_MS);
+ assert.equal(f.controller.getSnapshot().status, available);
+ assert.equal(f.controller.getSnapshot().dialogOpen, false);
+ assert.equal(f.controller.getSnapshot().checking, false);
+ f.api.getUpdateStatus = async () => ({ status: 'manifest_invalid', error: 'bad json' });
+ await f.advance(MIN_UPDATE_CHECK_DELAY_MS);
+ assert.equal(f.controller.getSnapshot().status, available);
+ assert.equal(automaticUpdateFailed(available, f.controller.getSnapshot().job), false);
+ assert.deepEqual(f.notices, []); f.controller.stop();
+});
+
+await test('automatic success uses the requested toast, stays closed and waits for restart', async () => {
+ const f = fixture({ getUpgradeConfig: async () => ({ auto_update: true }),
+ getUpdateJob: async (id) => ({ job_id: id, state: 'succeeded', automatic: true,
+ target_version: '2.0.0', restart_required: true, automatic_failures: 0 }),
+ });
+ await f.controller.start(); await flush();
+ assert.equal(f.calls.start, 1);
+ assert.deepEqual(f.notices, [{ kind: 'ok', text: '更新已完成,重启后生效' }]);
+ assert.equal(f.controller.getSnapshot().dialogOpen, false);
+ await f.advance(MIN_UPDATE_CHECK_DELAY_MS * 3);
+ assert.equal(f.calls.start, 1); assert.equal(f.notices.length, 1);
+ f.controller.stop();
+});
+
+await test('three distinct automatic failures show a retryable failure without a toast', async () => {
+ let attempts = 0;
+ const f = fixture({ getUpgradeConfig: async () => ({ auto_update: true }),
+ getUpdateJob: async (id) => ({ job_id: id, state: 'failed', automatic: true,
+ target_version: '2.0.0', automatic_failures: ++attempts, error: 'download failed' }),
+ });
+ await f.controller.start(); await flush();
+ assert.equal(automaticUpdateFailed(available, f.controller.getSnapshot().job), false);
+ await f.advance(MIN_UPDATE_CHECK_DELAY_MS);
+ assert.equal(automaticUpdateFailed(available, f.controller.getSnapshot().job), false);
+ await f.advance(MIN_UPDATE_CHECK_DELAY_MS);
+ assert.equal(automaticUpdateFailed(available, f.controller.getSnapshot().job), true);
+ assert.equal(f.controller.getSnapshot().dialogOpen, false);
+ assert.deepEqual(f.notices, []);
+ f.controller.openDialog(); assert.equal(f.controller.getSnapshot().dialogOpen, true);
+ await f.controller.startUpdate(); assert.equal(f.calls.start, 4);
+ assert.equal(automaticUpdateFailed({ ...available, latest_version: '3.0.0' }, f.controller.getSnapshot().job), false);
+ f.controller.stop();
+});
+
+await test('progress failures keep the actual job running and recover silently with bounded backoff', async () => {
+ const job = { job_id: 'recover', state: 'running', automatic: true, target_version: '2.0.0', automatic_failures: 2 };
+ const f = fixture({ getLatestUpdateJob: async () => job,
+ getUpdateJob: async () => { throw new Error('network'); }, getUpgradeConfig: async () => ({ auto_update: true }),
+ });
+ await f.controller.start(); await flush();
+ await f.advance(5 * 60 * 1000);
+ assert.equal(f.controller.getSnapshot().job.state, 'running');
+ assert.equal(f.controller.getSnapshot().job.automatic_failures, 2);
+ assert.equal(f.controller.getSnapshot().dialogOpen, false);
+ assert.equal(f.calls.start, 0); assert.deepEqual(f.notices, []);
+ f.api.getUpdateJob = async () => ({ ...job, state: 'succeeded', restart_required: true, automatic_failures: 0 });
+ await f.advance(60000); assert.equal(f.notices.length, 1);
+ f.controller.stop();
+});
+
+await test('reopening failed automatic jobs does not count them again or open the dialog', async () => {
+ const job = { job_id: 'failed', state: 'failed', automatic: true, target_version: '2.0.0', automatic_failures: 3 };
+ const f = fixture({ getLatestUpdateJob: async () => job });
+ await f.controller.start(); await f.advance(MIN_UPDATE_CHECK_DELAY_MS);
+ assert.equal(automaticUpdateFailed(available, f.controller.getSnapshot().job), true);
+ assert.equal(f.controller.getSnapshot().dialogOpen, false); assert.deepEqual(f.notices, []);
+ f.controller.stop();
+});
+
+await test('manual check joining a failed background check gets one error and no empty dialog', async () => {
+ const response = deferred(); const f = fixture({ getUpdateStatus: () => response.promise });
+ const startup = f.controller.start(); await flush();
+ const manual = f.controller.check({ manual: true });
+ response.resolve({ status: 'manifest_unavailable', error: 'offline' });
+ await Promise.all([startup, manual]);
+ assert.equal(f.notices.length, 1); assert.equal(f.notices[0].kind, 'err');
+ assert.equal(f.controller.getSnapshot().dialogOpen, false); f.controller.stop();
+});
+
+await test('late checks after unmount cannot start installation or reschedule', async () => {
+ const response = deferred(); const f = fixture({ getUpdateStatus: () => response.promise,
+ getUpgradeConfig: async () => ({ auto_update: true }) });
+ const work = f.controller.start(); await flush(); f.controller.stop();
+ response.resolve(available); await work;
+ assert.equal(f.calls.start, 0); assert.equal(f.timers.size, 0); assert.deepEqual(f.notices, []);
+});
+
+await test('double manual clicks start once and automatic conflicts retain silent task origin', async () => {
+ const f = fixture(); await f.controller.start();
+ const response = deferred(); let starts = 0;
+ f.api.startUpdate = () => { starts += 1; return response.promise; };
+ const first = f.controller.startUpdate(); const second = f.controller.startUpdate();
+ await flush(); assert.equal(starts, 1);
+ response.resolve({ job_id: 'one', state: 'running', automatic: true });
+ await Promise.all([first, second]);
+ f.api.getUpdateJob = async () => ({ job_id: 'one', state: 'failed', automatic: true, automatic_failures: 1 });
+ await f.advance(350); assert.deepEqual(f.notices, []); f.controller.stop();
+});
+
+await test('disabled or store-managed automatic updates never start', async () => {
+ const f = fixture({ getUpdateStatus: async () => ({ status: 'store_managed', update_available: false }),
+ getUpgradeConfig: async () => ({ auto_update: true }) });
+ await f.controller.start(); await f.advance(MIN_UPDATE_CHECK_DELAY_MS);
+ assert.equal(f.calls.start, 0); assert.deepEqual(f.notices, []); f.controller.stop();
+});
+
+await test('manual cancellation is terminal and a late progress response cannot restore running', async () => {
+ const f = fixture(); await f.controller.start();
+ const progress = deferred(); f.api.getUpdateJob = () => progress.promise;
+ await f.controller.startUpdate();
+ f.controller.setJob({ job_id: 'job-1', state: 'cancelled', automatic: false });
+ progress.resolve({ job_id: 'job-1', state: 'running' }); await flush();
+ assert.equal(f.controller.getSnapshot().job.state, 'cancelled');
+ assert.equal(f.notices.length, 1); assert.equal(f.notices[0].kind, 'info');
+ assert.equal([...f.timers.values()].filter((t) => t.delay < MIN_UPDATE_CHECK_DELAY_MS).length, 0);
+ f.controller.stop();
+});
+
+await test('configuration changes during a check recheck the saved preference after it settles', async () => {
+ const config = deferred(); let reads = 0;
+ const f = fixture({ getUpgradeConfig: () => ++reads === 1 ? config.promise : Promise.resolve({ auto_update: true }) });
+ const startup = f.controller.start(); await flush();
+ f.controller.refreshConfiguration(); config.resolve({ auto_update: false });
+ await startup; await flush();
+ assert.equal(f.calls.check, 2); assert.equal(f.calls.start, 1); f.controller.stop();
+});
+
+await test('changing daemon connection invalidates pending automatic work', async () => {
+ let sameConnection = true; const config = deferred();
+ const f = fixture({ getUpgradeConfig: () => config.promise }, { isCurrentConnection: () => sameConnection });
+ const startup = f.controller.start(); await flush();
+ sameConnection = false; config.resolve({ auto_update: true }); await startup;
+ assert.equal(f.calls.start, 0); assert.equal(f.timers.size, 0); assert.deepEqual(f.notices, []);
+ f.controller.stop();
+});
diff --git a/web/src/lib/composerEditabilityArchitecture.test.js b/web/src/lib/composerEditabilityArchitecture.test.js
index 97525464..98fa972b 100644
--- a/web/src/lib/composerEditabilityArchitecture.test.js
+++ b/web/src/lib/composerEditabilityArchitecture.test.js
@@ -20,6 +20,7 @@ import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
+import { parseSync, traverse } from '@babel/core';
const srcRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
@@ -39,21 +40,29 @@ function run(name, fn) {
run('提交在途只走 submitting,绝不并进 InputBar 的 disabled', () => {
const chatView = source('components/ChatView.jsx');
+ const openingTags = [];
+ traverse(parseSync(chatView, { configFile: false, babelrc: false, parserOpts: { plugins: ['jsx'] } }), {
+ JSXOpeningElement({ node }) {
+ if (node.name?.name === 'InputBar') openingTags.push(chatView.slice(node.start, node.end));
+ },
+ });
+ assert.ok(openingTags.length >= 2);
+ const composerInputs = openingTags.join('\n');
// 会话 composer 与主页 composer 两处都不能再把 *Submitting 并进 disabled。
assert.doesNotMatch(
- chatView,
+ composerInputs,
/disabled=\{[^}]*composerSubmitting[^}]*\}/,
'composerSubmitting 进 disabled 会让编辑区在发送期间变成只读',
);
assert.doesNotMatch(
- chatView,
+ composerInputs,
/disabled=\{[^}]*homeSubmitting[^}]*\}/,
'homeSubmitting 进 disabled 会让主页输入框在建会话期间变成只读',
);
assert.doesNotMatch(
- chatView,
+ composerInputs,
/disabled=\{[^}]*reasoningSwitching[^}]*\}/,
'reasoning mutation must disable submission without making the editor read-only',
);
diff --git a/web/src/lib/composerPromptReplacement.test.js b/web/src/lib/composerPromptReplacement.test.js
new file mode 100644
index 00000000..ec927b3c
--- /dev/null
+++ b/web/src/lib/composerPromptReplacement.test.js
@@ -0,0 +1,86 @@
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import vm from 'node:vm';
+import { parseSync, traverse } from '@babel/core';
+import { createEditor, Editor, Transforms } from 'slate';
+import { HistoryEditor, withHistory } from 'slate-history';
+import * as model from './richComposerModel.js';
+
+// Run the production replacement against real Slate history, including its
+// selection snapshots; a parent-state-only test would miss cleared undo stacks.
+const source = fs.readFileSync(new URL('../components/RichComposer.jsx', import.meta.url), 'utf8');
+const functions = new Map();
+traverse(parseSync(source, { configFile: false, babelrc: false, parserOpts: { plugins: ['jsx'] } }), {
+ FunctionDeclaration({ node }) { functions.set(node.id.name, source.slice(node.start, node.end)); },
+});
+const context = vm.createContext({ ...model, Editor, Transforms, HistoryEditor });
+for (const name of ['withComposerInlineTags', 'legalComposerDocument', 'safeDeselectEditor',
+ 'ensureLegalEditorDocument', 'replaceEditorDocument']) {
+ context[name] = vm.runInContext(`(${functions.get(name)})`, context);
+}
+
+const flush = async () => { for (let i = 0; i < 4; i += 1) await Promise.resolve(); };
+const clone = (value) => JSON.parse(JSON.stringify(value));
+function fixture(text = 'Original draft', attachments = []) {
+ const editor = withHistory(context.withComposerInlineTags(createEditor()));
+ editor.children = model.composerDocumentFromText(text, [], attachments);
+ editor.selection = model.composerSelectionFromPlainTextRange(editor.children, text.length, text.length);
+ const replace = async (text, options = { undoable: true }) => {
+ assert.equal(context.replaceEditorDocument(editor, model.composerDocumentFromText(text), options), true);
+ await flush();
+ };
+ return { editor, replace, text: () => model.composerTextFromDocument(editor.children) };
+}
+async function test(name, run) { await run(); console.log(`[pass] prompt replacement: ${name}`); }
+
+await test('replaces the full text and Ctrl+Z restores the draft without losing older edits', async () => {
+ const f = fixture();
+ Transforms.insertText(f.editor, ' edited'); await flush();
+ const selection = clone(f.editor.selection);
+ const before = f.text();
+ const earlierBatches = f.editor.history.undos.length;
+ await f.replace('Selected prompt');
+ assert.equal(f.text(), 'Selected prompt');
+ assert.equal(f.editor.history.undos.length, earlierBatches + 1);
+ HistoryEditor.undo(f.editor); await flush();
+ assert.equal(f.text(), before);
+ assert.deepEqual(clone(f.editor.selection), selection);
+ HistoryEditor.undo(f.editor); await flush();
+ assert.equal(f.text(), 'Original draft');
+});
+
+await test('each card replacement is a separate undo step and can be redone', async () => {
+ const f = fixture();
+ await f.replace('First prompt'); await f.replace('Second prompt');
+ HistoryEditor.undo(f.editor); await flush(); assert.equal(f.text(), 'First prompt');
+ HistoryEditor.undo(f.editor); await flush(); assert.equal(f.text(), 'Original draft');
+ HistoryEditor.redo(f.editor); await flush(); assert.equal(f.text(), 'First prompt');
+});
+
+await test('undo restores inline path and attachment nodes exactly', async () => {
+ const f = fixture('Inspect @main.cpp please', [{ id: 'file1', kind: 'file', name: 'notes.md' }]);
+ const before = clone(f.editor.children);
+ assert.ok(model.composerContentFromDocument(f.editor.children).parts.some((part) => part.type === 'attachment'));
+ await f.replace('Write a new plan');
+ HistoryEditor.undo(f.editor); await flush();
+ assert.deepEqual(clone(f.editor.children), before);
+});
+
+await test('workspace hydration stays outside undo while the subsequent prompt is undoable', async () => {
+ const f = fixture('Workspace A');
+ await f.replace('Workspace B draft', { clearHistory: true });
+ assert.equal(f.editor.history.undos.length, 0);
+ await f.replace('Suggested task for B');
+ HistoryEditor.undo(f.editor); await flush();
+ assert.equal(f.text(), 'Workspace B draft');
+ assert.equal(f.editor.history.undos.length, 0);
+});
+
+await test('replacing an empty draft remains a single reversible operation', async () => {
+ const f = fixture('');
+ await f.replace('New prompt');
+ assert.equal(f.editor.history.undos.length, 1);
+ HistoryEditor.undo(f.editor); await flush();
+ assert.equal(f.text(), '');
+ assert.ok(f.editor.children.length > 0);
+});
diff --git a/web/src/lib/configRecoveryNotice.test.js b/web/src/lib/configRecoveryNotice.test.js
index 586025f4..bdf7654a 100644
--- a/web/src/lib/configRecoveryNotice.test.js
+++ b/web/src/lib/configRecoveryNotice.test.js
@@ -61,7 +61,7 @@ run('App shows only the blocking recovery modal and keeps notice failures silent
path.join(srcRoot, 'components', 'ConfigRecoveryDialog.jsx'), 'utf8');
const recoveryFlow = app.slice(
app.indexOf('api.getConfigRecoveryNotice()'),
- app.indexOf('const pollUpdateJob'),
+ app.indexOf('\n });', app.indexOf('const acknowledgeConfigRecovery')),
);
assert.match(app, /normalizeConfigRecoveryNotice/);
diff --git a/web/src/lib/expertPrefill.test.js b/web/src/lib/expertPrefill.test.js
index 7f9902d5..305214c7 100644
--- a/web/src/lib/expertPrefill.test.js
+++ b/web/src/lib/expertPrefill.test.js
@@ -94,6 +94,11 @@ function fixture(workspaceHash = 'workspace-a') {
});
const scope = {
...refs, api, homeRefFromWorkspace, aiThemeCreationRef, scheduledTaskCreationRef, health: {}, homeComposerDraft, composerDraftFingerprint,
+ inputRef: { current: { replacePrompt(text) {
+ composer = text;
+ changeDraft(route.workspaceHash || '', text);
+ return true;
+ } } },
createApi: () => api, refreshWorkspaceGitInfo: async () => {},
navigateToRef: navigate, replaceActiveRef: navigate,
rememberRecentExpert() {}, onRememberExpert() {},
@@ -134,7 +139,7 @@ function fixture(workspaceHash = 'workspace-a') {
refs.composerValueRef.current = composer;
refs.draftSessionKeyRef.current = draftSessionKey;
const context = {
- ...scope, sid, draftSessionKey, draftWorkspaceHash: workspace,
+ ...scope, sid, ref: route, draftSessionKey, draftWorkspaceHash: workspace,
homeComposerDrafts: drafts,
homeDraftWorkspaceHash: sid ? '' : homeDraftWorkspace,
stagedExpertDraft: expertDispatchDraftFromRef(route),
@@ -182,6 +187,18 @@ function fixture(workspaceHash = 'workspace-a') {
const expert = { id: 'expert-a', quick_prompts: ['first prompt', 'second prompt', 'third prompt'] };
const cases = [
+ ['home intent replaces in the source workspace through the editor and is consumed only once', async () => {
+ const view = fixture();
+ view.navigate({ ...homeRefFromWorkspace({ workspaceHash: 'workspace-b' }), initialHomePrompt: 'next task' });
+ await nextTask(0); view.render();
+ assert.equal(view.text, 'next task');
+ assert.equal(view.route.workspaceHash, 'workspace-b');
+ assert.equal(view.route.initialHomePrompt, undefined);
+ assert.equal(view.drafts['workspace-a'], 'older draft A');
+ view.edit('edited next task'); view.render();
+ assert.equal(view.text, 'edited next task');
+ assert.equal(view.consumed, 1);
+ }],
['scheduled task callback prefills an isolated editable skill draft without creating or sending', () => {
const view = fixture();
view.createScheduled();
diff --git a/web/src/lib/homeIntentPrompts.js b/web/src/lib/homeIntentPrompts.js
new file mode 100644
index 00000000..3277bcf6
--- /dev/null
+++ b/web/src/lib/homeIntentPrompts.js
@@ -0,0 +1,129 @@
+import { homeRefFromWorkspace, noHomeWorkspaceOption } from './homeWorkspaceSelection.js';
+
+export const HOME_INTENT_REFRESH_MS = 30 * 60 * 1000;
+export const HOME_INTENT_CATEGORIES = Object.freeze({
+ writing: { icon: 'edit', label: '写作' },
+ exploration: { icon: 'searchSparkle', label: '探索' },
+ execution: { icon: 'run', label: '执行' },
+ thinking: { icon: 'lightbulb', label: '思考' },
+});
+
+export const DEFAULT_HOME_INTENT_PROMPTS = Object.freeze([
+ { category: 'writing', title: '写一份清晰的文档',
+ prompt: '请帮我起草一份文档。先结合当前上下文梳理目标、读者和关键内容;缺少必要信息时向我提问,再给出结构清晰、可以继续修改的初稿。' },
+ { category: 'exploration', title: '探索一个问题',
+ prompt: '请帮我探索当前项目或问题。先阅读相关资料,梳理关键结构、调用关系和已有结论,用具体证据解释你的发现,并列出最值得继续调查的问题。' },
+ { category: 'execution', title: '完成一项任务',
+ prompt: '请帮我完成接下来要做的任务。先确认目标和当前状态,给出简短步骤,然后实施必要的修改并验证结果;保留无关的已有改动。' },
+ { category: 'thinking', title: '一起推敲方案',
+ prompt: '请和我一起推敲一个方案。先明确问题、目标与约束,比较可行选择的收益、代价和风险,指出关键假设,再给出建议与下一步行动。' },
+]);
+
+export function normalizeHomeIntentPrompts(value) {
+ if (!Array.isArray(value) || value.length > 4) return [];
+ return value.filter((item) => item && Object.hasOwn(HOME_INTENT_CATEGORIES, item.category)
+ && typeof item.title === 'string' && item.title.trim()
+ && typeof item.description === 'string' && typeof item.prompt === 'string' && item.prompt.trim()
+ && item.workspace && typeof item.workspace.noWorkspace === 'boolean'
+ && (item.workspace.noWorkspace || (typeof item.workspace.hash === 'string' && item.workspace.hash
+ && typeof item.workspace.cwd === 'string' && item.workspace.cwd)))
+ .map((item) => ({ ...item, workspace: item.workspace.noWorkspace ? noHomeWorkspaceOption() : item.workspace }));
+}
+
+export function homeIntentPromptRef(item, currentWorkspace, currentRef, health) {
+ const workspace = item.workspace || currentWorkspace || noHomeWorkspaceOption();
+ return { ...homeRefFromWorkspace(workspace, currentRef, health), initialHomePrompt: item.prompt };
+}
+
+export function isHomeIntentRefreshShortcut(event) {
+ return !!event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey
+ && !event.isComposing && String(event.key).toLowerCase() === 'y';
+}
+
+// App-owned, independent of the active page. Timers and clock are injectable so
+// refresh, sleep/resume and concurrency behavior do not require real waits.
+export function createHomeIntentController({ api, onChange = () => {}, onError = () => {},
+ now = Date.now, setTimer = setTimeout, clearTimer = clearTimeout }) {
+ let snapshot = { suggestions: [], refreshing: false, next_refresh_at_ms: 0 };
+ let disposed = false;
+ let timer;
+ let inFlight;
+ let inFlightMode;
+ let abort;
+ let manualPending = false;
+ let reportManualResult = false;
+ let pollingSince = 0;
+
+ function publish(value) {
+ if (disposed) return;
+ snapshot = { ...value, suggestions: normalizeHomeIntentPrompts(value?.suggestions) };
+ onChange(snapshot);
+ }
+
+ function schedule() {
+ clearTimer(timer);
+ if (disposed) return;
+ const delay = snapshot.refreshing ? 1500 : Math.max(0, snapshot.next_refresh_at_ms - now());
+ timer = setTimer(() => { void request(snapshot.refreshing ? 'read' : 'refresh'); }, delay);
+ }
+
+ function request(kind) {
+ if (disposed) return Promise.resolve(snapshot);
+ if (kind === 'force') reportManualResult = true;
+ if (inFlight) {
+ if (kind === 'force' && inFlightMode === 'read' && !snapshot.refreshing) manualPending = true;
+ return inFlight;
+ }
+ clearTimer(timer);
+ abort = new AbortController();
+ const controller = abort;
+ const mode = snapshot.refreshing ? 'read' : (manualPending ? 'force' : kind);
+ inFlightMode = mode;
+ if (mode === 'force') manualPending = false;
+ inFlight = Promise.resolve().then(async () => {
+ try {
+ const value = mode === 'read'
+ ? await api.getHomeIntentPrompts({ signal: controller.signal })
+ : await api.refreshHomeIntentPrompts(mode === 'force', { signal: controller.signal });
+ if (disposed) return snapshot;
+ const next = { ...value, next_refresh_at_ms: Number(value?.next_refresh_at_ms) || now() };
+ if (next.refreshing) {
+ if (!pollingSince) pollingSince = now();
+ if (now() - pollingSince > 90_000) throw new Error('刷新超时,请按 Ctrl+Y 重试');
+ } else pollingSince = 0;
+ publish(next);
+ if (!next.refreshing) {
+ if (next.error && reportManualResult) onError(next.error);
+ reportManualResult = false;
+ }
+ } catch (error) {
+ if (disposed || error?.name === 'AbortError') return snapshot;
+ publish({ ...snapshot, refreshing: false, next_refresh_at_ms: now() + HOME_INTENT_REFRESH_MS });
+ if (reportManualResult) onError(error?.message || '刷新失败');
+ reportManualResult = false;
+ } finally {
+ inFlight = null;
+ inFlightMode = null;
+ abort = null;
+ if (!disposed) {
+ if (manualPending && !snapshot.refreshing) { manualPending = false; void request('force'); }
+ else schedule();
+ }
+ }
+ return snapshot;
+ });
+ return inFlight;
+ }
+
+ return {
+ start: () => request('read'),
+ refresh: () => request('force'),
+ wake() {
+ if (snapshot.refreshing) return request('read');
+ if (now() >= snapshot.next_refresh_at_ms) return request('refresh');
+ return Promise.resolve(snapshot);
+ },
+ snapshot: () => snapshot,
+ dispose() { disposed = true; clearTimer(timer); abort?.abort(); },
+ };
+}
diff --git a/web/src/lib/homeIntentPrompts.test.js b/web/src/lib/homeIntentPrompts.test.js
new file mode 100644
index 00000000..222259c9
--- /dev/null
+++ b/web/src/lib/homeIntentPrompts.test.js
@@ -0,0 +1,149 @@
+import assert from 'node:assert/strict';
+import {
+ DEFAULT_HOME_INTENT_PROMPTS, HOME_INTENT_CATEGORIES, HOME_INTENT_REFRESH_MS,
+ createHomeIntentController, homeIntentPromptRef,
+ isHomeIntentRefreshShortcut, normalizeHomeIntentPrompts,
+} from './homeIntentPrompts.js';
+import { homeWorkspaceOptionForHash } from './homeWorkspaceSelection.js';
+
+const generated = {
+ category: 'execution', title: '完成任务', description: '验证剩余修改', prompt: '请完成剩余任务并验证。',
+ workspace: { hash: 'other', cwd: 'N:/other', name: 'Other', noWorkspace: false },
+ source_session_id: 'source',
+};
+async function test(name, fn) { await fn(); console.log(`[pass] home intent: ${name}`); }
+function fixture() {
+ let time = 1000;
+ const timers = new Map();
+ const calls = [];
+ const errors = [];
+ let timerId = 0;
+ const response = () => ({ suggestions: [generated], next_refresh_at_ms: time + HOME_INTENT_REFRESH_MS });
+ const api = {
+ async getHomeIntentPrompts() { calls.push('read'); return { suggestions: [], next_refresh_at_ms: 0 }; },
+ async refreshHomeIntentPrompts(force) { calls.push(force ? 'force' : 'refresh'); return response(); },
+ };
+ const controller = createHomeIntentController({ api, now: () => time, onError: (error) => errors.push(error),
+ setTimer: (callback, delay) => { const id = ++timerId; timers.set(id, { callback, at: time + delay }); return id; },
+ clearTimer: (id) => timers.delete(id),
+ });
+ return { api, controller, calls, errors, timers, response,
+ advance(ms) { time += ms; },
+ async flush() { for (let i = 0; i < 15; i += 1) await Promise.resolve(); },
+ async tick(ms = 0) {
+ time += ms;
+ for (const [id, timer] of [...timers]) if (timer.at <= time) { timers.delete(id); timer.callback(); }
+ for (let i = 0; i < 15; i += 1) await Promise.resolve();
+ },
+ };
+}
+
+await test('four editable defaults and the original SVG mapping', () => {
+ assert.equal(DEFAULT_HOME_INTENT_PROMPTS.length, 4);
+ assert.deepEqual(DEFAULT_HOME_INTENT_PROMPTS.map((x) => HOME_INTENT_CATEGORIES[x.category].icon),
+ ['edit', 'searchSparkle', 'run', 'lightbulb']);
+ assert.ok(DEFAULT_HOME_INTENT_PROMPTS.every((item) => item.prompt.length > 30));
+});
+
+await test('validates generated categories and explicit workspace identity', () => {
+ assert.equal(normalizeHomeIntentPrompts([generated]).length, 1);
+ assert.equal(normalizeHomeIntentPrompts([{ ...generated, category: '__proto__' }]).length, 0);
+ assert.equal(normalizeHomeIntentPrompts([{ ...generated, workspace: { cwd: 'N:/guess' } }]).length, 0);
+ assert.equal(normalizeHomeIntentPrompts(Array(5).fill(generated)).length, 0);
+ const [noWorkspace] = normalizeHomeIntentPrompts([{ ...generated,
+ workspace: { noWorkspace: true, hash: 'stale', cwd: 'N:/cache/private' } }]);
+ assert.equal(noWorkspace.workspace.cwd, '');
+ assert.equal(noWorkspace.workspace.hash, '');
+});
+
+await test('prefill keeps workspace source and hidden workspace before list hydration', () => {
+ const current = { workspaceHash: 'old', cwd: 'N:/old', port: 1234, token: 'local-test' };
+ const next = homeIntentPromptRef(generated, null, current, {});
+ assert.equal(next.workspaceHash, 'other');
+ assert.equal(next.cwd, 'N:/other');
+ assert.equal(next.initialHomePrompt, generated.prompt);
+ assert.equal(next.sessionId, undefined);
+ assert.equal(homeWorkspaceOptionForHash([], 'other', generated.workspace).cwd, 'N:/other');
+ const empty = homeIntentPromptRef({ ...generated, workspace: { noWorkspace: true } }, null, current, {});
+ assert.equal(empty.noWorkspace, true);
+ assert.equal(empty.cwd, '');
+ assert.equal(empty.workspaceHash, '');
+ const preset = homeIntentPromptRef(DEFAULT_HOME_INTENT_PROMPTS[0], generated.workspace, current, {});
+ assert.equal(preset.workspaceHash, 'other');
+});
+
+await test('only Ctrl+Y triggers debug refresh', () => {
+ assert.equal(isHomeIntentRefreshShortcut({ ctrlKey: true, key: 'Y' }), true);
+ for (const extra of [{ altKey: true }, { shiftKey: true }, { metaKey: true }, { isComposing: true }])
+ assert.equal(isHomeIntentRefreshShortcut({ ctrlKey: true, key: 'y', ...extra }), false);
+ assert.equal(isHomeIntentRefreshShortcut({ key: 'y' }), false);
+});
+
+await test('loads cache then refreshes every half hour without homepage dependence', async () => {
+ const f = fixture();
+ await f.controller.start();
+ await f.tick();
+ assert.deepEqual(f.calls, ['read', 'refresh']);
+ await f.tick(HOME_INTENT_REFRESH_MS - 1);
+ assert.equal(f.calls.length, 2);
+ await f.tick(1);
+ assert.equal(f.calls.at(-1), 'refresh');
+ assert.equal(f.calls.length, 3);
+ f.controller.dispose();
+ assert.equal(f.timers.size, 0);
+});
+
+await test('manual refresh is immediate and concurrent presses deduplicate', async () => {
+ const f = fixture(); let resolve;
+ f.api.refreshHomeIntentPrompts = (force) => { f.calls.push(force); return new Promise((r) => { resolve = r; }); };
+ const first = f.controller.refresh(); await f.flush();
+ const second = f.controller.refresh();
+ assert.equal(first, second);
+ resolve(f.response()); await first; await f.flush();
+ assert.deepEqual(f.calls, [true]);
+ f.controller.dispose();
+});
+
+await test('sleep wake catches up once and errors preserve earlier cards', async () => {
+ const f = fixture(); await f.controller.refresh();
+ f.advance(HOME_INTENT_REFRESH_MS * 4);
+ await f.controller.wake();
+ assert.equal(f.calls.length, 2);
+ f.api.refreshHomeIntentPrompts = async () => { throw new Error('offline'); };
+ await f.controller.refresh();
+ assert.equal(f.controller.snapshot().suggestions[0].prompt, generated.prompt);
+ assert.deepEqual(f.errors, ['offline']);
+ await f.controller.wake();
+ assert.equal(f.errors.length, 1);
+ f.controller.dispose();
+});
+
+await test('multi-window generation polls GET without launching another model request', async () => {
+ const f = fixture();
+ f.api.refreshHomeIntentPrompts = async () => { f.calls.push('force'); return { ...f.response(), refreshing: true }; };
+ f.api.getHomeIntentPrompts = async () => { f.calls.push('read'); return f.response(); };
+ await f.controller.refresh();
+ await f.tick(1500);
+ assert.deepEqual(f.calls, ['force', 'read']);
+ f.controller.dispose();
+});
+
+await test('dispose aborts reads and ignores their late results', async () => {
+ const f = fixture(); let resolve; let signal;
+ f.api.getHomeIntentPrompts = (options) => { signal = options.signal; return new Promise((r) => { resolve = r; }); };
+ const pending = f.controller.start(); await f.flush();
+ f.controller.dispose(); assert.equal(signal.aborted, true);
+ resolve(f.response()); await pending;
+ assert.equal(f.controller.snapshot().suggestions.length, 0);
+ assert.equal(f.timers.size, 0);
+});
+
+await test('manual refresh reports a failure delivered by another windows pending request', async () => {
+ const f = fixture();
+ f.api.refreshHomeIntentPrompts = async () => ({ ...f.response(), refreshing: true });
+ f.api.getHomeIntentPrompts = async () => ({ ...f.response(), error: 'INVALID_RESPONSE' });
+ await f.controller.refresh();
+ await f.tick(1500);
+ assert.deepEqual(f.errors, ['INVALID_RESPONSE']);
+ f.controller.dispose();
+});
diff --git a/web/src/lib/homeWorkspaceSelection.js b/web/src/lib/homeWorkspaceSelection.js
index 284a844d..b1c6c251 100644
--- a/web/src/lib/homeWorkspaceSelection.js
+++ b/web/src/lib/homeWorkspaceSelection.js
@@ -68,9 +68,10 @@ export function resolveHomeWorkspaceHash({
return '';
}
-export function homeWorkspaceOptionForHash(options = [], hash = '') {
+export function homeWorkspaceOptionForHash(options = [], hash = '', explicitOption = null) {
if (!hash) return noHomeWorkspaceOption();
return (Array.isArray(options) ? options : []).find((w) => w?.hash === hash)
+ || (explicitOption?.hash === hash && !explicitOption.noWorkspace ? explicitOption : null)
|| noHomeWorkspaceOption();
}
diff --git a/web/src/lib/remoteWebArchitecture.test.js b/web/src/lib/remoteWebArchitecture.test.js
index 41222aa1..087c32b2 100644
--- a/web/src/lib/remoteWebArchitecture.test.js
+++ b/web/src/lib/remoteWebArchitecture.test.js
@@ -29,16 +29,15 @@ run('general settings keeps remote Web mode as its final card', () => {
assert.ok(remoteIndex > backgroundIndex);
assert.ok(appearanceIndex > remoteIndex);
assert.match(page, />远程 Web 模式);
- assert.match(page, /启动独立的反向代理进程/);
- assert.match(page, /daemon 仍仅监听 127\.0\.0\.1/);
+ assert.match(page, /ariaLabel="远程 Web 模式"/);
assert.match(page, />\s*复制连接\s*);
});
run('remote Web card preserves token warning and network safety guidance', () => {
const page = source('components/SettingsPage.jsx');
assert.match(page, /此连接包含访问 Token,请勿将此连接公开给别人。/);
- assert.match(page, /系统防火墙、路由器或云安全组可能仍需放行代理端口/);
- assert.match(page, /可信 VPN,并在上游配置 HTTPS/);
+ assert.match(page, /连接失败时请检查端口放行/);
+ assert.match(page, /公网访问请使用 VPN 和 HTTPS/);
assert.match(page, /copyTextToSystemClipboard\(connection\.url\)/);
});
diff --git a/web/src/lib/runTests.js b/web/src/lib/runTests.js
index 61491893..61e3394b 100644
--- a/web/src/lib/runTests.js
+++ b/web/src/lib/runTests.js
@@ -1,4 +1,6 @@
import './desktopOffice.test.js';
+import './automaticUpdates.test.js';
+import './upgradeSettings.test.js';
import './officeCharacters.test.js';
import './htmlPreview.test.js';
import './desktopOfficePreferences.test.js';
@@ -6,6 +8,8 @@ import './questionPicker.test.js';
import './turnCancellation.test.js';
import './modelReasoningSync.test.js';
import './homeComposerDraftStore.test.js';
+import './homeIntentPrompts.test.js';
+import './composerPromptReplacement.test.js';
import './homeComposerDraftLifecycle.test.js';
import './composerGoal.test.js';
import './developerMode.test.js';
diff --git a/web/src/lib/settingsNavigation.js b/web/src/lib/settingsNavigation.js
index d38e1ecb..8f159ce5 100644
--- a/web/src/lib/settingsNavigation.js
+++ b/web/src/lib/settingsNavigation.js
@@ -4,7 +4,7 @@ export const SETTINGS_NAV_GROUPS = [
label: '个人',
items: [
{ key: 'general', label: '常规', icon: 'settings' },
- { key: 'appearance', label: '外观', icon: 'brightness' },
+ { key: 'appearance', label: '外观', icon: 'palette' },
{ key: 'shortcuts', label: '快捷键', icon: 'keyboard' },
{ key: 'config', label: '配置', icon: 'terminal' },
{ key: 'personalization', label: '个性化', icon: 'eye' },
diff --git a/web/src/lib/settingsSearch.js b/web/src/lib/settingsSearch.js
index 6916e12e..05f443fc 100644
--- a/web/src/lib/settingsSearch.js
+++ b/web/src/lib/settingsSearch.js
@@ -23,6 +23,7 @@ export function settingsSearchEntries(developerModeUnlocked = false) {
['appearance', '消息自动折叠', '会话 conversation messages auto collapse fold expand tools'],
['shortcuts', '搜索快捷键', 'keyboard shortcuts hotkeys 快捷键 键盘 搜索 前进 后退 思维深度 Ctrl Alt Cmd'],
['config', '升级服务 URL', 'upgrade update service url'],
+ ['config', '自动更新', 'automatic update upgrade'],
['config', 'Skill 扫描路径', 'skill skills scan search paths directories 技能 扫描 搜索 路径 目录'],
['config', 'Python 工具', 'python uv ruff mypy path directory'],
['config', 'Node.js 工具', 'node nodejs npm pnpm tsx path directory'],
diff --git a/web/src/lib/settingsWindowArchitecture.test.js b/web/src/lib/settingsWindowArchitecture.test.js
index 3a67a6af..cd3bc946 100644
--- a/web/src/lib/settingsWindowArchitecture.test.js
+++ b/web/src/lib/settingsWindowArchitecture.test.js
@@ -96,11 +96,14 @@ run('Settings panel keeps normal caps and an exact 13px expanded inset', () => {
run('upgrade URL and personalization editors save on blur without save buttons', () => {
const settings = source('components/SettingsPage.jsx');
const config = source('components/SettingsConfigSection.jsx');
+ const upgrade = source('components/UpgradeSettings.jsx');
+ const upgradeStore = source('lib/upgradeSettings.js');
const personalization = between(settings, 'function SectionPersonalization(', '// ─── 技能');
- assert.match(config, /onBlur=\{\(\) => \{ void saveUpgradeUrl\(\); \}\}/);
- assert.match(config, /api\.setUpgradeConfig\(\{ base_url: baseUrl \}\)/);
- assert.doesNotMatch(config, /onClick=\{saveUpgradeUrl\}/);
+ assert.match(config, /
\{ void store.save\(\); \}\}/);
+ assert.match(upgradeStore, /client\.setUpgradeConfig\(patch\)/);
+ assert.doesNotMatch(upgrade, />保存<|>应用<|>取消更改);
assert.match(personalization, /onBlur=\{\(\) => \{ void save\(\); \}\}/);
assert.match(personalization, /api\.setCustomInstructions\(\{ text: candidate \}\)/);
assert.doesNotMatch(personalization, /onClick=\{save\}/);
diff --git a/web/src/lib/stableCallbacksArchitecture.test.js b/web/src/lib/stableCallbacksArchitecture.test.js
index 95b535b3..0d38b323 100644
--- a/web/src/lib/stableCallbacksArchitecture.test.js
+++ b/web/src/lib/stableCallbacksArchitecture.test.js
@@ -34,7 +34,9 @@ const KEEP = {
applyAppearance: '作为 useRef 的初值',
},
};
-const MIN_STABLE = { Sidebar: 13, ChatView: 100, App: 34 };
+// Three update callbacks now live in the external update controller, outside
+// App's render closure. Keep the guard for the remaining App callbacks.
+const MIN_STABLE = { Sidebar: 13, ChatView: 100, App: 31 };
const FILES = { Sidebar: '../components/Sidebar.jsx', ChatView: '../components/ChatView.jsx', App: '../App.jsx' };
function report(name) {
diff --git a/web/src/lib/themeManagementUi.test.js b/web/src/lib/themeManagementUi.test.js
index 14a5f120..86b1a593 100644
--- a/web/src/lib/themeManagementUi.test.js
+++ b/web/src/lib/themeManagementUi.test.js
@@ -71,7 +71,7 @@ await run('production theme cards render only two custom management links with n
assert.equal((html.match(/>导出主题<\/button>/g) || []).length, 1);
assert.equal((html.match(/>删除主题<\/button>/g) || []).length, 1);
assert.match(html, /使用中/);
- assert.match(html, /AI主题/);
+ assert.match(html, /AI 创建主题/);
const { LocalThemeCard } = components();
const tree = LocalThemeCard({ entry: local, selected: true, onSelect() {}, onExport() {}, onDelete() {} });
const buttons = descendants(tree).filter((node) => node.type === 'button');
diff --git a/web/src/lib/upgradeSettings.js b/web/src/lib/upgradeSettings.js
new file mode 100644
index 00000000..b4e922fb
--- /dev/null
+++ b/web/src/lib/upgradeSettings.js
@@ -0,0 +1,84 @@
+import { apiConnectionScope } from './api.js';
+import { UPGRADE_CONFIG_CHANGED_EVENT } from './themeImports.js';
+
+const stores = new WeakMap();
+const normalize = (value) => ({ base_url: value?.base_url || '', auto_update: value?.auto_update === true });
+
+export function upgradeSettingsStore(client) {
+ const scope = apiConnectionScope(client);
+ if (stores.has(scope)) return stores.get(scope);
+ let state = { snapshot: null, draft: null, loading: false, saving: false, error: '' };
+ let reading;
+ let writing;
+ let requested = false;
+ const listeners = new Set();
+ const publish = (patch) => { state = { ...state, ...patch }; listeners.forEach((fn) => fn()); };
+ const dirty = () => state.draft && Object.keys(state.draft).some((key) => state.draft[key] !== state.snapshot[key]);
+ const requireConnection = () => {
+ if (apiConnectionScope(client) !== scope) throw new Error('更新设置连接已变更');
+ };
+ const store = {
+ getSnapshot: () => state,
+ subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); },
+ update(key, value) {
+ if (state.draft) publish({ draft: { ...state.draft, [key]: value }, error: '' });
+ },
+ load() {
+ if (reading) return reading;
+ const pendingWrite = writing;
+ reading = Promise.resolve().then(async () => {
+ if (pendingWrite) await pendingWrite;
+ if (dirty()) return;
+ publish({ loading: true, error: '' });
+ try {
+ requireConnection();
+ const snapshot = normalize(await client.getUpgradeConfig());
+ requireConnection();
+ publish({ snapshot, draft: snapshot });
+ } catch (error) { publish({ error: error?.message || '读取更新设置失败' }); }
+ }).finally(() => { reading = null; publish({ loading: false }); });
+ return reading;
+ },
+ save() {
+ requested = true;
+ if (writing) return writing;
+ const pendingRead = reading;
+ writing = Promise.resolve().then(async () => {
+ if (pendingRead) await pendingRead;
+ while (requested) {
+ requested = false;
+ if (!dirty()) continue;
+ const submitted = state.draft;
+ const patch = Object.fromEntries(Object.entries(submitted)
+ .filter(([key, value]) => value !== state.snapshot[key]));
+ if ('base_url' in patch) {
+ patch.base_url = patch.base_url.trim();
+ if (!/^https?:\/\//i.test(patch.base_url)) {
+ publish({ error: '升级服务 URL 必须使用 http 或 https' });
+ return false;
+ }
+ }
+ publish({ saving: true, error: '' });
+ try {
+ requireConnection();
+ const snapshot = normalize(await client.setUpgradeConfig(patch));
+ requireConnection();
+ const draft = { ...snapshot };
+ for (const key of Object.keys(draft)) {
+ if (state.draft[key] !== submitted[key]) draft[key] = state.draft[key];
+ }
+ publish({ snapshot, draft });
+ if (typeof window !== 'undefined') window.dispatchEvent(new Event(UPGRADE_CONFIG_CHANGED_EVENT));
+ } catch (error) {
+ publish({ error: error?.message || '保存更新设置失败' });
+ return false;
+ }
+ }
+ return true;
+ }).finally(() => { writing = null; publish({ saving: false }); });
+ return writing;
+ },
+ };
+ stores.set(scope, store);
+ return store;
+}
diff --git a/web/src/lib/upgradeSettings.test.js b/web/src/lib/upgradeSettings.test.js
new file mode 100644
index 00000000..7c8e9bea
--- /dev/null
+++ b/web/src/lib/upgradeSettings.test.js
@@ -0,0 +1,51 @@
+import assert from 'node:assert/strict';
+import { upgradeSettingsStore } from './upgradeSettings.js';
+
+const flush = async () => { for (let i = 0; i < 15; i += 1) await Promise.resolve(); };
+const deferred = () => { let resolve; const promise = new Promise((r) => { resolve = r; }); return { promise, resolve }; };
+async function test(name, fn) { await fn(); console.log(`[pass] ${name}`); }
+
+await test('upgrade settings default off and serialize edits across navigation', async () => {
+ let saved = { base_url: 'https://updates.example/' };
+ const patches = []; const first = deferred();
+ const client = { getUpgradeConfig: async () => saved, setUpgradeConfig: async (patch) => {
+ patches.push(patch); if (patches.length === 1) await first.promise;
+ saved = { ...saved, ...patch }; return saved;
+ } };
+ const store = upgradeSettingsStore(client); await store.load();
+ assert.equal(store.getSnapshot().draft.auto_update, false);
+ store.update('auto_update', true); const saving = store.save(); await flush();
+ store.update('auto_update', false); store.save();
+ store.update('base_url', 'https://new.example/'); store.save();
+ const reopened = upgradeSettingsStore(client); assert.equal(reopened, store);
+ const reload = reopened.load();
+ assert.equal(patches.length, 1);
+ first.resolve(); await Promise.all([saving, reload]);
+ assert.deepEqual(patches, [{ auto_update: true }, { base_url: 'https://new.example/', auto_update: false }]);
+ assert.equal(store.getSnapshot().draft.auto_update, false);
+ assert.equal(store.getSnapshot().snapshot.base_url, 'https://new.example/');
+});
+
+await test('upgrade settings retain failed drafts and retry without a success illusion', async () => {
+ let fail = true;
+ const client = { getUpgradeConfig: async () => ({ base_url: 'https://updates.example/', auto_update: false }),
+ setUpgradeConfig: async (patch) => { if (fail) throw new Error('disk full'); return { base_url: 'https://updates.example/', ...patch }; },
+ };
+ const store = upgradeSettingsStore(client); await store.load();
+ store.update('auto_update', true); assert.equal(await store.save(), false);
+ assert.equal(store.getSnapshot().snapshot.auto_update, false);
+ assert.equal(store.getSnapshot().draft.auto_update, true);
+ assert.equal(store.getSnapshot().error, 'disk full');
+ await store.load(); assert.equal(store.getSnapshot().draft.auto_update, true);
+ fail = false; assert.equal(await store.save(), true);
+ assert.equal(store.getSnapshot().snapshot.auto_update, true);
+});
+
+await test('load and unmount flush in the same tick do not deadlock', async () => {
+ const client = { getUpgradeConfig: async () => ({ base_url: 'https://updates.example/' }), setUpgradeConfig: async (value) => value };
+ const store = upgradeSettingsStore(client);
+ await Promise.all([store.load(), store.save()]);
+ assert.equal(store.getSnapshot().loading, false);
+ store.update('base_url', 'invalid'); assert.equal(await store.save(), false);
+ assert.equal(store.getSnapshot().draft.base_url, 'invalid');
+});
diff --git a/web/src/lib/useAppUpdates.js b/web/src/lib/useAppUpdates.js
new file mode 100644
index 00000000..18e7106a
--- /dev/null
+++ b/web/src/lib/useAppUpdates.js
@@ -0,0 +1,36 @@
+import { useEffect, useMemo, useSyncExternalStore } from 'react';
+import { api, apiConnectionScope } from './api.js';
+import { createUpdateController, automaticUpdateFailed } from './automaticUpdates.js';
+import { UPGRADE_CONFIG_CHANGED_EVENT } from './themeImports.js';
+import { toast } from '../components/Toast.jsx';
+
+export function useAppUpdates(authState) {
+ const scope = apiConnectionScope(api);
+ const controller = useMemo(() => createUpdateController({
+ api, notify: toast, isCurrentConnection: () => apiConnectionScope(api) === scope,
+ }), [scope]);
+ const state = useSyncExternalStore(controller.subscribe, controller.getSnapshot);
+ useEffect(() => {
+ if (authState !== 'ok') return undefined;
+ void controller.start();
+ const changed = () => controller.refreshConfiguration();
+ window.addEventListener(UPGRADE_CONFIG_CHANGED_EVENT, changed);
+ return () => {
+ window.removeEventListener(UPGRADE_CONFIG_CHANGED_EVENT, changed);
+ controller.stop();
+ };
+ }, [authState, controller]);
+ return {
+ updateStatus: state.status,
+ updateJob: state.job,
+ updateStarting: state.starting,
+ updateChecking: state.checking,
+ updateDialogOpen: state.dialogOpen,
+ updateFailed: automaticUpdateFailed(state.status, state.job),
+ setUpdateJob: controller.setJob,
+ setUpdateDialogOpen: controller.setDialogOpen,
+ openUpdateDialog: controller.openDialog,
+ checkForUpdates: () => controller.check({ manual: true }),
+ startUpdate: () => controller.startUpdate(),
+ };
+}
diff --git a/web/src/lib/useHomeIntentPrompts.js b/web/src/lib/useHomeIntentPrompts.js
new file mode 100644
index 00000000..69d30aae
--- /dev/null
+++ b/web/src/lib/useHomeIntentPrompts.js
@@ -0,0 +1,35 @@
+import { useEffect, useState } from 'react';
+import { apiConnectionScope } from './api.js';
+import { createHomeIntentController, isHomeIntentRefreshShortcut } from './homeIntentPrompts.js';
+import { toast } from '../components/Toast.jsx';
+
+export function useHomeIntentPrompts(api, enabled) {
+ const [state, setState] = useState({ suggestions: [] });
+ const scope = apiConnectionScope(api);
+ useEffect(() => {
+ if (!enabled) return undefined;
+ setState({ suggestions: [] });
+ const controller = createHomeIntentController({ api, onChange: setState,
+ onError: () => toast({ kind: 'err', text: '主页提示刷新失败,请检查模型配置后按 Ctrl+Y 重试' }) });
+ const wake = () => { if (!document.hidden) void controller.wake(); };
+ // TODO(home-intent-prompts): Remove Ctrl+Y after validating intent generation.
+ // Capture before Slate/browser redo; one key press must not edit the draft.
+ const onKey = (event) => {
+ if (!isHomeIntentRefreshShortcut(event)) return;
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ if (!event.repeat) void controller.refresh();
+ };
+ window.addEventListener('keydown', onKey, true);
+ window.addEventListener('focus', wake);
+ document.addEventListener('visibilitychange', wake);
+ void controller.start();
+ return () => {
+ window.removeEventListener('keydown', onKey, true);
+ window.removeEventListener('focus', wake);
+ document.removeEventListener('visibilitychange', wake);
+ controller.dispose();
+ };
+ }, [enabled, scope]);
+ return state;
+}
diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css
index 2a080e4e..1411dee0 100644
--- a/web/src/styles/globals.css
+++ b/web/src/styles/globals.css
@@ -3014,7 +3014,6 @@ body {
.ace-ai-theme-action .ace-theme-card-choice { color: var(--ace-accent); padding-right: 40px; }
.ace-ai-theme-action .ace-theme-card-choice:hover { background: var(--ace-accent-soft); }
.ace-ai-theme-wand { flex: 0 0 29px; margin-bottom: 3px; }
-.ace-ai-theme-description { font-size: 11px; margin-top: 3px; line-height: 1.4; }
.ace-ai-theme-arrow { position: absolute; right: 15px; bottom: 16px; }
.ace-theme-card-state { font-size: 11px; color: var(--ace-fg-mute); margin-top: 3px; }
.ace-theme-card-download { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; padding: 16px; font-size: 11px; line-height: 1.5; color: white; background: rgb(0 0 0 / .58); }
@@ -3303,11 +3302,32 @@ body {
gap: 12px;
}
.ace-home-hint-card {
+ min-width: 0;
text-align: left;
border: 1px solid var(--ace-border-soft);
border-radius: 10px;
background: var(--ace-surface);
padding: 12px;
+ cursor: pointer;
+ overflow-wrap: anywhere;
+}
+.ace-home-hint-card:hover {
+ border-color: var(--ace-accent);
+}
+.ace-home-hint-card:focus-visible {
+ outline: 2px solid var(--ace-accent);
+ outline-offset: 3px;
+}
+.ace-home-hint-card:disabled {
+ cursor: default;
+ opacity: 0.6;
+}
+.ace-home-hint-workspace {
+ min-width: 0;
+ margin-left: auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
@media (max-width: 640px) {
.ace-home-hints {
@@ -7970,3 +7990,28 @@ div.ace-markdown-opaque > div.ace-markdown-opaque-rendered {
visibility: visible;
transform: translateY(0);
}
+
+/* Two quick polish passes each minute; the remaining cycle is idle. */
+.ace-sidebar-update-attention::after {
+ content: '';
+ position: absolute;
+ inset: -2px;
+ pointer-events: none;
+ background: linear-gradient(110deg, transparent 30%, rgba(255, 255, 255, 0.55) 50%, transparent 70%);
+ opacity: 0;
+ transform: translateX(-100%);
+ animation: ace-update-polish 60s linear 60s infinite;
+}
+@keyframes ace-update-polish {
+ 0% { opacity: 0; transform: translateX(-100%); }
+ 0.01% { opacity: 1; transform: translateX(-100%); }
+ 0.6% { opacity: 1; transform: translateX(100%); }
+ 0.61% { opacity: 0; transform: translateX(100%); }
+ 0.8% { opacity: 0; transform: translateX(-100%); }
+ 0.81% { opacity: 1; transform: translateX(-100%); }
+ 1.4% { opacity: 1; transform: translateX(100%); }
+ 1.41%, 100% { opacity: 0; transform: translateX(100%); }
+}
+@media (prefers-reduced-motion: reduce) {
+ .ace-sidebar-update-attention::after { animation: none; }
+}