Windows 工作电脑上,multica 通过自定义运行时,连接局域网内 Linux 中运行的 Devin-cli 智能体

一、背景

Devin CLI 是 Cognition 推出的命令行编码智能体,支持 ACP(Agent Client Protocol)协议,可被任何 ACP-aware 客户端作为子进程驱动,通过 stdin/stdout 交换 JSON-RPC 消息。

multica 是一个多智能体运行时管理器,能在同一台机器上统一调度多种编码智能体(Claude、Codex、Copilot、Hermes 等)。它在 Windows 上以 daemon 形式常驻,启动时会扫描 PATH,确认至少存在一个它认识的 agent CLI,然后按 profile 配置 exec 对应的可执行文件。

实际工作中常见这样一种拓扑:

  • 工作机是 Windows,multica 装在这里,方便用 Web UI 统一管理。
  • Devin CLI 装在局域网内一台 Linux 上(开发环境、算力、凭证都在那边),不想在 Windows 上再装一份。

目标是让 Windows 上的 multica 把 Linux 上的 Devin 当成一个本地运行时来调度。

二、原理

核心难点是:multica daemon 只会用 exec.Command 在本机启动可执行文件,不会跨机调用;而 Devin CLI 在另一台 Linux 上。解决办法是利用 ACP 协议”JSON-RPC over stdio”的特性——stdio 可以被 SSH 透明转发,于是跨机调用在协议层等价于本地调用。

整条链路如下:

1
2
Windows: multica daemon ──exec──> hermes.cmd ──ssh──> Linux: devin acp
(按白名单扫PATH) (wrapper转发stdio) (强制命令,JSON-RPC over stdio)

分三层解决三个问题:

1. 协议层:ACP over stdio 天然可转发

devin acp 子命令启动一个 ACP server,通过 stdin/stdout 收发 JSON-RPC 消息,不依赖任何 socket、端口、PTY。SSH 的默认行为就是把远端进程的 stdio 接到本地,所以 ssh user@host devin acp 在协议上和直接 devin acp 完全等价。multica 只看到本地一个子进程的 stdio,无需任何改造。

2. 安全层:SSH 强制命令限制密钥能力

daemon 是无人值守后台进程,不能用交互式密码登录,必须用密钥。但这把密钥如果泄露,攻击者就能 SSH 进 Linux 执行任意命令,风险不可接受。解决办法是在 Linux 的 ~/.ssh/authorized_keys 里给这把公钥加 command="..." 强制命令:

1
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/home/guotao/.local/bin/devin acp" ssh-ed25519 AAAA... devin-multica-bridge

这样无论 SSH 客户端在命令行写什么,远端都只执行 devin acp,其它一律忽略。配合 no-pty,no-port-forwarding,... 等限制,这把钥匙被锁死成”只能启动 Devin ACP server”的单功能钥匙,泄露风险可控。

3. 发现层:借白名单名字过 daemon 预检

multica daemon 启动时有一个硬编码白名单预检:claude, codebuddy, codex, copilot, opencode, deveco, openclaw, hermes, pi, cursor-agent, kimi, kiro-cli, agy, qodercli, traecli, grok。PATH 上一个都不认识就直接退出,profile 配置再正确也救不回来。devin / devin-acp 不在白名单里。

解决办法是借 hermes 这个名字——它本身是个通用协议族名,不是某个具体产品,适合作为自定义运行时的载体。把 wrapper 文件命名为 hermes.cmd,profile 的 --command-name 也用 hermes,daemon 预检就能找到、放行。wrapper 内部内容不变,仍然是 SSH 转发到 devin acp

三、完整步骤

Linux 侧(一次性准备)

1. 确认 sshd 运行且 devin 可用

1
2
3
4
systemctl status sshd | head -5
ss -tlnp | grep ':22\b'
which devin # 期望: /home/guotao/.local/bin/devin
timeout 5 devin acp < /dev/null # 期望: 看到 ACP server 启动日志后退出

2. 生成专用密钥对(无 passphrase)

