@dnalec/dsh-auto-approve 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js CHANGED
@@ -4,11 +4,11 @@
4
4
  * 纯 JS:无 TS / JSX / import。React 用 createElement。
5
5
  * 网页文案:locales.mjs 的 zh/en,apply 时 ctx.locale.register。
6
6
  * Host API 走 connection.rpc.call('/api', 'dsh-auto-approve', …)(已鉴权),不用裸 HTTP。
7
- * 设置页折叠状态用 React state 控 details,避免 snapshot 刷新后合上。
7
+ * 设置页折叠状态用 React state 控 details,避免 snapshot 刷新后合上。人工只走原网页审批框。
8
8
  * 恢复默认:第一次点武装,5 秒内再点才执行。
9
9
  */
10
10
  window.__ModuleLoader__.load({
11
- id: 'dsh-auto-approve',
11
+ id: '@dnalec/dsh-auto-approve',
12
12
  factory: (require) => {
13
13
  var module = { exports: {} }
14
14
  var exports = module.exports
@@ -74,7 +74,7 @@ window.__ModuleLoader__.load({
74
74
  .ab-set-fold-body{padding:12px 14px 14px;display:flex;flex-direction:column;gap:12px}
75
75
  .ab-set-fold-sum{flex:none;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;text-align:right;max-width:46%}
76
76
  .ab-set-fold-chev{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}
77
- .ab-set-fold[open] .ab-set-fold-chev{transform:rotate(180deg)}
77
+ .ab-set-fold[open]>summary .ab-set-fold-chev{transform:rotate(180deg)}
78
78
  .ab-set-card-head{flex-direction:column;gap:4px;display:flex}
79
79
  .ab-set-card-title{color:var(--dsw-alias-label-primary);align-items:center;gap:8px;font-size:14px;font-weight:500;line-height:20px;display:flex}
80
80
  .ab-set-card-sub{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:20px}
@@ -105,16 +105,12 @@ window.__ModuleLoader__.load({
105
105
  .ab-set-crit-top{display:flex;align-items:center;gap:8px;min-width:0}
106
106
  .ab-set-item-fields{min-width:0;flex-direction:column;gap:6px;display:flex}
107
107
  .ab-set-item-id{flex:1 1 auto;min-width:0;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px;font-family:var(--ds-font-family-code)}
108
- .ab-set-item-label{flex:1 1 auto;min-width:0;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;font-family:var(--ds-font-family-code);word-break:break-all}
109
108
  .ab-set-item-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;white-space:nowrap}
110
109
  .ab-set-item-del{flex:none;width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:transparent;border:none;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;padding:0}
111
110
  .ab-set-item-del:hover{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}
112
111
  .ab-set-empty{color:var(--dsw-alias-label-caption);margin:0;font-size:13px;line-height:20px;padding:4px 2px}
113
112
  .ab-set-tag{box-sizing:border-box;flex:none;height:18px;border-radius:9px;align-items:center;padding:0 8px;font-size:11px;line-height:18px;display:inline-flex}
114
113
  .ab-set-tag-blue{color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}
115
- .ab-set-tag-green{color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-state-success-tertiary)}
116
- .ab-set-tag-gray{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform)}
117
- .ab-set-tag-red{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger)}
118
114
  .ab-set-steps{display:flex;flex-wrap:wrap;gap:6px}
119
115
  .ab-set-step{box-sizing:border-box;margin:0;border:1px solid var(--dsw-alias-border-l2);background:transparent;border-radius:10px;padding:8px 10px;min-width:108px;flex:1 1 108px;cursor:pointer;display:flex;flex-direction:column;gap:2px;align-items:flex-start;text-align:left;font:inherit;color:inherit}
120
116
  .ab-set-step:hover{background:var(--dsw-alias-interactive-bg-hover)}
@@ -131,18 +127,11 @@ window.__ModuleLoader__.load({
131
127
  .ab-set-choice-on{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}
132
128
  .ab-set-choice-t{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}
133
129
  .ab-set-choice-d{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
134
- .ab-set-learn-info{flex:1 1 auto;min-width:0;flex-direction:column;gap:2px;display:flex}
135
- .ab-set-learn-sub{color:var(--dsw-alias-label-tertiary);font-size:11px}
136
- .ab-chat{cursor:pointer}
137
- .ab-chat:hover{background:var(--dsw-alias-interactive-bg-hover)}
138
- .ab-set-qr{text-align:center;padding:4px 0 8px}
139
- .ab-set-qr img{box-sizing:border-box;border-radius:8px;background:#fff;padding:8px;max-width:240px;width:100%;height:auto}
140
- .ab-set-qr-tip{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:12px;line-height:18px}
141
130
  `
142
131
 
143
132
  const NS = "dsh-auto-approve"
144
- const ZH = JSON.parse(String.raw`{"slot.notice":"自动放行提示","slot.history":"审批","slot.settings":"自动审批","action.reject":"拒绝","action.allow":"允许","action.human":"人工","verdict.keyword-allow":"关键词允许","verdict.keyword-reject":"关键词拒绝","path.keyword-reject":"关键词拒绝","path.keyword-allow":"关键词允许","path.keyword-human":"关键词转人工","path.criteria-reject":"审核表拒绝","path.criteria-allow":"审核表允许","path.criteria-human":"审核表转人工","path.judge-failed":"判定失败转人工","path.missing-payload":"缺少工具参数转人工","path.truncated-payload":"参数过长转人工","path.plugin-error":"插件异常转人工","notice.feedError":"审批提示暂时不可用","notice.feedErrorTag":"连接失败","notice.pendingTitle":"等待人工审批{ticket}:{preview}","notice.pendingTag":"人工审批中{ticket}","notice.qqApproved":"已在 QQ 批准{ticket}:{preview}","notice.webApproved":"人工审批通过{ticket}:{preview}","notice.qqApprovedTag":"已在 QQ 批准{ticket}","notice.webApprovedTag":"人工审批通过","notice.rejectedTitle":"已拒绝{ticket}:{preview}","notice.rejectedTag":"已拒绝{ticket}","notice.autoTag":"自动放行 · {verdict}","notice.autoDefault":"自动放行","notice.rejectedDefault":"已拒绝","notice.hint":"提示","notice.close":"关闭","notice.feedLoadFailed":"审批提示加载失败,将自动重试","history.noSession":"未选择会话","history.loadFailed":"加载失败:{error}","history.loading":"加载中…","history.title":"审批","history.emptySub":"当前会话还没有审批记录","history.emptyHint":"自动放行与转人工都会出现在这里","history.sub":"最新在上 · 点开看命令与详情","history.tagAuto":"自动放行","history.tagPending":"待处理 {ticket}","history.tagQqAllow":"QQ 批准 {ticket}","history.tagAllow":"批准 {ticket}","history.tagReject":"拒绝 {ticket}","detail.request":"请求","detail.audit":"审核","detail.command":"命令","detail.path":"路径","detail.description":"描述","detail.old":"原文","detail.new":"改成","detail.content":"写入内容","detail.code":"代码","detail.url":"URL","detail.query":"查询","detail.script":"脚本","detail.sql":"SQL","detail.prompt":"提示词","detail.cwd":"工作目录","detail.sandbox":"沙箱","detail.justification":"模型理由","detail.pipe":"管道","detail.keyword":"关键词","detail.judgeModel":"审核模型","detail.judgeCategory":"审核类别","detail.judgeAction":"执行动作","detail.judgeReason":"审核理由","detail.judgeError":"审核失败","detail.judgeRaw":"审核结果原文","detail.ticket":"短号","detail.source":"来源","detail.judgeFailed":"未完成(解析失败/超时转人工,不执行「其他」的动作)","set.title":"自动审批","set.intro":"需要审批的行为会自动审批,并可推送到已配置的消息平台。按私人 bot 设计:未绑定聊天时,私聊回「是」会绑成审批目标,不要让陌生人能私聊这个 bot。","set.allowlistCorrupt":"规则文件损坏,当前是内存默认,普通保存不会覆盖磁盘。请修好 allowlist.json,或点「恢复默认」写回出厂规则。","set.pluginCorrupt":"插件配置损坏,拒绝保存以免清空 chatId。请修好 ~/.dsh/approval-bridge/config.json,或","set.pluginCorruptOverwrite":"覆盖损坏配置","set.qqCorrupt":"QQ 凭据文件损坏,未覆盖磁盘。请修好 ~/.dsh/approval-bridge/qqbot.json,或重新保存凭据 / 扫码。","set.loading":"加载中…","set.loadFailed":"加载失败:{error}","set.resetConfirm":"再点一次确认恢复","set.presetMissingTitle":"权限预设","set.presetMissingSub":"插件启动时会写入 auto-approve。当前未检测到,可手动补写。","set.presetWrite":"写入权限预设","set.presetWriteNote":"写入后需重启 dsh web","set.presetWrote":"已写入,请重启 dsh web","set.modeTitle":"自动审批模式","set.modeWs":"workspace-write(推荐)","set.modeWsHint":"工作区内直接放行,越出工作区需审核。","set.modeRo":"read-only","set.modeRoHint":"工作区内写操作也需审核。","set.save":"保存","set.hotOk":"已生效(热更新,无需重启)","set.modeSaved":"已写入。请重启 dsh web,并重新选择「自动审批」或开新会话","set.overview":"审批总览","set.overviewSub":"关键词(拒绝优先于人工优先于允许)→ 审核表由模型归类,程序按表执行。解析失败转人工。","set.stepKeywords":"关键词","set.stepCriteria":"审核表","set.stepJudge":"审核模型","set.counts":"拒 {reject} · 人 {human} · 允 {allow}","set.criteriaCount":"{n} 项","set.followDefault":"跟随默认","set.unconfigured":"未配置","set.kwSub":"匹配命令、路径和工作目录。拒绝/人工词也匹配工具名;允许词不匹配工具名,避免把 bash/write 整类放行。","set.empty":"暂无","set.presetTag":"预置","set.kwEditTitle":"点击修改","set.kwPlaceholder":"新关键词","set.add":"添加","set.resetKeywords":"恢复默认关键词","set.resetKeywordsOk":"已恢复默认关键词","set.confirm":"确认","set.delete":"删除","set.criteriaSub":"审核模型以此表作为审核标准。「其他」作为兜底选项不能删除。","set.labelPlaceholder":"标签","set.descPlaceholder":"说明(写入审核提示词)","set.resetCriteria":"恢复默认审核表({lang})","set.resetCriteriaOk":"已恢复为{lang}默认审核表","set.addCriterion":"添加审核项","set.criterionId":"id","set.judgeTitle":"审核模型","set.judgeSubLead":"独立于当前会话模型。空则跟随部署默认","set.judgeSubFallback":"({provider} / {model})","set.judgeSubNoFallback":"(当前没有默认可跟随)","set.judgeSubTail":"。没有可用路由时当次转人工,不会再猜一个模型。建议思考强度 off。","set.followProvider":"跟随默认提供方","set.followModel":"跟随默认模型","set.modelDefaultEffort":"模型默认","set.effortOff":"off(建议)","set.judgeTimeoutMs":"审核超时(ms):","set.judgeSaved":"审核模型已保存","set.judgeLang":"提示词语言","set.judgeLangZh":"中文","set.judgeLangEn":"English","set.judgeLangHint":"只改发给审核模型的框架语言,不会改当前审核表。恢复默认审核表时使用这里选的语言。","set.qqTitle":"QQ 审批通道","set.qqSub":"扫码或填凭据。按私人 bot 设计:未绑定 chatId 时,私聊回「是」会绑成审批聊天。不要让陌生人能私聊这个 bot。群必须 @机器人并另配 userId。","set.qqScan":"扫码接入机器人","set.qqRescan":"重新扫码接入","set.qqCancelScan":"取消扫码","set.qqCancelOk":"已取消扫码","set.qqScanStart":"已开始扫码,请用手机 QQ 扫描","set.qqScanOk":"扫码成功,凭据已保存","set.qqScanFail":"扫码失败","set.qqScanStatus":"扫码接入:{status}","set.qqQrAlt":"QQ 机器人扫码二维码","set.qqQrTip":"请用手机 QQ 扫码并确认创建机器人(约 5 分钟有效)","set.qqAppId":"AppID","set.qqAppSecret":"AppSecret","set.qqSecretKeep":"AppSecret(留空则保留)","set.qqSaveConnect":"保存并连接","set.qqCredsReconnect":"凭据已保存并重连","set.qqReconnect":"重连","set.qqReconnected":"正在重连","set.qqUnknown":"未知","set.qqChatOn":"已选定聊天","set.qqChatOff":"未选定","set.qqPush":"推送:{state}","set.qqPushOn":"","set.qqPushOff":"","set.qqPushToggled":"已切换推送","set.qqEnablePush":"启用推送","set.qqPausePush":"暂停推送","set.qqCurrentChat":"当前 chatId:{chatId}","set.qqUnset":"(未选定)","set.qqChatPlaceholder":"chatId(私聊 openid / 群 g:openid)","set.qqGroupUser":"群 userId","set.qqTimeoutS":"超时s","set.prov.loggingIn":"登录中","set.prov.waitScan":"等待扫码","set.prov.saving":"保存凭据","set.prov.connected":"已连接","set.chatId":"chatId","set.userId":"userId(群)","set.notifyTimeout":"提醒间隔(秒)","set.saveTarget":"保存目标/超时","set.targetSaved":"推送目标已保存","set.recent":"最近来信(点选设为审批聊天;群必须 @机器人,还需匹配 userId)","set.noMail":"还没有来信。加好友后给机器人发任意一句。","set.group":"群","set.c2c":"私聊","set.chatPicked":"已选定审批聊天","set.overwriteOk":"已覆盖损坏的插件配置","rpc.unavailable":"connection.rpc 不可用","rpc.failed":"RPC 失败"}`)
145
- const EN = JSON.parse(String.raw`{"slot.notice":"Auto-approve notice","slot.history":"Approvals","slot.settings":"Auto-approve","action.reject":"Reject","action.allow":"Allow","action.human":"Human","verdict.keyword-allow":"Keyword allow","verdict.keyword-reject":"Keyword reject","path.keyword-reject":"Keyword reject","path.keyword-allow":"Keyword allow","path.keyword-human":"Keyword → human","path.criteria-reject":"Criteria reject","path.criteria-allow":"Criteria allow","path.criteria-human":"Criteria → human","path.judge-failed":"Judge failed → human","path.missing-payload":"Missing tool args → human","path.truncated-payload":"Truncated args → human","path.plugin-error":"Plugin error → human","notice.feedError":"Approval notices unavailable","notice.feedErrorTag":"Connection failed","notice.pendingTitle":"Waiting for human{ticket}: {preview}","notice.pendingTag":"Human review{ticket}","notice.qqApproved":"Approved in QQ{ticket}: {preview}","notice.webApproved":"Approved{ticket}: {preview}","notice.qqApprovedTag":"Approved in QQ{ticket}","notice.webApprovedTag":"Approved","notice.rejectedTitle":"Rejected{ticket}: {preview}","notice.rejectedTag":"Rejected{ticket}","notice.autoTag":"Auto-allowed · {verdict}","notice.autoDefault":"Auto-allowed","notice.rejectedDefault":"Rejected","notice.hint":"Notice","notice.close":"Dismiss","notice.feedLoadFailed":"Approval notices failed to load; retrying","history.noSession":"No session selected","history.loadFailed":"Failed to load: {error}","history.loading":"Loading…","history.title":"Approvals","history.emptySub":"This session has no approval records yet","history.emptyHint":"Auto-allows and human escalations appear here","history.sub":"Newest first · expand for command and details","history.tagAuto":"Auto-allowed","history.tagPending":"Pending {ticket}","history.tagQqAllow":"QQ allow {ticket}","history.tagAllow":"Allow {ticket}","history.tagReject":"Reject {ticket}","detail.request":"Request","detail.audit":"Review","detail.command":"Command","detail.path":"Path","detail.description":"Description","detail.old":"Original","detail.new":"Replacement","detail.content":"Write contents","detail.code":"Code","detail.url":"URL","detail.query":"Query","detail.script":"Script","detail.sql":"SQL","detail.prompt":"Prompt","detail.cwd":"Working directory","detail.sandbox":"Sandbox","detail.justification":"Model justification","detail.pipe":"Pipeline","detail.keyword":"Keyword","detail.judgeModel":"Judge model","detail.judgeCategory":"Category","detail.judgeAction":"Action","detail.judgeReason":"Judge reason","detail.judgeError":"Judge error","detail.judgeRaw":"Judge raw output","detail.ticket":"Ticket","detail.source":"Source","detail.judgeFailed":"Incomplete (parse failure/timeout → human; does not run Other)","set.title":"Auto-approve","set.intro":"Actions that need approval are auto-approved and can be pushed to a configured chat. Designed for a private bot: if no chat is bound, a C2C reply of “是” binds that chat. Do not let strangers DM this bot.","set.allowlistCorrupt":"The rules file is corrupt. This process is using in-memory defaults and will not overwrite the disk. Repair allowlist.json, or Restore defaults to write shipped rules.","set.pluginCorrupt":"Plugin config is corrupt; saves are refused so chatId is not wiped. Repair ~/.dsh/approval-bridge/config.json, or ","set.pluginCorruptOverwrite":"Overwrite corrupt config","set.qqCorrupt":"QQ credentials file is corrupt and was not overwritten. Repair ~/.dsh/approval-bridge/qqbot.json, or save credentials / scan again.","set.loading":"Loading…","set.loadFailed":"Failed to load: {error}","set.resetConfirm":"Click again to confirm restore","set.presetMissingTitle":"Permission preset","set.presetMissingSub":"Startup writes auto-approve. It was not detected; you can write it now.","set.presetWrite":"Write permission preset","set.presetWriteNote":"Restart dsh web after writing","set.presetWrote":"Written. Restart dsh web","set.modeTitle":"Auto-approve mode","set.modeWs":"workspace-write (recommended)","set.modeWsHint":"In-workspace writes skip approval; outside the workspace still goes through the judge.","set.modeRo":"read-only","set.modeRoHint":"In-workspace writes also go through the judge.","set.save":"Save","set.hotOk":"Applied (live; no restart)","set.modeSaved":"Written. Restart dsh web and re-select Auto-approve or start a new session","set.overview":"Approval overview","set.overviewSub":"Keywords (reject > human > allow) → the judge classifies; the table decides. Parse failure → human.","set.stepKeywords":"Keywords","set.stepCriteria":"Criteria","set.stepJudge":"Judge model","set.counts":"rej {reject} · hum {human} · all {allow}","set.criteriaCount":"{n} rows","set.followDefault":"Follow default","set.unconfigured":"Not configured","set.kwSub":"Match command, path, and workdir. Reject/human also match the tool name; allow keywords do not, so bash/write is not allowed as a class.","set.empty":"None","set.presetTag":"Shipped","set.kwEditTitle":"Click to edit","set.kwPlaceholder":"New keyword","set.add":"Add","set.resetKeywords":"Restore default keywords","set.resetKeywordsOk":"Default keywords restored","set.confirm":"Confirm","set.delete":"Delete","set.criteriaSub":"The judge uses this table. Other cannot be deleted.","set.labelPlaceholder":"Label","set.descPlaceholder":"Description (goes into the judge prompt)","set.resetCriteria":"Restore default criteria ({lang})","set.resetCriteriaOk":"Restored {lang} default criteria","set.addCriterion":"Add criterion","set.criterionId":"id","set.judgeTitle":"Judge model","set.judgeSubLead":"Independent of the session model. Empty follows the deployment default","set.judgeSubFallback":" ({provider} / {model})","set.judgeSubNoFallback":" (no default to follow)","set.judgeSubTail":". With no usable route, that request goes to a human; the plugin will not guess a model. Prefer effort off.","set.followProvider":"Follow default provider","set.followModel":"Follow default model","set.modelDefaultEffort":"Model default","set.effortOff":"off (recommended)","set.judgeTimeoutMs":"Judge timeout (ms):","set.judgeSaved":"Judge model saved","set.judgeLang":"Prompt language","set.judgeLangZh":"Chinese","set.judgeLangEn":"English","set.judgeLangHint":"Changes only the judge prompt framework, not the current criteria table. Restore default criteria uses this language.","set.qqTitle":"QQ approval channel","set.qqSub":"Scan or paste credentials. Designed for a private bot: if no chatId is bound, a C2C reply of “是” binds that chat. Do not let strangers DM this bot. Groups must @ the bot and set userId.","set.qqScan":"Scan to create bot","set.qqRescan":"Scan again","set.qqCancelScan":"Cancel scan","set.qqCancelOk":"Scan cancelled","set.qqScanStart":"Scan started; use mobile QQ","set.qqScanOk":"Scan succeeded; credentials saved","set.qqScanFail":"Scan failed","set.qqScanStatus":"Scan: {status}","set.qqQrAlt":"QQ bot QR code","set.qqQrTip":"Scan with mobile QQ and confirm creating the bot (about 5 minutes)","set.qqAppId":"AppID","set.qqAppSecret":"AppSecret","set.qqSecretKeep":"AppSecret (leave blank to keep)","set.qqSaveConnect":"Save and connect","set.qqCredsReconnect":"Credentials saved; reconnecting","set.qqReconnect":"Reconnect","set.qqReconnected":"Reconnecting","set.qqUnknown":"Unknown","set.qqChatOn":"Chat selected","set.qqChatOff":"No chat selected","set.qqPush":"Push: {state}","set.qqPushOn":"on","set.qqPushOff":"off","set.qqPushToggled":"Push toggled","set.qqEnablePush":"Enable push","set.qqPausePush":"Pause push","set.qqCurrentChat":"Current chatId: {chatId}","set.qqUnset":"(none)","set.qqChatPlaceholder":"chatId (C2C openid / group g:openid)","set.qqGroupUser":"Group userId","set.qqTimeoutS":"Timeout s","set.prov.loggingIn":"Signing in","set.prov.waitScan":"Waiting for scan","set.prov.saving":"Saving credentials","set.prov.connected":"Connected","set.chatId":"chatId","set.userId":"userId (group)","set.notifyTimeout":"Reminder interval (seconds)","set.saveTarget":"Save target / timeout","set.targetSaved":"Push target saved","set.recent":"Recent incoming (click to set as the approval chat; groups must @ the bot and match userId)","set.noMail":"No messages yet. Friend the bot and send it anything.","set.group":"Group","set.c2c":"C2C","set.chatPicked":"Approval chat selected","set.overwriteOk":"Corrupt plugin config overwritten","rpc.unavailable":"connection.rpc is unavailable","rpc.failed":"RPC failed"}`)
133
+ const ZH = JSON.parse(String.raw`{"slot.notice":"自动放行提示","slot.history":"审批","slot.settings":"自动审批","action.reject":"拒绝","action.allow":"允许","action.human":"人工","criterion.deletion":"删除/覆盖不可再生数据","criterion.credential":"凭据/密钥/授权修改","criterion.remote":"远程系统/生产环境/数据库","criterion.system":"系统级路径/配置","criterion.bulk":"批量不可回补操作","criterion.approval-config":"自动审批配置","criterion.safe":"安全/常规可回补","criterion.other":"其他(拿不准)","sandbox.read-only":"只读","sandbox.workspace-write":"工作区可写","sandbox.danger-full-access":"全权限","source.web":"网页","verdict.keyword-allow":"关键词允许","verdict.keyword-reject":"关键词拒绝","verdict.keyword-human":"关键词转人工","verdict.criteria-reject":"审核表拒绝","verdict.criteria-allow":"审核表允许","verdict.criteria-human":"审核表转人工","verdict.judge-failed":"判定失败转人工","verdict.missing-payload":"缺少工具参数转人工","verdict.truncated-payload":"参数过长转人工","verdict.plugin-error":"插件异常转人工","verdict.human":"转人工","verdict.cancelled":"人工取消","verdict.unavailable":"审批不可用","path.keyword-reject":"关键词拒绝","path.keyword-allow":"关键词允许","path.keyword-human":"关键词转人工","path.criteria-reject":"审核表拒绝","path.criteria-allow":"审核表允许","path.criteria-human":"审核表转人工","path.judge-failed":"判定失败转人工","path.missing-payload":"缺少工具参数转人工","path.truncated-payload":"参数过长转人工","path.plugin-error":"插件异常转人工","notice.feedError":"审批提示暂时不可用","notice.feedErrorTag":"连接失败","notice.pendingTitle":"等待人工审批:{preview}","notice.pendingTag":"人工审批中","notice.webApproved":"人工审批通过:{preview}","notice.webApprovedTag":"人工审批通过","notice.rejectedTitle":"已拒绝:{preview}","notice.rejectedTag":"已拒绝","notice.cancelledTitle":"已取消人工审批:{preview}","notice.unavailableTitle":"审批不可用:{preview}","notice.autoTag":"自动放行 · {verdict}","notice.autoDefault":"自动放行","notice.rejectedDefault":"已拒绝","notice.hint":"提示","notice.close":"关闭","notice.feedLoadFailed":"审批提示加载失败,将自动重试","history.noSession":"未选择会话","history.loadFailed":"加载失败:{error}","history.loading":"加载中…","history.title":"审批","history.emptySub":"当前会话还没有审批记录","history.emptyHint":"自动放行与转人工都会出现在这里","history.sub":"最新在上 · 点开看命令与详情","history.tagAuto":"自动放行","history.tagPending":"转人工","history.tagAllow":"人工批准","history.tagReject":"人工拒绝","history.tagCancel":"人工取消","history.tagUnavailable":"审批不可用","history.unknownTool":"工具","detail.request":"请求","detail.audit":"审核","detail.command":"命令","detail.path":"路径","detail.description":"描述","detail.old":"原文","detail.new":"改成","detail.content":"写入内容","detail.code":"代码","detail.url":"URL","detail.query":"查询","detail.script":"脚本","detail.sql":"SQL","detail.prompt":"提示词","detail.cwd":"工作目录","detail.sandbox":"沙箱","detail.justification":"模型理由","detail.pipe":"管道","detail.keyword":"关键词","detail.judgeModel":"审核模型","detail.judgeCategory":"审核类别","detail.judgeAction":"执行动作","detail.judgeReason":"审核理由","detail.judgeError":"审核失败","detail.judgeRaw":"审核结果原文","detail.source":"来源","detail.judgeFailed":"未完成(解析失败/超时转人工,不执行「其他」的动作)","set.title":"自动审批","set.intro":"需要审批的行为由审核模型自动判定。允许或拒绝与在网页点按钮相同;拿不准则交给原来的人工审批框。","set.allowlistCorrupt":"规则文件损坏,当前是内存默认,普通保存不会覆盖磁盘。请修好 allowlist.json,或点「恢复默认」写回出厂规则。","set.pluginCorrupt":"插件配置损坏,拒绝保存以免清空审核模型。请修好 ~/.dsh/auto-approve/config.json,或","set.pluginCorruptOverwrite":"覆盖损坏配置","set.loading":"加载中…","set.loadFailed":"加载失败:{error}","set.resetConfirm":"再点一次确认恢复","set.presetMissingTitle":"权限预设","set.presetMissingSub":"插件启动时会写入 auto-approve。当前未检测到,可手动补写。","set.presetWrite":"写入权限预设","set.presetWriteNote":"写入后需重启 dsh web","set.presetWrote":"已写入,请重启 dsh web","set.modeTitle":"自动审批模式","set.modeWs":"工作区可写(推荐)","set.modeWsHint":"工作区内直接放行,越出工作区需审核。","set.modeRo":"只读","set.modeRoHint":"工作区内写操作也需审核。","set.save":"保存","set.hotOk":"已生效(热更新,无需重启)","set.modeSaved":"已写入。请重启 dsh web,并重新选择「自动审批」或开新会话","set.overview":"审批总览","set.overviewSub":"关键词(拒绝优先于人工优先于允许)→ 审核表由模型归类,程序按表执行。解析失败转人工。","set.stepKeywords":"关键词","set.stepCriteria":"审核表","set.stepJudge":"审核模型","set.counts":"拒 {reject} · 人 {human} · 允 {allow}","set.criteriaCount":"{n} 项","set.followDefault":"跟随默认","set.unconfigured":"未配置","set.kwSub":"匹配命令、路径和工作目录。拒绝/人工词也匹配工具名;允许词不匹配工具名,避免把 bash/write 整类放行。","set.empty":"暂无","set.presetTag":"预置","set.kwEditTitle":"点击修改","set.kwPlaceholder":"新关键词","set.add":"添加","set.resetKeywords":"恢复默认关键词","set.resetKeywordsOk":"已恢复默认关键词","set.confirm":"确认","set.delete":"删除","set.criteriaSub":"审核模型以此表作为审核标准。「其他」作为兜底选项不能删除。","set.labelPlaceholder":"标签","set.descPlaceholder":"说明(写入审核提示词)","set.resetCriteria":"恢复默认审核表({lang})","set.resetCriteriaOk":"已恢复为{lang}默认审核表","set.addCriterion":"添加审核项","set.criterionId":"id","set.judgeTitle":"审核模型","set.judgeSubLead":"独立于当前会话模型。空则跟随部署默认","set.judgeSubFallback":"({provider} / {model})","set.judgeSubNoFallback":"(当前没有默认可跟随)","set.judgeSubTail":"。没有可用路由时当次转人工,不会再猜一个模型。建议思考强度 off。","set.followProvider":"跟随默认提供方","set.followModel":"跟随默认模型","set.modelDefaultEffort":"模型默认","set.effortOff":"off(建议)","set.judgeTimeoutMs":"审核超时(ms):","set.judgeSaved":"审核模型已保存","set.judgeLang":"提示词语言","set.judgeLangZh":"中文","set.judgeLangEn":"English","set.judgeLangHint":"只改发给审核模型的框架语言,不会改当前审核表。恢复默认审核表时使用这里选的语言。","set.overwriteOk":"已覆盖损坏的插件配置","err.missingPayload":"工具参数不完整,转人工","err.missingPayloadUncaptured":"未捕获工具参数,转人工","err.truncatedPayload":"工具参数过长已截断,转人工(禁止按前缀自动放行)","err.pluginError":"插件判定异常,转人工","err.allowlistCorrupt":"规则文件损坏,拒绝覆盖。请先「恢复默认」写回出厂规则,或修好磁盘上的 allowlist.json","err.allowlistWrite":"写入 allowlist 失败","err.criterionNotFound":"未找到该审核项","err.criterionNeedId":"需要 id 或 label","err.criterionIdExists":"id 已存在","err.criterionOtherLocked":"「其他」不可删除","err.criteriaOp":"审核表请用添加 / 修改 / 删除 / 恢复默认","err.opMustSet":"{kind} 只能用修改操作","err.invalidNumber":"无效数值","err.keywordEmpty":"关键词不能为空","err.keywordNotFound":"未找到该关键词","err.keywordsOp":"关键词请用添加 / 修改 / 删除 / 恢复默认","err.valueEmpty":"值不能为空","err.unknownKind":"未知规则类型:{kind}","err.unknownOp":"未知操作:{op}","err.ruleNotFound":"未找到匹配的规则","err.badBody":"请求体不是 JSON","err.needSessionId":"需要 sessionId","err.pluginCorrupt":"插件配置损坏,拒绝覆盖。请修好磁盘文件,或点「覆盖损坏配置」","err.pluginWrite":"写入配置失败","err.unknownEndpoint":"未知接口:{endpoint}","err.internal":"内部错误:{error}","err.catalog":"无法列出模型:{error}","err.info":"无法读取模型信息:{error}","err.judgeUnconfigured":"未配置审核模型","err.judgeEffort":"思考强度 {effort} 不受支持","err.judgeTimeout":"审核超时({ms}ms)","err.judgeRetryTimeout":"审核重试超时({ms}ms)","err.judgeFailed":"审核失败","err.judgeCall":"审核模型调用失败:{error}","err.judgeEmpty":"审核模型输出为空","err.judgeParse":"审核模型输出无法解析","err.judgeUpstream":"审核模型路由失败:{error}","err.noPresetsKey":"permission 条目缺少 presets 键,请手动添加","err.preset":"写入预设失败:{error}","rpc.unavailable":"connection.rpc 不可用","rpc.failed":"RPC 失败"}`)
134
+ const EN = JSON.parse(String.raw`{"slot.notice":"Auto-approve notice","slot.history":"Approvals","slot.settings":"Auto-approve","action.reject":"Reject","action.allow":"Allow","action.human":"Human","criterion.deletion":"Delete/overwrite irreplaceable data","criterion.credential":"Credentials/keys/auth changes","criterion.remote":"Remote/production/database","criterion.system":"System paths/config","criterion.bulk":"Bulk irreversible operations","criterion.approval-config":"Auto-approve configuration","criterion.safe":"Safe/routine reversible","criterion.other":"Other (unsure)","sandbox.read-only":"Read-only","sandbox.workspace-write":"Workspace write","sandbox.danger-full-access":"Full access","source.web":"Web","verdict.keyword-allow":"Keyword allow","verdict.keyword-reject":"Keyword reject","verdict.keyword-human":"Keyword → human","verdict.criteria-reject":"Criteria reject","verdict.criteria-allow":"Criteria allow","verdict.criteria-human":"Criteria → human","verdict.judge-failed":"Judge failed → human","verdict.missing-payload":"Missing tool args → human","verdict.truncated-payload":"Truncated args → human","verdict.plugin-error":"Plugin error → human","verdict.human":"To human","verdict.cancelled":"Human cancel","verdict.unavailable":"Unavailable","path.keyword-reject":"Keyword reject","path.keyword-allow":"Keyword allow","path.keyword-human":"Keyword → human","path.criteria-reject":"Criteria reject","path.criteria-allow":"Criteria allow","path.criteria-human":"Criteria → human","path.judge-failed":"Judge failed → human","path.missing-payload":"Missing tool args → human","path.truncated-payload":"Truncated args → human","path.plugin-error":"Plugin error → human","notice.feedError":"Approval notices unavailable","notice.feedErrorTag":"Connection failed","notice.pendingTitle":"Waiting for human: {preview}","notice.pendingTag":"Human review","notice.webApproved":"Approved: {preview}","notice.webApprovedTag":"Approved","notice.rejectedTitle":"Rejected: {preview}","notice.rejectedTag":"Rejected","notice.cancelledTitle":"Cancelled: {preview}","notice.unavailableTitle":"Unavailable: {preview}","notice.autoTag":"Auto-allowed · {verdict}","notice.autoDefault":"Auto-allowed","notice.rejectedDefault":"Rejected","notice.hint":"Notice","notice.close":"Dismiss","notice.feedLoadFailed":"Approval notices failed to load; retrying","history.noSession":"No session selected","history.loadFailed":"Failed to load: {error}","history.loading":"Loading…","history.title":"Approvals","history.emptySub":"This session has no approval records yet","history.emptyHint":"Auto-allows and human escalations appear here","history.sub":"Newest first · expand for command and details","history.tagAuto":"Auto-allowed","history.tagPending":"To human","history.tagAllow":"Human allow","history.tagReject":"Human reject","history.tagCancel":"Human cancel","history.tagUnavailable":"Unavailable","history.unknownTool":"Tool","detail.request":"Request","detail.audit":"Review","detail.command":"Command","detail.path":"Path","detail.description":"Description","detail.old":"Original","detail.new":"Replacement","detail.content":"Write contents","detail.code":"Code","detail.url":"URL","detail.query":"Query","detail.script":"Script","detail.sql":"SQL","detail.prompt":"Prompt","detail.cwd":"Working directory","detail.sandbox":"Sandbox","detail.justification":"Model justification","detail.pipe":"Pipeline","detail.keyword":"Keyword","detail.judgeModel":"Judge model","detail.judgeCategory":"Category","detail.judgeAction":"Action","detail.judgeReason":"Judge reason","detail.judgeError":"Judge error","detail.judgeRaw":"Judge raw output","detail.source":"Source","detail.judgeFailed":"Incomplete (parse failure/timeout → human; does not run Other)","set.title":"Auto-approve","set.intro":"Actions that need approval are judged automatically. Allow and reject match clicking the Web buttons; uncertainty goes to the original human dialog.","set.allowlistCorrupt":"The rules file is corrupt. This process is using in-memory defaults and will not overwrite the disk. Repair allowlist.json, or Restore defaults to write shipped rules.","set.pluginCorrupt":"Plugin config is corrupt; saves are refused so the judge model is not wiped. Repair ~/.dsh/auto-approve/config.json, or ","set.pluginCorruptOverwrite":"Overwrite corrupt config","set.loading":"Loading…","set.loadFailed":"Failed to load: {error}","set.resetConfirm":"Click again to confirm restore","set.presetMissingTitle":"Permission preset","set.presetMissingSub":"Startup writes auto-approve. It was not detected; you can write it now.","set.presetWrite":"Write permission preset","set.presetWriteNote":"Restart dsh web after writing","set.presetWrote":"Written. Restart dsh web","set.modeTitle":"Auto-approve mode","set.modeWs":"Workspace write (recommended)","set.modeWsHint":"In-workspace writes skip approval; outside the workspace still goes through the judge.","set.modeRo":"Read-only","set.modeRoHint":"In-workspace writes also go through the judge.","set.save":"Save","set.hotOk":"Applied (live; no restart)","set.modeSaved":"Written. Restart dsh web and re-select Auto-approve or start a new session","set.overview":"Approval overview","set.overviewSub":"Keywords (reject > human > allow) → the judge classifies; the table decides. Parse failure → human.","set.stepKeywords":"Keywords","set.stepCriteria":"Criteria","set.stepJudge":"Judge model","set.counts":"rej {reject} · hum {human} · all {allow}","set.criteriaCount":"{n} rows","set.followDefault":"Follow default","set.unconfigured":"Not configured","set.kwSub":"Match command, path, and workdir. Reject/human also match the tool name; allow keywords do not, so bash/write is not allowed as a class.","set.empty":"None","set.presetTag":"Shipped","set.kwEditTitle":"Click to edit","set.kwPlaceholder":"New keyword","set.add":"Add","set.resetKeywords":"Restore default keywords","set.resetKeywordsOk":"Default keywords restored","set.confirm":"Confirm","set.delete":"Delete","set.criteriaSub":"The judge uses this table. Other cannot be deleted.","set.labelPlaceholder":"Label","set.descPlaceholder":"Description (goes into the judge prompt)","set.resetCriteria":"Restore default criteria ({lang})","set.resetCriteriaOk":"Restored {lang} default criteria","set.addCriterion":"Add criterion","set.criterionId":"id","set.judgeTitle":"Judge model","set.judgeSubLead":"Independent of the session model. Empty follows the deployment default","set.judgeSubFallback":" ({provider} / {model})","set.judgeSubNoFallback":" (no default to follow)","set.judgeSubTail":". With no usable route, that request goes to a human; the plugin will not guess a model. Prefer effort off.","set.followProvider":"Follow default provider","set.followModel":"Follow default model","set.modelDefaultEffort":"Model default","set.effortOff":"off (recommended)","set.judgeTimeoutMs":"Judge timeout (ms):","set.judgeSaved":"Judge model saved","set.judgeLang":"Prompt language","set.judgeLangZh":"Chinese","set.judgeLangEn":"English","set.judgeLangHint":"Changes only the judge prompt framework, not the current criteria table. Restore default criteria uses this language.","set.overwriteOk":"Corrupt plugin config overwritten","err.missingPayload":"Tool arguments are incomplete; sending to human","err.missingPayloadUncaptured":"Tool arguments were not captured; sending to human","err.truncatedPayload":"Tool arguments were truncated; sending to human (no prefix auto-allow)","err.pluginError":"Plugin error; sending to human","err.allowlistCorrupt":"Rules file is corrupt; refusing to overwrite. Restore defaults or fix allowlist.json on disk.","err.allowlistWrite":"Failed to write allowlist","err.criterionNotFound":"Criterion not found","err.criterionNeedId":"id or label is required","err.criterionIdExists":"id already exists","err.criterionOtherLocked":"“Other” cannot be deleted","err.criteriaOp":"Criteria accept add / set / remove / reset","err.opMustSet":"{kind} only accepts set","err.invalidNumber":"Invalid number","err.keywordEmpty":"Keyword cannot be empty","err.keywordNotFound":"Keyword not found","err.keywordsOp":"Keywords accept add / set / remove / reset","err.valueEmpty":"Value cannot be empty","err.unknownKind":"Unknown rule kind: {kind}","err.unknownOp":"Unknown operation: {op}","err.ruleNotFound":"No matching rule","err.badBody":"Request body is not JSON","err.needSessionId":"sessionId is required","err.pluginCorrupt":"Plugin config is corrupt; refusing to overwrite. Fix the file on disk, or overwrite the corrupt config.","err.pluginWrite":"Failed to write plugin config","err.unknownEndpoint":"Unknown endpoint: {endpoint}","err.internal":"Internal error: {error}","err.catalog":"Could not list models: {error}","err.info":"Could not load model info: {error}","err.judgeUnconfigured":"Judge model is not configured","err.judgeEffort":"reasoningEffort {effort} is not supported","err.judgeTimeout":"Judge timed out ({ms}ms)","err.judgeRetryTimeout":"Judge retry timed out ({ms}ms)","err.judgeFailed":"Judge failed","err.judgeCall":"Judge call failed: {error}","err.judgeEmpty":"Judge output was empty","err.judgeParse":"Judge output could not be parsed","err.judgeUpstream":"Judge route failed: {error}","err.noPresetsKey":"The permission entry has no presets key; add it manually","err.preset":"Failed to write preset: {error}","rpc.unavailable":"connection.rpc is unavailable","rpc.failed":"RPC failed"}`)
146
135
 
147
136
  function fillLocale(template, params) {
148
137
  if (!params) return template
@@ -152,6 +141,7 @@ window.__ModuleLoader__.load({
152
141
  }
153
142
 
154
143
  function tFrom(props) {
144
+ if (props && typeof props.t === 'function') return props.t
155
145
  const sp = (props && props.slotsProps) || {}
156
146
  if (typeof sp.t === 'function') return sp.t
157
147
  return function (key, params) {
@@ -159,42 +149,39 @@ window.__ModuleLoader__.load({
159
149
  }
160
150
  }
161
151
 
152
+ function lookupLabel(t, prefix, id) {
153
+ if (!id) return ''
154
+ const key = prefix + '.' + id
155
+ const v = t(key)
156
+ return v === key ? '' : v
157
+ }
158
+
162
159
  function verdictLabel(t, verdict) {
163
160
  if (!verdict) return t('notice.autoDefault')
164
- const key = 'verdict.' + verdict
165
- const v = t(key)
166
- return v === key ? String(verdict) : v
161
+ return lookupLabel(t, 'verdict', verdict) || lookupLabel(t, 'path', verdict) || String(verdict)
167
162
  }
168
163
 
169
164
  function pathLabel(t, path) {
170
- if (!path) return ''
171
- const key = 'path.' + path
172
- const v = t(key)
173
- return v === key ? String(path) : v
165
+ return lookupLabel(t, 'path', path) || lookupLabel(t, 'verdict', path) || (path ? String(path) : '')
174
166
  }
175
167
 
176
168
  function actionLabel(t, action) {
177
- if (!action) return ''
178
- const key = 'action.' + action
179
- const v = t(key)
180
- return v === key ? String(action) : v
169
+ return lookupLabel(t, 'action', action) || (action ? String(action) : '')
181
170
  }
182
171
 
183
- function provisionLabel(t, status) {
184
- const map = {
185
- '登录中': 'set.prov.loggingIn',
186
- '等待扫码': 'set.prov.waitScan',
187
- '保存凭据': 'set.prov.saving',
188
- '已连接': 'set.prov.connected',
189
- loggingIn: 'set.prov.loggingIn',
190
- waitScan: 'set.prov.waitScan',
191
- saving: 'set.prov.saving',
192
- connected: 'set.prov.connected',
193
- }
194
- const key = map[status]
195
- return key ? t(key) : String(status || '')
172
+ function criterionLabel(t, criterion, storedLabel) {
173
+ return lookupLabel(t, 'criterion', criterion) || storedLabel || (criterion ? String(criterion) : '')
174
+ }
175
+
176
+ function sandboxLabel(t, mode) {
177
+ return lookupLabel(t, 'sandbox', mode) || (mode ? String(mode) : '')
196
178
  }
197
179
 
180
+ function sourceLabel(t, source) {
181
+ return lookupLabel(t, 'source', source) || (source ? String(source) : '')
182
+ }
183
+
184
+
198
185
  function countsLabel(t, reject, human, allow) {
199
186
  return t('set.counts', { reject: String(reject), human: String(human), allow: String(allow) })
200
187
  }
@@ -276,18 +263,37 @@ window.__ModuleLoader__.load({
276
263
  }
277
264
 
278
265
  function latestUnsettledPending(evs) {
279
- const settled = Object.create(null)
280
266
  for (let i = evs.length - 1; i >= 0; i--) {
281
- const ev = evs[i]
282
- const kind = ev.kind || ''
283
- if (kind === 'manual-approved' || kind === 'manual-rejected' || kind === 'manual-cancelled') {
284
- if (ev.ticket != null) settled[ev.ticket] = true
285
- }
286
- if (kind === 'manual-pending' && (ev.ticket == null || !settled[ev.ticket])) return ev
267
+ const kind = evs[i].kind || ''
268
+ if (kind === 'manual-approved' || kind === 'manual-rejected' || kind === 'manual-cancelled' || kind === 'manual-unavailable') return null
269
+ if (kind === 'manual-pending') return evs[i]
287
270
  }
288
271
  return null
289
272
  }
290
273
 
274
+
275
+ function formatErr(t, code, details) {
276
+ if (!code) return ''
277
+ const d = details && typeof details === 'object' ? details : {}
278
+ const raw = String(code)
279
+ const key = raw.indexOf('err.') === 0 || raw.indexOf('rpc.') === 0 ? raw : 'err.' + raw
280
+ const v = t(key, d)
281
+ if (v && v !== key) return v
282
+ if (raw.indexOf('err.') !== 0 && raw.indexOf('rpc.') !== 0) {
283
+ const direct = t(raw, d)
284
+ if (direct && direct !== raw) return direct
285
+ }
286
+ if (d.error) return String(d.error)
287
+ return raw
288
+ }
289
+
290
+ function codedText(t, text, details) {
291
+ if (!text) return ''
292
+ const s = String(text)
293
+ if (s.indexOf('err.') === 0 || s.indexOf('rpc.') === 0) return formatErr(t, s, details || {}) || s
294
+ return s
295
+ }
296
+
291
297
  function makeRpc(connection, t) {
292
298
  const tr = typeof t === 'function' ? t : function (key) { return ZH[key] || key }
293
299
  return function rpc(endpoint, payload) {
@@ -299,8 +305,8 @@ window.__ModuleLoader__.load({
299
305
  payload: payload || {},
300
306
  }).then(function (r) {
301
307
  if (!r || r.ok === false) {
302
- const msg = r && r.error && r.error.message ? r.error.message : tr('rpc.failed')
303
- throw new Error(msg)
308
+ const err = r && r.error
309
+ throw new Error(formatErr(tr, err && err.code, err && (err.details || {})) || tr('rpc.failed'))
304
310
  }
305
311
  return r.value
306
312
  })
@@ -381,8 +387,7 @@ window.__ModuleLoader__.load({
381
387
  if (!notice) return null
382
388
  const kind = notice.kind || 'auto'
383
389
  const isPending = kind === 'manual-pending'
384
- const isManual = kind === 'manual-approved' || kind === 'manual-rejected'
385
- const ticket = notice.ticket ? (' #' + notice.ticket) : ''
390
+ const isManual = kind === 'manual-approved' || kind === 'manual-rejected' || kind === 'manual-cancelled' || kind === 'manual-unavailable'
386
391
  let title = ''
387
392
  let tagText = ''
388
393
  let glyph = null
@@ -391,17 +396,25 @@ window.__ModuleLoader__.load({
391
396
  tagText = t('notice.feedErrorTag')
392
397
  glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '!')
393
398
  } else if (isPending) {
394
- title = t('notice.pendingTitle', { ticket: ticket, preview: eventPreview(notice) })
395
- tagText = t('notice.pendingTag', { ticket: ticket })
399
+ title = t('notice.pendingTitle', { preview: eventPreview(notice) })
400
+ tagText = t('notice.pendingTag')
396
401
  glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '◔')
397
402
  } else if (kind === 'manual-approved') {
398
- title = t(notice.source === 'qq' ? 'notice.qqApproved' : 'notice.webApproved', { ticket: ticket, preview: eventPreview(notice) })
399
- tagText = t(notice.source === 'qq' ? 'notice.qqApprovedTag' : 'notice.webApprovedTag', { ticket: ticket })
403
+ title = t('notice.webApproved', { preview: eventPreview(notice) })
404
+ tagText = t('notice.webApprovedTag')
400
405
  glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✓')
406
+ } else if (kind === 'manual-cancelled') {
407
+ title = t('notice.cancelledTitle', { preview: eventPreview(notice) })
408
+ tagText = t('history.tagCancel')
409
+ glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✕')
410
+ } else if (kind === 'manual-unavailable') {
411
+ title = t('notice.unavailableTitle', { preview: eventPreview(notice) })
412
+ tagText = t('history.tagUnavailable')
413
+ glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '!')
401
414
  } else if (kind === 'manual-rejected' || isAutoReject(notice.verdict)) {
402
- title = t('notice.rejectedTitle', { ticket: ticket, preview: eventPreview(notice) })
415
+ title = t('notice.rejectedTitle', { preview: eventPreview(notice) })
403
416
  tagText = kind === 'manual-rejected'
404
- ? t('notice.rejectedTag', { ticket: ticket })
417
+ ? t('notice.rejectedTag')
405
418
  : (verdictLabel(t, notice.verdict) || t('notice.rejectedDefault'))
406
419
  glyph = React.createElement('span', { className: 'ab-notice-glyph-err' }, '✕')
407
420
  } else {
@@ -417,11 +430,11 @@ window.__ModuleLoader__.load({
417
430
  React.createElement('span', { className: 'ab-notice-glyph' }, glyph),
418
431
  React.createElement('div', { className: 'ab-notice-body' },
419
432
  React.createElement('div', { className: 'ab-notice-head' },
420
- React.createElement('span', { className: 'ab-notice-tool' }, notice.tool || (kind === 'feed-error' ? t('notice.hint') : 'tool')),
433
+ React.createElement('span', { className: 'ab-notice-tool' }, notice.tool || (kind === 'feed-error' ? t('notice.hint') : t('history.unknownTool'))),
421
434
  React.createElement('span', { className: 'ab-notice-text' }, title),
422
435
  ),
423
436
  React.createElement('div', { className: 'ab-notice-meta' },
424
- React.createElement('span', { className: isPending ? 'ab-tag-warn' : (kind === 'manual-rejected' || isAutoReject(notice.verdict) || kind === 'feed-error') ? 'ab-tag-err' : kind === 'manual-approved' ? 'ab-tag-warn' : 'ab-tag' }, tagText),
437
+ React.createElement('span', { className: isPending || kind === 'manual-approved' || kind === 'manual-cancelled' || kind === 'manual-unavailable' ? 'ab-tag-warn' : (kind === 'manual-rejected' || isAutoReject(notice.verdict) || kind === 'feed-error') ? 'ab-tag-err' : 'ab-tag' }, tagText),
425
438
  React.createElement('span', { className: 'ab-time' }, fmtTime(notice.ts)),
426
439
  ),
427
440
  ),
@@ -437,7 +450,7 @@ window.__ModuleLoader__.load({
437
450
  function HistoryView(props) {
438
451
  const t = tFrom(props)
439
452
  const rpc = props.rpc
440
- const sessionId = (props && (props.sessionId || (props.slotsProps && props.slotsProps.sessionId))) || null
453
+ const sessionId = (props && props.sessionId) || resolveFrame(props.slotsProps || props).sessionId || null
441
454
  const [events, setEvents] = React.useState(null)
442
455
  const [error, setError] = React.useState(null)
443
456
  const [openId, setOpenId] = React.useState(null)
@@ -481,22 +494,29 @@ window.__ModuleLoader__.load({
481
494
  React.createElement('div', { className: 'ab-list' },
482
495
  events.map(function (ev) {
483
496
  const kind = ev.kind || 'auto'
484
- const ticket = ev.ticket ? '#' + ev.ticket : ''
485
497
  let glyph = React.createElement(GlyphCheck, null)
486
498
  let tag = t('history.tagAuto')
487
499
  let tagCls = 'ab-tag'
488
500
  if (kind === 'manual-pending') {
489
501
  glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '◔')
490
- tag = t('history.tagPending', { ticket: ticket })
502
+ tag = t('history.tagPending')
491
503
  tagCls = 'ab-tag-warn'
492
504
  } else if (kind === 'manual-approved') {
493
505
  glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✓')
494
- tag = t(ev.source === 'qq' ? 'history.tagQqAllow' : 'history.tagAllow', { ticket: ticket })
506
+ tag = t('history.tagAllow')
495
507
  tagCls = 'ab-tag-warn'
496
508
  } else if (kind === 'manual-rejected') {
497
509
  glyph = React.createElement('span', { className: 'ab-notice-glyph-err' }, '✕')
498
- tag = t('history.tagReject', { ticket: ticket })
510
+ tag = t('history.tagReject')
499
511
  tagCls = 'ab-tag-err'
512
+ } else if (kind === 'manual-cancelled') {
513
+ glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✕')
514
+ tag = t('history.tagCancel')
515
+ tagCls = 'ab-tag-warn'
516
+ } else if (kind === 'manual-unavailable') {
517
+ glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '!')
518
+ tag = t('history.tagUnavailable')
519
+ tagCls = 'ab-tag-warn'
500
520
  } else if (isAutoReject(ev.verdict)) {
501
521
  glyph = React.createElement('span', { className: 'ab-notice-glyph-err' }, '✕')
502
522
  tag = verdictLabel(t, ev.verdict) || t('notice.rejectedDefault')
@@ -522,7 +542,7 @@ window.__ModuleLoader__.load({
522
542
  onClick: function () { setOpenId(open ? null : ev.id) },
523
543
  },
524
544
  React.createElement('div', { className: 'ab-row-top' },
525
- React.createElement('span', { className: 'ab-row-tool' }, ev.tool || 'tool'),
545
+ React.createElement('span', { className: 'ab-row-tool' }, ev.tool || t('history.unknownTool')),
526
546
  React.createElement('span', { className: tagCls }, tag),
527
547
  React.createElement('span', { className: 'ab-time' }, fmtTime(ev.ts)),
528
548
  React.createElement('span', { className: 'ab-row-chev' }, '▾'),
@@ -545,7 +565,7 @@ window.__ModuleLoader__.load({
545
565
  [t('detail.sql'), args.sql],
546
566
  [t('detail.prompt'), args.prompt],
547
567
  [t('detail.cwd'), ev.cwd],
548
- [t('detail.sandbox'), ev.mode],
568
+ [t('detail.sandbox'), sandboxLabel(t, ev.mode)],
549
569
  [t('detail.justification'), ev.justification || ev.reason],
550
570
  ]),
551
571
  DetailSection(t('detail.audit'), [
@@ -554,13 +574,12 @@ window.__ModuleLoader__.load({
554
574
  [t('detail.judgeModel'), [j.provider, j.model, j.effort].filter(Boolean).join(' / ')],
555
575
  [t('detail.judgeCategory'), j.failed
556
576
  ? t('detail.judgeFailed')
557
- : (j.label && j.criterion ? j.criterion + ' · ' + j.label : (j.criterion || ev.category || ''))],
577
+ : criterionLabel(t, j.criterion || ev.category, j.label)],
558
578
  [t('detail.judgeAction'), actionLabel(t, j.action) || j.action],
559
- [t('detail.judgeReason'), j.reason || ev.judgeReason],
560
- [t('detail.judgeError'), j.error],
579
+ [t('detail.judgeReason'), codedText(t, j.reason || ev.judgeReason)],
580
+ [t('detail.judgeError'), formatErr(t, j.errorCode || j.error, { ms: j.errorMs || '', error: j.errorDetail || '', effort: j.errorEffort || '' })],
561
581
  [t('detail.judgeRaw'), j.raw],
562
- [t('detail.ticket'), ev.ticket != null ? '#' + ev.ticket : ''],
563
- [t('detail.source'), ev.source],
582
+ [t('detail.source'), sourceLabel(t, ev.source)],
564
583
  ]),
565
584
  )
566
585
  : null,
@@ -571,7 +590,7 @@ window.__ModuleLoader__.load({
571
590
  )
572
591
  }
573
592
 
574
- /** 设置 → 自动审批。QQ 卡折在底部;恢复默认需连点两次。 */
593
+ /** 设置 → 自动审批。恢复默认需连点两次。 */
575
594
  function SettingsPage(props) {
576
595
  const t = tFrom(props)
577
596
  const rpc = props.rpc
@@ -584,11 +603,6 @@ window.__ModuleLoader__.load({
584
603
  const [newKeywordAction, setNewKeywordAction] = React.useState('human')
585
604
  const [newCriterion, setNewCriterion] = React.useState({ id: '', label: '', description: '', action: 'human' })
586
605
  const [timeoutMs, setTimeoutMs] = React.useState('20000')
587
- const [appId, setAppId] = React.useState('')
588
- const [appSecret, setAppSecret] = React.useState('')
589
- const [chatIdInput, setChatIdInput] = React.useState('')
590
- const [userIdInput, setUserIdInput] = React.useState('')
591
- const [timeoutSecs, setTimeoutSecs] = React.useState('120')
592
606
  const [models, setModels] = React.useState([])
593
607
  const [efforts, setEfforts] = React.useState([])
594
608
  const [judgeProvider, setJudgeProvider] = React.useState('')
@@ -596,10 +610,15 @@ window.__ModuleLoader__.load({
596
610
  const [judgeEffort, setJudgeEffort] = React.useState('')
597
611
  const [judgePromptLang, setJudgePromptLang] = React.useState('zh')
598
612
  const [presetSandbox, setPresetSandbox] = React.useState('workspace-write')
599
- const [foldOpen, setFoldOpen] = React.useState({ keywords: false, criteria: false, qq: false })
613
+ const [foldOpen, setFoldOpen] = React.useState({ keywords: false, criteria: false })
600
614
  const [kwEdit, setKwEdit] = React.useState(null)
601
615
  const [resetArmed, setResetArmed] = React.useState(null)
602
-
616
+ const feedbackTimerRef = React.useRef(null)
617
+ React.useEffect(function () {
618
+ return function () {
619
+ if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current)
620
+ }
621
+ }, [])
603
622
  const load = function (opts) {
604
623
  const keepEdits = opts && opts.keepEdits
605
624
  rpc('snapshot').then(function (data) {
@@ -611,9 +630,6 @@ window.__ModuleLoader__.load({
611
630
  setJudgeEffort((data.plugin.judge && data.plugin.judge.reasoningEffort) || '')
612
631
  setJudgePromptLang((data.plugin && data.plugin.judgePromptLang) === 'en' ? 'en' : 'zh')
613
632
  setPresetSandbox((data.plugin && data.plugin.presetSandbox) === 'read-only' ? 'read-only' : 'workspace-write')
614
- setChatIdInput((data.plugin.notify && data.plugin.notify.chatId) || '')
615
- setUserIdInput((data.plugin.notify && data.plugin.notify.userId) || '')
616
- setTimeoutSecs(String((data.plugin.notify && data.plugin.notify.timeoutSecs) || 120))
617
633
  }
618
634
  setError(null)
619
635
  }).catch(function (e) {
@@ -627,23 +643,6 @@ window.__ModuleLoader__.load({
627
643
  return function () { clearTimeout(disarm) }
628
644
  }, [resetArmed])
629
645
 
630
- const provisioning = snapshot && snapshot.qq && snapshot.qq.provisioning ? snapshot.qq.provisioning : null
631
- const provisioningStatus = provisioning && provisioning.status ? provisioning.status : ''
632
- const provisioningActive = provisioningStatus === '登录中' || provisioningStatus === '等待扫码' || provisioningStatus === '保存凭据'
633
- const qqOpenedForProvision = React.useRef(false)
634
- React.useEffect(function () {
635
- if (provisioningActive && !qqOpenedForProvision.current) {
636
- qqOpenedForProvision.current = true
637
- setFoldOpen(function (s) { return Object.assign({}, s, { qq: true }) })
638
- }
639
- if (!provisioningActive) qqOpenedForProvision.current = false
640
- }, [provisioningActive])
641
- React.useEffect(function () {
642
- if (!provisioningActive) return
643
- const timer = setInterval(function () { load({ keepEdits: true }) }, 2000)
644
- return function () { clearInterval(timer) }
645
- }, [provisioningActive])
646
-
647
646
  React.useEffect(function () {
648
647
  const provider = judgeProvider || (snapshot && snapshot.fallback && snapshot.fallback.provider)
649
648
  if (!provider || !rpc) return
@@ -663,7 +662,8 @@ window.__ModuleLoader__.load({
663
662
 
664
663
  const showFeedback = function (msg, ok) {
665
664
  setFeedback({ msg: String(msg), ok: ok !== false })
666
- setTimeout(function () { setFeedback(null) }, 4000)
665
+ if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current)
666
+ feedbackTimerRef.current = setTimeout(function () { setFeedback(null) }, 4000)
667
667
  }
668
668
 
669
669
  const run = function (endpoint, payload, okMsg) {
@@ -686,9 +686,7 @@ window.__ModuleLoader__.load({
686
686
  const setup = snapshot.setup || { configured: false }
687
687
  const predefined = snapshot.predefined || {}
688
688
  const preDeny = new Set(predefined.rejectKeywords || predefined.denyKeywords || [])
689
- const qq = snapshot.qq || {}
690
689
  const plugin = snapshot.plugin || {}
691
- const notify = plugin.notify || {}
692
690
  const providers = snapshot.providers || []
693
691
  const fallback = snapshot.fallback || {}
694
692
  const judgeTimeoutLabel = Math.round((Number(cfg.judgeTimeoutMs) || 20000) / 1000) + 's'
@@ -743,10 +741,6 @@ window.__ModuleLoader__.load({
743
741
  }, t('set.pluginCorruptOverwrite')),
744
742
  )
745
743
  : null,
746
- (snapshot.qq && snapshot.qq.credsCorrupt)
747
- ? React.createElement('div', { className: 'ab-set-err' }, t('set.qqCorrupt'))
748
- : null,
749
-
750
744
  !setup.configured ? React.createElement('div', { className: 'ab-set-card' },
751
745
  React.createElement('div', { className: 'ab-set-card-head' },
752
746
  React.createElement('div', { className: 'ab-set-card-title' }, t('set.presetMissingTitle')),
@@ -822,7 +816,7 @@ window.__ModuleLoader__.load({
822
816
  return React.createElement('span', {
823
817
  className: 'ab-set-inv ' + (c.action === 'reject' ? 'ab-set-inv-hard' : c.action === 'allow' ? 'ab-set-inv-ok' : 'ab-set-inv-mode'),
824
818
  key: c.id, title: c.id,
825
- }, (c.label || c.id) + ' · ' + (actionLabel(t, c.action) || c.action))
819
+ }, (criterionLabel(t, c.id, c.label) || c.id) + ' · ' + (actionLabel(t, c.action) || c.action))
826
820
  }),
827
821
  ),
828
822
  ),
@@ -961,7 +955,7 @@ window.__ModuleLoader__.load({
961
955
  }
962
956
  return React.createElement('div', { className: 'ab-set-item ab-set-item-crit', key: c.id },
963
957
  React.createElement('div', { className: 'ab-set-crit-top' },
964
- React.createElement('span', { className: 'ab-set-item-id' }, c.id),
958
+ React.createElement('span', { className: 'ab-set-item-id', title: c.id }, criterionLabel(t, c.id) || c.id),
965
959
  React.createElement('select', {
966
960
  className: 'ab-set-select', value: c.action || 'human',
967
961
  onChange: function (e) { commitCrit({ action: e.target.value }) },
@@ -984,7 +978,7 @@ window.__ModuleLoader__.load({
984
978
  },
985
979
  }, resetArmed === ('del-c:' + c.id) ? t('set.confirm') : '✕'),
986
980
  ),
987
- React.createElement('div', { className: 'ab-set-item-fields' },
981
+ React.createElement('div', { className: 'ab-set-item-fields', key: c.id + ':' + (c.label || '') + ':' + (c.description || '') },
988
982
  React.createElement('input', {
989
983
  className: 'ab-set-input ab-set-input-ui',
990
984
  defaultValue: c.label || '',
@@ -1136,113 +1130,8 @@ window.__ModuleLoader__.load({
1136
1130
  ),
1137
1131
  ),
1138
1132
 
1139
- React.createElement('details', {
1140
- className: 'ab-set-card ab-set-fold', 'data-ab-stage': 'qq',
1141
- open: foldOpen.qq,
1142
- onToggle: function (e) {
1143
- const open = e.currentTarget.open
1144
- setFoldOpen(function (prev) { return Object.assign({}, prev, { qq: open }) })
1145
- },
1146
- },
1147
- React.createElement('summary', null,
1148
- React.createElement('div', { className: 'ab-set-card-head' },
1149
- React.createElement('div', { className: 'ab-set-card-title' },
1150
- t('set.qqTitle'),
1151
- React.createElement('span', { className: 'ab-set-fold-chev' }, '▾')),
1152
- React.createElement('p', { className: 'ab-set-card-sub' }, t('set.qqSub'))),
1153
- React.createElement('div', { className: 'ab-set-fold-sum' },
1154
- (provisionLabel(t, qq.status) || t('set.qqUnknown')) + ' · ' + (notify.chatId ? t('set.qqChatOn') : t('set.qqChatOff')))),
1155
- React.createElement('div', { className: 'ab-set-fold-body' },
1156
- (provisioning && provisioning.error && !provisioningActive)
1157
- ? React.createElement('p', { className: 'ab-set-err' }, (provisionLabel(t, provisioningStatus) || t('set.qqScanFail')) + ':' + provisioning.error)
1158
- : null,
1159
- provisioningActive
1160
- ? React.createElement('div', null,
1161
- React.createElement('p', { className: 'ab-set-card-sub' }, t('set.qqScanStatus', { status: provisionLabel(t, provisioningStatus) || provisioningStatus })),
1162
- provisioning.qrDataUrl
1163
- ? React.createElement('div', { className: 'ab-set-qr' },
1164
- React.createElement('img', { src: provisioning.qrDataUrl, alt: t('set.qqQrAlt') }),
1165
- React.createElement('p', { className: 'ab-set-qr-tip' }, t('set.qqQrTip')),
1166
- )
1167
- : null,
1168
- React.createElement('div', { className: 'ab-set-row' },
1169
- React.createElement('button', {
1170
- type: 'button', className: 'ab-set-btn', disabled: busy,
1171
- onClick: function () { run('qq-cancel-provision', {}, t('set.qqCancelOk')) },
1172
- }, t('set.qqCancelScan')),
1173
- ),
1174
- )
1175
- : React.createElement('div', { className: 'ab-set-row' },
1176
- React.createElement('button', {
1177
- type: 'button',
1178
- className: 'ab-set-btn' + (qq.hasSecret ? '' : ' ab-set-btn-primary'),
1179
- disabled: busy,
1180
- onClick: function () { run('qq-provision', {}, t('set.qqScanStart')) },
1181
- }, qq.hasSecret ? t('set.qqRescan') : t('set.qqScan')),
1182
- provisioningStatus === '已连接'
1183
- ? React.createElement('span', { className: 'ab-set-ok' }, t('set.qqScanOk'))
1184
- : null,
1185
- ),
1186
- React.createElement('div', { className: 'ab-set-row' },
1187
- React.createElement('input', { className: 'ab-set-input ab-set-input-grow', placeholder: t('set.qqAppId'), value: appId, onChange: function (e) { setAppId(e.target.value) } }),
1188
- React.createElement('input', { className: 'ab-set-input ab-set-input-grow', type: 'password', placeholder: qq.hasSecret ? t('set.qqSecretKeep') : t('set.qqAppSecret'), value: appSecret, onChange: function (e) { setAppSecret(e.target.value) } }),
1189
- React.createElement('button', { type: 'button', className: 'ab-set-btn' + (qq.hasSecret ? ' ab-set-btn-primary' : ''), disabled: busy, onClick: function () { run('save-qq-creds', { appId: appId, appSecret: appSecret }, t('set.qqCredsReconnect')) } }, t('set.qqSaveConnect')),
1190
- React.createElement('button', { type: 'button', className: 'ab-set-btn', disabled: busy, onClick: function () { run('reconnect-qq', {}, t('set.qqReconnected')) } }, t('set.qqReconnect')),
1191
- ),
1192
- React.createElement('div', { className: 'ab-set-row' },
1193
- React.createElement('span', { className: 'ab-set-item-meta' }, t('set.qqPush', { state: notify.enabled !== false ? t('set.qqPushOn') : t('set.qqPushOff') })),
1194
- React.createElement('button', {
1195
- type: 'button', className: 'ab-set-btn', disabled: busy,
1196
- onClick: function () { run('save-plugin', { notify: { enabled: notify.enabled === false } }, t('set.qqPushToggled')) },
1197
- }, notify.enabled === false ? t('set.qqEnablePush') : t('set.qqPausePush')),
1198
- React.createElement('span', { className: 'ab-set-item-meta' }, t('set.qqCurrentChat', { chatId: notify.chatId || t('set.qqUnset') })),
1199
- ),
1200
- React.createElement('div', { className: 'ab-set-row' },
1201
- React.createElement('input', {
1202
- className: 'ab-set-input ab-set-input-grow', placeholder: t('set.qqChatPlaceholder'),
1203
- value: chatIdInput, onChange: function (e) { setChatIdInput(e.target.value) },
1204
- }),
1205
- React.createElement('input', {
1206
- className: 'ab-set-input', placeholder: t('set.qqGroupUser'),
1207
- value: userIdInput, onChange: function (e) { setUserIdInput(e.target.value) },
1208
- }),
1209
- React.createElement('input', {
1210
- className: 'ab-set-input ab-set-input-num', type: 'number', min: 30, placeholder: t('set.qqTimeoutS'),
1211
- value: timeoutSecs, onChange: function (e) { setTimeoutSecs(e.target.value) },
1212
- }),
1213
- React.createElement('button', {
1214
- type: 'button', className: 'ab-set-btn', disabled: busy,
1215
- onClick: function () {
1216
- run('save-plugin', {
1217
- notify: {
1218
- enabled: notify.enabled !== false,
1219
- chatId: chatIdInput.trim(),
1220
- userId: userIdInput.trim(),
1221
- timeoutSecs: Number(timeoutSecs) || 120,
1222
- },
1223
- }, t('set.targetSaved'))
1224
- },
1225
- }, t('set.saveTarget')),
1226
- ),
1227
- React.createElement('p', { className: 'ab-set-card-sub' }, t('set.recent')),
1228
- (!qq.recentChats || qq.recentChats.length === 0)
1229
- ? React.createElement('p', { className: 'ab-set-empty' }, t('set.noMail'))
1230
- : React.createElement('div', { className: 'ab-set-list' },
1231
- qq.recentChats.map(function (c) {
1232
- return React.createElement('div', {
1233
- className: 'ab-set-item ab-chat', key: c.chatId + c.userId,
1234
- onClick: function () { run('set-chat', { chatId: c.chatId, userId: c.isGroup ? c.userId : '' }, t('set.chatPicked')) },
1235
- },
1236
- React.createElement('span', { className: 'ab-set-item-label' },
1237
- (c.isGroup ? t('set.group') + ' ' : t('set.c2c') + ' ') + (c.username || c.userId) + ' · ' + c.chatId),
1238
- React.createElement('span', { className: 'ab-set-item-meta' }, c.preview || ''),
1239
- )
1240
- }),
1241
- ),
1242
- ),
1243
- ),
1133
+ feedback
1244
1134
 
1245
- feedback
1246
1135
  ? React.createElement('div', { className: feedback.ok ? 'ab-set-ok' : 'ab-set-err' }, feedback.msg)
1247
1136
  : null,
1248
1137
  )
@@ -1277,7 +1166,7 @@ window.__ModuleLoader__.load({
1277
1166
  slots.inject('conversation.input.dock', function () {
1278
1167
  return slots.register(
1279
1168
  { name: 'conversation.input.dock', id: 'dsh-auto-approve.notice', order: 30, locale: NS, label: function () { return t('slot.notice') } },
1280
- function (props) { return React.createElement(NoticeStrip, { slotsProps: props, rpc: rpc }) },
1169
+ function (props) { return React.createElement(NoticeStrip, { slotsProps: props, rpc: rpc, t: t }) },
1281
1170
  )
1282
1171
  })
1283
1172
 
@@ -1291,14 +1180,14 @@ window.__ModuleLoader__.load({
1291
1180
  label: function () { return t('slot.history') },
1292
1181
  inject: (sessionId) => ({ sessionId }),
1293
1182
  },
1294
- function (props) { return React.createElement(HistoryView, { slotsProps: props, rpc: rpc }) },
1183
+ function (props) { return React.createElement(HistoryView, { slotsProps: props, rpc: rpc, t: t }) },
1295
1184
  )
1296
1185
  })
1297
1186
 
1298
1187
  slots.inject('settings.section', function () {
1299
1188
  return slots.register(
1300
1189
  { name: 'settings.section', id: 'dsh-auto-approve.settings', order: 55, locale: NS, label: function () { return t('slot.settings') } },
1301
- function (props) { return React.createElement(SettingsPage, { slotsProps: props, rpc: rpc }) },
1190
+ function (props) { return React.createElement(SettingsPage, { slotsProps: props, rpc: rpc, t: t }) },
1302
1191
  )
1303
1192
  })
1304
1193
  },
@@ -1306,7 +1195,7 @@ window.__ModuleLoader__.load({
1306
1195
 
1307
1196
  exports.apply = plugin.apply
1308
1197
  exports.inject = plugin.inject
1309
- exports.name = 'dsh-auto-approve'
1198
+ exports.name = '@dnalec/dsh-auto-approve'
1310
1199
  return module.exports
1311
1200
  },
1312
1201
  })