@keyou007/agent-bot 0.1.24-alpha.6 → 0.1.24-alpha.8
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/CHANGELOG.md +24 -0
- package/README.md +6 -4
- package/README.zh.md +6 -4
- package/dist/codex/CodexEventMapper.js +1 -0
- package/dist/codex/CodexEventMapper.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +3 -1
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +70 -8
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/codex/CodexVersion.js +2 -2
- package/dist/codex/CodexVersion.js.map +1 -1
- package/dist/codex/ProviderSwitch.js +1 -1
- package/dist/codex/ProviderSwitch.js.map +1 -1
- package/dist/codex/TraePlanEntry.js +70 -0
- package/dist/codex/TraePlanEntry.js.map +1 -0
- package/dist/feishu/CardRenderer.js +27 -1
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +5 -0
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/supervision/SafeRestartNotifier.js +32 -1
- package/dist/supervision/SafeRestartNotifier.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +3 -0
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/terminal/TerminalCommandTracker.js +74 -0
- package/dist/terminal/TerminalCommandTracker.js.map +1 -0
- package/dist/terminal/TerminalProcess.js +25 -1
- package/dist/terminal/TerminalProcess.js.map +1 -1
- package/dist/terminal/TerminalServer.js +44 -7
- package/dist/terminal/TerminalServer.js.map +1 -1
- package/docs/technical-reference.md +2 -0
- package/docs/technical-reference.zh.md +2 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +6 -4
- package/src/codex/CodexEventMapper.ts +1 -0
- package/src/codex/CodexProcessManager.ts +3 -1
- package/src/codex/CodexRuntime.ts +64 -8
- package/src/codex/CodexVersion.ts +2 -2
- package/src/codex/ProviderSwitch.ts +1 -1
- package/src/codex/TraePlanEntry.ts +63 -0
- package/src/feishu/CardRenderer.ts +30 -2
- package/src/index.ts +14 -3
- package/src/proxy/ProxySessionController.ts +4 -0
- package/src/supervision/SafeRestartNotifier.ts +34 -2
- package/src/supervision/SafeRestartScheduler.ts +7 -0
- package/src/terminal/TerminalCommandTracker.ts +74 -0
- package/src/terminal/TerminalProcess.ts +24 -2
- package/src/terminal/TerminalServer.ts +45 -7
|
@@ -3,8 +3,11 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import type { IPty } from "node-pty";
|
|
6
|
+
import { powerShellTrackingScript, TerminalCommandTracker, type TerminalCommandState } from "./TerminalCommandTracker.js";
|
|
6
7
|
|
|
7
|
-
export type TerminalProcess = Pick<IPty, "write" | "resize" | "kill" | "onData" | "onExit"
|
|
8
|
+
export type TerminalProcess = Pick<IPty, "write" | "resize" | "kill" | "onData" | "onExit"> & {
|
|
9
|
+
commandState?(): TerminalCommandState;
|
|
10
|
+
};
|
|
8
11
|
export type TerminalSpawner = (cwd: string, cols: number, rows: number) => Promise<TerminalProcess>;
|
|
9
12
|
|
|
10
13
|
export function terminalShell(
|
|
@@ -63,9 +66,28 @@ export const spawnTerminal: TerminalSpawner = async (cwd, cols, rows) => {
|
|
|
63
66
|
prepareTerminalHelper(path.dirname(require.resolve("node-pty/package.json")));
|
|
64
67
|
}
|
|
65
68
|
const shell = terminalShell();
|
|
66
|
-
|
|
69
|
+
const tracker = new TerminalCommandTracker();
|
|
70
|
+
const powershell = /^(pwsh|powershell)(\.exe)?$/iu.test(path.basename(shell.file));
|
|
71
|
+
const args = powershell ? [...shell.args, "-NoExit", "-EncodedCommand",
|
|
72
|
+
Buffer.from(powerShellTrackingScript(tracker.nonce), "utf16le").toString("base64")] : shell.args;
|
|
73
|
+
const processPty = pty.spawn(shell.file, args, {
|
|
67
74
|
cwd, cols, rows, name: "xterm-256color", env: terminalEnvironment(), useConpty: true,
|
|
68
75
|
// The bundled ConPTY closes via its input pipe, avoiding node-pty's console-list helper on kill.
|
|
69
76
|
useConptyDll: process.platform === "win32",
|
|
70
77
|
});
|
|
78
|
+
return {
|
|
79
|
+
write: (data) => processPty.write(data), resize: (cols, rows) => processPty.resize(cols, rows), kill: () => processPty.kill(),
|
|
80
|
+
onExit: (listener) => processPty.onExit(listener),
|
|
81
|
+
onData: (listener) => processPty.onData((data) => { const visible = tracker.consume(data); if (visible) listener(visible); }),
|
|
82
|
+
commandState: () => {
|
|
83
|
+
const tracked = tracker.snapshot();
|
|
84
|
+
if (tracked.status !== "unknown" || process.platform === "win32") return tracked;
|
|
85
|
+
try {
|
|
86
|
+
const name = processPty.process;
|
|
87
|
+
// Unix PTYs can report a foreground process even without shell integration.
|
|
88
|
+
if (name && path.basename(name) !== path.basename(shell.file)) return { status: "running", command: name, source: "process" };
|
|
89
|
+
} catch { /* An exited PTY may no longer have a foreground process. */ }
|
|
90
|
+
return tracked;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
71
93
|
};
|
|
@@ -12,6 +12,7 @@ import type { CardAction } from "../feishu/types.js";
|
|
|
12
12
|
import { spawnTerminal, type TerminalProcess, type TerminalSpawner } from "./TerminalProcess.js";
|
|
13
13
|
import { terminalPage, TERMINAL_CSS, TERMINAL_SCRIPT } from "./TerminalPage.js";
|
|
14
14
|
import { TerminalLinks } from "./TerminalLinks.js";
|
|
15
|
+
import type { TerminalCommandState } from "./TerminalCommandTracker.js";
|
|
15
16
|
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
17
18
|
const AUTH_TTL = 5 * 60_000;
|
|
@@ -36,6 +37,11 @@ export interface TerminalCardView extends TerminalAuthorization {
|
|
|
36
37
|
terminalId: string; reopenToken: string; reopenUrl: string; recovery: boolean; state: "pending" | "approved" | "running" | "closed";
|
|
37
38
|
connected: boolean; message?: string;
|
|
38
39
|
}
|
|
40
|
+
export interface ActiveTerminal {
|
|
41
|
+
terminalId: string; token: string; cwd: string;
|
|
42
|
+
state: "starting" | "running" | "closing"; connected: boolean;
|
|
43
|
+
active: boolean; foreground: boolean; command: TerminalCommandState;
|
|
44
|
+
}
|
|
39
45
|
export interface TerminalServerOptions {
|
|
40
46
|
stateDirectory: string;
|
|
41
47
|
ownerId(): string | undefined;
|
|
@@ -133,6 +139,37 @@ export class TerminalServer {
|
|
|
133
139
|
return [...this.sessions.values()].filter((s) => s.pty || s.starting).length;
|
|
134
140
|
}
|
|
135
141
|
|
|
142
|
+
listActiveTerminals(): ActiveTerminal[] {
|
|
143
|
+
return [...this.sessions.values()].filter((s) => s.pty || s.starting).map((s) => {
|
|
144
|
+
const foreground = s.socket?.readyState === WebSocket.OPEN && (s.foregroundUntil ?? 0) > this.now();
|
|
145
|
+
let command: TerminalCommandState = { status: "unknown" };
|
|
146
|
+
if (s.state !== "closed") {
|
|
147
|
+
try { command = s.pty?.commandState?.() ?? command; }
|
|
148
|
+
catch { /* Command inspection must not prevent safe-restart activity checks. */ }
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
terminalId: s.requestId, token: s.reopenToken, cwd: s.cwd,
|
|
152
|
+
state: s.state === "closed" ? "closing" : s.pty ? "running" : "starting",
|
|
153
|
+
connected: s.socket?.readyState === WebSocket.OPEN,
|
|
154
|
+
active: s.state !== "closed" && (foreground || command.status === "running" || this.now() - s.lastActivity < 30_000),
|
|
155
|
+
foreground, command,
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async stopActiveTerminal(terminalId: string, userId: string): Promise<boolean> {
|
|
161
|
+
const ownerId = this.options.ownerId();
|
|
162
|
+
if (!ownerId || userId !== ownerId) throw new Error("仅 AgentBot 所有者可结束终端。");
|
|
163
|
+
const session = this.sessions.get(terminalId);
|
|
164
|
+
if (!session || session.state === "closed" || (!session.pty && !session.starting)) return false;
|
|
165
|
+
if (session.ownerId !== userId) throw new Error("终端所有者已变更,请刷新状态。");
|
|
166
|
+
// Keep blocking restart if the OS refuses to terminate the Shell.
|
|
167
|
+
if (session.pty) session.pty.kill();
|
|
168
|
+
this.disposeSession(session, "所有者已从安全重启卡片结束终端", true);
|
|
169
|
+
await this.cardUpdates;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
136
173
|
async resolveApproval(action: CardAction): Promise<string> {
|
|
137
174
|
this.sweep();
|
|
138
175
|
const id = String(action.value.requestId ?? "");
|
|
@@ -425,11 +462,13 @@ export class TerminalServer {
|
|
|
425
462
|
this.assertCanStart();
|
|
426
463
|
const starting = (async () => {
|
|
427
464
|
const pty = await (this.options.spawn ?? spawnTerminal)(session.cwd, 100, 30);
|
|
465
|
+
session.pty = pty;
|
|
466
|
+
pty.onExit(({ exitCode }) => { session.pty = undefined; this.disposeSession(session, `Shell 已退出(${exitCode})`); });
|
|
428
467
|
if (this.closed || this.options.canStart?.() === false || session.state === "closed"
|
|
429
468
|
|| session.ownerId !== this.options.ownerId() || this.now() >= session.expiresAt) {
|
|
430
|
-
|
|
469
|
+
this.disposeSession(session, "服务正在重启或授权已失效", true); pty.kill(); return;
|
|
431
470
|
}
|
|
432
|
-
session.
|
|
471
|
+
session.state = "running"; session.lastActivity = this.now();
|
|
433
472
|
this.markDisconnected(session);
|
|
434
473
|
pty.onData((data) => {
|
|
435
474
|
if (session.state !== "running") return;
|
|
@@ -437,7 +476,6 @@ export class TerminalServer {
|
|
|
437
476
|
session.output = (session.output + data).slice(-256 * 1024);
|
|
438
477
|
if (session.socket) this.send(session.socket, { type: "data", data });
|
|
439
478
|
});
|
|
440
|
-
pty.onExit(({ exitCode }) => { session.pty = undefined; this.disposeSession(session, `Shell 已退出(${exitCode})`); });
|
|
441
479
|
this.options.logger.info({ requestId: session.requestId }, "Interactive terminal started.");
|
|
442
480
|
})();
|
|
443
481
|
session.starting = starting;
|
|
@@ -469,16 +507,16 @@ export class TerminalServer {
|
|
|
469
507
|
if (s.state === "running" && now - Math.max(s.lastActivity, s.foregroundUntil ?? 0) >= IDLE_TTL) {
|
|
470
508
|
this.disposeSession(s, "连续 30 分钟无输入或输出,Shell 已自动关闭");
|
|
471
509
|
}
|
|
472
|
-
if (s.state === "closed" && !s.starting && now - s.lastActivity >= AUTH_TTL) this.sessions.delete(s.requestId);
|
|
510
|
+
if (s.state === "closed" && !s.pty && !s.starting && now - s.lastActivity >= AUTH_TTL) this.sessions.delete(s.requestId);
|
|
473
511
|
}
|
|
474
512
|
}
|
|
475
|
-
private disposeSession(s: TerminalSession, message: string): void {
|
|
513
|
+
private disposeSession(s: TerminalSession, message: string, waitForExit = false): void {
|
|
476
514
|
if (s.state === "closed") return;
|
|
477
515
|
this.clearDisconnectTimer(s);
|
|
478
516
|
s.state = "closed"; s.message = message; s.lastActivity = this.now();
|
|
479
|
-
const pty = s.pty; s.pty = undefined;
|
|
517
|
+
const pty = s.pty; if (!waitForExit) s.pty = undefined;
|
|
480
518
|
if (s.socket) { this.send(s.socket, { type: "exit", message }); s.socket.close(); s.socket = undefined; }
|
|
481
|
-
try { pty?.kill(); } catch (error) { this.options.logger.warn({ error, requestId: s.requestId }, "Failed to close terminal process."); }
|
|
519
|
+
try { if (!waitForExit) pty?.kill(); } catch (error) { this.options.logger.warn({ error, requestId: s.requestId }, "Failed to close terminal process."); }
|
|
482
520
|
s.output = "";
|
|
483
521
|
for (const recovery of this.recoveries.values()) {
|
|
484
522
|
if (recovery.targetId === s.requestId) this.closeRecovery(recovery, message);
|