1
ssh-keygen -t ed25519 -f /tmp/devin-multica -N "" -C "devin-multica-bridge" -q

3. 装入 authorized_keys,带强制命令与限制

1
2
3
4
5
6
7
8
9
10
11
mkdir -p ~/.ssh && chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys

PUB=$(cat /tmp/devin-multica.pub)
LINE="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=\"/home/guotao/.local/bin/devin acp\" ${PUB}"

# 去重:若已存在同注释的行先删掉
grep -v "devin-multica-bridge$" ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp || true
mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys
echo "$LINE" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

4. 修复家目录权限(sshd StrictModes 要求)

sshd 默认开启 StrictModes,家目录若对 group/other 可写(如 777),会拒绝使用 authorized_keys。收窄到 755 即可,无需 sudo:

1
2
chmod g-w,o-w ~
ls -ld ~ # 期望: drwxr-xr-x

5. 验证强制命令生效

1
2
# 用这把钥匙发任意危险命令,应被强制改成 devin acp
timeout 4 ssh -i /tmp/devin-multica -o StrictHostKeyChecking=no -o BatchMode=yes localhost "cat /etc/shadow; rm -rf /" < /dev/null 2>&1 | head -5

期望输出是 Devin ACP server 的启动日志(Starting ACP server),而不是 shadow 文件内容——说明强制命令把任意命令都拦截了。

