- task event stream (_append_event) is now the sole realtime channel for web and CLI - status snapshots: idle 5s polling of /api/status (was status_update push); operation initiator gets state from REST responses - terminal panel: REST polling (list 5s, output 1.5s, prefix-matched incremental xterm writes) - multi-tab passive sync deliberately degrades to polling-based perception - delete socket_handlers/broadcast/useLegacySocket (2103-line dead file); extensions.py keeps only run_background - remove socket token chain (/api/socket-token, prune/consume_socket_token, pending_socket_tokens) - app.run(threaded=True) replaces socketio.run; reapers switched to plain threading - drop flask-socketio/socket.io-client/websockets dependencies - docs: AGENTS.md section 12.5 (replacement map + hard constraints), plus CLI rewrite companion doc updates Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
160 lines
5.9 KiB
Python
160 lines
5.9 KiB
Python
from __future__ import annotations
|
||
from server.chat import chat_bp
|
||
import json, time
|
||
from datetime import datetime
|
||
from typing import Dict, Any, Optional
|
||
from pathlib import Path
|
||
from io import BytesIO
|
||
import zipfile
|
||
import os
|
||
|
||
from flask import Blueprint, jsonify, request, session, send_file
|
||
from werkzeug.utils import secure_filename
|
||
from werkzeug.exceptions import RequestEntityTooLarge
|
||
import secrets
|
||
|
||
from config import MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||
from modules.personalization_manager import (
|
||
load_personalization_config,
|
||
resolve_context_compression_settings,
|
||
save_personalization_config,
|
||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||
)
|
||
from modules.skills_manager import (
|
||
get_skills_catalog,
|
||
infer_private_skills_dir,
|
||
merge_enabled_skills,
|
||
sync_workspace_skills,
|
||
)
|
||
from modules.upload_security import UploadSecurityError
|
||
from modules.host_sandbox_policy import load_policy, save_policy
|
||
from modules.user_manager import UserWorkspace
|
||
from core.web_terminal import WebTerminal
|
||
from config.model_profiles import get_model_context_window
|
||
|
||
from server.auth_helpers import api_login_required, resolve_admin_policy, get_current_user_record, get_current_username
|
||
from server.context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker
|
||
from server.security import rate_limited
|
||
from server.utils_common import debug_log
|
||
from server.state import PROJECT_MAX_STORAGE_MB
|
||
from server.state import tool_approval_manager, user_question_manager
|
||
from server.monitor import get_cached_monitor_snapshot
|
||
|
||
from modules.i18n import tr
|
||
|
||
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
|
||
@chat_bp.route('/api/memory', methods=['GET'])
|
||
@api_login_required
|
||
@with_terminal
|
||
def api_memory_entries(terminal: WebTerminal, workspace: UserWorkspace, username: str):
|
||
"""返回主/任务记忆条目列表,供虚拟显示器加载"""
|
||
memory_type = request.args.get('type', 'main')
|
||
if memory_type not in ('main', 'task'):
|
||
return jsonify({"success": False, "error": tr("chat_misc.invalid_memory_type")}), 400
|
||
try:
|
||
entries = terminal.memory_manager._read_entries(memory_type) # type: ignore
|
||
return jsonify({"success": True, "type": memory_type, "entries": entries})
|
||
except Exception as exc:
|
||
return jsonify({"success": False, "error": str(exc)}), 500
|
||
|
||
@chat_bp.route('/api/gui/monitor_snapshot', methods=['GET'])
|
||
@api_login_required
|
||
def get_monitor_snapshot_api():
|
||
execution_id = request.args.get('executionId') or request.args.get('execution_id') or request.args.get('id')
|
||
if not execution_id:
|
||
return jsonify({
|
||
'success': False,
|
||
'error': tr('chat_misc.missing_execution_id')
|
||
}), 400
|
||
stage = (request.args.get('stage') or 'before').lower()
|
||
if stage not in {'before', 'after'}:
|
||
stage = 'before'
|
||
snapshot = get_cached_monitor_snapshot(execution_id, stage, username=get_current_username())
|
||
if not snapshot:
|
||
return jsonify({
|
||
'success': False,
|
||
'error': tr('chat_misc.snapshot_not_found')
|
||
}), 404
|
||
return jsonify({
|
||
'success': True,
|
||
'snapshot': snapshot,
|
||
'stage': stage
|
||
})
|
||
|
||
@chat_bp.route('/api/focused')
|
||
@api_login_required
|
||
@with_terminal
|
||
def get_focused_files(terminal: WebTerminal, workspace: UserWorkspace, username: str):
|
||
"""聚焦功能已废弃,返回空列表保持接口兼容。"""
|
||
return jsonify({})
|
||
|
||
@chat_bp.route('/api/todo-list')
|
||
@api_login_required
|
||
@with_terminal
|
||
def get_todo_list(terminal: WebTerminal, workspace: UserWorkspace, username: str):
|
||
"""获取当前待办列表"""
|
||
todo_snapshot = terminal.context_manager.get_todo_snapshot()
|
||
return jsonify({
|
||
"success": True,
|
||
"data": todo_snapshot
|
||
})
|
||
|
||
@chat_bp.route('/api/tool-settings', methods=['GET', 'POST'])
|
||
@api_login_required
|
||
@with_terminal
|
||
def tool_settings(terminal: WebTerminal, workspace: UserWorkspace, username: str):
|
||
"""获取或更新工具启用状态"""
|
||
if request.method == 'GET':
|
||
snapshot = terminal.get_tool_settings_snapshot()
|
||
return jsonify({
|
||
"success": True,
|
||
"categories": snapshot
|
||
})
|
||
|
||
data = request.get_json() or {}
|
||
category = data.get('category')
|
||
if category is None:
|
||
return jsonify({
|
||
"success": False,
|
||
"error": tr("chat_misc.missing_category"),
|
||
"message": tr("chat_misc.missing_category_field")
|
||
}), 400
|
||
|
||
if 'enabled' not in data:
|
||
return jsonify({
|
||
"success": False,
|
||
"error": tr("chat_misc.missing_enabled"),
|
||
"message": tr("chat_misc.missing_enabled_field")
|
||
}), 400
|
||
|
||
try:
|
||
policy = resolve_admin_policy(get_current_user_record())
|
||
if policy.get("ui_blocks", {}).get("block_tool_toggle"):
|
||
return jsonify({
|
||
"success": False,
|
||
"error": tr("chat_misc.tool_toggle_admin_disabled"),
|
||
"message": tr("chat_misc.admin_forced_disabled")
|
||
}), 403
|
||
enabled = bool(data['enabled'])
|
||
forced = getattr(terminal, "admin_forced_category_states", {}) or {}
|
||
if isinstance(forced.get(category), bool) and forced[category] != enabled:
|
||
return jsonify({
|
||
"success": False,
|
||
"error": tr("chat_misc.category_forced"),
|
||
"message": tr("chat_misc.admin_forced_state")
|
||
}), 403
|
||
terminal.set_tool_category_enabled(category, enabled)
|
||
snapshot = terminal.get_tool_settings_snapshot()
|
||
# tool_settings 变更不再经 WebSocket 广播:发起方直接使用本响应中的
|
||
# categories,其他标签页在下次操作或 status 轮询时同步。
|
||
return jsonify({
|
||
"success": True,
|
||
"categories": snapshot
|
||
})
|
||
except ValueError as exc:
|
||
return jsonify({
|
||
"success": False,
|
||
"error": str(exc)
|
||
}), 400
|