agent-Specialization/static/src/app/computed.ts
JOJO 4fcd0ccc4d feat(multi-agent): 子智能体系统升级
- prompt改造:子智能体注入AGENTS.md/执行环境/工作区信息/skills/项目记忆
- 工具增加:read_skill/recall_project_memory/todo_create/todo_update_task/save_webpage
- 上下文压缩:深度压缩机制,记录current_context_tokens,默认150k阈值可配置
- 模型升级:sub_agent_models.json支持thinkmode_status/extra_parameter,与主智能体对齐
- 角色管理三层结构:源码树预设multi_agent_roles/ + 运行态host/web预设 + web按用户隔离
- 启动同步:initialize_system调用sync_preset_roles同步预设到host和web运行态
- 模式判断:API用session.host_mode,工具用data_dir路径判断,不依赖IS_HOST_MODE
- 前端:个人空间新增子智能体管理页(角色CRUD/压缩阈值/模型选择),复用个人空间样式
- 入口改造:登录页移除多智能体按钮,QuickMenu加模式切换项,运行中对话禁止切换
- 工具调整:多智能体模式create_sub_agent去掉timeout/deliverables_dir参数
- skill禁止:sub-agent-guide在多智能体模式下禁止阅读
- .agents/统一为.astrion/路径修复
2026-07-14 02:28:45 +08:00