6. 记录连接信息

  • 主机:局域网 IP(如 192.168.9.101,优先)/ 公网 IP(如 113.98.232.61
  • 用户:guotao
  • 端口:22
  • 私钥文件:/tmp/devin-multica(内容要传到 Windows)

Windows 侧(PowerShell,每步可复现)

第 1 步:保存私钥

1
2
3
4
5
6
7
8
$key = @'
-----BEGIN OPENSSH PRIVATE KEY-----
(粘贴 Linux 上 /tmp/devin-multica 的完整内容)
-----END OPENSSH PRIVATE KEY-----
'@
$dir = "$env:USERPROFILE\.ssh"
New-Item -ItemType Directory -Force -Path $dir | Out-Null
$key | Set-Content -Path "$dir\devin-multica" -Encoding ascii -NoNewline

第 2 步:锁紧私钥权限

Windows 自带 ssh.exe 和 Linux 一样严格,私钥若能被其他用户读会拒绝使用:

1
2
3
4
$k = "$env:USERPROFILE\.ssh\devin-multica"
icacls $k /inheritance:r
icacls $k /grant:r "$($env:USERNAME):(R)"
icacls $k # 确认只剩你自己一条

第 3 步:建 wrapper 目录并加入 PATH

1
2
3
4
5
6
7
8
9
10
$dir = "$env:USERPROFILE\.local\bin"
New-Item -ItemType Directory -Force -Path $dir | Out-Null

# 永久加入用户 PATH
$userPath = [Environment]::GetEnvironmentVariable("Path","User")
if ($userPath -notlike "*$dir*") {
[Environment]::SetEnvironmentVariable("Path", "$userPath;$dir", "User")
}
# 当前会话立即生效
$env:Path += ";$dir"

第 4 步:创建 hermes.cmd wrapper

关键:文件名必须是 hermes(在 multica 白名单里),不是 devin-acp IP 用你实测能连通的那个,局域网优先。

1
2
3
4
5
6
7
$linuxHost = "192.168.9.101"   # 不通就换公网 IP
@"
@echo off
ssh -i "%USERPROFILE%\.ssh\devin-multica" -o StrictHostKeyChecking=accept-new -o BatchMode=yes -o ConnectTimeout=10 guotao@$linuxHost
"@ | Set-Content -Path "$dir\hermes.cmd" -Encoding ascii

Get-Content "$dir\hermes.cmd" # 验证内容

要点:

  • wrapper 里不带任何远端命令参数——Linux 侧的强制命令会自动把它变成 devin acp,无论 SSH 客户端发什么。
  • -o BatchMode=yes 保证 daemon 无人值守时不会因任何交互提示卡死。
  • -o ConnectTimeout=10 防止网络问题时 daemon 永久挂起。

第 5 步:测试 wrapper 端到端

1
& "$env:USERPROFILE\.local\bin\hermes.cmd"

期望输出:

1
2
3
4
PTY allocation request failed on channel 0
... INFO chisel: version=... binary=devin startup
... INFO chisel_server::acp: Starting ACP server
... INFO chisel_server::acp: ACP server PID: ...

然后挂起等输入,Ctrl+C 退出。看到这个就说明 SSH + 强制命令 + ACP over stdio 全链路通了。

常见问题:

  • Permission denied (publickey) → 第 2 步权限没锁紧,重做 icacls
  • Connection timed out → 换 IP 重写 wrapper
  • UNPROTECTED PRIVATE KEY FILE → 同上,权限问题

第 6 步:把 $multica 永久化(可选但推荐)

1
2
3
4
if (-not (Test-Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force | Out-Null }
Add-Content -Path $PROFILE -Value '$multica = "C:\Users\Administrator\AppData\Local\Programs\@multicadesktop\resources\app.asar.unpacked\resources\bin\multica.exe"'
. $PROFILE
$multica # 验证

第 7 步:创建 multica runtime profile

关键:--command-name hermes,不是 devin-acp daemon 预检按白名单扫 PATH,hermes 在白名单里,devin-acp 不在。

1
2
3
4
5
& $multica runtime profile create `
--display-name "Devin" `
--protocol-family hermes `
--command-name hermes `
--description "Devin via SSH+ACP"

记下返回的 profile-id(下面用 <ID> 代替)。

第 8 步:钉死绝对路径(绕开 daemon PATH 解析)

daemon 被桌面端拉起时继承的 PATH 比交互式 PowerShell 窄,用 set-path 给绝对路径最稳。单行写,不要用反引号续行(反引号后不能有任何字符,否则续行失效):

1
& $multica runtime profile set-path <ID> --path "C:\Users\Administrator\.local\bin\hermes.cmd"

第 9 步:重启 daemon 并验证

1
2
& $multica daemon restart
& $multica daemon status

daemon status 应显示 running,agents 列表里出现 hermes/Devin。

第 10 步:Web UI 确认

打开 Multica Web UI → Runtimes,应看到 Devin 自定义运行时显示「在线」。

四、踩过的两个坑

坑 1:PowerShell 反引号续行

反引号 ` 后面不能有任何字符(包括空格),否则续行不生效,命令被拆成两半,第二行被当成新语句报 Missing expression after unary operator '--'。长命令一律单行写,或用括号续行。

坑 2:command-name 必须是白名单里的名字

daemon 启动预检是硬编码白名单扫描,devin-acp 不在名单里,无论怎么 set-path 都过不了预检,daemon 直接退出 1。借 hermes 这个通用协议族名字,wrapper 内容不变,就能过预检。这是整个方案能成立的关键一招。

五、验证清单

命令 预期
私钥权限 icacls "$env:USERPROFILE\.ssh\devin-multica" 只有你自己一条
wrapper 能跑 & "$env:USERPROFILE\.local\bin\hermes.cmd" ACP server 启动日志
profile 已注册 & $multica runtime profile list COMMAND_NAME=hermes
daemon 运行 & $multica daemon status running
Web UI Multica → Runtimes Devin 显示在线

六、安全说明

  • 专用密钥对独立于个人 SSH 密钥,泄露影响面只限于”启动 Devin ACP server”。
  • 强制命令 + no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding 把这把钥匙锁成单功能钥匙,无法用于其它任何操作。
  • 私钥在 Windows 上用 icacls 锁到仅本人可读,避免被同机其它用户盗用。
  • 通信走 SSH 加密通道,ACP 协议内容(含 Devin 凭证)不裸传。
  • 如需进一步收紧,可在 Linux 侧用 iptables/ufw 限制 22 端口来源 IP 为 Windows 工作机的固定地址。
分享到:

评论完整模式加载中...如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理