@keyou007/agent-bot 0.1.20-alpha.1 → 0.1.20
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 +11 -0
- package/README.md +13 -0
- package/README.zh.md +9 -0
- package/dist/cli/AgentPrerequisites.js +11 -47
- package/dist/cli/AgentPrerequisites.js.map +1 -1
- package/dist/cli/DefaultAgentSelection.js +1 -1
- package/dist/cli/DefaultAgentSelection.js.map +1 -1
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexLocalActivityDetector.js +32 -10
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +53 -7
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +225 -164
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/codex/CodexVersion.js +18 -0
- package/dist/codex/CodexVersion.js.map +1 -0
- package/dist/codex/ProviderSwitch.js +32 -0
- package/dist/codex/ProviderSwitch.js.map +1 -0
- package/dist/feishu/CardRenderer.js +36 -11
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +17 -5
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/InboundText.js +11 -0
- package/dist/feishu/InboundText.js.map +1 -1
- package/dist/feishu/MergedForwardMessage.js +17 -5
- package/dist/feishu/MergedForwardMessage.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/local-files/LocalFileViewerServer.js +14 -4
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/local-files/MarkdownFileLinks.js +65 -0
- package/dist/local-files/MarkdownFileLinks.js.map +1 -0
- package/dist/proxy/ProxySessionController.js +399 -257
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +58 -65
- package/dist/state/StateStore.js.map +1 -1
- package/dist/utils/semanticVersion.js +39 -0
- package/dist/utils/semanticVersion.js.map +1 -0
- package/docs/technical-reference.md +5 -3
- package/docs/technical-reference.zh.md +5 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +7 -1
- package/src/cli/AgentPrerequisites.ts +14 -40
- package/src/cli/DefaultAgentSelection.ts +1 -1
- package/src/cli.ts +13 -0
- package/src/codex/CodexLocalActivityDetector.ts +41 -9
- package/src/codex/CodexProcessManager.ts +44 -7
- package/src/codex/CodexRuntime.ts +244 -195
- package/src/codex/CodexVersion.ts +18 -0
- package/src/codex/ProviderSwitch.ts +36 -0
- package/src/feishu/CardRenderer.ts +37 -27
- package/src/feishu/FeishuConnector.ts +15 -5
- package/src/feishu/InboundText.ts +11 -0
- package/src/feishu/MergedForwardMessage.ts +15 -5
- package/src/index.ts +1 -1
- package/src/local-files/LocalFileViewerServer.ts +14 -4
- package/src/local-files/MarkdownFileLinks.ts +64 -0
- package/src/proxy/ProxySessionController.ts +369 -238
- package/src/runtime/types.ts +15 -2
- package/src/state/StateStore.ts +77 -90
- package/src/utils/semanticVersion.ts +28 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { codexVersionIssue, MINIMUM_CODEX_VERSION } from "../codex/CodexVersion.js";
|
|
3
|
+
import { compareSemanticVersions } from "../utils/semanticVersion.js";
|
|
4
|
+
export { compareSemanticVersions } from "../utils/semanticVersion.js";
|
|
2
5
|
|
|
3
6
|
export type SupportedAgentId = "codex" | "traex";
|
|
4
7
|
export type SupportedAgentState = "missing" | "outdated" | "ready";
|
|
@@ -29,6 +32,8 @@ export interface SupportedAgentInspection {
|
|
|
29
32
|
installedVersion?: string;
|
|
30
33
|
latestVersion?: string;
|
|
31
34
|
latestCheckFailed?: boolean;
|
|
35
|
+
minimumVersion?: string;
|
|
36
|
+
compatibilityIssue?: string;
|
|
32
37
|
action?: {
|
|
33
38
|
kind: AgentMaintenanceKind;
|
|
34
39
|
command: string;
|
|
@@ -100,6 +105,15 @@ async function inspectCodex(
|
|
|
100
105
|
const installedVersion = commandVersion(installed);
|
|
101
106
|
if (!installedVersion) return missingInspection("codex", "Codex", platform, env);
|
|
102
107
|
|
|
108
|
+
const compatibilityIssue = codexVersionIssue(installedVersion);
|
|
109
|
+
if (compatibilityIssue) {
|
|
110
|
+
return {
|
|
111
|
+
id: "codex", name: "Codex", state: "outdated", installedVersion,
|
|
112
|
+
minimumVersion: MINIMUM_CODEX_VERSION, compatibilityIssue,
|
|
113
|
+
action: maintenanceAction("codex", "upgrade", platform, env),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
103
117
|
const latest = await run(capturedCommand(
|
|
104
118
|
"npm",
|
|
105
119
|
["view", CODEX_PACKAGE, "version", "--json"],
|
|
@@ -294,46 +308,6 @@ function versionInText(value: string): string | undefined {
|
|
|
294
308
|
return /\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)\b/u.exec(value)?.[1];
|
|
295
309
|
}
|
|
296
310
|
|
|
297
|
-
export function compareSemanticVersions(left: string, right: string): number {
|
|
298
|
-
const leftVersion = parseSemanticVersion(left);
|
|
299
|
-
const rightVersion = parseSemanticVersion(right);
|
|
300
|
-
if (!leftVersion || !rightVersion) return 0;
|
|
301
|
-
for (let index = 0; index < 3; index += 1) {
|
|
302
|
-
const difference = leftVersion.core[index]! - rightVersion.core[index]!;
|
|
303
|
-
if (difference !== 0) return difference;
|
|
304
|
-
}
|
|
305
|
-
if (!leftVersion.prerelease && !rightVersion.prerelease) return 0;
|
|
306
|
-
if (!leftVersion.prerelease) return 1;
|
|
307
|
-
if (!rightVersion.prerelease) return -1;
|
|
308
|
-
const length = Math.max(leftVersion.prerelease.length, rightVersion.prerelease.length);
|
|
309
|
-
for (let index = 0; index < length; index += 1) {
|
|
310
|
-
const leftPart = leftVersion.prerelease[index];
|
|
311
|
-
const rightPart = rightVersion.prerelease[index];
|
|
312
|
-
if (leftPart === undefined) return -1;
|
|
313
|
-
if (rightPart === undefined) return 1;
|
|
314
|
-
if (leftPart === rightPart) continue;
|
|
315
|
-
const leftNumber = /^\d+$/u.test(leftPart) ? Number(leftPart) : undefined;
|
|
316
|
-
const rightNumber = /^\d+$/u.test(rightPart) ? Number(rightPart) : undefined;
|
|
317
|
-
if (leftNumber !== undefined && rightNumber !== undefined) return leftNumber - rightNumber;
|
|
318
|
-
if (leftNumber !== undefined) return -1;
|
|
319
|
-
if (rightNumber !== undefined) return 1;
|
|
320
|
-
return leftPart.localeCompare(rightPart);
|
|
321
|
-
}
|
|
322
|
-
return 0;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function parseSemanticVersion(value: string): {
|
|
326
|
-
core: [number, number, number];
|
|
327
|
-
prerelease?: string[];
|
|
328
|
-
} | undefined {
|
|
329
|
-
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(value);
|
|
330
|
-
if (!match) return undefined;
|
|
331
|
-
return {
|
|
332
|
-
core: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
333
|
-
...(match[4] ? { prerelease: match[4].split(".") } : {}),
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
311
|
export function runAgentCommand(command: AgentCommandSpec): Promise<AgentCommandResult> {
|
|
338
312
|
return new Promise((resolve) => {
|
|
339
313
|
const invocation = resolveInvocation(command);
|
|
@@ -12,7 +12,7 @@ export function selectableDefaultAgents(
|
|
|
12
12
|
const supported = new Map(inspections.map((inspection) => [inspection.id, inspection]));
|
|
13
13
|
return configured.flatMap((agent) => {
|
|
14
14
|
const inspection = supported.get(agent.name as SupportedAgentInspection["id"]);
|
|
15
|
-
if (!inspection?.installedVersion) return [];
|
|
15
|
+
if (!inspection?.installedVersion || inspection.compatibilityIssue) return [];
|
|
16
16
|
return [{
|
|
17
17
|
...agent,
|
|
18
18
|
installedVersion: inspection.installedVersion,
|
package/src/cli.ts
CHANGED
|
@@ -338,6 +338,9 @@ async function initCommand(
|
|
|
338
338
|
);
|
|
339
339
|
const configuredAgents = readConfiguredAgentSelection(paths.config.path).agents.map((agent) => agent.name);
|
|
340
340
|
const configuredAgentNames = new Set(configuredAgents);
|
|
341
|
+
for (const agent of inspectedAgents) {
|
|
342
|
+
if (configuredAgentNames.has(agent.id) && agent.compatibilityIssue) throw new Error(agent.compatibilityIssue);
|
|
343
|
+
}
|
|
341
344
|
const agents = inspectedAgents.map((agent) => ({
|
|
342
345
|
...agent,
|
|
343
346
|
configured: configuredAgentNames.has(agent.id),
|
|
@@ -554,6 +557,8 @@ async function configureAgentsAndDefault(
|
|
|
554
557
|
|
|
555
558
|
const choices = selectableDefaultAgents(configured.agents, inspections);
|
|
556
559
|
if (choices.length === 0) {
|
|
560
|
+
const issue = inspections.find((inspection) => inspection.compatibilityIssue)?.compatibilityIssue;
|
|
561
|
+
if (issue) throw new Error(issue);
|
|
557
562
|
throw new Error(cliText(
|
|
558
563
|
"No installed supported Agent was detected. Install Codex or TraeX, then run agentbot init again.",
|
|
559
564
|
"没有检测到已安装且受支持的 Agent。请安装 Codex 或 TraeX,然后重新运行 agentbot init。",
|
|
@@ -679,6 +684,10 @@ function printInteractiveAgentInspection(
|
|
|
679
684
|
inspection: SupportedAgentInspection,
|
|
680
685
|
ui: InitUi,
|
|
681
686
|
): void {
|
|
687
|
+
if (inspection.compatibilityIssue) {
|
|
688
|
+
ui.warn(inspection.compatibilityIssue);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
682
691
|
if (inspection.state === "missing") {
|
|
683
692
|
ui.info(cliText(
|
|
684
693
|
`${inspection.name}: not installed`,
|
|
@@ -710,6 +719,10 @@ function printAgentInspection(
|
|
|
710
719
|
inspection: SupportedAgentInspection,
|
|
711
720
|
output: NodeJS.WriteStream,
|
|
712
721
|
): void {
|
|
722
|
+
if (inspection.compatibilityIssue) {
|
|
723
|
+
output.write(` ${inspection.compatibilityIssue}\n`);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
713
726
|
if (inspection.state === "missing") {
|
|
714
727
|
output.write(cliText(
|
|
715
728
|
` ${inspection.name}: not installed\n`,
|
|
@@ -9,6 +9,16 @@ const TASK_COMPLETE = Buffer.from('"type":"task_complete"');
|
|
|
9
9
|
const TURN_ABORTED = Buffer.from('"type":"turn_aborted"');
|
|
10
10
|
const PATTERN_OVERLAP = Math.max(TASK_STARTED.length, TASK_COMPLETE.length, TURN_ABORTED.length) - 1;
|
|
11
11
|
|
|
12
|
+
interface RolloutState {
|
|
13
|
+
size: number;
|
|
14
|
+
mtimeMs: number;
|
|
15
|
+
ctimeMs: number;
|
|
16
|
+
ino: number;
|
|
17
|
+
dev: number;
|
|
18
|
+
tail: Buffer;
|
|
19
|
+
task: { active: boolean; turnId?: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
interface ThreadPathRow {
|
|
13
23
|
id: string;
|
|
14
24
|
rollout_path: string;
|
|
@@ -35,6 +45,7 @@ export interface CodexLocalThreadSettings {
|
|
|
35
45
|
* even while Codex Desktop is actively appending events to its rollout.
|
|
36
46
|
*/
|
|
37
47
|
export class CodexLocalActivityDetector {
|
|
48
|
+
private readonly rollouts = new Map<string, RolloutState>();
|
|
38
49
|
constructor(private readonly codexHome: string) {}
|
|
39
50
|
|
|
40
51
|
async activeThreadIds(threadIds: string[]): Promise<Set<string>> {
|
|
@@ -64,7 +75,7 @@ export class CodexLocalActivityDetector {
|
|
|
64
75
|
|
|
65
76
|
const active = new Map<string, string | undefined>();
|
|
66
77
|
for (const row of rows) {
|
|
67
|
-
const task = await latestTask(row.rollout_path);
|
|
78
|
+
const task = await latestTask(row.rollout_path, this.rollouts);
|
|
68
79
|
if (task.active) active.set(row.id, task.turnId);
|
|
69
80
|
}
|
|
70
81
|
return active;
|
|
@@ -104,16 +115,31 @@ export class CodexLocalActivityDetector {
|
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
|
|
107
|
-
async function latestTask(rolloutPath: string): Promise<{ active: boolean; turnId?: string }> {
|
|
118
|
+
async function latestTask(rolloutPath: string, cache: Map<string, RolloutState>): Promise<{ active: boolean; turnId?: string }> {
|
|
108
119
|
let file;
|
|
109
120
|
try {
|
|
110
121
|
file = await open(rolloutPath, "r");
|
|
111
|
-
const size =
|
|
122
|
+
const { size, mtimeMs, ctimeMs, ino, dev } = await file.stat();
|
|
123
|
+
const previous = cache.get(rolloutPath);
|
|
124
|
+
const sameFile = previous?.ino === ino && previous.dev === dev;
|
|
125
|
+
if (sameFile && previous.size === size && previous.mtimeMs === mtimeMs && previous.ctimeMs === ctimeMs) {
|
|
126
|
+
return previous.task;
|
|
127
|
+
}
|
|
128
|
+
let stopAt = 0;
|
|
129
|
+
let task: RolloutState["task"] = { active: false };
|
|
130
|
+
if (sameFile && size > previous.size) {
|
|
131
|
+
const boundary = Buffer.alloc(previous.tail.length);
|
|
132
|
+
await file.read(boundary, 0, boundary.length, previous.size - boundary.length);
|
|
133
|
+
if (boundary.equals(previous.tail)) {
|
|
134
|
+
stopAt = Math.max(0, previous.size - PATTERN_OVERLAP);
|
|
135
|
+
task = previous.task;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
112
138
|
let position = size;
|
|
113
139
|
let laterPrefix = Buffer.alloc(0);
|
|
114
140
|
|
|
115
|
-
while (position >
|
|
116
|
-
const length = Math.min(READ_CHUNK_SIZE, position);
|
|
141
|
+
while (position > stopAt) {
|
|
142
|
+
const length = Math.min(READ_CHUNK_SIZE, position - stopAt);
|
|
117
143
|
position -= length;
|
|
118
144
|
const chunk = Buffer.allocUnsafe(length);
|
|
119
145
|
await file.read(chunk, 0, length, position);
|
|
@@ -123,14 +149,20 @@ async function latestTask(rolloutPath: string): Promise<{ active: boolean; turnI
|
|
|
123
149
|
const abortedAt = searchable.lastIndexOf(TURN_ABORTED);
|
|
124
150
|
const terminalAt = Math.max(completedAt, abortedAt);
|
|
125
151
|
if (startedAt >= 0 || terminalAt >= 0) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
152
|
+
task = startedAt <= terminalAt ? { active: false }
|
|
153
|
+
: { active: true, turnId: await readTurnId(file, position + startedAt, size) };
|
|
154
|
+
break;
|
|
129
155
|
}
|
|
130
156
|
laterPrefix = chunk.subarray(0, Math.min(PATTERN_OVERLAP, chunk.length));
|
|
131
157
|
}
|
|
132
|
-
|
|
158
|
+
const tail = Buffer.alloc(Math.min(1_024, size));
|
|
159
|
+
await file.read(tail, 0, tail.length, size - tail.length);
|
|
160
|
+
cache.delete(rolloutPath);
|
|
161
|
+
cache.set(rolloutPath, { size, mtimeMs, ctimeMs, ino, dev, tail, task });
|
|
162
|
+
if (cache.size > 512) cache.delete(cache.keys().next().value!);
|
|
163
|
+
return task;
|
|
133
164
|
} catch {
|
|
165
|
+
cache.delete(rolloutPath);
|
|
134
166
|
return { active: false };
|
|
135
167
|
} finally {
|
|
136
168
|
await file?.close();
|
|
@@ -11,12 +11,15 @@ import {
|
|
|
11
11
|
} from "../runtime/agentEnvironment.js";
|
|
12
12
|
import type { AgentProcessInfo } from "../runtime/types.js";
|
|
13
13
|
import { spawnStdioCommand } from "../utils/spawnCommand.js";
|
|
14
|
+
import { assertSupportedCodexVersion } from "./CodexVersion.js";
|
|
14
15
|
|
|
15
16
|
export class CodexProcessManager implements AppServerClientProvider {
|
|
16
17
|
private static readonly RELEASE_TIMEOUT_MS = 5_000;
|
|
17
18
|
private client?: AppServerConnection;
|
|
18
19
|
private child?: ChildProcessWithoutNullStreams;
|
|
19
20
|
private version?: string;
|
|
21
|
+
private agentFamily?: "codex" | "traex";
|
|
22
|
+
private initializing?: Promise<AppServerClient>;
|
|
20
23
|
private readonly disconnectListeners = new Set<(error: Error) => void>();
|
|
21
24
|
|
|
22
25
|
constructor(
|
|
@@ -28,7 +31,18 @@ export class CodexProcessManager implements AppServerClientProvider {
|
|
|
28
31
|
) {}
|
|
29
32
|
|
|
30
33
|
async getClient(): Promise<AppServerClient> {
|
|
34
|
+
if (this.initializing) return this.initializing;
|
|
31
35
|
if (this.client) return this.client;
|
|
36
|
+
const initialization = this.startClient();
|
|
37
|
+
this.initializing = initialization;
|
|
38
|
+
try {
|
|
39
|
+
return await initialization;
|
|
40
|
+
} finally {
|
|
41
|
+
if (this.initializing === initialization) this.initializing = undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async startClient(): Promise<AppServerClient> {
|
|
32
46
|
this.version = undefined;
|
|
33
47
|
const environmentContext = this.environmentContext();
|
|
34
48
|
const child = spawnStdioCommand(
|
|
@@ -52,13 +66,25 @@ export class CodexProcessManager implements AppServerClientProvider {
|
|
|
52
66
|
const error = new Error(`App Server exited (code=${code ?? "null"}, signal=${signal ?? "null"}).`);
|
|
53
67
|
for (const listener of this.disconnectListeners) listener(error);
|
|
54
68
|
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
try {
|
|
70
|
+
const initializeResult = await client.request("initialize", {
|
|
71
|
+
clientInfo: { name: "agent-bot", title: "Agent Bot", version: "0.1.0" },
|
|
72
|
+
capabilities: { experimentalApi: true },
|
|
73
|
+
});
|
|
74
|
+
this.version = initializedAgentVersion(initializeResult);
|
|
75
|
+
const userAgent = initializeResult && typeof initializeResult === "object"
|
|
76
|
+
? (initializeResult as Record<string, unknown>).userAgent : undefined;
|
|
77
|
+
if (!this.getAgentFamily() && typeof userAgent === "string") {
|
|
78
|
+
if (/^codex(?:-cli)?[ /]/iu.test(userAgent)) this.agentFamily = "codex";
|
|
79
|
+
else if (/^trae(?:cli|x)?[ /]/iu.test(userAgent)) this.agentFamily = "traex";
|
|
80
|
+
}
|
|
81
|
+
if (this.getAgentFamily() === "codex") assertSupportedCodexVersion(this.version);
|
|
82
|
+
client.notify("initialized", {});
|
|
83
|
+
return client;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (this.client === client) this.close();
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
62
88
|
}
|
|
63
89
|
|
|
64
90
|
getProcessInfo(): AgentProcessInfo {
|
|
@@ -70,6 +96,17 @@ export class CodexProcessManager implements AppServerClientProvider {
|
|
|
70
96
|
};
|
|
71
97
|
}
|
|
72
98
|
|
|
99
|
+
getAgentFamily(): "codex" | "traex" | undefined {
|
|
100
|
+
if (this.agentFamily) return this.agentFamily;
|
|
101
|
+
const candidates = [this.command, this.args[0], this.environmentContext().agentName];
|
|
102
|
+
for (const value of candidates) {
|
|
103
|
+
const executable = value?.replaceAll("\\", "/").split("/").at(-1) ?? "";
|
|
104
|
+
if (/^codex(?:\.(?:exe|cmd|bat|m?js))?$/iu.test(executable)) return "codex";
|
|
105
|
+
if (/^(?:traex|traecli)(?:\.(?:exe|cmd|bat|m?js))?$/iu.test(executable)) return "traex";
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
73
110
|
getCodexHome(): string {
|
|
74
111
|
return this.env.CODEX_HOME ?? process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
|
|
75
112
|
}
|