403 lines
13 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 { mapState, mapWritableState } from 'pinia';
import { useConnectionStore } from '../stores/connection';
import { useFileStore } from '../stores/file';
import { useUiStore } from '../stores/ui';
import { useConversationStore } from '../stores/conversation';
import { useModelStore } from '../stores/model';
import { useChatStore } from '../stores/chat';
import { useInputStore } from '../stores/input';
import { useToolStore } from '../stores/tool';
import { useResourceStore } from '../stores/resource';
import { useFocusStore } from '../stores/focus';
import { useUploadStore } from '../stores/upload';
import { useMonitorStore } from '../stores/monitor';
import { usePolicyStore } from '../stores/policy';
import { useSubAgentStore } from '../stores/subAgent';
import { useBackgroundCommandStore } from '../stores/backgroundCommand';
import { usePersonalizationStore } from '../stores/personalization';
import { getToolStatusText, getToolDescription } from '../utils/chatDisplay';
import { toolFaceKey } from '../utils/avatarFace';
// 取最后一段连续的 tool actions对应模型一次并行调用的一批工具
function getLatestToolSegment(actions) {
if (!Array.isArray(actions)) return [];
let last = -1;
for (let i = actions.length - 1; i >= 0; i--) {
if (actions[i]?.type === 'tool') {
last = i;
break;
}
if (actions[i]?.type === 'thinking') break;
}
if (last < 0) return [];
let start = last;
while (start > 0 && actions[start - 1]?.type === 'tool') start--;
return actions.slice(start, last + 1);
}
const AVATAR_RUNNING_TOOL_STATUS = new Set([
'preparing',
'running',
'pending',
'queued',
'awaiting_approval',
'pending_approval',
'awaiting_user_answer'
]);
const AVATAR_TERMINAL_STATUS = new Set([
'completed',
'failed',
'timeout',
'terminated',
'cancelled'
]);
export const computed = {
...mapWritableState(useConnectionStore, [
'isConnected',
'socket',
'stopRequested',
'projectPath',
'agentVersion',
'thinkingMode',
'runMode'
]),
...mapState(useFileStore, ['contextMenu', 'fileTree', 'expandedFolders', 'todoList']),
...mapWritableState(useUiStore, [
'sidebarCollapsed',
'workspaceCollapsed',
'chatDisplayMode',
'panelMode',
'panelMenuOpen',
'leftWidth',
'rightWidth',
'rightCollapsed',
'isResizing',
'resizingPanel',
'minPanelWidth',
'maxPanelWidth',
'quotaToast',
'toastQueue',
'confirmDialog',
'easterEgg',
'isMobileViewport',
'mobileOverlayMenuOpen',
'activeMobileOverlay'
]),
...mapWritableState(useConversationStore, [
'conversations',
'conversationsLoading',
'hasMoreConversations',
'loadingMoreConversations',
'currentConversationId',
'currentConversationTitle',
'searchQuery',
'searchTimer',
'searchResults',
'conversationInsertAnimations',
'conversationListAnimationMode',
'pendingDeletingConversationIds',
'searchActive',
'searchInProgress',
'searchMoreAvailable',
'searchOffset',
'searchTotal',
'conversationsOffset',
'conversationsLimit',
'runningWorkspaceTasks',
'acknowledgedCompletedTaskIds',
'workspaceGroups'
]),
...mapWritableState(useModelStore, ['currentModelKey']),
...mapState(useModelStore, ['models']),
...mapWritableState(useChatStore, [
'messages',
'currentMessageIndex',
'streamingMessage',
'expandedBlocks',
'autoScrollEnabled',
'userScrolling',
'thinkingScrollLocks'
]),
...mapWritableState(useInputStore, [
'inputMessage',
'inputLineCount',
'inputIsMultiline',
'inputIsFocused',
'quickMenuOpen',
'toolMenuOpen',
'settingsOpen',
'imagePickerOpen',
'videoPickerOpen',
'selectedImages',
'selectedVideos',
'goalModeArmed',
'goalRunning',
'goalProgress',
'goalDialogOpen'
]),
resolvedRunMode() {
const allowed = ['fast', 'thinking', 'deep'];
if (allowed.includes(this.runMode)) {
return this.runMode;
}
return this.thinkingMode ? 'thinking' : 'fast';
},
headerRunModeOptions() {
return [
{ value: 'fast', label: '快速模式', desc: '低思考,响应更快' },
{ value: 'thinking', label: '思考模式', desc: '更长思考,综合回答' },
{ value: 'deep', label: '深度思考', desc: '持续推理,适合复杂任务' }
];
},
headerRunModeLabel() {
const current = this.headerRunModeOptions.find((o) => o.value === this.resolvedRunMode);
return current ? current.label : '快速模式';
},
currentModelLabel() {
const modelStore = useModelStore();
return modelStore.currentModel?.label || '未选择模型';
},
policyUiBlocks() {
const store = usePolicyStore();
return store.uiBlocks || {};
},
adminDisabledModels() {
const store = usePolicyStore();
return store.disabledModelSet;
},
modelOptions() {
const disabledSet = this.adminDisabledModels || new Set();
const options = this.models || [];
return options.map((opt) => ({
...opt,
disabled: disabledSet.has(opt.key)
}));
},
titleRibbonVisible() {
return !this.isMobileViewport && this.chatDisplayMode === 'chat';
},
chatContainerStyle() {
const minHeight = 90;
const raw = Number(this.composerReservedHeight || 0);
const height = Number.isFinite(raw) ? Math.max(minHeight, Math.round(raw)) : minHeight;
const growth = Math.max(0, height - minHeight);
return {
'--composer-base-height': `calc(${minHeight}px + var(--app-bottom-inset, 0px))`,
'--composer-reserved-height': `calc(${height}px + var(--app-bottom-inset, 0px))`,
'--composer-growth-height': `${growth}px`
};
},
...mapWritableState(useToolStore, [
'preparingTools',
'activeTools',
'toolActionIndex',
'toolStacks',
'toolSettings',
'toolSettingsLoading'
]),
...mapWritableState(useResourceStore, [
'tokenPanelCollapsed',
'currentContextTokens',
'currentConversationTokens',
'projectStorage',
'containerStatus',
'containerNetRate',
'usageQuota'
]),
...mapWritableState(useFocusStore, ['focusedFiles']),
...mapWritableState(useUploadStore, ['uploading', 'mediaUploading']),
...mapState(useMonitorStore, {
monitorIsLocked: (store) => store.isLocked
}),
displayModeSwitchDisabled() {
return !!this.policyUiBlocks.block_virtual_monitor;
},
displayLockEngaged() {
return !!this.compressionInProgress || !!this.compressing || !!this.currentWorkspaceHasRunningTask;
},
currentWorkspaceHasRunningTask() {
if (!(this.versioningHostMode || this.dockerProjectMode) || !this.currentHostWorkspaceId) {
return false;
}
const tasks = Array.isArray(this.runningWorkspaceTasks) ? this.runningWorkspaceTasks : [];
return tasks.some((task: any) => {
const status = String(task?.status || '');
return (
task?.workspace_id === this.currentHostWorkspaceId &&
task?.conversation_id !== this.currentConversationId &&
['pending', 'running', 'cancel_requested'].includes(status)
);
});
},
anyWorkspaceHasRunningTask() {
const tasks = Array.isArray(this.runningWorkspaceTasks) ? this.runningWorkspaceTasks : [];
return tasks.some((task: any) => {
const status = String(task?.status || '');
return ['pending', 'running', 'cancel_requested'].includes(status);
});
},
streamingUi() {
return this.streamingMessage || this.hasPendingToolActions();
},
composerBusy() {
const monitorLock = this.monitorIsLocked && this.chatDisplayMode === 'monitor';
return (
this.streamingUi ||
this.taskInProgress ||
monitorLock ||
this.stopRequested ||
this.compressionInProgress ||
this.compressing
);
},
composerStreamingForInput() {
return this.composerBusy && !this.displayLockEngaged;
},
composerHeroActive() {
return (this.blankHeroActive || this.blankHeroExiting) && !this.composerInteractionActive;
},
showStatusAvatar() {
return usePersonalizationStore().form?.show_status_avatar !== false;
},
composerInteractionActive() {
const hasText = !!(this.inputMessage && this.inputMessage.trim().length > 0);
const hasImages = Array.isArray(this.selectedImages) && this.selectedImages.length > 0;
return this.quickMenuOpen || hasText || hasImages;
},
showScrollToBottomButton() {
return this.chatDisplayMode === 'chat' && !this.stickIsNearBottom;
},
rightPanelsStyle() {
const w = Math.min(this.rightWidth || 420, 600);
const t = this.terminalPanelOpen;
const g = this.gitChangesPanelOpen;
const ratio = this.rightSplitRatio ?? 0.5;
const r = Math.max(0.15, Math.min(0.85, Number(ratio) || 0.5));
let rows: string;
if (t && g) rows = `${r}fr 2px ${1 - r}fr`;
else if (t) rows = '1fr 0px 0fr';
else if (g) rows = '0fr 0px 1fr';
else rows = '0fr 0px 0fr';
return {
width: w + 'px',
gridTemplateRows: rows
};
},
// 状态形象:驱动 StatusAvatar欢迎页 + 对话末尾指示器)
avatarStatus() {
const messages = Array.isArray(this.messages) ? this.messages : [];
const lastAssistant = (() => {
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i]?.role === 'assistant') return messages[i];
}
return null;
})();
// 后台运行计数(用于 work 文案)
const subAgentStore = useSubAgentStore();
const bgStore = useBackgroundCommandStore();
const countRunning = (arr) =>
(Array.isArray(arr) ? arr : []).filter((item) => {
if (!item) return false;
const status = String(item?.status || '').toLowerCase();
return status ? !AVATAR_TERMINAL_STATUS.has(status) : true;
}).length;
const agentCount = countRunning(subAgentStore.subAgents);
const cmdCount = countRunning(bgStore.commands);
const bgText = (() => {
const parts = [];
if (cmdCount > 0) parts.push(`${cmdCount}个后台指令`);
if (agentCount > 0) parts.push(`${agentCount}个后台智能体`);
return parts.length ? `${parts.join('')}运行中...` : '';
})();
const intentEnabled = !!usePersonalizationStore().form?.tool_intent_enabled;
// 1) 思考中
const isThinking =
!!lastAssistant &&
(lastAssistant.currentStreamingType === 'thinking' || lastAssistant.activeThinkingId != null);
// 2) 工具执行中(取最后一段并行工具,过滤未完成的)
let runningTools = [];
if (lastAssistant) {
const segment = getLatestToolSegment(lastAssistant.actions || []);
runningTools = segment.filter((a) => {
const s = String(a?.tool?.status || '').toLowerCase();
return a?.tool?.awaiting_content || !s || AVATAR_RUNNING_TOOL_STATUS.has(s);
});
}
if (runningTools.length === 0) {
const mapActions = [
...Array.from(this.preparingTools?.values?.() || []),
...Array.from(this.activeTools?.values?.() || [])
];
runningTools = mapActions
.map((item) => (item?.type === 'tool' ? item : { type: 'tool', tool: item }))
.filter((a) => {
const tool = a?.tool;
if (!tool) return false;
const s = String(tool?.status || '').toLowerCase();
return tool?.awaiting_content || !s || AVATAR_RUNNING_TOOL_STATUS.has(s);
});
}
const hasMapTools =
(this.preparingTools && this.preparingTools.size > 0) ||
(this.activeTools && this.activeTools.size > 0);
// 3) 运行/等待态标志
const running =
this.streamingMessage ||
this.taskInProgress ||
this.waitingForSubAgent ||
this.waitingForBackgroundCommand ||
this.stopRequested ||
hasMapTools ||
runningTools.length > 0 ||
agentCount > 0 ||
cmdCount > 0;
// ---- 决策 ----
if (isThinking) {
return { mode: 'think', toolKeys: [], toolTexts: [], text: '思考中...', tracking: false };
}
if (runningTools.length > 0) {
const keys = runningTools.map((a) => toolFaceKey(a?.tool?.name));
const getFinalIntentText = (tool: any) => {
if (!tool) return '';
const full = tool.intent_full || '';
const rendered = tool.intent_rendered || '';
// 只在 intent 打字效果完成后才显示完整文案,避免头像上出现逐字动画
return full && rendered === full ? full : '';
};
const toolTexts = runningTools.map((a) => {
return intentEnabled ? getFinalIntentText(a?.tool) : '';
});
let text = toolTexts[0] || '';
if (runningTools.length > 1) {
text = toolTexts[0] || '';
} else if (!text) {
text = intentEnabled ? getFinalIntentText(runningTools[0]?.tool) : '';
}
return { mode: 'tool', toolKeys: keys, toolTexts, text, tracking: false };
}
if (running) {
// 工作态:等 API / 后台运行 / 流式输出正文
let text = bgText;
if (!text) {
const awaitingMsg = lastAssistant && lastAssistant.awaitingFirstContent ? lastAssistant : null;
if (awaitingMsg) {
text =
(typeof awaitingMsg.generatingLabel === 'string' && awaitingMsg.generatingLabel.trim()) ||
'';
}
}
return { mode: 'work', toolKeys: [], toolTexts: [], text, tracking: false };
}
// 空闲
return { mode: 'idle', toolKeys: [], toolTexts: [], text: '', tracking: true };
}
};