diff --git a/web/static/css/style.css b/web/static/css/style.css
index 4bb3c660..fc4320e1 100644
--- a/web/static/css/style.css
+++ b/web/static/css/style.css
@@ -1513,7 +1513,7 @@ header {
.hitl-pending-payload {
white-space: pre-wrap;
word-break: break-all;
- max-height: 160px;
+ max-height: min(20vh, 180px);
overflow: auto;
margin: 0 0 4px 0;
padding: 10px 12px;
@@ -1595,6 +1595,473 @@ header {
opacity: 0.8;
}
+.hitl-context-block {
+ margin-top: 10px;
+}
+
+.hitl-context-label {
+ font-size: 12px;
+ font-weight: 600;
+ color: #475569;
+ margin-bottom: 4px;
+}
+
+.hitl-context-text {
+ margin: 0;
+ padding: 8px 10px;
+ border-radius: 8px;
+ background: #f8fafc;
+ border: 1px solid #e2e8f0;
+ font-size: 12px;
+ line-height: 1.45;
+ white-space: pre-wrap;
+ word-break: break-word;
+ max-height: min(24vh, 210px);
+ overflow: auto;
+}
+
+.hitl-log-modal-content .hitl-context-text,
+.hitl-log-detail-payload .hitl-context-text {
+ max-height: min(28vh, 260px);
+}
+
+.hitl-log-readonly-section {
+ margin-bottom: 12px;
+}
+
+.hitl-context-block--execution .hitl-context-text {
+ background: #f0fdf4;
+ border-color: #bbf7d0;
+ max-height: min(20vh, 180px);
+}
+
+.hitl-logs-summary {
+ font-size: 12px;
+ color: #64748b;
+ max-width: 140px;
+}
+
+.hitl-reviewer-toggle {
+ display: inline-flex;
+ gap: 0;
+ padding: 3px;
+ border-radius: 10px;
+ background: #f1f5f9;
+ border: 1px solid rgba(15, 23, 42, 0.08);
+}
+
+.hitl-reviewer-toggle-btn {
+ appearance: none;
+ border: none;
+ background: transparent;
+ color: #64748b;
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 1.2;
+ padding: 8px 12px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
+ white-space: nowrap;
+}
+
+.hitl-reviewer-toggle-btn:hover {
+ color: #0f172a;
+}
+
+.hitl-reviewer-toggle-btn.is-active {
+ background: #fff;
+ color: var(--accent-color, #0066ff);
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.12);
+}
+
+.hitl-reviewer-toggle-btn:focus-visible {
+ outline: 2px solid var(--accent-color, #0066ff);
+ outline-offset: 2px;
+}
+
+.hitl-page-reviewer-bar {
+ margin-bottom: 16px;
+ padding: 14px 16px;
+ border-radius: 14px;
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
+}
+
+.hitl-page-reviewer-main {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 12px 16px;
+}
+
+.hitl-page-reviewer-label {
+ font-size: 14px;
+ font-weight: 600;
+ color: #0f172a;
+}
+
+.hitl-page-reviewer-hint {
+ margin: 10px 0 0;
+ font-size: 12px;
+ line-height: 1.5;
+ color: #64748b;
+}
+
+.hitl-page-whitelist-bar {
+ width: 100%;
+ padding: 14px 16px;
+ border-radius: 14px;
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
+ box-sizing: border-box;
+}
+
+.hitl-page-whitelist-header {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px 12px;
+ margin-bottom: 8px;
+}
+
+.hitl-page-whitelist-label {
+ font-size: 14px;
+ font-weight: 600;
+ color: #0f172a;
+}
+
+.hitl-page-whitelist-hint {
+ margin: 0 0 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ color: #64748b;
+}
+
+.hitl-page-whitelist-textarea {
+ width: 100%;
+ min-height: 140px;
+ box-sizing: border-box;
+ border: 1px solid rgba(15, 23, 42, 0.12);
+ border-radius: 10px;
+ padding: 10px 12px;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ font-size: 12px;
+ line-height: 1.55;
+ color: #0f172a;
+ background: #f8fafc;
+ resize: vertical;
+}
+
+.hitl-page-whitelist-textarea:focus {
+ outline: none;
+ border-color: var(--accent-color, #0066ff);
+ box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.12);
+ background: #fff;
+}
+
+.hitl-page-strategy-bar {
+ width: 100%;
+ padding: 14px 16px;
+ border-radius: 14px;
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
+ box-sizing: border-box;
+}
+
+.hitl-page-strategy-header {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px 12px;
+ margin-bottom: 8px;
+}
+
+.hitl-page-strategy-label {
+ font-size: 14px;
+ font-weight: 600;
+ color: #0f172a;
+}
+
+.hitl-page-strategy-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.hitl-strategy-subtabs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin: 0 0 8px;
+}
+
+.hitl-strategy-subtab {
+ border: 1px solid rgba(15, 23, 42, 0.12);
+ background: #f8fafc;
+ color: #475569;
+ border-radius: 8px;
+ padding: 5px 12px;
+ font-size: 12px;
+ font-weight: 500;
+ cursor: pointer;
+}
+
+.hitl-strategy-subtab:hover {
+ background: #f1f5f9;
+ color: #0f172a;
+}
+
+.hitl-strategy-subtab--active {
+ background: #e0e7ff;
+ border-color: #a5b4fc;
+ color: #3730a3;
+}
+
+.hitl-page-strategy-hint {
+ margin: 0 0 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ color: #64748b;
+}
+
+.hitl-strategy-textarea {
+ width: 100%;
+ min-height: 320px;
+ box-sizing: border-box;
+ border: 1px solid rgba(15, 23, 42, 0.12);
+ border-radius: 10px;
+ padding: 10px 12px;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ font-size: 12px;
+ line-height: 1.55;
+ color: #0f172a;
+ background: #f8fafc;
+ resize: vertical;
+}
+
+.hitl-strategy-textarea:focus {
+ outline: none;
+ border-color: var(--accent-color, #0066ff);
+ box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.12);
+ background: #fff;
+}
+
+.hitl-page-tabs {
+ display: flex;
+ gap: 8px;
+ margin-bottom: 16px;
+ border-bottom: 1px solid #e2e8f0;
+ padding-bottom: 0;
+}
+
+.hitl-page-tab {
+ appearance: none;
+ border: none;
+ background: transparent;
+ color: var(--text-secondary, #64748b);
+ font-size: 14px;
+ font-weight: 500;
+ padding: 10px 14px;
+ border-bottom: 2px solid transparent;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.hitl-page-tab--active {
+ color: var(--accent-color, #0066ff);
+ border-bottom-color: var(--accent-color, #0066ff);
+}
+
+.hitl-tab-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 18px;
+ height: 18px;
+ padding: 0 6px;
+ border-radius: 999px;
+ background: #ef4444;
+ color: #fff;
+ font-size: 11px;
+ font-weight: 600;
+}
+
+.hitl-filters {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ align-items: flex-end;
+ margin-bottom: 16px;
+}
+
+.hitl-filters label {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--text-secondary, #64748b);
+}
+
+.hitl-filter-input,
+.hitl-filter-select {
+ min-width: 220px;
+ border: 1px solid #e2e8f0;
+ border-radius: 8px;
+ padding: 8px 10px;
+ font-size: 13px;
+ background: #fff;
+}
+
+.hitl-logs-table-wrap {
+ overflow-x: auto;
+ border: 1px solid #e2e8f0;
+ border-radius: 10px;
+ background: #fff;
+}
+
+.hitl-logs-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 13px;
+}
+
+.hitl-logs-table th,
+.hitl-logs-table td {
+ padding: 10px 12px;
+ border-bottom: 1px solid #f1f5f9;
+ text-align: left;
+ vertical-align: top;
+}
+
+.hitl-logs-table th {
+ background: #f8fafc;
+ color: #475569;
+ font-weight: 600;
+ white-space: nowrap;
+}
+
+.hitl-logs-cell-mono {
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 12px;
+ max-width: 180px;
+ word-break: break-all;
+}
+
+.hitl-logs-actions {
+ white-space: nowrap;
+}
+
+.hitl-decision-tag {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 999px;
+ font-size: 12px;
+ background: #f1f5f9;
+ color: #334155;
+}
+
+.hitl-decision-tag.hitl-decision--approve {
+ background: #dcfce7;
+ color: #166534;
+}
+
+.hitl-decision-tag.hitl-decision--reject {
+ background: #fee2e2;
+ color: #991b1b;
+}
+
+.hitl-logs-pagination,
+.hitl-pending-pagination {
+ margin-top: 12px;
+ padding: 0;
+}
+
+.hitl-logs-pagination .monitor-pagination,
+.hitl-pending-pagination .monitor-pagination {
+ margin-top: 0;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+}
+
+.hitl-log-modal-content {
+ max-width: 640px;
+ width: calc(100vw - 32px);
+}
+
+.hitl-log-detail-meta {
+ margin: 0;
+ padding: 0;
+ display: grid;
+ gap: 10px;
+}
+
+.hitl-log-detail-row {
+ display: grid;
+ grid-template-columns: 88px 1fr;
+ gap: 12px;
+ align-items: start;
+ margin: 0;
+}
+
+.hitl-log-detail-row--full {
+ grid-template-columns: 1fr;
+}
+
+.hitl-log-detail-row--full dt {
+ margin-bottom: 4px;
+}
+
+.hitl-log-detail-row dt {
+ margin: 0;
+ font-size: 12px;
+ font-weight: 600;
+ color: #64748b;
+}
+
+.hitl-log-detail-row dd {
+ margin: 0;
+ font-size: 14px;
+ color: #0f172a;
+ word-break: break-word;
+}
+
+.hitl-log-detail-mono {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ font-size: 12px;
+}
+
+.hitl-log-detail-payload {
+ margin-top: 14px;
+}
+
+.hitl-logs-empty-hint {
+ margin: 8px 0 0;
+ font-size: 13px;
+ color: #64748b;
+}
+
+.btn-link {
+ appearance: none;
+ border: none;
+ background: none;
+ color: var(--accent-color, #0066ff);
+ cursor: pointer;
+ font-size: 13px;
+ padding: 0 6px 0 0;
+}
+
+.btn-link--danger {
+ color: #dc2626;
+}
+
.hitl-config-select:focus,
.hitl-config-textarea:focus {
outline: none;
diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json
index 0a018bda..af2fca55 100644
--- a/web/static/i18n/en-US.json
+++ b/web/static/i18n/en-US.json
@@ -645,7 +645,10 @@
"agentModeOrchSupervisor": "Supervisor",
"hitlTitle": "Human-in-the-loop",
"hitlCardSubtitle": "Approvals & allowlist",
- "hitlReviewer": "Review",
+ "hitlReviewerLabel": "Reviewer",
+ "hitlReviewerHuman": "Human approval",
+ "hitlReviewerAgent": "Audit Agent",
+ "hitlReviewerHint": "Switch between human and Audit Agent anytime; rules and whitelist stay the same. You can pre-select even when HITL is off.",
"hitlConfigTitle": "Collaboration mode config",
"hitlModeLabel": "Mode",
"hitlModeOff": "Off",
@@ -664,7 +667,75 @@
},
"hitl": {
"pageTitle": "HITL approvals",
+ "pageReviewerLabel": "Current reviewer",
+ "pageReviewerHint": "Applies to the selected conversation. Without a conversation, saved locally for new chats. Takes effect immediately.",
+ "pageReviewerSaved": "Reviewer saved.",
+ "whitelistLabel": "Tool whitelist (no approval)",
+ "whitelistHint": "One per line or comma-separated. Saved to config.yaml global whitelist and takes effect immediately (synced with chat sidebar).",
+ "whitelistSaved": "Whitelist saved.",
+ "whitelistSaveFailed": "Failed to save whitelist",
+ "strategyLabel": "Audit strategy (prompt)",
+ "strategyHint": "Whitelisted tools skip approval. Other tools are judged by the model using this prompt when Audit Agent is selected.",
+ "strategyTabApproval": "Approval mode",
+ "strategyTabReviewEdit": "Review & edit mode",
+ "strategyHintApproval": "Whitelisted tools skip approval. In approval mode the Audit Agent only approves or rejects.",
+ "strategyHintReviewEdit": "In review & edit mode the Audit Agent may narrow parameters via editedArguments before approve; reject if parameters cannot be safely adjusted.",
+ "strategyReset": "Reset to default",
+ "strategySaved": "Audit strategy saved.",
+ "strategySaveFailed": "Failed to save audit strategy",
+ "tabPending": "Pending",
+ "tabLogs": "Audit logs",
+ "tabStrategy": "Audit strategy",
+ "tabWhitelist": "Tool whitelist",
"pendingTitle": "Pending approvals",
+ "searchLabel": "Search",
+ "searchPlaceholder": "Tool, conversation, payload, comment…",
+ "searchApply": "Search",
+ "filterDecision": "Decision",
+ "filterDecidedBy": "Reviewer",
+ "filterAll": "All",
+ "decisionApprove": "Approve",
+ "decisionReject": "Reject",
+ "reviewerHuman": "Human",
+ "reviewerAgent": "Audit Agent",
+ "reviewerSystem": "System",
+ "reviewerManual": "Manual entry",
+ "logCreate": "New log",
+ "logModalTitle": "Audit log",
+ "logModalEdit": "Edit audit log",
+ "fieldConversation": "Conversation ID",
+ "fieldTool": "Tool name",
+ "fieldComment": "Comment",
+ "fieldPayload": "Payload (JSON)",
+ "fieldUserMessage": "User message",
+ "fieldThinking": "Thinking",
+ "fieldReasoning": "Reasoning chain",
+ "fieldPlanning": "Planning",
+ "colId": "ID",
+ "colTool": "Tool",
+ "colConversation": "Conversation",
+ "colDecision": "Decision",
+ "colDecidedBy": "Reviewer",
+ "colContext": "Context",
+ "colTime": "Time",
+ "colActions": "Actions",
+ "viewDetail": "Detail",
+ "logModalView": "Audit log detail",
+ "fieldExecutionResult": "Execution result",
+ "executionSuccess": "success",
+ "executionFailed": "failed",
+ "edit": "Edit",
+ "delete": "Delete",
+ "logsEmpty": "No audit logs",
+ "logsEmptyHint": "Records are created automatically when HITL approvals are approved or rejected.",
+ "pageInfo": "{{total}} total",
+ "prevPage": "Previous",
+ "nextPage": "Next",
+ "conversationRequired": "Conversation ID is required",
+ "toolRequired": "Tool name is required",
+ "saveFailed": "Save failed",
+ "deleteConfirm": "Delete this audit log?",
+ "deleteFailed": "Delete failed",
"loading": "Loading...",
"emptyState": "No pending approvals",
"dismiss": "Dismiss",
diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json
index bcff1b72..be583e13 100644
--- a/web/static/i18n/zh-CN.json
+++ b/web/static/i18n/zh-CN.json
@@ -633,7 +633,10 @@
"agentModeOrchSupervisor": "Supervisor",
"hitlTitle": "人机协同",
"hitlCardSubtitle": "审批与白名单",
- "hitlReviewer": "Review",
+ "hitlReviewerLabel": "审批方",
+ "hitlReviewerHuman": "人工审批",
+ "hitlReviewerAgent": "审计 Agent",
+ "hitlReviewerHint": "可在人工与审计 Agent 之间随时切换;规则与白名单不变。人机协同为「关闭」时也可预先选择。",
"hitlConfigTitle": "协同模式配置",
"hitlModeLabel": "模式",
"hitlModeOff": "关闭",
@@ -642,7 +645,7 @@
"hitlSensitiveTools": "敏感工具(逗号分隔)",
"hitlWhitelistTools": "白名单工具(免审批,逗号分隔)",
"hitlWhitelistPlaceholder": "例:read_file, grep 或每行一个工具名(与 config 全局白名单合并)",
- "hitlWhitelistHint": "每行一个或逗号分隔;与 config 中全局白名单合并展示。",
+ "hitlWhitelistHint": "白名单内工具免审批;每行一个或逗号分隔,与 config 全局白名单合并。",
"hitlApply": "应用",
"hitlApplyOkSync": "人机协同配置已保存并同步到服务器。",
"hitlApplyOkWhitelistYaml": "免审批工具已合并进 config.yaml 并生效。协同模式、超时等仍须选中会话后再点「应用」才会写入服务器。",
@@ -652,7 +655,75 @@
},
"hitl": {
"pageTitle": "人机协同审批",
+ "pageReviewerLabel": "当前审批方",
+ "pageReviewerHint": "作用于当前选中会话;未选会话时保存到本机,新建会话时沿用。切换后立即生效。",
+ "pageReviewerSaved": "审批方已保存。",
+ "whitelistLabel": "免审批工具白名单",
+ "whitelistHint": "每行一个或逗号分隔;保存后写入 config.yaml 全局白名单并立即生效(与聊天侧栏同步展示)。",
+ "whitelistSaved": "白名单已保存。",
+ "whitelistSaveFailed": "保存白名单失败",
+ "strategyLabel": "审计策略(提示词)",
+ "strategyHint": "白名单内工具免审批;其余工具在审批方为「审计 Agent」时,由模型按此提示词自主裁决。",
+ "strategyTabApproval": "审批模式",
+ "strategyTabReviewEdit": "审查编辑模式",
+ "strategyHintApproval": "白名单内工具免审批;审批模式下审计 Agent 仅裁决通过/拒绝。",
+ "strategyHintReviewEdit": "审查编辑模式下审计 Agent 可通过 editedArguments 收窄参数后放行;无法安全改参时应拒绝。",
+ "strategyReset": "恢复默认",
+ "strategySaved": "审计策略已保存。",
+ "strategySaveFailed": "保存审计策略失败",
+ "tabPending": "待审计",
+ "tabLogs": "审计日志",
+ "tabStrategy": "审计策略",
+ "tabWhitelist": "工具白名单",
"pendingTitle": "待处理审批",
+ "searchLabel": "搜索",
+ "searchPlaceholder": "工具名、会话 ID、载荷、备注…",
+ "searchApply": "搜索",
+ "filterDecision": "决策",
+ "filterDecidedBy": "审批方",
+ "filterAll": "全部",
+ "decisionApprove": "通过",
+ "decisionReject": "拒绝",
+ "reviewerHuman": "人工",
+ "reviewerAgent": "审计 Agent",
+ "reviewerSystem": "系统",
+ "reviewerManual": "手动录入",
+ "logCreate": "新建日志",
+ "logModalTitle": "审计日志",
+ "logModalEdit": "编辑审计日志",
+ "fieldConversation": "会话 ID",
+ "fieldTool": "工具名",
+ "fieldComment": "备注",
+ "fieldPayload": "载荷 (JSON)",
+ "fieldUserMessage": "用户原话",
+ "fieldThinking": "本轮思考",
+ "fieldReasoning": "推理链",
+ "fieldPlanning": "规划",
+ "colId": "ID",
+ "colTool": "工具",
+ "colConversation": "会话",
+ "colDecision": "决策",
+ "colDecidedBy": "审批方",
+ "colContext": "上下文",
+ "colTime": "时间",
+ "colActions": "操作",
+ "viewDetail": "详情",
+ "logModalView": "审计日志详情",
+ "fieldExecutionResult": "执行结果",
+ "executionSuccess": "成功",
+ "executionFailed": "失败",
+ "edit": "编辑",
+ "delete": "删除",
+ "logsEmpty": "暂无审计日志",
+ "logsEmptyHint": "人机协同审批通过或拒绝后会自动记录在此。",
+ "pageInfo": "共 {{total}} 条",
+ "prevPage": "上一页",
+ "nextPage": "下一页",
+ "conversationRequired": "请填写会话 ID",
+ "toolRequired": "请填写工具名",
+ "saveFailed": "保存失败",
+ "deleteConfirm": "确定删除这条审计日志?",
+ "deleteFailed": "删除失败",
"loading": "加载中...",
"emptyState": "暂无待审批项",
"dismiss": "忽略",
diff --git a/web/static/js/audit.js b/web/static/js/audit.js
index 52fd7cd1..44e323f8 100644
--- a/web/static/js/audit.js
+++ b/web/static/js/audit.js
@@ -22,7 +22,7 @@ const AUDIT_ACTIONS_BY_CATEGORY = {
task: ['create_queue', 'start_queue', 'delete_queue', 'pause_queue', 'rerun_queue', 'delete_batch_task'],
tool: ['execution_delete', 'execution_delete_batch'],
file: ['upload', 'delete'],
- hitl: ['decision'],
+ hitl: ['decision', 'audit_strategy_update'],
role: ['create', 'update', 'delete'],
skill: ['create', 'update', 'delete'],
agent: ['markdown_create', 'markdown_update', 'markdown_delete']
diff --git a/web/static/js/chat.js b/web/static/js/chat.js
index 77cb0aeb..b44d03d0 100644
--- a/web/static/js/chat.js
+++ b/web/static/js/chat.js
@@ -139,11 +139,18 @@ function normalizeHitlMode(mode) {
function defaultHitlConfig() {
return {
mode: HITL_MODE_OFF,
+ reviewer: 'human',
sensitiveTools: '',
updatedAt: ''
};
}
+function normalizeHitlReviewer(v) {
+ const x = String(v || '').trim().toLowerCase();
+ if (x === 'audit_agent' || x === 'agent' || x === 'ai') return 'audit_agent';
+ return 'human';
+}
+
/** 白名单字符串拆成数组(逗号或换行分隔,与 textarea 一致) */
function hitlToolsSplitToArray(s) {
return String(s || '')
@@ -218,6 +225,7 @@ function getHitlLastGlobalConfig() {
if (!parsed || typeof parsed !== 'object') return null;
return {
mode: normalizeHitlMode(parsed.mode),
+ reviewer: normalizeHitlReviewer(parsed.reviewer),
sensitiveTools: typeof parsed.sensitiveTools === 'string' ? parsed.sensitiveTools : fallback.sensitiveTools,
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : ''
};
@@ -248,6 +256,7 @@ function getHitlConfigForConversation(conversationId) {
if (parsed && typeof parsed === 'object') {
draftCfg = {
mode: normalizeHitlMode(parsed.mode),
+ reviewer: normalizeHitlReviewer(parsed.reviewer),
sensitiveTools: typeof parsed.sensitiveTools === 'string' ? parsed.sensitiveTools : fallback.sensitiveTools,
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : ''
};
@@ -258,6 +267,7 @@ function getHitlConfigForConversation(conversationId) {
}
const g = globalLast ? {
mode: normalizeHitlMode(globalLast.mode),
+ reviewer: normalizeHitlReviewer(globalLast.reviewer),
sensitiveTools: typeof globalLast.sensitiveTools === 'string' ? globalLast.sensitiveTools : fallback.sensitiveTools,
updatedAt: typeof globalLast.updatedAt === 'string' ? globalLast.updatedAt : ''
} : null;
@@ -280,6 +290,7 @@ function getHitlConfigForConversation(conversationId) {
}
return {
mode: normalizeHitlMode(parsed.mode),
+ reviewer: normalizeHitlReviewer(parsed.reviewer),
sensitiveTools: typeof parsed.sensitiveTools === 'string' ? parsed.sensitiveTools : fallback.sensitiveTools,
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : ''
};
@@ -288,10 +299,52 @@ function getHitlConfigForConversation(conversationId) {
}
}
+function setHitlReviewerUI(reviewer) {
+ const v = normalizeHitlReviewer(reviewer);
+ const hidden = document.getElementById('hitl-reviewer-select');
+ if (hidden) hidden.value = v;
+ document.querySelectorAll('.hitl-reviewer-toggle-btn').forEach(function (btn) {
+ const active = btn.getAttribute('data-reviewer') === v;
+ btn.classList.toggle('is-active', active);
+ btn.setAttribute('aria-pressed', active ? 'true' : 'false');
+ });
+}
+
+async function onHitlReviewerChanged(reviewer) {
+ setHitlReviewerUI(reviewer);
+ const cfg = readHitlConfigFromForm();
+ const cid = typeof currentConversationId === 'string' ? currentConversationId.trim() : '';
+ saveHitlConfigForConversation(cid, cfg, { syncGlobalLast: true });
+ if (cid && typeof window.saveHitlConversationConfig === 'function') {
+ try {
+ await window.saveHitlConversationConfig(cid, cfg);
+ const ok = typeof window.t === 'function' ? window.t('hitl.pageReviewerSaved') : '审批方已保存。';
+ showChatToast(ok, 'success');
+ } catch (e) {
+ console.warn('onHitlReviewerChanged', e);
+ const prefix = typeof window.t === 'function' ? window.t('chat.hitlApplyFail') : '同步到服务器失败';
+ showChatToast(prefix, 'error');
+ }
+ }
+}
+
+function bindHitlReviewerToggleListeners() {
+ document.querySelectorAll('.hitl-reviewer-toggle-btn').forEach(function (btn) {
+ if (btn.dataset.hitlReviewerBound === '1') return;
+ btn.dataset.hitlReviewerBound = '1';
+ btn.addEventListener('click', function () {
+ const v = btn.getAttribute('data-reviewer');
+ if (!v) return;
+ onHitlReviewerChanged(v);
+ });
+ });
+}
+
function saveHitlConfigForConversation(conversationId, cfg, opts) {
const syncGlobalLast = !!(opts && opts.syncGlobalLast);
const payload = {
mode: normalizeHitlMode(cfg && cfg.mode),
+ reviewer: normalizeHitlReviewer(cfg && cfg.reviewer),
sensitiveTools: typeof (cfg && cfg.sensitiveTools) === 'string' ? cfg.sensitiveTools : '',
updatedAt: typeof (cfg && cfg.updatedAt) === 'string' ? cfg.updatedAt : ''
};
@@ -308,8 +361,10 @@ function saveHitlConfigForConversation(conversationId, cfg, opts) {
function readHitlConfigFromForm() {
const modeEl = document.getElementById('hitl-mode-select');
+ const reviewerEl = document.getElementById('hitl-reviewer-select');
const toolsEl = document.getElementById('hitl-sensitive-tools');
const mode = normalizeHitlMode(modeEl ? modeEl.value : HITL_MODE_OFF);
+ const reviewer = normalizeHitlReviewer(reviewerEl ? reviewerEl.value : 'human');
let sensitiveTools = toolsEl ? String(toolsEl.value || '').trim() : '';
const g = typeof window !== 'undefined' ? window.csaiHitlGlobalToolWhitelist : null;
if (Array.isArray(g) && g.length > 0) {
@@ -317,6 +372,7 @@ function readHitlConfigFromForm() {
}
return {
mode,
+ reviewer,
sensitiveTools,
updatedAt: new Date().toISOString()
};
@@ -330,7 +386,9 @@ function applyHitlConfigToUI(cfg) {
const conf = cfg || defaultHitlConfig();
const modeEl = document.getElementById('hitl-mode-select');
const toolsEl = document.getElementById('hitl-sensitive-tools');
- if (modeEl) modeEl.value = normalizeHitlMode(conf.mode);
+ const uiMode = normalizeHitlMode(conf.mode);
+ if (modeEl) modeEl.value = uiMode;
+ setHitlReviewerUI(conf.reviewer);
let toolsVal = conf.sensitiveTools || '';
const g = typeof window !== 'undefined' ? window.csaiHitlGlobalToolWhitelist : null;
if (Array.isArray(g) && g.length > 0) {
@@ -341,6 +399,15 @@ function applyHitlConfigToUI(cfg) {
updateHitlStatusUI(conf);
}
+function bindHitlSidebarModeListener() {
+ const modeEl = document.getElementById('hitl-mode-select');
+ if (!modeEl || modeEl.dataset.hitlModeBound === '1') return;
+ modeEl.dataset.hitlModeBound = '1';
+ modeEl.addEventListener('change', function () {
+ applyHitlConfigToUI(readHitlConfigFromForm());
+ });
+}
+
function refreshHitlConfigByCurrentConversation() {
const cfg = getHitlConfigForConversation(currentConversationId || '');
applyHitlConfigToUI(cfg);
@@ -413,6 +480,9 @@ async function applyHitlSidebarConfig() {
const localOnly = typeof window.t === 'function' ? window.t('chat.hitlApplyOkLocal') : '已保存到本浏览器。';
showHitlApplyFeedback(localOnly, false);
}
+ if (typeof window.refreshHitlPageWhitelist === 'function') {
+ window.refreshHitlPageWhitelist();
+ }
} catch (e) {
console.warn('applyHitlSidebarConfig', e);
const prefix = typeof window.t === 'function' ? window.t('chat.hitlApplyFail') : '同步到服务器失败';
@@ -449,6 +519,12 @@ if (typeof window !== 'undefined') {
window.readHitlConfigFromForm = readHitlConfigFromForm;
window.applyHitlConfigToUI = applyHitlConfigToUI;
window.saveHitlConfigForConversation = saveHitlConfigForConversation;
+ window.getHitlConfigForConversation = getHitlConfigForConversation;
+ bindHitlSidebarModeListener();
+ bindHitlReviewerToggleListeners();
+ window.setHitlReviewerUI = setHitlReviewerUI;
+ window.onHitlReviewerChanged = onHitlReviewerChanged;
+ window.bindHitlReviewerToggleListeners = bindHitlReviewerToggleListeners;
window.getHitlLastGlobalConfig = getHitlLastGlobalConfig;
window.hitlMergeToolsForDisplay = hitlMergeToolsForDisplay;
window.hitlStripGlobalToolsFromFormString = hitlStripGlobalToolsFromFormString;
@@ -743,6 +819,9 @@ async function initChatAgentModeFromConfig() {
window.csaiHitlGlobalToolWhitelist = tw.slice();
}
}
+ if (typeof window.refreshHitlPageWhitelist === 'function') {
+ window.refreshHitlPageWhitelist();
+ }
document.querySelectorAll('.agent-mode-option').forEach(function (el) {
const v = el.getAttribute('data-value');
if (v === 'deep' || v === 'plan_execute' || v === 'supervisor') {
@@ -959,6 +1038,7 @@ async function sendMessage() {
body.hitl = {
enabled: true,
mode: normalizeHitlMode(hitlCfg.mode),
+ reviewer: normalizeHitlReviewer(hitlCfg.reviewer),
sensitiveTools: sensitiveTools
};
}
diff --git a/web/static/js/hitl.js b/web/static/js/hitl.js
index 142bdbac..b6907939 100644
--- a/web/static/js/hitl.js
+++ b/web/static/js/hitl.js
@@ -1,3 +1,79 @@
+function hitlReviewerNormalize(v) {
+ const x = String(v || '').trim().toLowerCase();
+ if (x === 'audit_agent' || x === 'agent' || x === 'ai') return 'audit_agent';
+ return 'human';
+}
+
+function hitlParsePayloadObject(raw) {
+ if (!raw) return {};
+ if (typeof raw === 'object') return raw;
+ try {
+ const o = JSON.parse(String(raw));
+ return o && typeof o === 'object' ? o : {};
+ } catch (e) {
+ return {};
+ }
+}
+
+function hitlRenderContextBlocks(payloadObj) {
+ if (!payloadObj || typeof payloadObj !== 'object') return '';
+ const blocks = [];
+ function addBlock(label, value) {
+ const s = String(value || '').trim();
+ if (!s) return;
+ blocks.push(
+ '
' +
+ '
' + escapeHtml(label) + '
' +
+ '
' + escapeHtml(s) + '
' +
+ '
'
+ );
+ }
+ addBlock(hitlT('fieldUserMessage', 'User message'), payloadObj.userMessage);
+ addBlock(hitlT('fieldThinking', 'Thinking'), payloadObj.thinking);
+ addBlock(hitlT('fieldReasoning', 'Reasoning'), payloadObj.reasoningChain);
+ addBlock(hitlT('fieldPlanning', 'Planning'), payloadObj.planning);
+ return blocks.join('');
+}
+
+function hitlRenderExecutionResultBlock(payloadObj) {
+ if (!payloadObj || typeof payloadObj !== 'object') return '';
+ const exec = payloadObj.executionResult;
+ if (!exec || typeof exec !== 'object') return '';
+ const ok = exec.success === true;
+ const label = hitlT('fieldExecutionResult', 'Execution result') + (ok ? ' (' + hitlT('executionSuccess', 'success') + ')' : ' (' + hitlT('executionFailed', 'failed') + ')');
+ const text = String(exec.result || '').trim();
+ if (!text) return '';
+ return (
+ '' +
+ '
' + escapeHtml(label) + '
' +
+ '
' + escapeHtml(text) + '
' +
+ '
'
+ );
+}
+
+function hitlFillLogModalReadonlySections(payloadObj) {
+ const ctxEl = document.getElementById('hitl-log-context-readonly');
+ const execEl = document.getElementById('hitl-log-execution-readonly');
+ const ctxHtml = hitlRenderContextBlocks(payloadObj);
+ const execHtml = hitlRenderExecutionResultBlock(payloadObj);
+ if (ctxEl) {
+ ctxEl.innerHTML = ctxHtml;
+ ctxEl.hidden = !ctxHtml;
+ }
+ if (execEl) {
+ execEl.innerHTML = execHtml;
+ execEl.hidden = !execHtml;
+ }
+}
+
+function hitlPayloadSummary(payloadObj) {
+ const parts = [];
+ if (payloadObj && payloadObj.userMessage) parts.push(hitlT('fieldUserMessage', 'User'));
+ if (payloadObj && payloadObj.thinking) parts.push(hitlT('fieldThinking', 'Thinking'));
+ if (payloadObj && payloadObj.executionResult) parts.push(hitlT('fieldExecutionResult', 'Result'));
+ return parts.length ? parts.join(' · ') : '—';
+}
+
function hitlModeNormalize(m) {
let v = String(m || '').trim().toLowerCase().replace(/-/g, '_');
if (v === 'feedback' || v === 'followup') {
@@ -20,6 +96,81 @@ function hitlT(key, fallback, params) {
return fallback;
}
+const HITL_LOGS_PAGE_SIZE_KEY = 'cyberstrike_hitl_logs_page_size';
+const HITL_PENDING_PAGE_SIZE_KEY = 'cyberstrike_hitl_pending_page_size';
+const HITL_PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
+
+function hitlPaginationT(key, opts, fallback) {
+ if (typeof window.t === 'function') {
+ const keys = (key === 'paginationInfo' || key === 'perPageLabel')
+ ? ['mcpMonitor.' + key, 'mcp.' + key]
+ : ['mcp.' + key];
+ for (let i = 0; i < keys.length; i++) {
+ const v = window.t(keys[i], opts || {});
+ if (typeof v === 'string' && v && v !== keys[i]) return v;
+ }
+ }
+ return fallback != null ? fallback : key;
+}
+
+function hitlLocale() {
+ if (typeof window.__locale === 'string' && window.__locale.length) {
+ return window.__locale.startsWith('zh') ? 'zh-CN' : 'en-US';
+ }
+ return (typeof navigator !== 'undefined' && navigator.language) ? navigator.language : 'en-US';
+}
+
+function initHitlPageSizeFromStorage(storageKey, fallbackSize, assignFn) {
+ try {
+ const saved = parseInt(localStorage.getItem(storageKey), 10);
+ if (HITL_PAGE_SIZE_OPTIONS.indexOf(saved) >= 0) {
+ assignFn(saved);
+ return;
+ }
+ } catch (e) { /* ignore */ }
+ assignFn(fallbackSize);
+}
+
+function renderHitlPagination(containerId, state, goPageFnName, pageSizeChangeFnName, pageSizeSelectId) {
+ const container = document.getElementById(containerId);
+ if (!container) return;
+ const esc = typeof escapeHtml === 'function' ? escapeHtml : function (s) { return String(s || ''); };
+ const total = state.total || 0;
+ const currentPage = state.page || 1;
+ const pageSize = state.pageSize || 20;
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
+ const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1;
+ const end = total === 0 ? 0 : Math.min(currentPage * pageSize, total);
+ const infoText = hitlPaginationT('paginationInfo', { start: start, end: end, total: total },
+ '显示 ' + start + '-' + end + ' / 共 ' + total + ' 条记录');
+ const perPageLabel = hitlPaginationT('perPageLabel', null, '每页显示');
+ const firstPageLabel = hitlPaginationT('firstPage', null, '首页');
+ const prevPageLabel = hitlPaginationT('prevPage', null, '上一页');
+ const pageInfoText = hitlPaginationT('pageInfo', { page: currentPage, total: totalPages },
+ '第 ' + currentPage + ' / ' + totalPages + ' 页');
+ const nextPageLabel = hitlPaginationT('nextPage', null, '下一页');
+ const lastPageLabel = hitlPaginationT('lastPage', null, '末页');
+ const disabledFirst = currentPage === 1 || total === 0;
+ const disabledLast = currentPage >= totalPages || total === 0;
+ let html = '';
+ container.innerHTML = html;
+}
+
function hitlEffectiveEnabled(cfg) {
if (!cfg) return false;
if (cfg.enabled === true) return true;
@@ -104,12 +255,189 @@ async function mergeHitlGlobalToolWhitelist(sensitiveTools) {
return [];
}
+function hitlPageToolsSplit(s) {
+ if (typeof window.hitlToolsSplitToArray === 'function') {
+ return window.hitlToolsSplitToArray(s);
+ }
+ return String(s || '').split(/[,\n\r]+/).map(function (x) { return x.trim(); }).filter(Boolean);
+}
+
+function hitlPageToolsMergeDisplay(globalArr, sessionToolsArr) {
+ if (typeof window.hitlMergeToolsForDisplay === 'function') {
+ return window.hitlMergeToolsForDisplay(globalArr, sessionToolsArr);
+ }
+ const out = [];
+ const seen = Object.create(null);
+ function addOne(t) {
+ const n = String(t || '').trim();
+ if (!n) return;
+ const k = n.toLowerCase();
+ if (seen[k]) return;
+ seen[k] = true;
+ out.push(n);
+ }
+ if (Array.isArray(globalArr)) globalArr.forEach(addOne);
+ if (Array.isArray(sessionToolsArr)) sessionToolsArr.forEach(addOne);
+ return out.join(', ');
+}
+
+function showHitlPageWhitelistFeedback(text, isError) {
+ const el = document.getElementById('hitl-page-whitelist-feedback');
+ if (!el) return;
+ const msg = String(text || '').trim();
+ if (!msg) {
+ el.hidden = true;
+ el.textContent = '';
+ el.className = 'hitl-apply-feedback';
+ return;
+ }
+ el.hidden = false;
+ el.textContent = msg;
+ el.className = 'hitl-apply-feedback' + (isError ? ' hitl-apply-feedback--error' : '');
+}
+
+function syncHitlSidebarWhitelistDisplay(toolsStr) {
+ const sidebarEl = document.getElementById('hitl-sensitive-tools');
+ if (sidebarEl) sidebarEl.value = toolsStr;
+}
+
+async function fetchHitlGlobalToolWhitelist() {
+ const resp = await hitlApiFetch('/api/hitl/tool-whitelist', { credentials: 'same-origin' });
+ if (!resp.ok) {
+ throw new Error(await readHitlApiError(resp));
+ }
+ const data = await resp.json();
+ const list = Array.isArray(data.toolWhitelist) ? data.toolWhitelist : (
+ Array.isArray(data.hitlGlobalToolWhitelist) ? data.hitlGlobalToolWhitelist : []
+ );
+ if (typeof window !== 'undefined') {
+ window.csaiHitlGlobalToolWhitelist = list;
+ }
+ return list;
+}
+
+async function resolveHitlGlobalToolWhitelist() {
+ try {
+ return await fetchHitlGlobalToolWhitelist();
+ } catch (e) {
+ if (typeof window !== 'undefined' && Array.isArray(window.csaiHitlGlobalToolWhitelist)) {
+ return window.csaiHitlGlobalToolWhitelist.slice();
+ }
+ try {
+ const resp = await hitlApiFetch('/api/config', { credentials: 'same-origin' });
+ if (resp.ok) {
+ const cfg = await resp.json();
+ const tw = cfg.hitl && cfg.hitl.tool_whitelist;
+ if (Array.isArray(tw)) {
+ if (typeof window !== 'undefined') {
+ window.csaiHitlGlobalToolWhitelist = tw.slice();
+ }
+ return tw.slice();
+ }
+ }
+ } catch (e2) {
+ console.warn('resolveHitlGlobalToolWhitelist fallback', e2);
+ }
+ throw e;
+ }
+}
+
+function hitlPageWhitelistDisplayValue(globalArr, sessionArr) {
+ return hitlPageToolsMergeDisplay(globalArr, sessionArr);
+}
+
+async function refreshHitlPageWhitelist() {
+ const ta = document.getElementById('hitl-page-sensitive-tools');
+ if (!ta) return;
+ const cached = typeof window !== 'undefined' && Array.isArray(window.csaiHitlGlobalToolWhitelist)
+ ? window.csaiHitlGlobalToolWhitelist
+ : [];
+ if (cached.length > 0) {
+ ta.value = hitlPageWhitelistDisplayValue(cached, []);
+ }
+ try {
+ const globalArr = await resolveHitlGlobalToolWhitelist();
+ const cid = getCurrentConversationIdForHitl();
+ let sessionArr = [];
+ if (cid) {
+ const cfg = typeof window.getHitlConfigForConversation === 'function'
+ ? window.getHitlConfigForConversation(cid)
+ : null;
+ sessionArr = hitlSensitiveToolsToArray(cfg || {});
+ }
+ ta.value = hitlPageWhitelistDisplayValue(globalArr, sessionArr);
+ syncHitlSidebarWhitelistDisplay(ta.value);
+ } catch (e) {
+ console.warn('refreshHitlPageWhitelist', e);
+ if (!ta.value.trim() && cached.length > 0) {
+ ta.value = hitlPageWhitelistDisplayValue(cached, []);
+ }
+ }
+}
+
+async function putHitlGlobalToolWhitelist(toolWhitelist) {
+ const list = Array.isArray(toolWhitelist) ? toolWhitelist : [];
+ const resp = await hitlApiFetch('/api/hitl/tool-whitelist', {
+ method: 'PUT',
+ credentials: 'same-origin',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ toolWhitelist: list })
+ });
+ if (!resp.ok) {
+ throw new Error(await readHitlApiError(resp));
+ }
+ const data = await resp.json();
+ const out = Array.isArray(data.toolWhitelist) ? data.toolWhitelist : (
+ Array.isArray(data.hitlGlobalToolWhitelist) ? data.hitlGlobalToolWhitelist : list
+ );
+ if (typeof window !== 'undefined') {
+ window.csaiHitlGlobalToolWhitelist = out;
+ }
+ return out;
+}
+
+async function saveHitlPageWhitelist() {
+ const ta = document.getElementById('hitl-page-sensitive-tools');
+ const btn = document.getElementById('hitl-page-whitelist-save-btn');
+ if (!ta) return;
+ showHitlPageWhitelistFeedback('', false);
+ if (btn) btn.disabled = true;
+ try {
+ const desired = hitlPageToolsSplit(ta.value);
+ const globalArr = await putHitlGlobalToolWhitelist(desired);
+ const displayStr = hitlPageToolsMergeDisplay(globalArr, []);
+ ta.value = displayStr;
+ syncHitlSidebarWhitelistDisplay(displayStr);
+
+ const cid = getCurrentConversationIdForHitl();
+ if (cid) {
+ const cfg = typeof window.getHitlConfigForConversation === 'function'
+ ? window.getHitlConfigForConversation(cid)
+ : { mode: 'off', reviewer: 'human', sensitiveTools: '', timeoutSeconds: 0 };
+ const nextCfg = Object.assign({}, cfg, { sensitiveTools: '' });
+ if (typeof window.saveHitlConfigForConversation === 'function') {
+ window.saveHitlConfigForConversation(cid, nextCfg);
+ }
+ if (typeof saveHitlConversationConfig === 'function') {
+ await saveHitlConversationConfig(cid, nextCfg);
+ }
+ }
+
+ showHitlPageWhitelistFeedback(hitlT('whitelistSaved', 'Whitelist saved.'), false);
+ } catch (e) {
+ showHitlPageWhitelistFeedback(hitlT('whitelistSaveFailed', 'Failed to save whitelist') + ': ' + (e.message || e), true);
+ } finally {
+ if (btn) btn.disabled = false;
+ }
+}
+
async function saveHitlConversationConfig(conversationId, config) {
if (!conversationId || !config) return false;
const mode = hitlModeNormalize(config.mode || 'off');
const enabled = typeof config.enabled === 'boolean' ? config.enabled : (mode !== 'off');
const sensitiveTools = hitlSensitiveToolsToArray(config);
const timeoutSeconds = normalizeHitlTimeoutSeconds(config.timeoutSeconds, 0);
+ const reviewer = hitlReviewerNormalize(config.reviewer || 'human');
const resp = await hitlApiFetch('/api/hitl/config', {
method: 'PUT',
credentials: 'same-origin',
@@ -118,6 +446,7 @@ async function saveHitlConversationConfig(conversationId, config) {
conversationId: conversationId,
enabled: enabled,
mode: mode,
+ reviewer: reviewer,
sensitiveTools: sensitiveTools,
timeoutSeconds: timeoutSeconds
})
@@ -192,6 +521,7 @@ async function syncHitlConfigFromServer(conversationId) {
const sessionOnlyStr = strip(globalWL, rawArr.join(', '));
const normalizedCfg = Object.assign({}, merged, {
mode: uiMode,
+ reviewer: hitlReviewerNormalize(merged.reviewer || cfg.reviewer || 'human'),
sensitiveTools: sessionOnlyStr
});
if (typeof window.saveHitlConfigForConversation === 'function') {
@@ -288,24 +618,18 @@ async function followAgentRunAfterHitlDecision(conversationId) {
}
}
-async function refreshHitlPending() {
+function renderHitlPendingList(items) {
const container = document.getElementById('hitl-pending-list');
if (!container) return;
- container.innerHTML = '' + escapeHtml(hitlT('loading', 'Loading...')) + '
';
- try {
- const resp = await hitlApiFetch('/api/hitl/pending', { credentials: 'same-origin' });
- if (!resp.ok) {
- throw new Error('request failed');
- }
- const data = await resp.json();
- const items = Array.isArray(data.items) ? data.items : [];
- if (!items.length) {
- container.innerHTML = '' + escapeHtml(hitlT('emptyState', 'No pending approvals')) + '
';
- return;
- }
- container.innerHTML = items.map(function (item) {
+ const list = Array.isArray(items) ? items : [];
+ if (!list.length) {
+ container.innerHTML = '' + escapeHtml(hitlT('emptyState', 'No pending approvals')) + '
';
+ return;
+ }
+ container.innerHTML = list.map(function (item) {
+ const payloadObj = hitlParsePayloadObject(item.payload || '');
const payload = String(item.payload || '');
- const preview = payload.length > 280 ? (payload.slice(0, 280) + '...') : payload;
+ const contextHtml = hitlRenderContextBlocks(payloadObj);
const mode = String(item.mode || '').trim().toLowerCase();
const allowEdit = mode === 'review_edit';
var escId = escapeHtml(String(item.id || ''));
@@ -321,7 +645,9 @@ async function refreshHitlPending() {
'' +
'' +
'' + escapeHtml(hitlT('conversationLabel', 'Conversation:')) + ' ' + escapeHtml(item.conversationId || '-') + '
' +
- '' + escapeHtml(preview) + '
' +
+ contextHtml +
+ hitlRenderExecutionResultBlock(payloadObj) +
+ '' + escapeHtml(payload) + '
' +
(allowEdit
? ('' + escapeHtml(hitlT('reviewEditHelp', 'Review & edit mode: provide a JSON object to override tool arguments. Example: {"command":"ls -la"}')) + '
' +
'')
@@ -335,11 +661,53 @@ async function refreshHitlPending() {
''
);
}).join('');
+}
+
+async function refreshHitlPending() {
+ const container = document.getElementById('hitl-pending-list');
+ if (!container) return;
+ container.innerHTML = '' + escapeHtml(hitlT('loading', 'Loading...')) + '
';
+ try {
+ const q = document.getElementById('hitl-pending-search');
+ const params = new URLSearchParams({
+ page: String(hitlPendingPage),
+ pageSize: String(hitlPendingPageSize)
+ });
+ if (q && q.value.trim()) params.set('q', q.value.trim());
+ const resp = await hitlApiFetch('/api/hitl/pending?' + params.toString(), { credentials: 'same-origin' });
+ if (!resp.ok) {
+ throw new Error('request failed');
+ }
+ const data = await resp.json();
+ const items = Array.isArray(data.items) ? data.items : [];
+ hitlPendingTotal = typeof data.total === 'number' ? data.total : items.length;
+ const maxPage = Math.max(1, Math.ceil(hitlPendingTotal / hitlPendingPageSize));
+ if (hitlPendingPage > maxPage) {
+ hitlPendingPage = maxPage;
+ await refreshHitlPending();
+ return;
+ }
+ const badge = document.getElementById('hitl-pending-count');
+ if (badge) {
+ badge.textContent = String(hitlPendingTotal);
+ badge.hidden = hitlPendingTotal <= 0;
+ }
+ hitlPendingCache = items;
+ hitlPendingLoaded = true;
+ renderHitlPendingList(items);
+ renderHitlPendingPagination();
} catch (e) {
+ hitlPendingLoaded = false;
container.innerHTML = '' + escapeHtml(hitlT('loadFailed', 'Failed to load')) + '
';
+ renderHitlPendingPagination();
}
}
+function filterHitlPending() {
+ hitlPendingPage = 1;
+ refreshHitlPending();
+}
+
async function submitHitlDecision(interruptId, decision, conversationIdOpt) {
const commentBox = document.getElementById('hitl-comment-' + interruptId);
const comment = (commentBox && commentBox.value) ? commentBox.value.trim() : '';
@@ -412,7 +780,475 @@ async function dismissHitlItem(interruptId, silent) {
refreshHitlPending();
}
+let hitlActiveTab = 'pending';
+let hitlLogsPage = 1;
+let hitlLogsPageSize = 20;
+let hitlLogsTotal = 0;
+let hitlLogsCache = [];
+let hitlLogsLoaded = false;
+let hitlPendingPage = 1;
+let hitlPendingPageSize = 20;
+let hitlPendingTotal = 0;
+let hitlPendingCache = [];
+let hitlPendingLoaded = false;
+
+function switchHitlPageTab(tab) {
+ const tabs = ['pending', 'logs', 'strategy', 'whitelist'];
+ hitlActiveTab = tabs.indexOf(tab) >= 0 ? tab : 'pending';
+ const pendingTab = document.getElementById('hitl-tab-pending');
+ const logsTab = document.getElementById('hitl-tab-logs');
+ const strategyTab = document.getElementById('hitl-tab-strategy');
+ const whitelistTab = document.getElementById('hitl-tab-whitelist');
+ const pendingPanel = document.getElementById('hitl-panel-pending');
+ const logsPanel = document.getElementById('hitl-panel-logs');
+ const strategyPanel = document.getElementById('hitl-panel-strategy');
+ const whitelistPanel = document.getElementById('hitl-panel-whitelist');
+ if (pendingTab) {
+ pendingTab.classList.toggle('hitl-page-tab--active', hitlActiveTab === 'pending');
+ pendingTab.setAttribute('aria-selected', hitlActiveTab === 'pending' ? 'true' : 'false');
+ }
+ if (logsTab) {
+ logsTab.classList.toggle('hitl-page-tab--active', hitlActiveTab === 'logs');
+ logsTab.setAttribute('aria-selected', hitlActiveTab === 'logs' ? 'true' : 'false');
+ }
+ if (strategyTab) {
+ strategyTab.classList.toggle('hitl-page-tab--active', hitlActiveTab === 'strategy');
+ strategyTab.setAttribute('aria-selected', hitlActiveTab === 'strategy' ? 'true' : 'false');
+ }
+ if (whitelistTab) {
+ whitelistTab.classList.toggle('hitl-page-tab--active', hitlActiveTab === 'whitelist');
+ whitelistTab.setAttribute('aria-selected', hitlActiveTab === 'whitelist' ? 'true' : 'false');
+ }
+ if (pendingPanel) pendingPanel.hidden = hitlActiveTab !== 'pending';
+ if (logsPanel) logsPanel.hidden = hitlActiveTab !== 'logs';
+ if (strategyPanel) strategyPanel.hidden = hitlActiveTab !== 'strategy';
+ if (whitelistPanel) whitelistPanel.hidden = hitlActiveTab !== 'whitelist';
+ refreshHitlActivePanel();
+}
+
+function refreshHitlPageReviewerBar() {
+ const cid = getCurrentConversationIdForHitl();
+ const cfg = typeof window.getHitlConfigForConversation === 'function'
+ ? window.getHitlConfigForConversation(cid)
+ : null;
+ if (cfg && typeof window.setHitlReviewerUI === 'function') {
+ window.setHitlReviewerUI(cfg.reviewer);
+ }
+ if (typeof window.bindHitlReviewerToggleListeners === 'function') {
+ window.bindHitlReviewerToggleListeners();
+ }
+}
+
+let hitlDefaultAuditPrompt = '';
+let hitlDefaultAuditPromptReviewEdit = '';
+let hitlStrategyMode = 'approval';
+
+function switchHitlStrategyMode(mode) {
+ hitlStrategyMode = mode === 'review_edit' ? 'review_edit' : 'approval';
+ const approvalTab = document.getElementById('hitl-strategy-tab-approval');
+ const reviewTab = document.getElementById('hitl-strategy-tab-review-edit');
+ const approvalTa = document.getElementById('hitl-audit-agent-prompt');
+ const reviewTa = document.getElementById('hitl-audit-agent-prompt-review-edit');
+ const hintApproval = document.getElementById('hitl-strategy-hint-approval');
+ const hintReview = document.getElementById('hitl-strategy-hint-review-edit');
+ if (approvalTab) {
+ approvalTab.classList.toggle('hitl-strategy-subtab--active', hitlStrategyMode === 'approval');
+ approvalTab.setAttribute('aria-selected', hitlStrategyMode === 'approval' ? 'true' : 'false');
+ }
+ if (reviewTab) {
+ reviewTab.classList.toggle('hitl-strategy-subtab--active', hitlStrategyMode === 'review_edit');
+ reviewTab.setAttribute('aria-selected', hitlStrategyMode === 'review_edit' ? 'true' : 'false');
+ }
+ if (approvalTa) approvalTa.hidden = hitlStrategyMode !== 'approval';
+ if (reviewTa) reviewTa.hidden = hitlStrategyMode !== 'review_edit';
+ if (hintApproval) hintApproval.hidden = hitlStrategyMode !== 'approval';
+ if (hintReview) hintReview.hidden = hitlStrategyMode !== 'review_edit';
+}
+
+function showHitlStrategyFeedback(text, isError) {
+ const el = document.getElementById('hitl-strategy-feedback');
+ if (!el) return;
+ const msg = String(text || '').trim();
+ if (!msg) {
+ el.hidden = true;
+ el.textContent = '';
+ el.className = 'hitl-apply-feedback';
+ return;
+ }
+ el.hidden = false;
+ el.textContent = msg;
+ el.className = 'hitl-apply-feedback' + (isError ? ' hitl-apply-feedback--error' : '');
+}
+
+async function refreshHitlAuditStrategy() {
+ const approvalTa = document.getElementById('hitl-audit-agent-prompt');
+ const reviewTa = document.getElementById('hitl-audit-agent-prompt-review-edit');
+ if (!approvalTa) return;
+ try {
+ const resp = await hitlApiFetch('/api/hitl/audit-strategy', { credentials: 'same-origin' });
+ if (!resp.ok) return;
+ const data = await resp.json();
+ hitlDefaultAuditPrompt = typeof data.defaultAuditAgentPrompt === 'string' ? data.defaultAuditAgentPrompt : '';
+ hitlDefaultAuditPromptReviewEdit = typeof data.defaultAuditAgentPromptReviewEdit === 'string' ? data.defaultAuditAgentPromptReviewEdit : '';
+ approvalTa.value = typeof data.auditAgentPrompt === 'string' ? data.auditAgentPrompt : hitlDefaultAuditPrompt;
+ if (reviewTa) {
+ reviewTa.value = typeof data.auditAgentPromptReviewEdit === 'string' ? data.auditAgentPromptReviewEdit : hitlDefaultAuditPromptReviewEdit;
+ }
+ switchHitlStrategyMode(hitlStrategyMode);
+ } catch (e) {
+ console.warn('refreshHitlAuditStrategy', e);
+ }
+}
+
+async function saveHitlAuditStrategy() {
+ const approvalTa = document.getElementById('hitl-audit-agent-prompt');
+ const reviewTa = document.getElementById('hitl-audit-agent-prompt-review-edit');
+ const btn = document.getElementById('hitl-strategy-save-btn');
+ if (!approvalTa) return;
+ showHitlStrategyFeedback('', false);
+ if (btn) btn.disabled = true;
+ try {
+ const resp = await hitlApiFetch('/api/hitl/audit-strategy', {
+ method: 'PUT',
+ credentials: 'same-origin',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ auditAgentPrompt: String(approvalTa.value || ''),
+ auditAgentPromptReviewEdit: reviewTa ? String(reviewTa.value || '') : ''
+ })
+ });
+ if (!resp.ok) throw new Error(await readHitlApiError(resp));
+ const data = await resp.json();
+ if (typeof data.auditAgentPrompt === 'string') approvalTa.value = data.auditAgentPrompt;
+ if (reviewTa && typeof data.auditAgentPromptReviewEdit === 'string') reviewTa.value = data.auditAgentPromptReviewEdit;
+ showHitlStrategyFeedback(hitlT('strategySaved', 'Audit strategy saved.'), false);
+ } catch (e) {
+ showHitlStrategyFeedback(hitlT('strategySaveFailed', 'Failed to save') + ': ' + (e.message || e), true);
+ } finally {
+ if (btn) btn.disabled = false;
+ }
+}
+
+function resetHitlAuditStrategy() {
+ const approvalTa = document.getElementById('hitl-audit-agent-prompt');
+ const reviewTa = document.getElementById('hitl-audit-agent-prompt-review-edit');
+ if (hitlStrategyMode === 'review_edit' && reviewTa) {
+ reviewTa.value = hitlDefaultAuditPromptReviewEdit || reviewTa.value;
+ } else if (approvalTa) {
+ approvalTa.value = hitlDefaultAuditPrompt || approvalTa.value;
+ }
+ showHitlStrategyFeedback('', false);
+}
+
+function refreshHitlActivePanel() {
+ refreshHitlPageReviewerBar();
+ if (hitlActiveTab === 'logs') {
+ refreshHitlLogs();
+ } else if (hitlActiveTab === 'strategy') {
+ refreshHitlAuditStrategy();
+ } else if (hitlActiveTab === 'whitelist') {
+ refreshHitlPageWhitelist();
+ } else {
+ refreshHitlPending();
+ }
+}
+
+function hitlDecidedByLabel(v) {
+ const key = 'reviewer' + String(v || 'human').replace(/_([a-z])/g, function (_, c) { return c.toUpperCase(); }).replace(/^./, function (c) { return c.toUpperCase(); });
+ const map = {
+ human: hitlT('reviewerHuman', 'Human'),
+ audit_agent: hitlT('reviewerAgent', 'Audit Agent'),
+ system: hitlT('reviewerSystem', 'System'),
+ manual: hitlT('reviewerManual', 'Manual')
+ };
+ return map[v] || v || '-';
+}
+
+function hitlFormatTime(v) {
+ if (!v) return '-';
+ try {
+ const d = new Date(v);
+ if (Number.isNaN(d.getTime())) return String(v);
+ return d.toLocaleString(hitlLocale(), {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: false
+ });
+ } catch (e) {
+ return String(v);
+ }
+}
+
+function renderHitlLogsTable(items) {
+ const wrap = document.getElementById('hitl-logs-table-wrap');
+ if (!wrap) return;
+ const list = Array.isArray(items) ? items : [];
+ if (!list.length) {
+ wrap.innerHTML =
+ '' +
+ '
' + escapeHtml(hitlT('logsEmpty', 'No audit logs')) + '
' +
+ '
' + escapeHtml(hitlT('logsEmptyHint', 'Records appear here after HITL decisions.')) + '
' +
+ '
';
+ renderHitlLogsPagination();
+ return;
+ }
+ const rows = list.map(function (item) {
+ const id = escapeHtml(String(item.id || ''));
+ const qId = JSON.stringify(String(item.id || '')).replace(/"/g, '"');
+ const payloadObj = hitlParsePayloadObject(item.payload || '');
+ const decision = String(item.decision || '-');
+ const decisionCls = decision === 'approve' ? 'hitl-decision--approve' : (decision === 'reject' ? 'hitl-decision--reject' : '');
+ const summary = hitlPayloadSummary(payloadObj);
+ return (
+ '' +
+ '| ' + id + ' | ' +
+ '' + escapeHtml(String(item.toolName || '-')) + ' | ' +
+ '' + escapeHtml(String(item.conversationId || '-')) + ' | ' +
+ '' + escapeHtml(hitlDecisionLabel(decision)) + ' | ' +
+ '' + escapeHtml(hitlDecidedByLabel(item.decidedBy)) + ' | ' +
+ '' + escapeHtml(summary) + ' | ' +
+ '' + escapeHtml(hitlFormatTime(item.decidedAt || item.createdAt)) + ' | ' +
+ '' +
+ '' +
+ ' | ' +
+ '
'
+ );
+ }).join('');
+ wrap.innerHTML =
+ '' +
+ '' +
+ '| ' + escapeHtml(hitlT('colId', 'ID')) + ' | ' +
+ '' + escapeHtml(hitlT('colTool', 'Tool')) + ' | ' +
+ '' + escapeHtml(hitlT('colConversation', 'Conversation')) + ' | ' +
+ '' + escapeHtml(hitlT('colDecision', 'Decision')) + ' | ' +
+ '' + escapeHtml(hitlT('colDecidedBy', 'Reviewer')) + ' | ' +
+ '' + escapeHtml(hitlT('colContext', 'Context')) + ' | ' +
+ '' + escapeHtml(hitlT('colTime', 'Time')) + ' | ' +
+ '' + escapeHtml(hitlT('colActions', 'Actions')) + ' | ' +
+ '
' + rows + '
';
+ renderHitlLogsPagination();
+}
+
+async function refreshHitlLogs() {
+ const wrap = document.getElementById('hitl-logs-table-wrap');
+ if (!wrap) return;
+ wrap.innerHTML = '' + escapeHtml(hitlT('loading', 'Loading...')) + '
';
+ try {
+ const params = new URLSearchParams({
+ page: String(hitlLogsPage),
+ pageSize: String(hitlLogsPageSize)
+ });
+ const qEl = document.getElementById('hitl-logs-search');
+ const decEl = document.getElementById('hitl-logs-decision-filter');
+ const byEl = document.getElementById('hitl-logs-decidedby-filter');
+ if (qEl && qEl.value.trim()) params.set('q', qEl.value.trim());
+ if (decEl && decEl.value && decEl.value !== 'all') params.set('decision', decEl.value);
+ if (byEl && byEl.value && byEl.value !== 'all') params.set('decidedBy', byEl.value);
+ const resp = await hitlApiFetch('/api/hitl/logs?' + params.toString(), { credentials: 'same-origin' });
+ if (!resp.ok) throw new Error('request failed');
+ const data = await resp.json();
+ const items = Array.isArray(data.items) ? data.items : [];
+ hitlLogsTotal = typeof data.total === 'number' ? data.total : items.length;
+ const maxPage = Math.max(1, Math.ceil(hitlLogsTotal / hitlLogsPageSize));
+ if (hitlLogsPage > maxPage) {
+ hitlLogsPage = maxPage;
+ await refreshHitlLogs();
+ return;
+ }
+ hitlLogsCache = items;
+ hitlLogsLoaded = true;
+ renderHitlLogsTable(items);
+ } catch (e) {
+ hitlLogsLoaded = false;
+ wrap.innerHTML = '' + escapeHtml(hitlT('loadFailed', 'Failed to load')) + '
';
+ renderHitlLogsPagination();
+ }
+}
+
+function filterHitlLogs() {
+ hitlLogsPage = 1;
+ refreshHitlLogs();
+}
+
+function refreshHitlLogsI18n() {
+ if (!document.getElementById('hitl-logs-table-wrap') || !hitlLogsLoaded) return;
+ renderHitlLogsTable(hitlLogsCache);
+}
+
+function refreshHitlPendingI18n() {
+ if (!document.getElementById('hitl-pending-list') || !hitlPendingLoaded) return;
+ renderHitlPendingList(hitlPendingCache);
+}
+
+function refreshHitlI18n() {
+ refreshHitlLogsI18n();
+ refreshHitlPendingI18n();
+ renderHitlLogsPagination();
+ renderHitlPendingPagination();
+}
+
+function renderHitlLogsPagination() {
+ renderHitlPagination('hitl-logs-pagination', {
+ total: hitlLogsTotal,
+ page: hitlLogsPage,
+ pageSize: hitlLogsPageSize
+ }, 'hitlLogsGoPage', 'onHitlLogsPageSizeChange', 'hitl-logs-page-size');
+}
+
+function renderHitlPendingPagination() {
+ renderHitlPagination('hitl-pending-pagination', {
+ total: hitlPendingTotal,
+ page: hitlPendingPage,
+ pageSize: hitlPendingPageSize
+ }, 'hitlPendingGoPage', 'onHitlPendingPageSizeChange', 'hitl-pending-page-size');
+}
+
+function onHitlLogsPageSizeChange() {
+ const sel = document.getElementById('hitl-logs-page-size');
+ if (!sel) return;
+ const n = parseInt(sel.value, 10);
+ if (HITL_PAGE_SIZE_OPTIONS.indexOf(n) < 0) return;
+ hitlLogsPageSize = n;
+ try {
+ localStorage.setItem(HITL_LOGS_PAGE_SIZE_KEY, String(n));
+ } catch (e) { /* ignore */ }
+ hitlLogsPage = 1;
+ refreshHitlLogs();
+}
+
+function onHitlPendingPageSizeChange() {
+ const sel = document.getElementById('hitl-pending-page-size');
+ if (!sel) return;
+ const n = parseInt(sel.value, 10);
+ if (HITL_PAGE_SIZE_OPTIONS.indexOf(n) < 0) return;
+ hitlPendingPageSize = n;
+ try {
+ localStorage.setItem(HITL_PENDING_PAGE_SIZE_KEY, String(n));
+ } catch (e) { /* ignore */ }
+ hitlPendingPage = 1;
+ refreshHitlPending();
+}
+
+function hitlLogsGoPage(page) {
+ const totalPages = Math.max(1, Math.ceil((hitlLogsTotal || 0) / (hitlLogsPageSize || 20)));
+ if (page < 1 || page > totalPages) return;
+ hitlLogsPage = page;
+ refreshHitlLogs();
+}
+
+function hitlPendingGoPage(page) {
+ const totalPages = Math.max(1, Math.ceil((hitlPendingTotal || 0) / (hitlPendingPageSize || 20)));
+ if (page < 1 || page > totalPages) return;
+ hitlPendingPage = page;
+ refreshHitlPending();
+}
+
+function hitlDecisionLabel(decision) {
+ const d = String(decision || '').toLowerCase();
+ if (d === 'approve') return hitlT('decisionApprove', 'Approve');
+ if (d === 'reject') return hitlT('decisionReject', 'Reject');
+ return decision || '—';
+}
+
+function hitlFormatPayloadForDisplay(raw) {
+ if (!raw) return '';
+ if (typeof raw === 'object') {
+ try {
+ return JSON.stringify(raw, null, 2);
+ } catch (e) {
+ return String(raw);
+ }
+ }
+ const s = String(raw).trim();
+ if (!s) return '';
+ try {
+ return JSON.stringify(JSON.parse(s), null, 2);
+ } catch (e) {
+ return s;
+ }
+}
+
+async function openHitlLogModal(idOpt) {
+ const modal = document.getElementById('hitl-log-modal');
+ if (!modal || !idOpt) return;
+ const resp = await hitlApiFetch('/api/hitl/logs/' + encodeURIComponent(idOpt), { credentials: 'same-origin' });
+ if (!resp.ok) {
+ alert(hitlT('loadFailed', 'Failed to load'));
+ return;
+ }
+ const item = await resp.json();
+ const payloadObj = hitlParsePayloadObject(item.payload || '');
+ const idEl = document.getElementById('hitl-log-detail-id');
+ const toolEl = document.getElementById('hitl-log-detail-tool');
+ const convEl = document.getElementById('hitl-log-detail-conversation');
+ const decisionEl = document.getElementById('hitl-log-detail-decision');
+ const decidedByEl = document.getElementById('hitl-log-detail-decided-by');
+ const timeEl = document.getElementById('hitl-log-detail-time');
+ const commentRow = document.getElementById('hitl-log-detail-comment-row');
+ const commentEl = document.getElementById('hitl-log-detail-comment');
+ const payloadWrap = document.getElementById('hitl-log-detail-payload-wrap');
+ const payloadEl = document.getElementById('hitl-log-detail-payload');
+ if (idEl) idEl.textContent = item.id || '—';
+ if (toolEl) toolEl.textContent = item.toolName || '—';
+ if (convEl) convEl.textContent = item.conversationId || '—';
+ if (decisionEl) {
+ const decision = String(item.decision || '');
+ const cls = decision === 'approve' ? 'hitl-decision--approve' : (decision === 'reject' ? 'hitl-decision--reject' : '');
+ decisionEl.innerHTML = '' + escapeHtml(hitlDecisionLabel(decision)) + '';
+ }
+ if (decidedByEl) decidedByEl.textContent = hitlDecidedByLabel(item.decidedBy);
+ if (timeEl) timeEl.textContent = hitlFormatTime(item.decidedAt || item.createdAt);
+ const comment = String(item.comment || '').trim();
+ if (commentRow && commentEl) {
+ if (comment) {
+ commentEl.textContent = comment;
+ commentRow.hidden = false;
+ } else {
+ commentEl.textContent = '';
+ commentRow.hidden = true;
+ }
+ }
+ hitlFillLogModalReadonlySections(payloadObj);
+ const payloadText = hitlFormatPayloadForDisplay(item.payload || '');
+ if (payloadWrap && payloadEl) {
+ if (payloadText) {
+ payloadEl.textContent = payloadText;
+ payloadWrap.hidden = false;
+ } else {
+ payloadEl.textContent = '';
+ payloadWrap.hidden = true;
+ }
+ }
+ modal.style.display = 'flex';
+}
+
+function closeHitlLogModal() {
+ const modal = document.getElementById('hitl-log-modal');
+ if (modal) modal.style.display = 'none';
+}
+
+window.saveHitlPageWhitelist = saveHitlPageWhitelist;
+window.refreshHitlPageWhitelist = refreshHitlPageWhitelist;
window.refreshHitlPending = refreshHitlPending;
+window.refreshHitlLogs = refreshHitlLogs;
+window.refreshHitlActivePanel = refreshHitlActivePanel;
+window.switchHitlPageTab = switchHitlPageTab;
+window.switchHitlStrategyMode = switchHitlStrategyMode;
+window.resetHitlAuditStrategy = resetHitlAuditStrategy;
+window.saveHitlAuditStrategy = saveHitlAuditStrategy;
+window.refreshHitlAuditStrategy = refreshHitlAuditStrategy;
+window.openHitlLogModal = openHitlLogModal;
+window.closeHitlLogModal = closeHitlLogModal;
+window.hitlLogsGoPage = hitlLogsGoPage;
+window.hitlPendingGoPage = hitlPendingGoPage;
+window.filterHitlLogs = filterHitlLogs;
+window.filterHitlPending = filterHitlPending;
+window.onHitlLogsPageSizeChange = onHitlLogsPageSizeChange;
+window.onHitlPendingPageSizeChange = onHitlPendingPageSizeChange;
window.submitHitlDecision = submitHitlDecision;
window.submitHitlDecisionWithPayload = submitHitlDecisionWithPayload;
window.dismissHitlItem = dismissHitlItem;
@@ -420,7 +1256,7 @@ window.followAgentRunAfterHitlDecision = followAgentRunAfterHitlDecision;
window.addEventListener('hitl-interrupt', function () {
if (typeof window.currentPage === 'function' && window.currentPage() === 'hitl') {
- refreshHitlPending();
+ refreshHitlActivePanel();
}
});
@@ -428,9 +1264,22 @@ window.addEventListener('pageshow', function () {
setTimeout(reconcileHitlUiState, 0);
});
document.addEventListener('DOMContentLoaded', function () {
+ initHitlPageSizeFromStorage(HITL_LOGS_PAGE_SIZE_KEY, 20, function (n) { hitlLogsPageSize = n; });
+ initHitlPageSizeFromStorage(HITL_PENDING_PAGE_SIZE_KEY, 20, function (n) { hitlPendingPageSize = n; });
+ if (typeof window.bindHitlReviewerToggleListeners === 'function') {
+ window.bindHitlReviewerToggleListeners();
+ }
setTimeout(reconcileHitlUiState, 0);
});
+document.addEventListener('languagechange', function () {
+ try {
+ refreshHitlI18n();
+ } catch (e) {
+ console.warn('languagechange hitl refresh failed', e);
+ }
+});
+
// 由 applyHitlSidebarConfig 调用,将侧栏配置同步到后端
window.syncHitlConfigToServerByCurrentConversation = syncHitlConfigToServerByCurrentConversation;
window.saveHitlConversationConfig = saveHitlConversationConfig;
diff --git a/web/static/js/router.js b/web/static/js/router.js
index 235db0d9..7abef667 100644
--- a/web/static/js/router.js
+++ b/web/static/js/router.js
@@ -335,7 +335,9 @@ async function initPage(pageId) {
}
break;
case 'hitl':
- if (typeof refreshHitlPending === 'function') {
+ if (typeof refreshHitlActivePanel === 'function') {
+ refreshHitlActivePanel();
+ } else if (typeof refreshHitlPending === 'function') {
refreshHitlPending();
}
break;
diff --git a/web/templates/index.html b/web/templates/index.html
index 0ddd96ab..716ea27c 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -942,6 +942,19 @@
+
+
+
+
+
+
+
+
可在人工与审计 Agent 之间随时切换;规则与白名单不变。人机协同为「关闭」时也可预先选择。
+