Skip to content

Fix some bugs, update backend api#17

Merged
writtingforfun merged 13 commits into
modelscope:mainfrom
tastelikefeet:feat/0618
Jul 2, 2026
Merged

Fix some bugs, update backend api#17
writtingforfun merged 13 commits into
modelscope:mainfrom
tastelikefeet:feat/0618

Conversation

@tastelikefeet

@tastelikefeet tastelikefeet commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

PR type

  • Bug Fix
  • New Feature
  • Document Updates
  • Server Enhancement

PR information

  1. 增量 commit 同步 — watch 不再每次全量 zip 上传,改为 create/update/delete 细粒度 commit,带宽占用降低 90%+,支持二进制文件(base64 fallback)

  2. Windows 100% 优雅退出 — 引入 watch.stop 文件轮询机制替代 Unix 信号,stop 命令跨平台统一行为:写停止文件 → 等待退出 → 超时强杀,Windows 下不再触发 TerminateProcess 硬杀

  3. --name / --repo 参数解耦--name 定位本地 sub-agent,--repo 指定远端仓库(支持 group/repo 格式),语义不再混淆

  4. --name 自动推断 — upload/watch 省略 --name 时自动发现:单 agent 直接选中,多 agent 报错提示,无 agent 走 global 模式

  5. 新增 ultron list 命令 — 列出当前 framework 可发现的所有 sub-agent 及其文件数量

  6. 文件列表分页list_repo_files 接口支持翻页(每页 100,上限 5000 文件),解决大仓库文件截断问题

  7. collect_bytes() 二进制安全 — allowlist 新增 bytes 采集路径,watch/upload 全链路 bytes 透传,彻底修复图片等二进制资源同步时的编码损坏

  8. 全局模式 (GLOBAL_AGENT_NAME) — 新增哨兵常量区分"共享文件"与"子 agent 专属文件",allowlist pattern 中含 {name} 的在全局模式下自动排除

  9. watch 日志增强 — 每次同步明确输出每个文件的操作类型(CREATE/DELETE/UPDATE)+ 路径 + 大小,排查问题不再猜

  10. watch_loop 信号响应从 120s → <5s — 用 threading.Event.wait + 5s 轮询替代 time.sleep(120),修复 PEP 475 导致的 SIGTERM 无法中断 sleep 问题

  11. stop 命令孤儿进程清理 — Unix 用 pgrep -f + -- 选项终止符,Windows 用 wmic process 扫描 command line,确保无残留

  12. file-per-agent 框架 watch 安全约束 — 共享文件布局的 framework 禁止 watch 指定单个 sub-agent(避免并发冲突),仅允许 global/default 模式

  13. 空文件 vs 删除语义修正detect_local_changes 返回值从 "" 改为 None 表示删除,不再误判空文件为删除操作

  14. 测试覆盖 — 新增/增强 upload dry-run、download 格式转换、watch 增量同步、allowlist 全局模式等测试,总计 633 tests 全绿


影响范围: cli/ 全模块 + allowlist.py,+963 / -260 行,13 files changed

Test results

Paste your test result here (if needed).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the daemon-stopping logic in ultron/cli/watcher.py to support custom pgrep patterns via an optional extra_patterns parameter. The review feedback recommends using Optional[List[str]] instead of string-quoted union types for consistency, deduplicating patterns to avoid redundant pgrep executions, and adding -- to the pgrep command to prevent potential option injection vulnerabilities.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread ultron/cli/watcher.py Outdated
Comment thread ultron/cli/watcher.py Outdated
Comment thread ultron/cli/watcher.py Outdated
Comment thread ultron/cli/watcher.py
@tastelikefeet tastelikefeet changed the title Fix watcher bugs Fix some bugs, update backend api Jul 1, 2026
@tastelikefeet

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Ultron CLI to support a two-step OSS upload process, binary file handling via raw bytes, and incremental commits. It also introduces a new list command, refactors remote repository resolution with the --repo argument, and improves path traversal protection. The review feedback highlights three key improvements: distinguishing between empty and deleted files in push_incremental by using None instead of empty bytes, resolving a cross-platform crash on Windows by avoiding direct usage of signal.SIGKILL in stop_daemon, and ensuring accurate file counts in cmd_list by using collect_bytes() instead of collect().

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread ultron/cli/sync.py
Comment on lines +110 to +127
def push_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) -> None:
"""Incremental push via commit interface.

Builds create/update/delete actions and commits in one request.
Raises on failure (caller should NOT update baseline on exception).
"""
actions: List[dict] = []
for fpath, content in changed.items():
if not content: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type = "update" if fpath in remote_paths else "create"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To support distinguishing between empty files (b"") and deleted files (represented as None), update push_incremental to check if content is None for deletions.

Suggested change
def push_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) -> None:
"""Incremental push via commit interface.
Builds create/update/delete actions and commits in one request.
Raises on failure (caller should NOT update baseline on exception).
"""
actions: List[dict] = []
for fpath, content in changed.items():
if not content: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type = "update" if fpath in remote_paths else "create"
def push_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, Union[bytes, None]],
remote_paths: set,
) -> None:
"""Incremental push via commit interface.
Builds create/update/delete actions and commits in one request.
Raises on failure (caller should NOT update baseline on exception).
"""
actions: List[dict] = []
for fpath, content in changed.items():
if content is None: # None = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type = "update" if fpath in remote_paths else "create"

Comment thread ultron/cli/watcher.py Outdated
Comment on lines +321 to +326
if tracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

On Windows, the signal module does not have a SIGKILL attribute. Attempting to access signal.SIGKILL will raise an AttributeError, causing stop_daemon to crash if the tracked process does not exit within 1 second.

To ensure cross-platform compatibility, use getattr(signal, "SIGKILL", signal.SIGTERM) or check if SIGKILL is available on the signal module.

Suggested change
if tracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass
if tracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, getattr(signal, "SIGKILL", signal.SIGTERM))
except (ProcessLookupError, PermissionError, OSError, AttributeError):
pass

Comment thread ultron/cli/commands.py Outdated
tmp = _build_allowlist(framework, GLOBAL_AGENT_NAME, getattr(args, 'local_dir', None))
else:
tmp = _build_allowlist(framework, a, getattr(args, 'local_dir', None))
count = len(tmp.collect())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The cmd_list command uses tmp.collect() to count the files for each sub-agent. However, collect() only gathers text files and ignores binary files (skipping on UnicodeDecodeError). To ensure the file count is accurate and consistent with cmd_upload (which uses collect_bytes()), use tmp.collect_bytes() instead.

Suggested change
count = len(tmp.collect())
count = len(tmp.collect_bytes())

@writtingforfun writtingforfun merged commit 801c162 into modelscope:main Jul 2, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants