@xiaoqiong0v0/opencode-ssh-tool 0.5.1 → 0.6.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.
Files changed (61) hide show
  1. package/README.md +27 -21
  2. package/dist/agent.d.ts +52 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +250 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/base-session.d.ts +79 -13
  7. package/dist/base-session.d.ts.map +1 -1
  8. package/dist/base-session.js +246 -100
  9. package/dist/base-session.js.map +1 -1
  10. package/dist/config.d.ts +6 -2
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +16 -7
  13. package/dist/config.js.map +1 -1
  14. package/dist/constants.d.ts +14 -15
  15. package/dist/constants.d.ts.map +1 -1
  16. package/dist/constants.js +14 -15
  17. package/dist/constants.js.map +1 -1
  18. package/dist/i18n.d.ts +24 -14
  19. package/dist/i18n.d.ts.map +1 -1
  20. package/dist/i18n.js +37 -12
  21. package/dist/i18n.js.map +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +90 -73
  24. package/dist/index.js.map +1 -1
  25. package/dist/last-match.d.ts +11 -0
  26. package/dist/last-match.d.ts.map +1 -1
  27. package/dist/last-match.js +24 -0
  28. package/dist/last-match.js.map +1 -1
  29. package/dist/local-session.d.ts +0 -1
  30. package/dist/local-session.d.ts.map +1 -1
  31. package/dist/local-session.js +12 -8
  32. package/dist/local-session.js.map +1 -1
  33. package/dist/server-entry.d.ts +2 -0
  34. package/dist/server-entry.d.ts.map +1 -0
  35. package/dist/server-entry.js +61 -0
  36. package/dist/server-entry.js.map +1 -0
  37. package/dist/server-manager.d.ts +9 -9
  38. package/dist/server-manager.d.ts.map +1 -1
  39. package/dist/server-manager.js +109 -20
  40. package/dist/server-manager.js.map +1 -1
  41. package/dist/server.d.ts +3 -28
  42. package/dist/server.d.ts.map +1 -1
  43. package/dist/server.js +417 -275
  44. package/dist/server.js.map +1 -1
  45. package/dist/session.d.ts +0 -1
  46. package/dist/session.d.ts.map +1 -1
  47. package/dist/session.js +2 -9
  48. package/dist/session.js.map +1 -1
  49. package/dist/shell-adapter.d.ts +40 -2
  50. package/dist/shell-adapter.d.ts.map +1 -1
  51. package/dist/shell-adapter.js +252 -24
  52. package/dist/shell-adapter.js.map +1 -1
  53. package/dist/utils.d.ts +3 -1
  54. package/dist/utils.d.ts.map +1 -1
  55. package/dist/utils.js +29 -10
  56. package/dist/utils.js.map +1 -1
  57. package/dist/web/app.css +12 -7
  58. package/dist/web/app.js +11 -2
  59. package/dist/web/index.html +6 -3
  60. package/dist/web/xterm.css +218 -0
  61. package/package.json +3 -2
package/README.md CHANGED
@@ -7,10 +7,11 @@ opencode 插件:让 opencode 像人一样操作**长驻交互式终端会话**
7
7
  - **长驻会话**:`term_cli connect` 建立连接后,`term_cli exec` 在同一会话执行命令,保留 cwd/环境/后台进程/sudo 缓存
8
8
  - **PTY 交互**:分配伪终端,可处理 sudo 密码、vi、top 等交互程序
9
9
  - **权限管控**:只读白名单直接放行、危险命令黑名单硬拒、其余走 `context.ask()` 用户审批
10
- - **命令完成判定**:哨兵标记 + 静默窗口 + 超时三重兜底,动画输出(进度条等)自动识别
11
- - **历史消息对**:命令+输出 全部存文件(`~/.opencode/plugins-cache/opencode-ssh-tool/<会话>/`),按对数保留(默认 100 对),重启不丢、会话关闭清理
12
- - **HTTP 终端查看**:默认开启本地服务,浏览器打开可滚动、自动刷新的终端记录页面
13
- - **多语言**:工具描述默认英文,`SSH_TOOL_LANG=zh` 切中文
10
+ - **命令完成判定**:完成标记法(`<SSH_DONE:seq:退出码>`)+ 静默窗口 + 超时三重兜底,动画输出(进度条等)自动识别;未闭合引号/反引号等会触发 shell 续行的命令提交前拦截
11
+ - **双视图**:transcript(命令+输出消息对)与 raw(xterm.js 实时画面,支持 vi/top 等全屏程序);终端尺寸前后端一致固定 `120×40`
12
+ - **历史消息对**:命令+输出 全部存文件(`~/.opencode/plugins-cache/opencode-ssh-tool/<会话>/`),按对数保留(默认 100 对),重启不丢、会话关闭清理
13
+ - **HTTP 终端查看**:默认开启本地服务,浏览器打开可查看终端记录(WebSocket 实时推送,无轮询)
14
+ - **多语言**:`toolLang`(工具描述/CLI/session 文案)与 `webLang`(页面 UI)独立配置,默认英文;环境变量 `SSH_TOOL_LANG` / `SSH_WEB_LANG` 分别覆盖
14
15
 
15
16
  ## 安装
16
17
 
@@ -59,8 +60,10 @@ term_cli help # 完整用法
59
60
  // 保留的消息对数上限(默认 100,最小 1)。超出时移除最旧的一对
60
61
  "maxMessages": 100
61
62
  },
62
- // 工具描述语言:"en" | "zh"(默认 "en",可用环境变量 SSH_TOOL_LANG 覆盖)
63
- "toolLang": "en",
63
+ // 工具语言(工具描述 / CLI / agent·session 文案):"en" | "zh"(默认 "en",可用环境变量 SSH_TOOL_LANG 覆盖)
64
+ "toolLang": "en",
65
+ // Web 界面语言(仅浏览器页面 UI 文案):"en" | "zh"(默认 "en",可用环境变量 SSH_WEB_LANG 覆盖)
66
+ "webLang": "en",
64
67
  // 权限自定义正则(追加到内置默认,控制"拒绝"与"需审批"命令)
65
68
  "permission": {
66
69
  // 内置默认危险命令黑名单(命中直接拒绝):
@@ -79,7 +82,7 @@ term_cli help # 完整用法
79
82
  }
80
83
  ```
81
84
 
82
- 多语言优先级:环境变量 `SSH_TOOL_LANG` > 配置文件 `toolLang` > 默认 `en`。
85
+ 多语言:`toolLang` 控制工具侧全部文案(工具描述、CLI 输出、agent/session 回传消息),`webLang` 仅控制浏览器页面 UI。优先级均为「环境变量 > 配置文件 > 默认 `en`」:工具用 `SSH_TOOL_LANG`,Web 用 `SSH_WEB_LANG`。
83
86
 
84
87
  权限自定义正则**追加**到内置默认(`DENY` 黑名单 + `ALLOW_READONLY` 白名单),`deny`/`allow` 为空数组时仅用内置默认规则,可自行补充扩展。
85
88
 
@@ -115,20 +118,21 @@ term_cli exec(command)
115
118
 
116
119
  服务默认开启(配置 `server.enabled`)。`term_cli status` / `term_cli read`(history 模式)会返回实际地址(端口 0 时自动分配,避免冲突):
117
120
 
118
- ```
119
- 浏览器访问 http://127.0.0.1:<port> 查看可滚动、每 2s 自动刷新的终端记录
120
- ```
121
-
122
- 页面按会话切换,展示「命令 + 输出」历史对。
121
+ ```
122
+ 浏览器访问 http://127.0.0.1:<port> 查看终端记录(WebSocket 实时推送)
123
+ ```
124
+
125
+ 页面按会话/终端切换,transcript 展示「命令 + 输出」历史对,raw 展示 xterm.js 实时画面。
123
126
 
124
127
  ## 架构
125
128
 
126
- ```
127
- opencode(插件进程)
128
- ├─ ssh2 客户端:长驻连接 + PTY shell(每个 opencode 会话一个)
129
- │ └─ 哨兵法执行命令(combo; echo __SSH_DONE_<rand>__)
130
- ├─ SessionHistory:命令+输出 消息对(按对数限制,超大落盘)
131
- └─ HTTP 服务:127.0.0.1 随机端口,浏览器查看终端记录
129
+ ```
130
+ opencode(插件进程)
131
+ ├─ agent:每会话多个命名终端,PTY 固定 120×40
132
+ │ ├─ SSH(ssh2 PTY shell)/ 本地·容器(Bun.Terminal)
133
+ │ └─ 完成标记法执行命令(`command ;markerCmd` → `<SSH_DONE:seq:退出码>`)
134
+ ├─ server:独立 HTTP 子进程(WS 统一事件流,proto 版本校验)
135
+ └─ web:transcript(消息对)/ raw(xterm.js)双视图,浏览器实时查看
132
136
  ```
