Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/hugagent-frontend-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ src/frontend/src/
│ ├── variables.css # CSS 自定义属性(颜色、阴影)
│ ├── common.css # 全局工具类
│ ├── chat.css / sidebar.css / catalog.css / tool.css
│ ├── automation.css / automation-timeline.css / batch(并入相关文件)
│ ├── automation.css / batch(并入相关文件)
│ ├── canvas.css / config.css / mcp.css / myspace.css / plan.css
│ └── projects.css / search-modal.css / settings.css / skill-distill.css / team-folder.css
```
Expand Down
4 changes: 2 additions & 2 deletions document/en/modules/automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Scheduled prompts and ordinary conversations share admission, capability resolut

Scheduled Tasks is a dedicated module under src/frontend/src/components/automation/. Its sidebar groups conversations by task ID and shows one entry per scheduled task, keeping distinct tasks separate even when their names match. New executions update the entry's latest activity time; clicking it opens the newest execution. The right control panel retains individual executions, and choosing a historical run keeps the same task selected in the sidebar. Setup conversations remain separate, and empty setup conversations are hidden. The oldest conversation anchors each entry, preserving its ordering, pin, favorite, and rename actions. Export and delete apply to the selected execution, or the newest execution when the task is not open; deleting one conversation does not delete the scheduled task. All Tasks opens configuration, status, and run history in the content area. Creating through chat first persists a conversation owned by Scheduled Tasks, references the installed task-management plugin, and prefills an editable template. Task conversations are excluded from the ordinary chat sidebar.

Execution conversations use /automation/:taskId/conversations/:chatId, with new as the task segment for setup conversations. Reloads, browser history, search hits, and notifications preserve task ownership. Search opens the exact matching conversation instead of the latest run. RunTimelinePanel groups executions by date. automationStore owns the single task list; automationChatStore owns the current run group and task pin/favorite preferences.
Execution conversations use /automation/:taskId/conversations/:chatId, with new as the task segment for setup conversations. Reloads, browser history, search hits, and notifications preserve task ownership. Search opens the exact matching conversation instead of the latest run. The run monitor rail beside an execution conversation shows a scheduled-task card listing every run, newest first. automationStore owns the single task list; automationChatStore owns the current run group and task pin/favorite preferences.

Opening the right-side Canvas in an execution conversation temporarily hides the run timeline to make room for the conversation and Canvas. Collapsing Canvas restores the timeline and preserves the current task, selected execution, and conversation.

Expand Down Expand Up @@ -106,7 +106,7 @@ Every morning an Industry morning brief execution conversation is created. The s
| Plan REST API | `src/backend/api/routes/v1/plans.py` |
| Plan generation / execution orchestration | `src/backend/orchestration/plans/`, `src/backend/core/chat/plans.py` |
| Plan / step models | `src/backend/core/db/models/agent.py` |
| Frontend automation UI | `src/frontend/src/components/automation/AutomationPanel.tsx` etc., `src/frontend/src/components/automation/RunTimelinePanel.tsx` |
| Frontend automation UI | `src/frontend/src/components/automation/AutomationPanel.tsx` etc., `src/frontend/src/components/monitor/ScheduledTaskCard.tsx` |
| Frontend state | `src/frontend/src/stores/automationStore.ts`, `automationChatStore.ts` |

Further reading: [Chat System](chat.md) · [MCP Tools](mcp-tools.md) · [Canvas & Artifacts](canvas-artifacts.md)
Expand Down
2 changes: 1 addition & 1 deletion document/en/modules/projects-myspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ Permission model: `TeamMember.role` (owner/admin/member) + `file_permission` (vi

Three complementary paths:

1. **Attachment injection (hooks)**: frontend requests send only `file_id`, `name`, and `mime_type` in each `attachments[]` item, never the file body. `core/harness/hooks.py` lazily downloads and parses by `file_id`, caching `parsed_text` and its summary; the first turn injects only metadata and a bounded preview of at most 5,000 characters, while xlsx previews also include total dimensions, headers, and a few data rows. The model reads content beyond the preview on demand through `read_artifact`; images still use multimodal injection. Every read validates `user_id` ownership to block forged cross-user access;
1. **Attachment injection (hooks)**: frontend requests send only `file_id`, `name`, and `mime_type` in each `attachments[]` item, never the file body. The attachment hook (`core/llm/context/attachment_hook.py`) neither downloads nor parses files: it injects only each file's name, MIME type, and `file_id` plus reading instructions, so attachments add nothing to time-to-first-token. When the model needs the content it calls `read_artifact`, which downloads and parses by `file_id`, caches `parsed_text` and its summary, and returns paginated text (xlsx by sheet, pptx by slide); images still use multimodal injection. Every read validates `user_id` ownership to block forged cross-user access;
2. **Project file access**: a project conversation's system prompt supplies project instructions and directory scope without an automatic file inventory or count. The agent discovers files on demand through directory, search and read tools;
3. **Sandbox virtual filesystem**: with code execution enabled, the `/myspace/...` path maps MySpace into the sandbox (lazy loading + reverse sync); team projects map the team folder — see [Sandbox](./sandbox.md).

Expand Down
4 changes: 2 additions & 2 deletions document/zh-CN/modules/automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ HugAgentOS 内置两种「把一句话变成可重复的生产力」机制,两

定时任务是独立模块,相关组件位于 src/frontend/src/components/automation/。左侧按任务 ID 合并会话,每个定时任务只显示一条记录;同名的不同任务分别显示。新运行更新入口的最近活动时间,点击入口打开最近一次运行。右侧控制面板保留各次执行记录,选择历史运行后左侧仍选中同一个任务。创建中的会话分别显示,空白创建会话隐藏。侧边栏沿用最早会话作为稳定入口,保留该会话的排序、置顶、收藏和重命名操作。导出和删除作用于当前选中的执行会话,未打开该任务时作用于最近一次执行;删除单次会话不删除定时任务。点击“全部任务”进入内容区管理任务配置、状态和执行记录。通过对话创建时,会先保存归属于定时任务模块的会话,引用已安装的定时任务管理插件并填入可编辑模板。普通对话列表不展示任务会话。

执行会话使用 /automation/:taskId/conversations/:chatId;创建会话使用任务段 new。刷新、前进后退、搜索命中及通知入口均保留任务归属;搜索执行结果直接打开命中的会话,避免误跳到最新执行。RunTimelinePanel 提供按日期分组的执行记录。automationStore 管理唯一任务列表,automationChatStore 仅管理当前执行组和任务置顶、收藏偏好。
执行会话使用 /automation/:taskId/conversations/:chatId;创建会话使用任务段 new。刷新、前进后退、搜索命中及通知入口均保留任务归属;搜索执行结果直接打开命中的会话,避免误跳到最新执行。执行会话右侧的运行监控栏提供「定时任务」卡片,按时间倒序列出全部执行记录。automationStore 管理唯一任务列表,automationChatStore 仅管理当前执行组和任务置顶、收藏偏好。

执行会话打开右侧 Canvas 时,执行记录面板暂时隐藏,为对话和 Canvas 留出空间。收起 Canvas 后恢复执行记录面板,保留当前任务、选中的执行和会话。

Expand Down Expand Up @@ -106,7 +106,7 @@ POST /v1/automations
| 计划 REST API | `src/backend/api/routes/v1/plans.py` |
| 计划生成 / 执行编排 | `src/backend/orchestration/plans/`、`src/backend/core/chat/plans.py` |
| 计划 / 步骤模型 | `src/backend/core/db/models/agent.py` |
| 前端自动化 UI | `src/frontend/src/components/automation/AutomationPanel.tsx` 等、`src/frontend/src/components/automation/RunTimelinePanel.tsx` |
| 前端自动化 UI | `src/frontend/src/components/automation/AutomationPanel.tsx` 等、`src/frontend/src/components/monitor/ScheduledTaskCard.tsx` |
| 前端状态 | `src/frontend/src/stores/automationStore.ts`、`automationChatStore.ts` |

延伸阅读:[对话系统](chat.md) · [MCP 工具](mcp-tools.md) · [数据画布与产物](canvas-artifacts.md)
Expand Down
2 changes: 1 addition & 1 deletion document/zh-CN/modules/projects-myspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ teams ─────────┬── team_members(role: owner/admin/memb

三条路径,互为补充:

1. **附件注入(hooks)**:前端请求的 `attachments[]` 只携带 `file_id`、`name`、`mime_type`,不携带文件正文。`core/harness/hooks.py` 按 `file_id` 延迟下载、解析并缓存 `parsed_text` 与摘要;首轮只注入文件元数据和最多 5,000 字符的有界预览,xlsx 额外提供总行列数、表头和少量数据行。模型需要预览外的正文时通过 `read_artifact` 按需读取;图片仍走多模态注入。所有读取都校验 `user_id` 归属,防止伪造 `file_id` 跨用户访问;
1. **附件注入(hooks)**:前端请求的 `attachments[]` 只携带 `file_id`、`name`、`mime_type`,不携带文件正文。附件钩子(`core/llm/context/attachment_hook.py`)不下载也不解析文件,只注入文件名、MIME 和 `file_id` 以及读取指引,因此附件不会拖慢首字响应。模型需要正文时调用 `read_artifact`,由它按 `file_id` 下载、解析并缓存 `parsed_text` 与摘要,再分页返回(xlsx 可按 sheet、pptx 可按页读取);图片仍走多模态注入。所有读取都校验 `user_id` 归属,防止伪造 `file_id` 跨用户访问;
2. **项目文件访问**:项目对话的 system prompt 提供项目指令和目录范围,不自动携带文件列表或文件总数。agent 按需使用目录、搜索和读取工具获取文件信息;
3. **沙箱虚拟文件系统**:开启代码执行时,`/myspace/...` 路径把我的空间映射进沙箱(懒加载 + 文件系统实时登记),团队项目映射团队文件夹——见 [沙箱](./sandbox.md)。

Expand Down
1 change: 1 addition & 0 deletions scripts/refactor/import_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
("automation", "desktop"), # Authorized local task dispatch; see module README.
("distillation", "marketplace"), # Explicit draft publication; see module README.
("security_console", "sandbox_ops"), # Read-only rebuild status; see module README.
("task_monitor", "security_console"), # Read-only health snapshot; see module README.
}
OUTER = ("features", "integrations", "personal_agent", "orchestration", "api")

Expand Down
28 changes: 28 additions & 0 deletions src/backend/alembic/versions/ce_0036_task_numbers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Task numbers: RW-YYYYMMDD-NNNN on every run, RW-…-NN on every sub-agent call.

Same schema and backfill as the EE revision ``tasknumber01`` (``core.db.task_number_migration``).

Revision ID: ce_0036
Revises: ce_0035
"""

import sqlalchemy as sa
from alembic import op

from core.db.task_number_migration import downgrade_task_numbers, upgrade_task_numbers

revision = "ce_0036"
down_revision = "ce_0035"
branch_labels = None
depends_on = None


def upgrade():
# CE's initial create_all may already contain the current model tables.
if "task_number_counters" in sa.inspect(op.get_bind()).get_table_names():
return
upgrade_task_numbers(op)


def downgrade():
downgrade_task_numbers(op)
4 changes: 2 additions & 2 deletions src/backend/api/routes/v1/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ def get_main_capabilities(
# context-usage gauge reflects the model's true window instead of a guess.
# 0 when unconfigured — the frontend falls back to a heuristic/default.
main_context_length = int(cfg.context_length) if cfg and cfg.context_length else 0
from core.files.artifacts.reader import ATTACHMENT_PREVIEW_MAX_CHARS
from core.llm.runtime.file_context import ATTACHMENT_HANDLE_EST_CHARS
# Image reading: either the main model is natively multimodal, or a `vision` role
# model is assigned and the vision bridge transcribes images into text evidence.
# The frontend uses `can_read_image` alone to decide whether to offer image upload;
Expand All @@ -547,7 +547,7 @@ def get_main_capabilities(
**effort_caps,
"supports_vision": main_native_vision,
"context_length": main_context_length,
"attachment_preview_chars": ATTACHMENT_PREVIEW_MAX_CHARS,
"attachment_preview_chars": ATTACHMENT_HANDLE_EST_CHARS,
},
"vision": {
"can_read_image": vision_cfg is not None,
Expand Down
14 changes: 12 additions & 2 deletions src/backend/core/chat/compaction/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
from core.chat.context import build_effective_user_message
from core.llm.context import compaction as C
from core.llm.context.ir_constants import SESSION_CONTEXT_META_KEY
from core.llm.runtime.model_steps import assistant_row_replay, record_assistant_step
from core.llm.runtime.model_steps import (
assistant_row_replay,
record_assistant_step,
split_before_input,
)
from core.chat.references import render_reference_block


Expand Down Expand Up @@ -56,6 +60,9 @@ def normalize_rows(rows: List[Any], live_run_ids: frozenset = frozenset()) -> Li
definition and would show the model a truncated version of its own reply.
"""
out: List[Dict[str, Any]] = []
# Where the latest user row starts: blocks the run injected ahead of the
# user's message (attachment previews) go back in front of it.
turn_start = 0
for msg in rows:
role = getattr(msg, "role", None)
extra = getattr(msg, "extra_data", None) or {}
Expand All @@ -79,9 +86,10 @@ def normalize_rows(rows: List[Any], live_run_ids: frozenset = frozenset()) -> Li
}
if isinstance(extra.get(SESSION_CONTEXT_META_KEY), dict):
row[SESSION_CONTEXT_META_KEY] = dict(extra[SESSION_CONTEXT_META_KEY])
turn_start = len(out)
out.append(row)
elif role == "assistant":
out.extend(
before, after = split_before_input(
assistant_row_replay(
content=content or "",
model_steps=(
Expand All @@ -92,6 +100,8 @@ def normalize_rows(rows: List[Any], live_run_ids: frozenset = frozenset()) -> Li
cancelled=bool(extra.get("cancelled")),
)
)
out[turn_start:turn_start] = before
out.extend(after)
else:
row = {"role": role or "user", "content": content}
if isinstance(extra.get(SESSION_CONTEXT_META_KEY), dict):
Expand Down
33 changes: 3 additions & 30 deletions src/backend/core/chat/runtime/chunks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
from core.infra.text_bounds import bound_result_for_display
from core.chat.tool_log import (
attach_subagent_step,
build_interaction_event,
build_thinking_event,
build_tool_call_delta_event,
build_tool_call_event,
build_tool_call_start_event,
build_tool_result_event,
build_user_question_event,
build_user_question_resolved_event,
)
from core.infra.logging import get_logger

Expand Down Expand Up @@ -131,34 +130,8 @@ async def consume_chunk(turn, chunk):
logger.debug("attach_subagent_step failed (ignored)", exc_info=True)
chunk["chat_id"] = turn.chat_id
await turn.emit(bound_result_for_display(chunk)[0])
elif chunk_type == "file_confirm":
await turn.emit(
{
"type": "file_confirm",
"chat_id": turn.chat_id,
"confirm_id": chunk.get("confirm_id"),
"op": chunk.get("op"),
"logical_path": chunk.get("logical_path"),
"message": chunk.get("message"),
"expired": chunk.get("expired", False),
}
)
elif chunk_type == "design_pick":
await turn.emit(
{
"type": "design_pick",
"chat_id": turn.chat_id,
"confirm_id": chunk.get("confirm_id"),
"question": chunk.get("question", ""),
"options": chunk.get("options", []),
"message": chunk.get("message"),
"expired": chunk.get("expired", False),
}
)
elif chunk_type == "user_question":
await turn.emit(build_user_question_event(chunk, turn.chat_id))
elif chunk_type == "user_question_resolved":
await turn.emit(build_user_question_resolved_event(chunk, turn.chat_id))
elif chunk_type in ("file_confirm", "design_pick", "user_question", "user_question_resolved"):
await turn.emit(build_interaction_event(chunk_type, chunk, turn.chat_id))
elif chunk_type == "plan_update":
await turn.emit(
{
Expand Down
2 changes: 2 additions & 0 deletions src/backend/core/chat/steer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from core.db.engine import SessionLocal
from core.db.models import ChatRun
from core.harness.request_links import link_request
from core.infra.ephemeral import get_ephemeral_state
from core.chat.steer_queue import SteerQueue, SteerQueueConflict

Expand Down Expand Up @@ -65,6 +66,7 @@ async def put_pending_steer(run_id: str, payload: Dict[str, Any]) -> Dict[str, A
delivery_mode=str(payload.get("delivery_mode") or "steer"),
replace_latest=bool(payload.get("replace_latest", True)),
)
await link_request(run_id, f"steer:{record.delivery_mode}")
await _notify(
run_id,
{
Expand Down
25 changes: 4 additions & 21 deletions src/backend/core/chat/tool_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,28 +164,11 @@ def build_tool_call_event(chunk: dict, chat_id: str, tool_calls_log: list) -> Di
return {"type": "tool_call", **tc, "chat_id": chat_id}


def build_user_question_event(chunk: dict, chat_id: str) -> Dict[str, Any]:
"""Build the authoritative pending-question request sent to the composer."""
def build_interaction_event(event_type: str, chunk: dict, chat_id: str) -> Dict[str, Any]:
"""Relay a confirm / design-pick / question signal; its producer owns the fields."""

return {
"type": "user_question",
"chat_id": chat_id,
"request_id": chunk.get("request_id"),
"questions": chunk.get("questions", []),
"created_at": chunk.get("created_at"),
"expires_at": chunk.get("expires_at"),
}


def build_user_question_resolved_event(chunk: dict, chat_id: str) -> Dict[str, Any]:
"""Build the server-owned event that removes a composer request."""

return {
"type": "user_question_resolved",
"chat_id": chat_id,
"request_id": chunk.get("request_id"),
"outcome": chunk.get("outcome"),
}
public = {k: v for k, v in chunk.items() if not k.startswith("_") and k != "event"}
return bound_result_for_display({**public, "type": event_type, "chat_id": chat_id})[0]


def _payload_carries_error(res: Any) -> bool:
Expand Down
3 changes: 2 additions & 1 deletion src/backend/core/db/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from core.db.edition import EDITION_MODEL_EXPORTS
from core.db.models.chat import ChatCompactionState, ChatMessage, ChatSession
from core.db.models.chat_runs import ChatRun, ChatRunOperation, ChatSteerQueueItem
from core.db.models.chat_runs import ChatRun, ChatRunOperation, ChatSteerQueueItem, TaskNumberCounter
from core.db.models.chat_resources import ChatSandboxSnapshot, ToolMediaBlob, MessageFeedback
from core.db.models.identity import LocalUser, UserApiKey, UserFolder, UserShadow
from core.db.models.project import Project, ProjectFavorite
Expand Down Expand Up @@ -88,6 +88,7 @@ def __getattr__(name):
'ChatMessage',
'ChatCompactionState',
'ChatRun',
'TaskNumberCounter',
'CommandTask',
'ChatRunOperation',
'ChatSteerQueueItem',
Expand Down
Loading
Loading