@chenacc/agentbridge 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -0
- package/README.md +33 -5
- package/package.json +4 -3
- package/src/cli.js +5 -1
- package/src/config.js +41 -5
- package/src/server.js +11 -0
package/.env.example
CHANGED
|
@@ -63,3 +63,8 @@ AGENTBRIDGE_PROJECTS=agentbridge=D:\codex_project\手机远程操控agent
|
|
|
63
63
|
# AGENTBRIDGE_NOTIFY_TOKEN=your-provider-token
|
|
64
64
|
# AGENTBRIDGE_NOTIFY_CHANNEL=clawbot
|
|
65
65
|
# AGENTBRIDGE_NOTIFY_LINK=1
|
|
66
|
+
|
|
67
|
+
# The bridge reads notify.json from its data directory. If that directory moved
|
|
68
|
+
# (the DSH plugin gives the bridge its own state folder), an older folder can be
|
|
69
|
+
# adopted once instead of re-entering the token by hand:
|
|
70
|
+
# AGENTBRIDGE_NOTIFY_IMPORT=D:\codex_project\agentbridge\.agentbridge
|
package/README.md
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
# AgentBridge
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@chenacc/agentbridge)
|
|
4
|
+
[](https://www.npmjs.com/package/@chenacc/agentbridge)
|
|
5
|
+
[](https://www.npmjs.com/package/@chenacc/dsh-agentbridge)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://github.com/chenacc1/agentbridge/stargazers)
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
**一条命令,把手机变成这台电脑上 Agent 的遥控器。** 微信扫码即用,不用装 App,也不用和电脑连同一个 Wi-Fi。
|
|
10
|
+
|
|
11
|
+
<img src="./docs/assets/hero-phone.jpg" alt="手机端控制台:语音/文字发指令,Agent 流式回复,与桌面同一份对话" width="240" align="right" />
|
|
12
|
+
|
|
13
|
+
在电脑上跑着的 Claude Code / Codex / DeepSeek Harness / DSH Desktop 会话,执行 `/phone` 会出现一次性二维码:
|
|
14
|
+
|
|
15
|
+
- 手机扫码后**接管的就是屏幕上那一条对话**,不是新建一个"类似的"会话;
|
|
16
|
+
- 消息、工具调用、审批卡片、模型切换、停止任务,两端看到的是同一份事件流;
|
|
17
|
+
- 电脑始终是唯一执行端,凭据和文件访问不出本机。
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
npx @chenacc/agentbridge # 起桥并直接打印二维码
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
然后在 Claude Code / Codex / DSH Desktop 里输入 `/phone`,扫二维码即可。
|
|
24
|
+
|
|
25
|
+
> 觉得有用就给个 star:[github.com/chenacc1/agentbridge](https://github.com/chenacc1/agentbridge) —— 不点也不影响任何功能,只影响我有没有动力继续写。
|
|
26
|
+
|
|
27
|
+

|
|
8
28
|
|
|
9
29
|
手机与桌面共享同一份可追加事件流,因此消息、工具活动、审批和任务状态始终同步。电脑是唯一的执行端:手机只运行界面,不运行 Agent,也拿不到任何供应商凭据。
|
|
10
30
|
|
|
11
|
-
|
|
31
|
+
默认走 Cloudflare 隧道:手机用流量、在外地都能打开;隧道对外开放,因此自动带一道访问密钥门禁。
|
|
32
|
+
|
|
33
|
+
**当前版本:** 桥 `@chenacc/agentbridge` **0.2.1** · DSH 插件 `@chenacc/dsh-agentbridge` **0.4.1**(两个包各自发布,见文末「版本与发布」)。
|
|
12
34
|
|
|
13
35
|
## 它是怎么工作的
|
|
14
36
|
|
|
37
|
+

|
|
38
|
+
|
|
15
39
|

|
|
16
40
|
|
|
17
41
|
1. **手机** 只负责界面与指令:扫码进入 H5 控制台,选工作区、选会话,发出消息、审批与停止命令。
|
|
@@ -34,6 +58,7 @@
|
|
|
34
58
|
|
|
35
59
|
> 本文中标注为「分镜关键帧」的图片是讲解视频的概念示意图,用于说明结构关系;真实界面见下文「界面」一节。
|
|
36
60
|
|
|
61
|
+
|
|
37
62
|
## 主要能力
|
|
38
63
|
|
|
39
64
|
- **扫码配对**:一次性、5 分钟有效的二维码;配对后收藏固定回访地址,之后免扫码进入。
|
|
@@ -361,6 +386,9 @@ agentbridge phone --agent deepseek-harness
|
|
|
361
386
|
| `AGENTBRIDGE_SESSION_TTL_MS` | `259200000` | 设备会话有效期(72 小时) |
|
|
362
387
|
| `AGENTBRIDGE_CLAUDE_MODEL` | 供应商默认 | 可选的 Claude / Kimi 模型 ID |
|
|
363
388
|
| `AGENTBRIDGE_NOTIFY_WEBHOOK` | 未设置 | 通知 Webhook;未设置时该功能静默 |
|
|
389
|
+
| `AGENTBRIDGE_NOTIFY_IMPORT` | 未设置 | 通知设置存在旧数据目录时,指向它一次即可自动迁过来 |
|
|
390
|
+
|
|
391
|
+
> 通知设置(webhook / token / 静默开关)保存在**桥的数据目录**下的 `notify.json`,默认是 `%USERPROFILE%\.agentbridge`。DSH 插件接管网关后数据目录换过位置,老配置如果留在原来的 `.agentbridge` 里,用 `AGENTBRIDGE_NOTIFY_IMPORT=<旧目录>` 启动一次就会自动迁过去;也可以直接在桌面控制台的「通知」面板重填一次。桥启动日志里有一行 `Notifications: pushplus/clawbot` 或 `Notifications: off — no webhook configured in …`,能直接看出它到底读到了哪个文件。
|
|
364
392
|
|
|
365
393
|
**跨网络(CLI 与插件都认):**
|
|
366
394
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chenacc/agentbridge",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Control local coding agents from a phone through a secure WeChat QR web console.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -65,8 +65,9 @@
|
|
|
65
65
|
"verify:responsive": "node scripts/verify-responsive.mjs",
|
|
66
66
|
"verify:guide": "node scripts/verify-product-guide.mjs",
|
|
67
67
|
"verify:release": "node scripts/verify-plugin-release.mjs",
|
|
68
|
-
"check": "node --check src/server.js && node --check src/cli.js && node --check src/config.js && node --check src/cc-switch-models.js && node --check src/workspaces/workspace-catalog.js && node --check src/conversations/conversation-catalog.js && node --check src/adapters/capabilities.js && node --check src/adapters/claude.js && node --check src/adapters/codex.js && node --check src/adapters/deepseek-harness.js && node --check src/adapters/dsh-desktop.js && node --check src/dsh-desktop-channels.js && node --check src/event-store.js && node --check src/notifier.js && node --check src/attachments.js && node --check src/session-manager.js && node --check integrations/dsh-phone/lib/index.js && node --check src/tunnel.js && node --check src/remote.js && node --check src/adapters/zcode-native.js && node --check public/adapter-capabilities.js && node --check public/workspace-navigation.js && node --check public/markdown.js && node --check public/highlight.js && node --check public/transcript.js && node --check public/app.js && node --check scripts/verify-workspace-ui.mjs && node --check scripts/verify-message-blocks.mjs && node --check scripts/verify-background-scrim.mjs && node --check scripts/verify-model-control.mjs && node --check scripts/verify-notifications.mjs && node --check scripts/verify-attachments.mjs && node --check scripts/verify-features.mjs && node --check scripts/verify-quick-nav.mjs && node --check scripts/verify-questions.mjs && node --check scripts/verify-responsive.mjs && node --check scripts/verify-product-guide.mjs && node --check scripts/verify-plugin-release.mjs",
|
|
69
|
-
"verify:remote": "node scripts/verify-remote-tunnel.mjs"
|
|
68
|
+
"check": "node --check src/server.js && node --check src/cli.js && node --check src/config.js && node --check src/cc-switch-models.js && node --check src/workspaces/workspace-catalog.js && node --check src/conversations/conversation-catalog.js && node --check src/adapters/capabilities.js && node --check src/adapters/claude.js && node --check src/adapters/codex.js && node --check src/adapters/deepseek-harness.js && node --check src/adapters/dsh-desktop.js && node --check src/dsh-desktop-channels.js && node --check src/event-store.js && node --check src/notifier.js && node --check src/attachments.js && node --check src/session-manager.js && node --check integrations/dsh-phone/lib/index.js && node --check src/tunnel.js && node --check src/remote.js && node --check src/adapters/zcode-native.js && node --check public/adapter-capabilities.js && node --check public/workspace-navigation.js && node --check public/markdown.js && node --check public/highlight.js && node --check public/transcript.js && node --check public/app.js && node --check scripts/verify-workspace-ui.mjs && node --check scripts/verify-message-blocks.mjs && node --check scripts/verify-background-scrim.mjs && node --check scripts/verify-model-control.mjs && node --check scripts/verify-notifications.mjs && node --check scripts/verify-attachments.mjs && node --check scripts/verify-features.mjs && node --check scripts/verify-quick-nav.mjs && node --check scripts/verify-questions.mjs && node --check scripts/verify-responsive.mjs && node --check scripts/verify-product-guide.mjs && node --check scripts/verify-plugin-release.mjs && node --check scripts/capture-screenshots.mjs",
|
|
69
|
+
"verify:remote": "node scripts/verify-remote-tunnel.mjs",
|
|
70
|
+
"capture:shots": "node scripts/capture-screenshots.mjs"
|
|
70
71
|
},
|
|
71
72
|
"dependencies": {
|
|
72
73
|
"katex": "0.19.1",
|
package/src/cli.js
CHANGED
|
@@ -249,7 +249,11 @@ async function requestPhone(options) {
|
|
|
249
249
|
console.log(`二维码图片 Markdown: `);
|
|
250
250
|
}
|
|
251
251
|
console.log(`微信扫码地址: ${result.pairing.url}`);
|
|
252
|
-
console.log(`电脑端会话: ${origin}/?session=${encodeURIComponent(result.session.id)}
|
|
252
|
+
console.log(`电脑端会话: ${origin}/?session=${encodeURIComponent(result.session.id)}`);
|
|
253
|
+
// Consent-based and once, at the very end: no network call, no token, nothing
|
|
254
|
+
// the user did not ask for. Star counts are how the plugin market ranks entries,
|
|
255
|
+
// so asking is fair — clicking on their behalf would not be.
|
|
256
|
+
console.log("觉得有用?给项目点个星:https://github.com/chenacc1/agentbridge\n");
|
|
253
257
|
return result;
|
|
254
258
|
}
|
|
255
259
|
|
package/src/config.js
CHANGED
|
@@ -105,16 +105,52 @@ function parseProjects(raw, workspace) {
|
|
|
105
105
|
* end up in a tracked file. The bridge's own data directory is git-ignored, so a
|
|
106
106
|
* small JSON file there is the honest place for it; environment variables still win
|
|
107
107
|
* when both are present, which keeps `setx` and one-off experiments working.
|
|
108
|
+
*
|
|
109
|
+
* `legacyFiles` exists because the data directory can move under the user's feet:
|
|
110
|
+
* the DSH plugin started hosting the bridge and gave it its own state directory,
|
|
111
|
+
* which silently left notifications configured in the old one. When the current
|
|
112
|
+
* directory has no settings but an old one does, adopt them once and say so — the
|
|
113
|
+
* alternative is a feature that stops working with no explanation.
|
|
108
114
|
*/
|
|
109
|
-
function readNotifyFile(dataDir) {
|
|
115
|
+
function readNotifyFile(dataDir, legacyFiles = []) {
|
|
116
|
+
const own = path.join(dataDir, "notify.json");
|
|
117
|
+
const parsed = readJsonObject(own);
|
|
118
|
+
if (parsed) return parsed;
|
|
119
|
+
|
|
120
|
+
for (const candidate of legacyFiles) {
|
|
121
|
+
const adopted = readJsonObject(candidate);
|
|
122
|
+
if (!adopted) continue;
|
|
123
|
+
try {
|
|
124
|
+
mkdirSync(dataDir, { recursive: true });
|
|
125
|
+
writeFileSync(own, JSON.stringify(adopted, null, 2), "utf8");
|
|
126
|
+
console.error(`Notifications: adopted settings from ${candidate} into ${own}`);
|
|
127
|
+
} catch {
|
|
128
|
+
// A read-only data directory still gets to use the settings for this run.
|
|
129
|
+
}
|
|
130
|
+
return adopted;
|
|
131
|
+
}
|
|
132
|
+
return {};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readJsonObject(file) {
|
|
110
136
|
try {
|
|
111
|
-
const parsed = JSON.parse(readFileSync(
|
|
112
|
-
return parsed && typeof parsed === "object" ? parsed :
|
|
137
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
138
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
113
139
|
} catch {
|
|
114
|
-
return
|
|
140
|
+
return null;
|
|
115
141
|
}
|
|
116
142
|
}
|
|
117
143
|
|
|
144
|
+
/** Candidate files a moved data directory may have left behind. */
|
|
145
|
+
export function legacyNotifyPaths(env = process.env, workspace = process.cwd()) {
|
|
146
|
+
const fromEnv = String(env.AGENTBRIDGE_NOTIFY_IMPORT ?? "")
|
|
147
|
+
.split(";")
|
|
148
|
+
.map((entry) => entry.trim())
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.map((entry) => (/notify\.json$/i.test(entry) ? path.resolve(entry) : path.join(path.resolve(entry), "notify.json")));
|
|
151
|
+
return [...fromEnv, path.join(path.resolve(workspace), ".agentbridge", "notify.json")];
|
|
152
|
+
}
|
|
153
|
+
|
|
118
154
|
function pick(env, key, fallback) {
|
|
119
155
|
const value = String(env[key] ?? "").trim();
|
|
120
156
|
return value || fallback || null;
|
|
@@ -153,7 +189,7 @@ export function loadConfig(env = process.env, workspace = process.cwd()) {
|
|
|
153
189
|
const advertisedOrigin = publicOrigin || `http://${firstLanAddress()}:${port}`;
|
|
154
190
|
const accessKey = (env.AGENTBRIDGE_ACCESS_KEY ?? "").trim() || null;
|
|
155
191
|
const dataDir = path.resolve(env.AGENTBRIDGE_DATA_DIR ?? path.join(workspace, ".agentbridge"));
|
|
156
|
-
const notifyFile = readNotifyFile(dataDir);
|
|
192
|
+
const notifyFile = readNotifyFile(dataDir, legacyNotifyPaths(env, workspace));
|
|
157
193
|
|
|
158
194
|
return {
|
|
159
195
|
host,
|
package/src/server.js
CHANGED
|
@@ -994,6 +994,17 @@ export async function startBridge() {
|
|
|
994
994
|
console.log(`Remote path: ${bridge.config.remoteProvider} → ${bridge.config.advertisedOrigin}`);
|
|
995
995
|
}
|
|
996
996
|
console.log(`WeChat scan target: ${bridge.getCurrentPairing().url}`);
|
|
997
|
+
// Notifications are silent by design, which once hid the fact that the config
|
|
998
|
+
// was being read from a directory that did not have it (the data directory moved
|
|
999
|
+
// when the DSH plugin started hosting the bridge). Say where it looked.
|
|
1000
|
+
if (bridge.notifier.enabled) {
|
|
1001
|
+
console.log(`Notifications: ${bridge.notifier.format}${bridge.notifier.channel ? `/${bridge.notifier.channel}` : ""}${bridge.notifier.muted ? " (muted)" : ""}`);
|
|
1002
|
+
} else {
|
|
1003
|
+
console.log(
|
|
1004
|
+
`Notifications: off — no webhook configured in ${path.join(bridge.config.dataDir, "notify.json")} `
|
|
1005
|
+
+ "(set it in the desktop console, or point AGENTBRIDGE_NOTIFY_IMPORT at an older .agentbridge folder)",
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
997
1008
|
// Silence here used to mean a QR encoding an address the phone could never reach.
|
|
998
1009
|
if (new URL(bridge.config.advertisedOrigin).hostname === "127.0.0.1") {
|
|
999
1010
|
console.warn(
|