@super-coding/cli 0.0.0-stage → 0.1.0
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/README.md +70 -2
- package/dist/cli.js +35 -0
- package/dist/daemon/adapters/base.js +37 -0
- package/dist/daemon/adapters/claude-code.js +184 -0
- package/dist/daemon/adapters/cursor.js +138 -0
- package/dist/daemon/adapters/features.js +51 -0
- package/dist/daemon/adapters/registry.js +9 -0
- package/dist/daemon/agent/builtin/agent.md +164 -0
- package/dist/daemon/agent/builtin/load.js +121 -0
- package/dist/daemon/agent/builtin/parse.js +73 -0
- package/dist/daemon/agent/index.js +60 -0
- package/dist/daemon/agent/manifest.js +72 -0
- package/dist/daemon/agent/materialize.js +220 -0
- package/dist/daemon/agent/renderers/claude-code.js +16 -0
- package/dist/daemon/agent/renderers/common.js +61 -0
- package/dist/daemon/agent/renderers/cursor.js +13 -0
- package/dist/daemon/agent/renderers/generic.js +5 -0
- package/dist/daemon/agent/resolve.js +212 -0
- package/dist/daemon/agent/slug.js +27 -0
- package/dist/daemon/agent/types.js +8 -0
- package/dist/daemon/device-store.js +61 -0
- package/dist/daemon/index.js +280 -0
- package/dist/daemon/manage.js +183 -0
- package/dist/daemon/orchestrator.js +364 -0
- package/dist/daemon/pairing.js +93 -0
- package/dist/daemon/paths.js +47 -0
- package/dist/daemon/rpc.js +41 -0
- package/dist/daemon/run-skills.js +148 -0
- package/dist/daemon/services/agent-config.js +1 -0
- package/dist/daemon/services/claude-provider.js +126 -0
- package/dist/daemon/services/cursor-provider.js +94 -0
- package/dist/daemon/services/json-file.js +21 -0
- package/dist/daemon/services/log-buffer.js +43 -0
- package/dist/daemon/services/mcp-secrets.js +36 -0
- package/dist/daemon/session/store.js +137 -0
- package/dist/daemon/transport/ws-client.js +121 -0
- package/package.json +44 -3
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
/**
|
|
3
|
+
* daemon 侧 WS transport:主动连出 remote-server。
|
|
4
|
+
* - 连接成功后自动发送 hello;
|
|
5
|
+
* - 收到 ping 自动回 pong,其余消息交给 onMessage;
|
|
6
|
+
* - 断线后指数退避重连(1s 起步,上限 30s);
|
|
7
|
+
* - 断线期间 send 静默丢弃——事件可靠性由本地 SQLite 落盘 + 重连后 resume 补发保证。
|
|
8
|
+
*
|
|
9
|
+
* 退避重置时机很关键:以前「连接一 open 就清零 attempts」,而服务端可能在鉴权通过前
|
|
10
|
+
* 就 close(4402 auth_unavailable / 4408 hello timeout),于是每次都是 1s 重试,
|
|
11
|
+
* 退避永远涨不上去 → webhook 抖动期变成重连风暴。现在改为:
|
|
12
|
+
* 「连接存活 ≥ 10s」或「收到任意服务端消息(说明鉴权已过)」才清零。
|
|
13
|
+
*/
|
|
14
|
+
const STABLE_CONNECTION_MS = 10_000;
|
|
15
|
+
export class DaemonWsClient {
|
|
16
|
+
opts;
|
|
17
|
+
ws;
|
|
18
|
+
attempts = 0;
|
|
19
|
+
closed = false;
|
|
20
|
+
reconnectTimer;
|
|
21
|
+
stableTimer;
|
|
22
|
+
constructor(opts) {
|
|
23
|
+
this.opts = opts;
|
|
24
|
+
}
|
|
25
|
+
connect() {
|
|
26
|
+
// 单连接不变式:已有一条在连/已连的 socket 时不再新建。
|
|
27
|
+
// 否则「旧 socket 的 close 晚于新 socket 建立」会让重连锁死:
|
|
28
|
+
// 每建一条,服务端 register 就把上一条踢掉,被踢的又触发重连 → 互相踢成风暴。
|
|
29
|
+
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (this.reconnectTimer) {
|
|
33
|
+
clearTimeout(this.reconnectTimer);
|
|
34
|
+
this.reconnectTimer = undefined;
|
|
35
|
+
}
|
|
36
|
+
const WsImpl = this.opts.wsImpl ?? WebSocket;
|
|
37
|
+
const ws = new WsImpl(this.opts.url);
|
|
38
|
+
this.ws = ws;
|
|
39
|
+
ws.on("open", () => {
|
|
40
|
+
if (ws !== this.ws)
|
|
41
|
+
return; // 已被取代
|
|
42
|
+
// 不再在这里清零:能连上 ≠ 能通过鉴权
|
|
43
|
+
this.send({
|
|
44
|
+
type: "hello",
|
|
45
|
+
device_token: this.opts.device_token,
|
|
46
|
+
daemon_version: this.opts.daemon_version,
|
|
47
|
+
agents: this.opts.agents,
|
|
48
|
+
...(this.opts.features?.length ? { features: this.opts.features } : {}),
|
|
49
|
+
});
|
|
50
|
+
if (this.stableTimer)
|
|
51
|
+
clearTimeout(this.stableTimer);
|
|
52
|
+
this.stableTimer = setTimeout(() => {
|
|
53
|
+
this.attempts = 0;
|
|
54
|
+
}, STABLE_CONNECTION_MS);
|
|
55
|
+
this.opts.onOpen?.();
|
|
56
|
+
});
|
|
57
|
+
ws.on("message", (data) => {
|
|
58
|
+
if (ws !== this.ws)
|
|
59
|
+
return; // 旧连接残留消息
|
|
60
|
+
let msg;
|
|
61
|
+
try {
|
|
62
|
+
msg = JSON.parse(String(data));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return; // 无法解析的消息直接忽略
|
|
66
|
+
}
|
|
67
|
+
// 服务端只会对已鉴权连接下发消息(首个通常是 resume)→ 收到即可认为链路可用
|
|
68
|
+
this.attempts = 0;
|
|
69
|
+
if (msg.type === "ping") {
|
|
70
|
+
this.send({ type: "pong", ts: msg.ts });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.opts.onMessage(msg);
|
|
74
|
+
});
|
|
75
|
+
const scheduleReconnect = () => {
|
|
76
|
+
if (this.stableTimer) {
|
|
77
|
+
clearTimeout(this.stableTimer);
|
|
78
|
+
this.stableTimer = undefined;
|
|
79
|
+
}
|
|
80
|
+
if (this.closed)
|
|
81
|
+
return;
|
|
82
|
+
if (this.reconnectTimer)
|
|
83
|
+
return; // 已排重连,避免多个 close 各排一次
|
|
84
|
+
const base = this.opts.reconnectBaseMs ?? 1_000;
|
|
85
|
+
const max = this.opts.reconnectMaxMs ?? 30_000;
|
|
86
|
+
// 加 0~30% 抖动,避免多台设备同一时刻一起重连(惊群)
|
|
87
|
+
const delay = Math.min(max, base * 2 ** this.attempts++) * (1 + Math.random() * 0.3);
|
|
88
|
+
this.reconnectTimer = setTimeout(() => {
|
|
89
|
+
this.reconnectTimer = undefined;
|
|
90
|
+
if (!this.closed)
|
|
91
|
+
this.connect();
|
|
92
|
+
}, Math.round(delay));
|
|
93
|
+
};
|
|
94
|
+
ws.on("close", (code, reason) => {
|
|
95
|
+
// 关键:只有「当前 socket」的关闭才代表链路断了。
|
|
96
|
+
// 旧 socket 的 close 晚到(服务端踢旧连接)时若也去重连,就会形成互踢风暴。
|
|
97
|
+
if (ws !== this.ws)
|
|
98
|
+
return;
|
|
99
|
+
this.ws = undefined;
|
|
100
|
+
this.opts.onClose?.(code, reason.toString());
|
|
101
|
+
scheduleReconnect();
|
|
102
|
+
});
|
|
103
|
+
ws.on("error", () => {
|
|
104
|
+
// error 之后必跟随 close,重连统一在 close 里调度
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** 断线期间静默丢弃(不抛错、不缓冲) */
|
|
108
|
+
send(msg) {
|
|
109
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
110
|
+
this.ws.send(JSON.stringify(msg));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
close() {
|
|
114
|
+
this.closed = true;
|
|
115
|
+
if (this.reconnectTimer)
|
|
116
|
+
clearTimeout(this.reconnectTimer);
|
|
117
|
+
if (this.stableTimer)
|
|
118
|
+
clearTimeout(this.stableTimer);
|
|
119
|
+
this.ws?.close();
|
|
120
|
+
}
|
|
121
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@super-coding/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Super Coding CLI:把本机 Claude Code / Cursor 接入远程控制台(scode 命令与后台 daemon)",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"scode": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/cli.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=24"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"super-coding",
|
|
22
|
+
"agent",
|
|
23
|
+
"daemon",
|
|
24
|
+
"claude-code",
|
|
25
|
+
"cursor",
|
|
26
|
+
"remote"
|
|
27
|
+
],
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^24.10.1",
|
|
30
|
+
"@types/ws": "^8.18.1",
|
|
31
|
+
"tsx": "^4.21.0",
|
|
32
|
+
"typescript": "^5.9.3",
|
|
33
|
+
"vitest": "^3.2.7"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"commander": "^14.0.2",
|
|
37
|
+
"ws": "^8.21.3",
|
|
38
|
+
"@super-coding/protocol": "0.1.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"dev": "tsx watch src/cli.ts",
|
|
42
|
+
"build": "pnpm --filter @super-coding/protocol build && rm -rf dist && tsc -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
43
|
+
"start": "node dist/cli.js daemon run",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
}
|
|
6
47
|
}
|