133
137
 
134
138
  ## 开发
@@ -168,8 +172,10 @@ npm publish # 只发布 dist/
168
172
 
169
173
  ## 文档
170
174
 
171
- - `docs/requirements/需求说明.md` — 原始需求
172
- - `docs/design/方案分析.md` — 技术分析、决策、风险
173
- - `docs/design/结构设计.md` — 编码依据
175
+ - `docs/requirements/需求说明.md` — 原始需求
176
+ - `docs/design/方案分析.md` — 技术分析、决策、风险
177
+ - `docs/design/结构设计.md` — 编码依据
178
+ - `docs/design/消息协议重构设计.md` — WS 统一事件流协议
179
+ - `docs/design/终端渲染与尺寸固定说明.md` — 终端尺寸固定、续行检测、渲染修复
174
180
 
175
181
 
@@ -0,0 +1,52 @@
1
+ /** 代理可操作的会话接口(SshSession / LocalSession 子集) */
2
+ export interface AgentSession {
3
+ exec(command: string): Promise<unknown>;
4
+ send(text: string): {
5
+ ok: boolean;
6
+ error?: string;
7
+ };
8
+ close(): void;
9
+ hasRunningStream(): boolean;
10
+ getRunningStream(): {
11
+ data: string;
12
+ done: boolean;
13
+ };
14
+ getRunningCommand(): string;
15
+ /** 按当前 shell 续行规则拆分多行命令为独立命令 */
16
+ splitCommand(command: string): string[];
17
+ /** 设置命令生命周期监听器(cmdStart/cmdDone 事件源) */
18
+ setLifecycle(listener: ((ev: {
19
+ type: "start";
20
+ command: string;
21
+ ts: number;
22
+ } | {
23
+ type: "done";
24
+ exitCode: number | null;
25
+ endTs: number;
26
+ output?: string;
27
+ }) => void) | null): void;
28
+ /** 读取原始字节流增量 */
29
+ readRawStream(pos: number): {
30
+ data: string;
31
+ pos: number;
32
+ reset?: boolean;
33
+ };
34
+ }
35
+ /** 代理状态 */
36
+ export interface AgentHandle {
37
+ close(): void;
38
+ /** 重新注册当前会话列表(增删会话后调用) */
39
+ reRegister(): void;
40
+ }
41
+ /**
42
+ * 启动代理客户端:连接独立服务并注册会话,处理转发命令
43
+ * @param url 服务 WS 地址(如 ws://127.0.0.1:PORT/ws)
44
+ * @param resolveSession 按 sessionID/name 取会话句柄
45
+ * @param listSessions 当前本进程持有的全部会话(返回 {sessionID, name}[])
46
+ * @returns 代理句柄
47
+ */
48
+ export declare function startAgent(url: string, resolveSession: (sessionID: string, name: string) => AgentSession | undefined, listSessions: () => Array<{
49
+ sessionID: string;
50
+ name: string;
51
+ }>): AgentHandle;
52
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAMA,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACvC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACnD,KAAK,IAAI,IAAI,CAAA;IACb,gBAAgB,IAAI,OAAO,CAAA;IAC3B,gBAAgB,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAA;IACnD,iBAAiB,IAAI,MAAM,CAAA;IAC3B,gCAAgC;IAChC,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACvC,wCAAwC;IACxC,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA;IAC/K,gBAAgB;IAChB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC3E;AAED,WAAW;AACX,MAAM,WAAW,WAAW;IAC1B,KAAK,IAAI,IAAI,CAAA;IACb,0BAA0B;IAC1B,UAAU,IAAI,IAAI,CAAA;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,YAAY,GAAG,SAAS,EAC7E,YAAY,EAAE,MAAM,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7D,WAAW,CA8Mb"}
package/dist/agent.js ADDED
@@ -0,0 +1,250 @@
1
+ // 代理客户端:插件进程连上独立服务(WS),注册本进程持有的会话,
2
+ // 处理服务转发来的交互命令(run-exec / run-send / run-delete),并把实时流推给服务。
3
+ import log from "./log.js";
4
+ import { INTERACTIVE_BUSY_MS } from "./constants.js";
5
+ /**
6
+ * 启动代理客户端:连接独立服务并注册会话,处理转发命令
7
+ * @param url 服务 WS 地址(如 ws://127.0.0.1:PORT/ws)
8
+ * @param resolveSession 按 sessionID/name 取会话句柄
9
+ * @param listSessions 当前本进程持有的全部会话(返回 {sessionID, name}[])
10
+ * @returns 代理句柄
11
+ */
12
+ export function startAgent(url, resolveSession, listSessions) {
13
+ let ws = null;
14
+ let closed = false;
15
+ let timer = null;
16
+ const wsUrl = url.startsWith("ws://") ? url : `ws://${url}`;
17
+ const send = (msg) => {
18
+ if (ws && ws.readyState === WebSocket.OPEN)
19
+ ws.send(JSON.stringify(msg));
20
+ };
21
+ const register = () => {
22
+ send({ type: "register", sessions: listSessions() });
23
+ };
24
+ const rawPosMap = new Map();
25
+ /** 已挂接生命周期监听的会话对象(按对象去重:同名会话断开重建是新对象,需重新挂接) */
26
+ const lifeHooked = new WeakSet();
27
+ /** 会话执行队列:命令 busy 时 web 提交的命令排队,当前命令完成后依次执行(key=sessionID:name) */
28
+ const execQueue = new Map();
29
+ /** 各终端最近一次命令提交时间戳(busy 分流:<阈值视为连续命令排队,≥阈值视为交互输入 send) */
30
+ const lastSubmitTs = new Map();
31
+ /** 为某个会话挂接生命周期监听:cmdStart/cmdDone 由 BaseSession 精确发出(web exec 与插件 exec 统一来源) */
32
+ const hookLifecycle = (key, session) => {
33
+ if (lifeHooked.has(session))
34
+ return;
35
+ lifeHooked.add(session);
36
+ const sep = key.indexOf(":");
37
+ const sid = key.slice(0, sep), name = key.slice(sep + 1);
38
+ session.setLifecycle((ev) => {
39
+ if (ev.type === "start") {
40
+ send({ type: "cmdStart", sessionID: sid, name, command: ev.command, ts: ev.ts });
41
+ }
42
+ else {
43
+ // 快速命令(生命周期落在单个 tick 内)可能没被 pushStreams 推过 out:
44
+ // done 携带完整输出时补推一次 out(final=true,server 替换而非追加,
45
+ // 避免与已推的增量重复),保证 web 能看到命令输出(不仅 cmdline)
46
+ if (ev.output) {
47
+ send({ type: "out", sessionID: sid, name, data: ev.output, final: true });
48
+ }
49
+ send({ type: "cmdDone", sessionID: sid, name, exitCode: ev.exitCode, endTs: ev.endTs });
50
+ // 出队下一条命令由 execQueued 在 promise resolve 后处理(见上),此处不再重复出队
51
+ }
52
+ });
53
+ };
54
+ /** 执行单条命令;完成后从队列取下一条继续(在 promise resolve 后才出队,
55
+ * 保证上一条的 running context 已清理,出队的下一条不会因 busy 误判被跳过) */
56
+ const execQueued = (sid, name, command) => {
57
+ const session = resolveSession(sid, name);
58
+ if (!session) {
59
+ send({ type: "cmdDone", sessionID: sid, name, exitCode: null, endTs: Date.now() });
60
+ return Promise.resolve();
61
+ }
62
+ const key = `${sid}:${name}`;
63
+ return session.exec(command).then((result) => {
64
+ // exec 同步等待:正常完成/动画等待均会触发生命周期 done(cmdDone)。
65
+ // 仅 quick-fail(busy/未连接,返回 {ok:false})不会发 done —— 补一条兜底防 server busy 卡死
66
+ if (result && typeof result === "object" && "ok" in result && !result.ok) {
67
+ // 失败(busy/未连接/引号不闭合等):把错误文本作为输出回传,web 端可见失败原因
68
+ const err = result.error;
69
+ if (err)
70
+ send({ type: "out", sessionID: sid, name, data: err, final: true });
71
+ send({ type: "cmdDone", sessionID: sid, name, exitCode: null, endTs: Date.now() });
72
+ }
73
+ // 出队下一条:此刻上一条已完成,running context 已清理(resolve 在 lifecycle done 之后)
74
+ const q = execQueue.get(key);
75
+ const next = q?.shift();
76
+ if (q && q.length === 0)
77
+ execQueue.delete(key);
78
+ if (next) {
79
+ log.info(`agent 队列出队执行 ${sid}/${name}: ${next}`);
80
+ return execQueued(sid, name, next);
81
+ }
82
+ return;
83
+ }).catch((e) => {
84
+ log.error(`代理执行失败 ${sid}/${name}`, e instanceof Error ? e.message : String(e));
85
+ // 命令未运行(异常):补充 cmdDone 兜底(正常完成由生命周期上报,server 幂等)
86
+ send({ type: "cmdDone", sessionID: sid, name, exitCode: null, endTs: Date.now() });
87
+ // 异常也应继续出队下一条,避免队列卡死
88
+ const q = execQueue.get(key);
89
+ const next = q?.shift();
90
+ if (q && q.length === 0)
91
+ execQueue.delete(key);
92
+ if (next) {
93
+ log.info(`agent 队列异常后出队 ${sid}/${name}: ${next}`);
94
+ return execQueued(sid, name, next);
95
+ }
96
+ return;
97
+ });
98
+ };
99
+ const pushStreams = () => {
100
+ for (const { sessionID, name } of listSessions()) {
101
+ const session = resolveSession(sessionID, name);
102
+ const key = `${sessionID}:${name}`;
103
+ if (!session)
104
+ continue;
105
+ hookLifecycle(key, session);
106
+ const raw = session.readRawStream(rawPosMap.get(key) ?? 0);
107
+ if (raw.data) {
108
+ rawPosMap.set(key, raw.pos);
109
+ send({ type: "raw", sessionID, name, data: raw.data, pos: raw.pos, reset: !!raw.reset });
110
+ }
111
+ if (!session.hasRunningStream())
112
+ continue;
113
+ const st = session.getRunningStream();
114
+ if (st.data)
115
+ send({ type: "out", sessionID, name, data: st.data });
116
+ }
117
+ };
118
+ const connect = () => {
119
+ if (closed)
120
+ return;
121
+ try {
122
+ ws = new WebSocket(wsUrl);
123
+ }
124
+ catch (e) {
125
+ log.error("代理连接服务失败", e instanceof Error ? e.message : String(e));
126
+ setTimeout(connect, 2000);
127
+ return;
128
+ }
129
+ ws.onopen = () => {
130
+ log.info(`代理已连接 ${wsUrl}`);
131
+ register();
132
+ timer = setInterval(pushStreams, 100);
133
+ };
134
+ ws.onmessage = (ev) => {
135
+ let msg;
136
+ try {
137
+ msg = JSON.parse(String(ev.data));
138
+ }
139
+ catch {
140
+ return;
141
+ }
142
+ const t = msg.type;
143
+ if (!t)
144
+ return;
145
+ if (t === "run-exec") {
146
+ const sid = typeof msg.sessionID === "string" ? msg.sessionID : "";
147
+ const name = typeof msg.name === "string" ? msg.name : "";
148
+ const command = typeof msg.command === "string" ? msg.command : "";
149
+ if (!sid || !command)
150
+ return;
151
+ const session = resolveSession(sid, name);
152
+ if (!session) {
153
+ send({ type: "cmdDone", sessionID: sid, name, exitCode: null, endTs: Date.now() });
154
+ return;
155
+ }
156
+ // web 输入框回车 = 提交命令:空闲立即执行;忙时按距上次提交间隔分流——
157
+ // 间隔 < INTERACTIVE_BUSY_MS(连续快速命令,模型连续多行提交)→ 排队依次执行;
158
+ // 间隔 ≥ 阈值仍 busy(用户给运行中的交互程序输入,如 sudo 密码/REPL)→ 原样 send 交程序。
159
+ // 不再用 hasRunningStream() 单点猜测——快速命令窗口内到达的后续命令会被误判为交互输入而裸执行。
160
+ const key = `${sid}:${name}`;
161
+ const now = Date.now();
162
+ const last = lastSubmitTs.get(key) ?? 0;
163
+ lastSubmitTs.set(key, now);
164
+ if (session.hasRunningStream()) {
165
+ if (now - last < INTERACTIVE_BUSY_MS) {
166
+ const q = execQueue.get(key);
167
+ if (q)
168
+ q.push(command);
169
+ else
170
+ execQueue.set(key, [command]);
171
+ log.info(`agent run-exec 排队 ${sid}/${name}: ${command} (队列 ${(execQueue.get(key) ?? []).length})`);
172
+ return;
173
+ }
174
+ log.info(`agent run-exec busy 超阈值按交互输入 ${sid}/${name}: ${command}`);
175
+ session.send(command + "\r");
176
+ return;
177
+ }
178
+ // 多行命令:终端空闲(非交互)时按 shell 续行规则拆成多个独立命令逐条执行(每条独立注入 marker
179
+ // 进 history,避免整段作为一条执行导致多行回显/多行 marker 剥离困难);busy(交互中)则不拆,
180
+ // 整体交给上面的分流逻辑原样处理。
181
+ const lines = session.splitCommand(command);
182
+ if (!session.hasRunningStream() && lines.length > 1) {
183
+ log.info(`agent run-exec 多行拆分 ${sid}/${name}: ${command.split("\n").length} 行 → ${lines.length} 条`);
184
+ for (const [i, c] of lines.entries()) {
185
+ if (i === 0)
186
+ void execQueued(sid, name, c);
187
+ else {
188
+ // 循环内每次重新取队列引用:首次用 set 建数组后,后续用 push 追加(避免 set 覆盖丢命令)
189
+ const q = execQueue.get(key);
190
+ if (q)
191
+ q.push(c);
192
+ else
193
+ execQueue.set(key, [c]);
194
+ }
195
+ }
196
+ return;
197
+ }
198
+ log.info(`agent run-exec 直接执行 ${sid}/${name}: ${command}`);
199
+ void execQueued(sid, name, command);
200
+ return;
201
+ }
202
+ if (t === "run-send") {
203
+ const sid = typeof msg.sessionID === "string" ? msg.sessionID : "";
204
+ const name = typeof msg.name === "string" ? msg.name : "";
205
+ const text = typeof msg.text === "string" ? msg.text : "";
206
+ const session = resolveSession(sid, name);
207
+ if (session) {
208
+ lastSubmitTs.set(`${sid}:${name}`, Date.now());
209
+ session.send(text);
210
+ }
211
+ return;
212
+ }
213
+ if (t === "run-delete") {
214
+ const sid = typeof msg.sessionID === "string" ? msg.sessionID : "";
215
+ const name = typeof msg.name === "string" ? msg.name : "";
216
+ const session = resolveSession(sid, name);
217
+ if (session)
218
+ session.close();
219
+ return;
220
+ }
221
+ };
222
+ ws.onclose = () => {
223
+ if (timer) {
224
+ clearInterval(timer);
225
+ timer = null;
226
+ }
227
+ if (closed)
228
+ return;
229
+ log.info("代理连接断开,2s 后重连");
230
+ setTimeout(connect, 2000);
231
+ };
232
+ ws.onerror = () => { };
233
+ };
234
+ connect();
235
+ return {
236
+ close: () => {
237
+ closed = true;
238
+ if (timer) {
239
+ clearInterval(timer);
240
+ timer = null;
241
+ }
242
+ try {
243
+ ws?.close();
244
+ }
245
+ catch { /* ignore */ }
246
+ },
247
+ reRegister: register,
248
+ };
249
+ }
250
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { SessionHistory } from "./history.js";
2
2
  import { type ShellAdapter } from "./shell-adapter.js";
