agent-Specialization/static/src/app/state.ts
JOJO 8d665ad6de feat(permission): add auto-approval mode with approval agent, UI flow, and docs sync
Implemented a new permission mode  between  and , including backend policy, runtime behavior, frontend display, and documentation updates.

Backend & policy changes:

- Added  to permission mode validation and persistence paths.

- Updated tool permission evaluation: workspace-local  direct pass, out-of-workspace requires approval.

- Kept  readonly-first flow and added auto-approval decision path when permission denial occurs.

- Added approval reason/decider support in tool approval manager.

- Improved rejection tool-content format to natural language: '工具调用被拒绝\n原因:...'

- Fixed permission-mode sync edge cases on conversation create/load and restart-first-switch behavior.

Approval agent subsystem:

- Added  and .

- Added lightweight auto-approval config at  (name/url/key/model/extra_params + timeouts).

- Added optional transcript debug switch in code and transcript output under logs/approval_agent.

- Aligned transcript saving toward cumulative messages format and captured reasoning/content/tool_calls/tool sequence.

Frontend changes:

- Added  option to personalization and permission menus.

- Reworked approval panel auto-review display into a dedicated block with simplified lines: start, command, final approve/reject + reason.

- Added delayed sidebar auto-close (10s) after approval resolved.

- Added stricter permission-switch verification and rollback on mismatch/error.

Docs updated:

- Synced AGENTS.md, README.md, and docs/host_sandbox_and_permission_model.md with new permission mode, approval-agent behavior, config, and current constraints (including docker caveat).
2026-05-11 17:55:27 +08:00

