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

@tastelikefeettastelikefeet 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-assistgemini-code-assistBot 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 threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py
@tastelikefeettastelikefeet changed the title Fix watcher bugsFix some bugs, update backend apiJul 1, 2026
@tastelikefeet

Copy link
Copy Markdown
CollaboratorAuthor

/gemini review

@gemini-code-assistgemini-code-assistBot 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 threadultron/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
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifnotcontent: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, Union[bytes, None]],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifcontentisNone: # None = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"

Comment threadultron/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
iftracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass
iftracked_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 threadultron/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:mainJul 2, 2026
0 of 4 checks passed
Sign up for freeto 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

@tastelikefeet@writtingforfun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Fix some bugs, update backend api by tastelikefeet · Pull Request #17 · modelscope/ultron · GitHub
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

@tastelikefeettastelikefeet 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-assistgemini-code-assistBot 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 threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py
@tastelikefeettastelikefeet changed the title Fix watcher bugsFix some bugs, update backend apiJul 1, 2026
@tastelikefeet

Copy link
Copy Markdown
CollaboratorAuthor

/gemini review

@gemini-code-assistgemini-code-assistBot 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 threadultron/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
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifnotcontent: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, Union[bytes, None]],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifcontentisNone: # None = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"

Comment threadultron/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
iftracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass
iftracked_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 threadultron/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:mainJul 2, 2026
0 of 4 checks passed
Sign up for freeto 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

@tastelikefeet@writtingforfun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix some bugs, update backend api by tastelikefeet · Pull Request #17 · modelscope/ultron · GitHub
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

@tastelikefeettastelikefeet 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-assistgemini-code-assistBot 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 threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py
@tastelikefeettastelikefeet changed the title Fix watcher bugsFix some bugs, update backend apiJul 1, 2026
@tastelikefeet

Copy link
Copy Markdown
CollaboratorAuthor

/gemini review

@gemini-code-assistgemini-code-assistBot 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 threadultron/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
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifnotcontent: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, Union[bytes, None]],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifcontentisNone: # None = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"

Comment threadultron/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
iftracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass
iftracked_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 threadultron/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:mainJul 2, 2026
0 of 4 checks passed
Sign up for freeto 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

@tastelikefeet@writtingforfun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix some bugs, update backend api by tastelikefeet · Pull Request #17 · modelscope/ultron · GitHub
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

@tastelikefeettastelikefeet 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-assistgemini-code-assistBot 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 threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py
@tastelikefeettastelikefeet changed the title Fix watcher bugsFix some bugs, update backend apiJul 1, 2026
@tastelikefeet

Copy link
Copy Markdown
CollaboratorAuthor

/gemini review

@gemini-code-assistgemini-code-assistBot 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 threadultron/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
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifnotcontent: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, Union[bytes, None]],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifcontentisNone: # None = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"

Comment threadultron/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
iftracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass
iftracked_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 threadultron/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:mainJul 2, 2026
0 of 4 checks passed
Sign up for freeto 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

@tastelikefeet@writtingforfun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Fix some bugs, update backend api by tastelikefeet · Pull Request #17 · modelscope/ultron · GitHub
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

@tastelikefeettastelikefeet 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-assistgemini-code-assistBot 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 threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py
@tastelikefeettastelikefeet changed the title Fix watcher bugsFix some bugs, update backend apiJul 1, 2026
@tastelikefeet

Copy link
Copy Markdown
CollaboratorAuthor

/gemini review

@gemini-code-assistgemini-code-assistBot 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 threadultron/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
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifnotcontent: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, Union[bytes, None]],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifcontentisNone: # None = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"

Comment threadultron/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
iftracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass
iftracked_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 threadultron/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:mainJul 2, 2026
0 of 4 checks passed
Sign up for freeto 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

@tastelikefeet@writtingforfun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix some bugs, update backend api by tastelikefeet · Pull Request #17 · modelscope/ultron · GitHub
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

@tastelikefeettastelikefeet 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-assistgemini-code-assistBot 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 threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py
@tastelikefeettastelikefeet changed the title Fix watcher bugsFix some bugs, update backend apiJul 1, 2026
@tastelikefeet

Copy link
Copy Markdown
CollaboratorAuthor

/gemini review

@gemini-code-assistgemini-code-assistBot 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 threadultron/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
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifnotcontent: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, Union[bytes, None]],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifcontentisNone: # None = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"

Comment threadultron/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
iftracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass
iftracked_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 threadultron/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:mainJul 2, 2026
0 of 4 checks passed
Sign up for freeto 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

@tastelikefeet@writtingforfun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix some bugs, update backend api by tastelikefeet · Pull Request #17 · modelscope/ultron · GitHub
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

@tastelikefeettastelikefeet 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-assistgemini-code-assistBot 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 threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py
@tastelikefeettastelikefeet changed the title Fix watcher bugsFix some bugs, update backend apiJul 1, 2026
@tastelikefeet

Copy link
Copy Markdown
CollaboratorAuthor

/gemini review

@gemini-code-assistgemini-code-assistBot 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 threadultron/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
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifnotcontent: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, Union[bytes, None]],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifcontentisNone: # None = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"

Comment threadultron/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
iftracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass
iftracked_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 threadultron/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:mainJul 2, 2026
0 of 4 checks passed
Sign up for freeto 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

@tastelikefeet@writtingforfun
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Fix some bugs, update backend api by tastelikefeet · Pull Request #17 · modelscope/ultron · GitHub
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

@tastelikefeettastelikefeet 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-assistgemini-code-assistBot 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 threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py Outdated
Comment threadultron/cli/watcher.py
@tastelikefeettastelikefeet changed the title Fix watcher bugsFix some bugs, update backend apiJul 1, 2026
@tastelikefeet

Copy link
Copy Markdown
CollaboratorAuthor

/gemini review

@gemini-code-assistgemini-code-assistBot 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 threadultron/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
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, bytes],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifnotcontent: # empty bytes = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"
defpush_incremental(
client: "UltronClient",
username: str,
name: str,
changed: Dict[str, Union[bytes, None]],
remote_paths: set,
) ->None:
"""Incrementalpushviacommitinterface.
Buildscreate/update/deleteactionsandcommitsinonerequest.
Raisesonfailure (callershouldNOTupdatebaselineonexception).
"""
actions: List[dict] = []
forfpath, contentinchanged.items():
ifcontentisNone: # None = delete
actions.append({"action": "delete", "file_path": fpath})
else:
action_type="update"iffpathinremote_pathselse"create"

Comment threadultron/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
iftracked_pid:
try:
os.kill(tracked_pid, 0) # Check if still alive.
os.kill(tracked_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass
iftracked_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 threadultron/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:mainJul 2, 2026
0 of 4 checks passed
Sign up for freeto 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

@tastelikefeet@writtingforfun