3
+ import { type Lang } from "./i18n.js";
3
4
  /** 命令执行结果 */
4
5
  export interface ExecResult {
5
6
  ok: boolean;
@@ -12,10 +13,22 @@ export interface ExecResult {
12
13
  command?: string;
13
14
  duration?: number;
14
15
  }
16
+ /** 生命周期事件(命令开始/完成),供 agent 转报 server(web 与插件自身 exec 统一来源) */
17
+ export type LifecycleEvent = {
18
+ type: "start";
19
+ command: string;
20
+ ts: number;
21
+ } | {
22
+ type: "done";
23
+ exitCode: number | null;
24
+ endTs: number;
25
+ output?: string;
26
+ };
15
27
  /** 会话命令执行与读取的公共实现 */
16
28
  export declare abstract class BaseSession {
17
29
  protected readonly sessionID: string;
18
30
  protected readonly name: string;
31
+ protected readonly _lang: Lang;
19
32
  protected _connected: boolean;
20
33
  protected _remoteBusy: boolean;
21
34
  protected _buffer: string;
@@ -24,6 +37,10 @@ export declare abstract class BaseSession {
24
37
  protected _runningStartPos: number | null;
25
38
  protected _runningCommand: string;
26
39
  protected _watchTimer: ReturnType<typeof setInterval> | null;
40
+ /** 命令序号(完成标记 <SSH_DONE:seq:code> 标识本次命令,防残留误判) */
41
+ private _cmdSeq;
42
+ /** 当前命令序号(_beginCapture 递增,供检测/拼接用) */
43
+ private _runningSeq;
27
44
  /** 下次捕获窗口起点(相对当前 buffer):前一条命令结束后,标记/残留从此处开始 */
28
45
  protected _cursor: number;
29
46
  /** WS 实时流已发送位置(相对 buffer 偏移);null = 尚未定位命令回显结束点 */
@@ -40,15 +57,22 @@ export declare abstract class BaseSession {
40
57
  /** 累积原始字节总数(单调递增,前端增量游标) */
41
58
  private _rawTotal;
42
59
  protected readonly _history: SessionHistory;
43
- constructor(sessionID: string, history: SessionHistory, name?: string);
60
+ /** 生命周期监听器(命令开始/完成事件,agent 转报 server) */
61
+ private _lifecycle;
62
+ /**
63
+ * 本次命令期间是否真实发送过中断(Ctrl-C)。
64
+ * 中断判定不依赖"命令回显定位":超宽命令被 readline 折行重绘时回显会多出字符,
65
+ * 精确匹配必然失败(echoEnd=0),若再以 echoEnd 为前置条件则 ^C 永远检测不到 → busy 卡死。
66
+ * 仅当确实发过 ^C 才去缓冲区找 ^C 回显,天然排除历史残留 ^C 的误判。
67
+ */
68
+ private _interruptSent;
69
+ constructor(sessionID: string, history: SessionHistory, name?: string, _lang?: Lang);
44
70
  /** 向传输层写入字节(子类实现:SSH 写 stream / 本地写 Bun.Terminal) */
45
71
  protected abstract _write(data: string): void;
46
72
  /** 关闭传输层资源(子类实现:SSH 关 client+stream / 本地关 term+proc) */
47
73
  protected abstract _closeTransport(): void;
48
74
  /** 结果中的类型专属字段(SSH 附加 host,本地为空) */
49
75
  protected get _extraResult(): Partial<ExecResult>;
50
- /** 忙碌提示中的状态命令名(ssh_status / local_status) */
51
- protected abstract _statusCmd: string;
52
76
  /**
53
77
  * 异步提交命令:立即返回,命令后台执行,输出由后台监听收集进 history
54
78
  * @param command 命令
@@ -83,6 +107,21 @@ export declare abstract class BaseSession {
83
107
  getHistory(): SessionHistory;
84
108
  getRunningOutput(): string;
85
109
  getRunningCommand(): string;
110
+ /**
111
+ * 按当前 shell 的行续行规则拆分多行命令为独立命令(供 agent 端多行命令拆条执行)
112
+ * @param command 可能含换行的命令文本
113
+ * @returns 拆分后的独立命令列表;未识别 shell 时按普通换行拆分
114
+ */
115
+ splitCommand(command: string): string[];
116
+ /**
117
+ * 设置命令生命周期监听器(命令开始/完成时回调)
118
+ * @param listener 回调(start 携带命令,done 携带退出码与结束时刻;null 表示清除)
119
+ */
120
+ setLifecycle(listener: ((ev: LifecycleEvent) => void) | null): void;
121
+ /** 触发命令开始事件 */
122
+ private _emitStart;
123
+ /** 触发命令完成事件 */
124
+ private _emitDone;
86
125
  hasRunningStream(): boolean;
87
126
  /**
88
127
  * 增量取运行中命令的实时输出(WS 推流用):
@@ -103,24 +142,51 @@ export declare abstract class BaseSession {
103
142
  };
104
143
  /** 是否连接且传输层就绪(子类检查自身传输对象) */
105
144
  protected abstract _ready(): boolean;
145
+ /**
146
+ * 本会话当前命令的完成标记命令(adapter 负责各自的标记语法,无 adapter 时用 POSIX 默认)
147
+ * @param seq 命令序号
148
+ * @returns 标记命令文本
149
+ */
150
+ private _markerCmd;
151
+ /**
152
+ * 组合写入命令(实际写入 PTY 的文本,尾部带 \r 回车)
153
+ * @param command 原始命令(history 存干净版本)
154
+ * @returns 实际写入 PTY 的文本
155
+ */
156
+ private _composeCommand;
157
+ /**
158
+ * 组合命令文本(无尾部 \r):命令 + 完成标记,同时供 extractOutputStart 定位回显结束。
159
+ * 普通命令用 `;` 同行拼接(免疫 prompt 框架覆盖):`python; printf ...` 中 printf 由 shell 在 python
160
+ * 退出后执行,不会被 REPL 当 stdin 消费(实测 top/read/python 均正常出 marker)。
161
+ * 含 heredoc 的命令改用换行分隔:标记若以 `;` 拼到分隔符行(`EOF ;printf ...`),
162
+ * heredoc 分隔符不再被识别 → 永不结束 → 卡死。
163
+ * 交互程序(top/vi/read/python 等)期间保留 running 上下文与 busy,marker 出现即判定完成。
164
+ * @param command 原始命令
165
+ * @returns 命令 + 标记文本(无 \r)
166
+ */
167
+ private _composeEchoText;
168
+ /**
169
+ * 提取命令纯输出:从命令回显后切到完成标记(无标记则切到中断回显 ^C 处),
170
+ * 顺带去掉追加的标记命令回显行。
171
+ * 回显定位使用组合命令文本(command + 标记段)而非裸命令:裸命令可能出现在程序错误输出中
172
+ * (如 `pw` 的错误行 `bash: pw: command not found`),而组合命令整行回显是唯一的,
173
+ * 可避免 last-match 误命中错误行导致输出被裁空。
174
+ * @param raw 本次执行窗口原始字节流
175
+ * @param command 原始命令(历史用)
176
+ * @returns 纯程序输出原始流
177
+ */
178
+ private _extractOutput;
106
179
  /** 命令执行/提交前准备:裁剪已消费缓冲 + 丢弃上一条命令的迟到残留标记 */
107
180
  private _beginCapture;
108
181
  /** 追加输出字节:维护业务缓冲 + 连续原始流 ring */
109
182
  protected _appendBuffer(text: string): void;
110
- /** 探测 shell 类型:deadline 内循环发送探测命令并解析输出(shell 慢启动时也能等到就绪) */
183
+ /** 探测 shell 类型:按各适配器 probeCommand 逐个探测(POSIX 系优先,pwsh/cmd 用各自独有命令),
184
+ * login shell 的 $0 带 - 前缀(-bash/-zsh)由对应 parseProbe 容忍 */
111
185
  protected _probeShell(deadline: number): Promise<ShellAdapter | null>;
112
186
  /** 等待探测输出稳定:buffer 有内容且 500ms 不再变化则返回,超时返回 null */
113
187
  private _waitProbeOutput;
114
- /**
115
- * deadline 内循环注入+等待确认,成功返回 true,超时返回 false
116
- * 逐行发送避免因 shell 未就绪导致的整块丢失;每轮等待最大 3s
117
- * 注入命令原样回显(不抑制),确认以 echo 唯一标识为准,避免与后续命令时序混淆
118
- */
119
- protected _injectAndSettle(deadline: number): Promise<boolean>;
120
- /** 等待缓冲中出现指定文本(最多 maxMs) */
121
- private _waitForBufferToken;
122
188
  private _waitCompletion;
123
- /** 后台监听:轮询 done 标记 → 收集输出进 history + busy=false */
189
+ /** 后台监听:轮询 done 标记 / ^C 中断 → 收集输出进 history + busy=false */
124
190
  private _startBackgroundWatch;
125
191
  protected _clearRunningContext(): void;
126
192
  private _truncate;
@@ -1 +1 @@
1
- {"version":3,"file":"base-session.d.ts","sourceRoot":"","sources":["../src/base-session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base-session.d.ts","sourceRoot":"","sources":["../src/base-session.ts"],"names":[],"mappings":"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"}