237 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @ts-nocheck
import { ICONS, TOOL_CATEGORY_ICON_MAP } from '../utils/icons';
export function dataState() {
return {
// 路由相关
initialRouteResolved: false,
dropToolEvents: false,
// 轮询模式标志(禁用 WebSocket 事件处理)
usePollingMode: true,
// 后台子智能体等待状态
waitingForSubAgent: false,
// 后台 run_command 等待状态(用于文案区分)
waitingForBackgroundCommand: false,
// 是否在对话区展示 system 消息
hideSystemMessages: true,
// 工具状态跟踪
preparingTools: new Map(),
activeTools: new Map(),
toolActionIndex: new Map(),
toolStacks: new Map(),
// 当前任务是否仍在进行中(用于保持输入区的"停止"状态)
taskInProgress: false,
// 等待后台通知期间,用于发现并接管新建任务的轮询定时器
waitingTaskProbeTimer: null,
// 运行期消息堆积(提前发送 / 引导对话)
runtimeQueuedMessages: [],
runtimeGuidanceFallbackQueue: [],
runtimeQueueSuppressedMessageIds: new Set(),
runtimeGuidanceSuppressedTextCounts: {},
runtimeQueueLimit: 5,
runtimeQueueAutoSendInProgress: false,
runtimeQueueSyncLockKey: '',
runtimeQueueSyncLockUntil: 0,
// 输入区动态保留高度(用于同步扩大消息区可滚动范围)
composerReservedHeight: 80,
// 记录上一次成功加载历史的对话ID防止初始化阶段重复加载导致动画播放两次
lastHistoryLoadedConversationId: null,
// ==========================================
// 对话管理相关状态
// ==========================================
// 搜索功能
// ==========================================
searchRequestSeq: 0,
searchActiveQuery: '',
searchResultIdSet: new Set(),
searchPreviewCache: {},
// Token统计相关状态修复版
// ==========================================
// 对话压缩状态
compressing: false,
compressionInProgress: false,
compressionMode: '',
compressionStage: '',
compressionError: '',
compressionToastId: null,
skipConversationLoadedEvent: false,
skipConversationHistoryReload: false,
_scrollListenerReady: false,
historyLoading: false,
historyLoadingFor: null,
historyLoadSeq: 0,
blankHeroActive: false,
blankHeroExiting: false,
blankWelcomeText: '',
lastBlankConversationId: null,
// 对话标题打字效果
titleTypingText: '',
titleTypingTarget: '',
titleTypingTimer: null,
titleReady: false,
suppressTitleTyping: false,
headerMenuOpen: false,
blankWelcomePool: [
'有什么可以帮忙的?',
'想了解些热点吗?',
'要我帮你完成作业吗?',
'整点代码?',
'随便聊点什么?',
'想让我帮你整理一下思路吗?',
'要不要我帮你写个小工具?',
'发我一句话,我来接着做。',
'我来给你画幅画?',
'想看小猫吗?',
'有什么难题丢过来吧。',
'想不想看个有意思的?',
'来,说说你的需求~'
],
mobileViewportQuery: null,
modeMenuOpen: false,
modelMenuOpen: false,
permissionMenuOpen: false,
currentPermissionMode: 'unrestricted',
executionModeEnabled: false,
currentExecutionMode: 'sandbox',
executionModeDirectUntil: null,
executionModeTtlSeconds: 600,
pathAuthorizationDialogOpen: false,
pathAuthorizationMode: 'writable',
pathAuthorizationWritableDraft: '',
pathAuthorizationReadableDraft: '',
pathAuthorizationDraft: '',
pathAuthorizationSaving: false,
versioningHostMode: false,
hostWorkspaces: [],
currentHostWorkspaceId: '',
defaultHostWorkspaceId: '',
hostWorkspaceSwitching: false,
hostWorkspaceCreateDialogOpen: false,
hostWorkspaceCreatePath: '',
hostWorkspaceCreateLabel: '',
hostWorkspaceCreateSubmitting: false,
hostWorkspaceCreateError: '',
versioningEnabled: false,
newConversationVersioningEnabled: false,
versioningTrackingMode: 'workspace_and_conversation',
newConversationVersioningTrackingMode: 'workspace_and_conversation',
versioningMode: 'overwrite',
versioningMismatch: false,
versioningWorkspaceMatched: true,
versioningDialogOpen: false,
versioningLoading: false,
versioningCheckpoints: [],
versioningSelectedSeq: null,
versioningSelectedDetail: null,
versioningDetailLoading: false,
versioningRestoring: false,
versioningRestoreMode: 'overwrite',
permissionModeOptions: [
{
value: 'readonly',
label: '只读',
description: '仅允许读取/搜索类工具,禁止修改工作区'
},
{
value: 'approval',
label: '批准',
description: '对工作区文件进行修改的工具需用户批准后才会执行'
},
{
value: 'auto_approval',
label: '自动审核',
description: '工作区内写入直通,高风险操作由后台审核智能体自动审批'
},
{
value: 'unrestricted',
label: '无限制',
description: '保持当前默认行为,不额外拦截'
}
],
executionModeOptions: [
{
value: 'sandbox',
label: '沙箱',
description: '所有指令会在系统沙箱中执行'
},
{
value: 'direct',
label: '完全访问权限',
description: '所有指令会在宿主机直接执行'
}
],
pendingToolApprovals: [],
decidingApprovalIds: [],
autoApprovalFeedLines: [],
autoApprovalFinalMessage: '',
approvalAutoCloseTimer: null,
imageEntries: [],
imageLoading: false,
videoEntries: [],
videoLoading: false,
conversationHasImages: false,
conversationHasVideos: false,
conversationListRequestSeq: 0,
conversationListRefreshToken: 0,
connectionHeartbeatTimer: null,
connectionHeartbeatActive: false,
connectionHeartbeatFailCount: 0,
connectionHeartbeatSeq: 0,
connectionHeartbeatLastLatencyMs: 0,
connectionHeartbeatLastError: '',
connectionHeartbeatLastStatusCode: null,
connectionHeartbeatLastChangeAt: 0,
connectionHeartbeatInFlight: false,
connectionHeartbeatFailThreshold: 3,
connectionHeartbeatRequestTimeoutMs: 5000,
connectionHeartbeatIntervalMs: 8000,
connectionHeartbeatDisconnectedIntervalMs: 1000,
composerDraftSaveTimer: null,
composerDraftDirty: false,
composerDraftLastSyncedContent: '',
composerDraftFetchSeq: 0,
// 工具控制菜单
icons: ICONS,
toolCategoryIcons: TOOL_CATEGORY_ICON_MAP,
// 对话回顾
reviewDialogOpen: false,
reviewSelectedConversationId: null,
reviewSubmitting: false,
reviewPreviewLines: [],
reviewPreviewLoading: false,
reviewPreviewError: null,
reviewPreviewLimit: 20,
reviewSendToModel: true,
reviewGeneratedPath: null,
// 新手教程首次引导弹窗
tutorialPromptVisible: false,
tutorialPromptLoading: false,
tutorialPromptUsername: '',
// 拖拽上传状态
dragOverActive: false,
// 拖拽事件绑定函数(用于正确移除监听)
_boundDragEnter: null,
_boundDragOver: null,
_boundDragLeave: null,
_boundDrop: null,
_manualScrollSuppressUntil: 0,
_escapedByUserScroll: false,
_autoRelockCooldownUntil: 0,
// stick-to-bottom 状态(用于“回到底部”按钮显隐)
stickIsAtBottom: true,
stickIsNearBottom: true
};
}