@keyou007/agent-bot 0.1.16 → 0.1.17-alpha.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/CHANGELOG.md +8 -0
- package/README.md +4 -2
- package/README.zh.md +4 -2
- package/config.example.yaml +8 -0
- package/dist/cli/help.js +4 -4
- package/dist/cli/taskTarget.js +7 -0
- package/dist/cli/taskTarget.js.map +1 -1
- package/dist/cli.js +12 -12
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexRuntime.js +40 -12
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/config/AgentExecutionDefaultsStore.js +71 -0
- package/dist/config/AgentExecutionDefaultsStore.js.map +1 -0
- package/dist/config/loadConfig.js +8 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +7 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/feishu/CardRenderer.js +29 -27
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +255 -30
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +62 -9
- package/dist/state/StateStore.js.map +1 -1
- package/dist/supervision/restartNotificationTarget.js +9 -0
- package/dist/supervision/restartNotificationTarget.js.map +1 -0
- package/docs/technical-reference.md +7 -3
- package/docs/technical-reference.zh.md +7 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +3 -1
- package/src/cli/help.ts +4 -4
- package/src/cli/taskTarget.ts +11 -0
- package/src/cli.ts +16 -20
- package/src/codex/CodexRuntime.ts +38 -10
- package/src/config/AgentExecutionDefaultsStore.ts +71 -0
- package/src/config/loadConfig.ts +8 -0
- package/src/config/schema.ts +13 -1
- package/src/feishu/CardRenderer.ts +30 -22
- package/src/index.ts +17 -2
- package/src/proxy/ProxySessionController.ts +330 -40
- package/src/runtime/types.ts +8 -0
- package/src/state/StateStore.ts +100 -9
- package/src/supervision/restartNotificationTarget.ts +12 -0
|
@@ -1112,6 +1112,18 @@ function remoteSessionSummary(thread: CodexThreadSnapshot): RemoteSessionSummary
|
|
|
1112
1112
|
const lastText = [...(lastTurn?.items ?? [])]
|
|
1113
1113
|
.reverse()
|
|
1114
1114
|
.find((item) => typeof item.text === "string" && item.text.trim())?.text?.trim();
|
|
1115
|
+
const completedTurns = (thread.turns ?? []).flatMap((turn) => {
|
|
1116
|
+
if (turn.status !== "completed") return [];
|
|
1117
|
+
const startedAt = remoteTurnStartedAt(turn);
|
|
1118
|
+
return [{
|
|
1119
|
+
id: turn.id,
|
|
1120
|
+
prompt: extractTurnUserPrompt(turn),
|
|
1121
|
+
startedAt,
|
|
1122
|
+
completedAt: startedAt === undefined
|
|
1123
|
+
? undefined
|
|
1124
|
+
: startedAt + Math.max(0, turn.durationMs ?? 0),
|
|
1125
|
+
}];
|
|
1126
|
+
});
|
|
1115
1127
|
return {
|
|
1116
1128
|
id: thread.id,
|
|
1117
1129
|
title: normalizeTaskTitle(thread.name) ?? normalizeTaskTitle(thread.preview),
|
|
@@ -1136,25 +1148,41 @@ function remoteSessionSummary(thread: CodexThreadSnapshot): RemoteSessionSummary
|
|
|
1136
1148
|
lastTurnCompletedToolCount: toolCounts?.completed,
|
|
1137
1149
|
lastTurnFailedToolCount: toolCounts?.failed,
|
|
1138
1150
|
lastTurnRunningToolCount: toolCounts?.running,
|
|
1151
|
+
completedTurns,
|
|
1139
1152
|
};
|
|
1140
1153
|
}
|
|
1141
1154
|
|
|
1142
1155
|
function extractLastUserPrompt(turns: CodexTurnSnapshot[] | undefined): string | undefined {
|
|
1143
1156
|
for (const turn of [...(turns ?? [])].reverse()) {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1157
|
+
const prompt = extractTurnUserPrompt(turn);
|
|
1158
|
+
if (prompt) return prompt;
|
|
1159
|
+
}
|
|
1160
|
+
return undefined;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function extractTurnUserPrompt(turn: CodexTurnSnapshot): string | undefined {
|
|
1164
|
+
for (const item of [...(turn.items ?? [])].reverse()) {
|
|
1165
|
+
if (item.type !== "userMessage") continue;
|
|
1166
|
+
const text = item.content
|
|
1167
|
+
?.filter((content) => content.type === "text" && typeof content.text === "string")
|
|
1168
|
+
.map((content) => content.text!.trim())
|
|
1169
|
+
.filter(Boolean)
|
|
1170
|
+
.join("\n\n")
|
|
1171
|
+
|| item.text?.trim();
|
|
1172
|
+
if (text) return text;
|
|
1154
1173
|
}
|
|
1155
1174
|
return undefined;
|
|
1156
1175
|
}
|
|
1157
1176
|
|
|
1177
|
+
function remoteTurnStartedAt(turn: CodexTurnSnapshot): number | undefined {
|
|
1178
|
+
return remoteTimestampMs(turn.startedAt ?? undefined);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function remoteTimestampMs(value: number | undefined): number | undefined {
|
|
1182
|
+
if (value === undefined || !Number.isFinite(value)) return undefined;
|
|
1183
|
+
return value >= 10_000_000_000 ? value : value * 1_000;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1158
1186
|
function summarizeTurnTools(turn: CodexTurnSnapshot): {
|
|
1159
1187
|
total: number;
|
|
1160
1188
|
completed: number;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isMap, parseDocument } from "yaml";
|
|
4
|
+
import type { AgentExecutionDefaults } from "./schema.js";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_KEYS = ["modelProvider", "model", "reasoningEffort", "permissionMode"] as const;
|
|
7
|
+
|
|
8
|
+
export function writeAgentExecutionDefaults(
|
|
9
|
+
configPath: string,
|
|
10
|
+
agentName: string,
|
|
11
|
+
defaults: AgentExecutionDefaults,
|
|
12
|
+
): boolean {
|
|
13
|
+
const original = fs.readFileSync(configPath, "utf8");
|
|
14
|
+
const document = parseDocument(original);
|
|
15
|
+
if (document.errors.length > 0) {
|
|
16
|
+
throw new Error(`Could not update configuration file ${configPath}: ${document.errors[0]?.message ?? "invalid YAML"}`);
|
|
17
|
+
}
|
|
18
|
+
if (!isMap(document.contents)) {
|
|
19
|
+
throw new Error(`Configuration file must contain a YAML mapping: ${configPath}`);
|
|
20
|
+
}
|
|
21
|
+
const agent = document.getIn(["agents", agentName], true);
|
|
22
|
+
if (!isMap(agent)) {
|
|
23
|
+
throw new Error(`Cannot save defaults for an Agent that is not configured: ${agentName}`);
|
|
24
|
+
}
|
|
25
|
+
const existingDefaults = agent.get("defaults", true);
|
|
26
|
+
if (existingDefaults !== undefined && !isMap(existingDefaults)) {
|
|
27
|
+
throw new Error(`Configuration agents.${agentName}.defaults must contain a YAML mapping: ${configPath}`);
|
|
28
|
+
}
|
|
29
|
+
if (!isMap(existingDefaults)) agent.set("defaults", {});
|
|
30
|
+
const target = agent.get("defaults", true);
|
|
31
|
+
if (!isMap(target)) throw new Error(`Could not create defaults for Agent: ${agentName}`);
|
|
32
|
+
|
|
33
|
+
for (const key of DEFAULT_KEYS) {
|
|
34
|
+
const value = defaults[key];
|
|
35
|
+
if (value !== undefined) target.set(key, value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const updated = document.toString();
|
|
39
|
+
if (updated === original) return false;
|
|
40
|
+
writeFileAtomically(configPath, updated);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function writeFileAtomically(filePath: string, contents: string): void {
|
|
45
|
+
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
46
|
+
let temporaryFile: number | undefined;
|
|
47
|
+
try {
|
|
48
|
+
temporaryFile = fs.openSync(temporaryPath, "wx", fs.statSync(filePath).mode);
|
|
49
|
+
fs.writeFileSync(temporaryFile, contents, "utf8");
|
|
50
|
+
fs.fsyncSync(temporaryFile);
|
|
51
|
+
fs.closeSync(temporaryFile);
|
|
52
|
+
temporaryFile = undefined;
|
|
53
|
+
fs.renameSync(temporaryPath, filePath);
|
|
54
|
+
syncDirectory(path.dirname(filePath));
|
|
55
|
+
} finally {
|
|
56
|
+
if (temporaryFile !== undefined) fs.closeSync(temporaryFile);
|
|
57
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function syncDirectory(directory: string): void {
|
|
62
|
+
let handle: number | undefined;
|
|
63
|
+
try {
|
|
64
|
+
handle = fs.openSync(directory, "r");
|
|
65
|
+
fs.fsyncSync(handle);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (process.platform !== "win32") throw error;
|
|
68
|
+
} finally {
|
|
69
|
+
if (handle !== undefined) fs.closeSync(handle);
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/config/loadConfig.ts
CHANGED
|
@@ -38,6 +38,12 @@ agents:
|
|
|
38
38
|
- "--listen"
|
|
39
39
|
- "stdio://"
|
|
40
40
|
env: {}
|
|
41
|
+
# Updated automatically by the Provider, Model, Thinking, and Permission settings.
|
|
42
|
+
defaults:
|
|
43
|
+
# modelProvider: "openai"
|
|
44
|
+
# model: "gpt-5"
|
|
45
|
+
# reasoningEffort: "high"
|
|
46
|
+
permissionMode: "auto"
|
|
41
47
|
|
|
42
48
|
traex:
|
|
43
49
|
kind: "app-server"
|
|
@@ -48,6 +54,8 @@ agents:
|
|
|
48
54
|
- "--listen"
|
|
49
55
|
- "stdio://"
|
|
50
56
|
env: {}
|
|
57
|
+
defaults:
|
|
58
|
+
permissionMode: "auto"
|
|
51
59
|
|
|
52
60
|
defaults:
|
|
53
61
|
agent: "codex"
|
package/src/config/schema.ts
CHANGED
|
@@ -40,12 +40,20 @@ const agentKindSchema = z.union([
|
|
|
40
40
|
z.literal("codex").transform(() => "app-server" as const),
|
|
41
41
|
]).default("acp");
|
|
42
42
|
|
|
43
|
+
export const agentExecutionDefaultsSchema = z.object({
|
|
44
|
+
modelProvider: z.string().min(1).optional(),
|
|
45
|
+
model: z.string().min(1).optional(),
|
|
46
|
+
reasoningEffort: z.string().min(1).optional(),
|
|
47
|
+
permissionMode: z.enum(["auto", "confirm"]).optional(),
|
|
48
|
+
}).default({});
|
|
49
|
+
|
|
43
50
|
export const agentConfigSchema = z.object({
|
|
44
51
|
kind: agentKindSchema,
|
|
45
52
|
title: z.string().min(1),
|
|
46
53
|
command: z.string().min(1),
|
|
47
54
|
args: z.array(z.string()).default([]),
|
|
48
55
|
env: envRecordSchema,
|
|
56
|
+
defaults: agentExecutionDefaultsSchema,
|
|
49
57
|
});
|
|
50
58
|
|
|
51
59
|
export const appConfigSchema = z.object({
|
|
@@ -84,6 +92,10 @@ export const appConfigSchema = z.object({
|
|
|
84
92
|
}).default({ level: "info", path: DEFAULT_LOG_PATH }),
|
|
85
93
|
});
|
|
86
94
|
|
|
87
|
-
export type
|
|
95
|
+
export type AgentExecutionDefaults = z.infer<typeof agentExecutionDefaultsSchema>;
|
|
96
|
+
type ParsedAgentConfig = z.infer<typeof agentConfigSchema>;
|
|
97
|
+
export type AgentConfig = Omit<ParsedAgentConfig, "defaults"> & {
|
|
98
|
+
defaults?: AgentExecutionDefaults;
|
|
99
|
+
};
|
|
88
100
|
export type GroupNameFormatConfig = z.infer<typeof groupNameFormatSchema>;
|
|
89
101
|
export type AppConfig = z.infer<typeof appConfigSchema>;
|
|
@@ -225,7 +225,9 @@ export interface ResetHistoryCardEntry extends TaskListCardEntry {
|
|
|
225
225
|
sequence: number;
|
|
226
226
|
graphNodeLine: string;
|
|
227
227
|
graphConnectorLine?: string;
|
|
228
|
+
timestamp?: string;
|
|
228
229
|
running?: boolean;
|
|
230
|
+
resetting?: boolean;
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
export interface ResetHistoryCardView {
|
|
@@ -1328,9 +1330,11 @@ export class CardRenderer {
|
|
|
1328
1330
|
entry.graphNodeLine,
|
|
1329
1331
|
entry.graphConnectorLine,
|
|
1330
1332
|
entry.lines,
|
|
1333
|
+
entry.timestamp,
|
|
1331
1334
|
action,
|
|
1332
1335
|
entry.current === true,
|
|
1333
1336
|
entry.running === true,
|
|
1337
|
+
entry.resetting === true,
|
|
1334
1338
|
));
|
|
1335
1339
|
});
|
|
1336
1340
|
}
|
|
@@ -2415,15 +2419,8 @@ function inlineCode(value: string): string {
|
|
|
2415
2419
|
}
|
|
2416
2420
|
|
|
2417
2421
|
function formatStartupTime(value: Date): string {
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
month: "2-digit",
|
|
2421
|
-
day: "2-digit",
|
|
2422
|
-
hour: "2-digit",
|
|
2423
|
-
minute: "2-digit",
|
|
2424
|
-
second: "2-digit",
|
|
2425
|
-
hourCycle: "h23",
|
|
2426
|
-
}).format(value);
|
|
2422
|
+
const pad = (part: number) => String(part).padStart(2, "0");
|
|
2423
|
+
return `${value.getFullYear()}/${pad(value.getMonth() + 1)}/${pad(value.getDate())} ${pad(value.getHours())}:${pad(value.getMinutes())}:${pad(value.getSeconds())}`;
|
|
2427
2424
|
}
|
|
2428
2425
|
|
|
2429
2426
|
function permissionModeLabel(mode?: PermissionMode): string {
|
|
@@ -2633,16 +2630,30 @@ function resetHistoryEntryRow(
|
|
|
2633
2630
|
graphNodeLine: string,
|
|
2634
2631
|
graphConnectorLine: string | undefined,
|
|
2635
2632
|
lines: string[],
|
|
2633
|
+
timestamp: string | undefined,
|
|
2636
2634
|
action: TaskListCardAction | undefined,
|
|
2637
2635
|
current: boolean,
|
|
2638
2636
|
running: boolean,
|
|
2637
|
+
resetting: boolean,
|
|
2639
2638
|
): Record<string, unknown> {
|
|
2639
|
+
const branchConnectorLine = graphConnectorLine && /[╱╲]/u.test(graphConnectorLine)
|
|
2640
|
+
? graphConnectorLine
|
|
2641
|
+
: undefined;
|
|
2640
2642
|
const graph = [
|
|
2641
|
-
`<font color='${current ? "green" : running ? "orange" : "blue"}'>${escapeCardHtml(graphNodeLine)}</font>`,
|
|
2642
|
-
...(
|
|
2643
|
-
? [`<font color='grey'>${escapeCardHtml(
|
|
2643
|
+
`<font color='${current ? "green" : running || resetting ? "orange" : "blue"}'>${escapeCardHtml(graphNodeLine)}</font>`,
|
|
2644
|
+
...(branchConnectorLine
|
|
2645
|
+
? [`<font color='grey'>${escapeCardHtml(branchConnectorLine)}</font>`]
|
|
2644
2646
|
: []),
|
|
2645
2647
|
].join("\n");
|
|
2648
|
+
const trailingElements = current
|
|
2649
|
+
? [markdown("✅ 当前")]
|
|
2650
|
+
: resetting
|
|
2651
|
+
? [markdown("⏳ 正在 Reset")]
|
|
2652
|
+
: running
|
|
2653
|
+
? [markdown("⏳ 运行中")]
|
|
2654
|
+
: action
|
|
2655
|
+
? [taskActionElement(action)]
|
|
2656
|
+
: [];
|
|
2646
2657
|
return {
|
|
2647
2658
|
tag: "column_set",
|
|
2648
2659
|
flex_mode: "none",
|
|
@@ -2664,25 +2675,22 @@ function resetHistoryEntryRow(
|
|
|
2664
2675
|
weight: 1,
|
|
2665
2676
|
vertical_align: "top",
|
|
2666
2677
|
elements: [
|
|
2667
|
-
markdown(
|
|
2678
|
+
markdown([
|
|
2679
|
+
lines[0] ?? "",
|
|
2680
|
+
...(timestamp ? [`<font color='grey'>${escapeCardHtml(timestamp)}</font>`] : []),
|
|
2681
|
+
].join(" ")),
|
|
2668
2682
|
...lines.slice(1).map((line) => ({
|
|
2669
2683
|
...markdown(`<font color='grey'>${line}</font>`),
|
|
2670
2684
|
text_size: "notation",
|
|
2671
2685
|
})),
|
|
2672
2686
|
],
|
|
2673
2687
|
},
|
|
2674
|
-
{
|
|
2688
|
+
...(trailingElements.length > 0 ? [{
|
|
2675
2689
|
tag: "column",
|
|
2676
2690
|
width: "auto",
|
|
2677
2691
|
vertical_align: "top",
|
|
2678
|
-
elements:
|
|
2679
|
-
|
|
2680
|
-
: running
|
|
2681
|
-
? [markdown("⏳ 运行中")]
|
|
2682
|
-
: action
|
|
2683
|
-
? [taskActionElement(action)]
|
|
2684
|
-
: [],
|
|
2685
|
-
},
|
|
2692
|
+
elements: trailingElements,
|
|
2693
|
+
}] : []),
|
|
2686
2694
|
],
|
|
2687
2695
|
};
|
|
2688
2696
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { controlEndpoint, type ControlRequest, type ControlResponse } from "./cl
|
|
|
6
6
|
import { readPackageVersion } from "./cli/packageVersion.js";
|
|
7
7
|
import { loadConfig } from "./config/loadConfig.js";
|
|
8
8
|
import { persistFeishuUserOpenIdIfMissing } from "./config/FeishuUserOpenIdStore.js";
|
|
9
|
+
import { writeAgentExecutionDefaults } from "./config/AgentExecutionDefaultsStore.js";
|
|
9
10
|
import {
|
|
10
11
|
agentBotHome,
|
|
11
12
|
defaultConfigPath,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
import { SafeRestartScheduler } from "./supervision/SafeRestartScheduler.js";
|
|
43
44
|
import type { RestartNotificationTarget } from "./supervision/SafeRestartScheduler.js";
|
|
44
45
|
import { SafeRestartNotifier } from "./supervision/SafeRestartNotifier.js";
|
|
46
|
+
import { privateRestartNotificationTarget } from "./supervision/restartNotificationTarget.js";
|
|
45
47
|
import {
|
|
46
48
|
nodeDiagnosticReportArguments,
|
|
47
49
|
prepareCrashReportDirectory,
|
|
@@ -72,9 +74,10 @@ const logger = createLogger(config);
|
|
|
72
74
|
const store = new StateStore(config.storage.sqlitePath);
|
|
73
75
|
|
|
74
76
|
const configuredConfigPath = process.env.AGENT_BOT_CONFIG?.trim();
|
|
77
|
+
const activeConfigPath = configuredConfigPath ? resolveUserPath(configuredConfigPath) : defaultConfigPath();
|
|
75
78
|
const agentEnvironmentContext: AgentEnvironmentContext = {
|
|
76
79
|
profilePath: agentBotHome(),
|
|
77
|
-
configPath:
|
|
80
|
+
configPath: activeConfigPath,
|
|
78
81
|
larkAppId: config.feishu.appId,
|
|
79
82
|
larkUserOpenId: config.feishu.userOpenId,
|
|
80
83
|
};
|
|
@@ -156,6 +159,10 @@ const controller = new ProxySessionController(config, store, runtimes, outbound,
|
|
|
156
159
|
"Stored the default Lark user Open ID from the first private chat message.",
|
|
157
160
|
);
|
|
158
161
|
},
|
|
162
|
+
persistAgentExecutionDefaults: (agentName, defaults) => {
|
|
163
|
+
const changed = writeAgentExecutionDefaults(activeConfigPath, agentName, defaults);
|
|
164
|
+
logger.info({ agentName, changed }, "Stored Agent execution defaults.");
|
|
165
|
+
},
|
|
159
166
|
});
|
|
160
167
|
|
|
161
168
|
if (feishuOutbound) {
|
|
@@ -362,7 +369,7 @@ async function handleControlRequest(request: ControlRequest): Promise<ControlRes
|
|
|
362
369
|
if (pendingSelfUpdatePlanPath) {
|
|
363
370
|
return { ok: false, message: "An Agent Bot update is already pending." };
|
|
364
371
|
}
|
|
365
|
-
const notificationTarget =
|
|
372
|
+
const notificationTarget = inferServerRestartTarget(request.notificationSessionId);
|
|
366
373
|
if (request.mode === "safe") {
|
|
367
374
|
const newlyScheduled = safeRestart.schedule(request.reason, notificationTarget);
|
|
368
375
|
return {
|
|
@@ -571,6 +578,14 @@ function inferControlRestartTarget(notificationSessionId?: string): RestartNotif
|
|
|
571
578
|
return session ? restartNotificationTargetForSession(session) : undefined;
|
|
572
579
|
}
|
|
573
580
|
|
|
581
|
+
function inferServerRestartTarget(notificationSessionId?: string): RestartNotificationTarget {
|
|
582
|
+
if (notificationSessionId?.trim()) {
|
|
583
|
+
const target = inferControlRestartTarget(notificationSessionId);
|
|
584
|
+
if (target) return target;
|
|
585
|
+
}
|
|
586
|
+
return privateRestartNotificationTarget(config.feishu.userOpenId);
|
|
587
|
+
}
|
|
588
|
+
|
|
574
589
|
function restartNotificationTargetForSession(
|
|
575
590
|
session: { localSessionId: string; contextKey: string },
|
|
576
591
|
): RestartNotificationTarget {
|