@keyou007/agent-bot 0.1.10 → 0.1.12
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/.env.example +1 -1
- package/CHANGELOG.md +25 -0
- package/README.md +55 -43
- package/README.zh.md +55 -43
- package/assets/agent-bot-logo.png +0 -0
- package/config.example.yaml +11 -1
- package/dist/acp/AcpSessionManager.js +78 -46
- package/dist/acp/AcpSessionManager.js.map +1 -1
- package/dist/cli/FeishuAppConfiguration.js +1 -1
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuAppRegistration.js +2 -2
- package/dist/cli/FeishuAppRegistration.js.map +1 -1
- package/dist/cli/Initializer.js +1 -1
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/ServerStarter.js +7 -2
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +20 -16
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +95 -0
- package/dist/cli/taskGroupOptions.js.map +1 -0
- package/dist/cli.js +74 -2
- package/dist/cli.js.map +1 -1
- package/dist/codex/AppServerConnection.js +15 -1
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexLocalActivityDetector.js +4 -1
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +1 -0
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +112 -11
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +64 -49
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/config/loadConfig.js +11 -1
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +6 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/console/ConsoleConnector.js +1 -1
- package/dist/console/ConsoleConnector.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +15 -8
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/deprecated-cli.js +5 -0
- package/dist/deprecated-cli.js.map +1 -0
- package/dist/feishu/CardRenderer.js +350 -15
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +51 -5
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +17 -8
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/transport.js +1 -1
- package/dist/feishu/transport.js.map +1 -1
- package/dist/index.js +38 -19
- package/dist/index.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +2 -2
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +2 -1
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +665 -225
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +1 -0
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/runtime/AgentRuntimeRegistry.js +7 -4
- package/dist/runtime/AgentRuntimeRegistry.js.map +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.js +22 -0
- package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -0
- package/dist/startup/SessionMetadataHydrator.js +2 -2
- package/dist/startup/SessionMetadataHydrator.js.map +1 -1
- package/dist/startup/StartupNotifier.js +1 -0
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/state/StateStore.js +21 -15
- package/dist/state/StateStore.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +5 -0
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/supervision/systemEnvironment.js +129 -0
- package/dist/supervision/systemEnvironment.js.map +1 -0
- package/dist/supervisor.js +13 -1
- package/dist/supervisor.js.map +1 -1
- package/docs/technical-reference.md +35 -21
- package/docs/technical-reference.zh.md +35 -21
- package/npm-shrinkwrap.json +4 -3
- package/package.json +4 -2
- package/skills/agent-bot/SKILL.md +73 -30
- package/src/acp/AcpSessionManager.ts +73 -54
- package/src/cli/FeishuAppConfiguration.ts +2 -2
- package/src/cli/FeishuAppRegistration.ts +4 -4
- package/src/cli/Initializer.ts +2 -2
- package/src/cli/ServerStarter.ts +8 -3
- package/src/cli/controlProtocol.ts +21 -1
- package/src/cli/help.ts +20 -16
- package/src/cli/taskGroupOptions.ts +149 -0
- package/src/cli.ts +98 -4
- package/src/codex/AppServerConnection.ts +21 -1
- package/src/codex/CodexLocalActivityDetector.ts +8 -1
- package/src/codex/CodexProcessManager.ts +1 -0
- package/src/codex/CodexRuntime.ts +137 -15
- package/src/commands/CommandRouter.ts +71 -49
- package/src/commands/commandTypes.ts +6 -11
- package/src/config/loadConfig.ts +11 -1
- package/src/config/schema.ts +7 -1
- package/src/console/ConsoleConnector.ts +1 -1
- package/src/console/ConsoleTurnPresenter.ts +21 -8
- package/src/deprecated-cli.ts +9 -0
- package/src/feishu/CardRenderer.ts +423 -15
- package/src/feishu/FeishuMessageClient.ts +66 -3
- package/src/feishu/FeishuTurnPresenter.ts +22 -3
- package/src/feishu/transport.ts +1 -1
- package/src/index.ts +54 -24
- package/src/presentation/OutboundRouter.ts +15 -3
- package/src/presentation/TurnStateReducer.ts +2 -0
- package/src/presentation/turnViewTypes.ts +1 -0
- package/src/proxy/ProxySessionController.ts +870 -228
- package/src/runtime/AcpRuntimeAdapter.ts +1 -0
- package/src/runtime/AgentRuntimeRegistry.ts +7 -6
- package/src/runtime/createAgentRuntimeRegistry.ts +25 -0
- package/src/runtime/types.ts +18 -0
- package/src/startup/SessionMetadataHydrator.ts +2 -2
- package/src/startup/StartupNotifier.ts +1 -0
- package/src/state/StateStore.ts +29 -15
- package/src/supervision/SafeRestartScheduler.ts +5 -0
- package/src/supervision/systemEnvironment.ts +174 -0
- package/src/supervisor.ts +12 -1
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import type { AgentConfig } from "../config/schema.js";
|
|
2
1
|
import type { AgentRuntime, RuntimeKind } from "./types.js";
|
|
3
2
|
|
|
4
3
|
export class AgentRuntimeRegistry {
|
|
5
|
-
constructor(private readonly runtimes: Record<
|
|
4
|
+
constructor(private readonly runtimes: Record<string, AgentRuntime>) {}
|
|
6
5
|
|
|
7
|
-
forAgent(
|
|
8
|
-
|
|
6
|
+
forAgent(agentName: string): AgentRuntime {
|
|
7
|
+
const runtime = this.runtimes[agentName];
|
|
8
|
+
if (!runtime) throw new Error(`Unknown agent runtime: ${agentName}`);
|
|
9
|
+
return runtime;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
return this.runtimes[kind
|
|
12
|
+
entries(kind?: RuntimeKind): Array<[agentName: string, runtime: AgentRuntime]> {
|
|
13
|
+
return Object.entries(this.runtimes).filter(([, runtime]) => kind === undefined || runtime.kind === kind);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
close(): void {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import { AcpProcessManager } from "../acp/AcpProcessManager.js";
|
|
3
|
+
import { AcpSessionManager } from "../acp/AcpSessionManager.js";
|
|
4
|
+
import { CodexProcessManager } from "../codex/CodexProcessManager.js";
|
|
5
|
+
import { CodexRuntime } from "../codex/CodexRuntime.js";
|
|
6
|
+
import type { AppConfig } from "../config/schema.js";
|
|
7
|
+
import { AcpRuntimeAdapter } from "./AcpRuntimeAdapter.js";
|
|
8
|
+
import { AgentRuntimeRegistry } from "./AgentRuntimeRegistry.js";
|
|
9
|
+
import type { AgentRuntime } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export function createAgentRuntimeRegistry(config: AppConfig, logger: Logger): AgentRuntimeRegistry {
|
|
12
|
+
const runtimes: Record<string, AgentRuntime> = {};
|
|
13
|
+
for (const [agentName, agent] of Object.entries(config.agents)) {
|
|
14
|
+
const runtimeLogger = logger.child({ agentName, runtimeKind: agent.kind });
|
|
15
|
+
if (agent.kind === "app-server") {
|
|
16
|
+
const processManager = new CodexProcessManager(agent.command, agent.args, agent.env, runtimeLogger);
|
|
17
|
+
runtimes[agentName] = new CodexRuntime(processManager, runtimeLogger);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const processManager = new AcpProcessManager(runtimeLogger);
|
|
21
|
+
const sessionManager = new AcpSessionManager(agentName, agent, processManager, runtimeLogger);
|
|
22
|
+
runtimes[agentName] = new AcpRuntimeAdapter(sessionManager);
|
|
23
|
+
}
|
|
24
|
+
return new AgentRuntimeRegistry(runtimes);
|
|
25
|
+
}
|
package/src/runtime/types.ts
CHANGED
|
@@ -109,6 +109,7 @@ export interface RemoteSessionSummary {
|
|
|
109
109
|
cwd: string;
|
|
110
110
|
source: string;
|
|
111
111
|
status: RemoteSessionStatus;
|
|
112
|
+
modelProvider?: string;
|
|
112
113
|
model?: string;
|
|
113
114
|
reasoningEffort?: string;
|
|
114
115
|
permissionMode?: PermissionMode;
|
|
@@ -144,6 +145,7 @@ export interface RuntimeSession {
|
|
|
144
145
|
agentName: string;
|
|
145
146
|
cwd: string;
|
|
146
147
|
title?: string;
|
|
148
|
+
modelProvider?: string;
|
|
147
149
|
model?: string;
|
|
148
150
|
reasoningEffort?: string;
|
|
149
151
|
permissionMode: PermissionMode;
|
|
@@ -155,6 +157,7 @@ export interface CreateRuntimeSessionInput {
|
|
|
155
157
|
agentName: string;
|
|
156
158
|
cwd: string;
|
|
157
159
|
title?: string;
|
|
160
|
+
modelProvider?: string;
|
|
158
161
|
model?: string;
|
|
159
162
|
reasoningEffort?: string;
|
|
160
163
|
permissionMode: PermissionMode;
|
|
@@ -180,6 +183,19 @@ export interface ModelOption {
|
|
|
180
183
|
defaultReasoningEffort?: string;
|
|
181
184
|
}
|
|
182
185
|
|
|
186
|
+
export interface ModelProviderOption {
|
|
187
|
+
id: string;
|
|
188
|
+
displayName?: string;
|
|
189
|
+
isDefault?: boolean;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface RuntimeExecutionSettings {
|
|
193
|
+
modelProvider: string;
|
|
194
|
+
model: string;
|
|
195
|
+
reasoningEffort: string;
|
|
196
|
+
permissionMode: PermissionMode;
|
|
197
|
+
}
|
|
198
|
+
|
|
183
199
|
export interface ReasoningEffortOption {
|
|
184
200
|
value: string;
|
|
185
201
|
description?: string;
|
|
@@ -208,8 +224,10 @@ export interface AgentRuntime {
|
|
|
208
224
|
setModel(sessionId: string, model: string): Promise<void>;
|
|
209
225
|
setReasoningEffort(sessionId: string, effort: string): Promise<void>;
|
|
210
226
|
setPermissionMode(sessionId: string, mode: PermissionMode): Promise<void>;
|
|
227
|
+
setExecutionSettings?(sessionId: string, settings: RuntimeExecutionSettings): Promise<RuntimeSession>;
|
|
211
228
|
respondToApproval(sessionId: string, requestId: string, decision: ApprovalDecision): Promise<void>;
|
|
212
229
|
listModels(): Promise<ModelOption[]>;
|
|
230
|
+
listModelProviders?(): Promise<ModelProviderOption[]>;
|
|
213
231
|
onEvent(listener: (event: RuntimeEvent) => void): () => void;
|
|
214
232
|
close(): void;
|
|
215
233
|
}
|
|
@@ -16,7 +16,7 @@ export class SessionMetadataHydrator {
|
|
|
16
16
|
&& session.status === "running"
|
|
17
17
|
&& session.lastTurnStatus === "running"
|
|
18
18
|
) {
|
|
19
|
-
const runtime = this.runtimes.
|
|
19
|
+
const runtime = this.runtimes.forAgent(session.agentName);
|
|
20
20
|
const loaded = runtime.getSession(session.localSessionId) ?? await runtime.resumeSession({
|
|
21
21
|
localSessionId: session.localSessionId,
|
|
22
22
|
remoteSessionId: session.remoteSessionId,
|
|
@@ -38,7 +38,7 @@ export class SessionMetadataHydrator {
|
|
|
38
38
|
}
|
|
39
39
|
if (session.title || !session.runtimeKind || !session.remoteSessionId) return session;
|
|
40
40
|
if (session.runtimeKind === "codex" && !session.lastTurnId) return session;
|
|
41
|
-
const metadata = await this.runtimes.
|
|
41
|
+
const metadata = await this.runtimes.forAgent(session.agentName).readSessionMetadata(session.remoteSessionId);
|
|
42
42
|
const title = normalizeTaskTitle(metadata.title);
|
|
43
43
|
if (title) this.store.updateRuntimeSession(session.localSessionId, { title });
|
|
44
44
|
return this.store.getSession(session.localSessionId) ?? session;
|
|
@@ -84,6 +84,7 @@ export class StartupNotifier {
|
|
|
84
84
|
? {
|
|
85
85
|
id: session.remoteSessionId ?? session.localSessionId,
|
|
86
86
|
title: session.title,
|
|
87
|
+
modelProvider: session.modelProvider,
|
|
87
88
|
model: session.model,
|
|
88
89
|
reasoningEffort: session.reasoningEffort,
|
|
89
90
|
permissionMode: session.permissionMode,
|
package/src/state/StateStore.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface SessionRecord {
|
|
|
32
32
|
runtimeKind?: "acp" | "codex";
|
|
33
33
|
remoteSessionId?: string;
|
|
34
34
|
title?: string;
|
|
35
|
+
modelProvider?: string;
|
|
35
36
|
model?: string;
|
|
36
37
|
reasoningEffort?: string;
|
|
37
38
|
permissionMode?: "auto" | "confirm";
|
|
@@ -100,6 +101,7 @@ interface SessionRow {
|
|
|
100
101
|
runtime_kind: "acp" | "codex" | null;
|
|
101
102
|
remote_session_id: string | null;
|
|
102
103
|
title: string | null;
|
|
104
|
+
model_provider: string | null;
|
|
103
105
|
model: string | null;
|
|
104
106
|
reasoning_effort: string | null;
|
|
105
107
|
permission_mode: "auto" | "confirm" | null;
|
|
@@ -149,10 +151,11 @@ export class StateStore {
|
|
|
149
151
|
this.ensureTurnSnapshotColumns();
|
|
150
152
|
this.ensureChatContextColumns();
|
|
151
153
|
this.initializeContextSessionMappings();
|
|
154
|
+
this.db.exec("DROP INDEX IF EXISTS idx_sessions_remote_session_id_unique");
|
|
152
155
|
this.reconcileDuplicateRemoteSessions();
|
|
153
156
|
this.db.exec(`
|
|
154
|
-
CREATE UNIQUE INDEX IF NOT EXISTS
|
|
155
|
-
ON sessions(remote_session_id)
|
|
157
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_agent_remote_session_unique
|
|
158
|
+
ON sessions(agent_name, remote_session_id)
|
|
156
159
|
WHERE remote_session_id IS NOT NULL
|
|
157
160
|
`);
|
|
158
161
|
this.db.exec(`
|
|
@@ -520,7 +523,7 @@ export class StateStore {
|
|
|
520
523
|
patch: Partial<
|
|
521
524
|
Pick<
|
|
522
525
|
SessionRecord,
|
|
523
|
-
"runtimeKind" | "remoteSessionId" | "title" | "model" | "reasoningEffort" | "permissionMode" | "lastTurnId" | "lastTurnStatus"
|
|
526
|
+
"runtimeKind" | "remoteSessionId" | "title" | "modelProvider" | "model" | "reasoningEffort" | "permissionMode" | "lastTurnId" | "lastTurnStatus"
|
|
524
527
|
>
|
|
525
528
|
>,
|
|
526
529
|
): void {
|
|
@@ -533,7 +536,7 @@ export class StateStore {
|
|
|
533
536
|
.prepare(
|
|
534
537
|
`
|
|
535
538
|
UPDATE sessions
|
|
536
|
-
SET runtime_kind = ?, remote_session_id = ?, title = ?, model = ?, reasoning_effort = ?, permission_mode = ?,
|
|
539
|
+
SET runtime_kind = ?, remote_session_id = ?, title = ?, model_provider = ?, model = ?, reasoning_effort = ?, permission_mode = ?,
|
|
537
540
|
last_turn_id = ?, last_turn_status = ?, updated_at = ?
|
|
538
541
|
WHERE local_session_id = ?
|
|
539
542
|
`,
|
|
@@ -542,6 +545,7 @@ export class StateStore {
|
|
|
542
545
|
patch.runtimeKind ?? existing.runtimeKind ?? null,
|
|
543
546
|
patch.remoteSessionId ?? existing.remoteSessionId ?? existing.acpSessionId ?? null,
|
|
544
547
|
patch.title ?? existing.title ?? null,
|
|
548
|
+
patch.modelProvider ?? existing.modelProvider ?? null,
|
|
545
549
|
patch.model ?? existing.model ?? null,
|
|
546
550
|
patch.reasoningEffort ?? existing.reasoningEffort ?? null,
|
|
547
551
|
patch.permissionMode ?? existing.permissionMode ?? null,
|
|
@@ -814,7 +818,11 @@ export class StateStore {
|
|
|
814
818
|
};
|
|
815
819
|
}
|
|
816
820
|
|
|
817
|
-
findSessionByRemoteSessionId(
|
|
821
|
+
findSessionByRemoteSessionId(
|
|
822
|
+
remoteSessionId: string,
|
|
823
|
+
contextKey?: string,
|
|
824
|
+
agentName?: string,
|
|
825
|
+
): SessionRecord | undefined {
|
|
818
826
|
const row = contextKey
|
|
819
827
|
? this.db
|
|
820
828
|
.prepare(`
|
|
@@ -823,17 +831,20 @@ export class StateStore {
|
|
|
823
831
|
JOIN context_sessions ON context_sessions.local_session_id = sessions.local_session_id
|
|
824
832
|
WHERE sessions.remote_session_id = ?
|
|
825
833
|
AND context_sessions.context_key = ?
|
|
834
|
+
AND (? IS NULL OR sessions.agent_name = ?)
|
|
826
835
|
AND sessions.status != 'closed'
|
|
827
836
|
LIMIT 1
|
|
828
837
|
`)
|
|
829
|
-
.get(remoteSessionId, contextKey) as SessionRow | undefined
|
|
838
|
+
.get(remoteSessionId, contextKey, agentName ?? null, agentName ?? null) as SessionRow | undefined
|
|
830
839
|
: this.db
|
|
831
840
|
.prepare(`
|
|
832
841
|
SELECT * FROM sessions
|
|
833
|
-
WHERE remote_session_id = ?
|
|
842
|
+
WHERE remote_session_id = ?
|
|
843
|
+
AND (? IS NULL OR agent_name = ?)
|
|
844
|
+
AND status != 'closed'
|
|
834
845
|
ORDER BY created_at ASC LIMIT 1
|
|
835
846
|
`)
|
|
836
|
-
.get(remoteSessionId) as SessionRow | undefined;
|
|
847
|
+
.get(remoteSessionId, agentName ?? null, agentName ?? null) as SessionRow | undefined;
|
|
837
848
|
return row ? { ...mapSession(row), ...(contextKey ? { contextKey } : {}) } : undefined;
|
|
838
849
|
}
|
|
839
850
|
|
|
@@ -1034,21 +1045,21 @@ export class StateStore {
|
|
|
1034
1045
|
|
|
1035
1046
|
private reconcileDuplicateRemoteSessions(): void {
|
|
1036
1047
|
const duplicateIds = this.db.prepare(`
|
|
1037
|
-
SELECT remote_session_id
|
|
1048
|
+
SELECT agent_name, remote_session_id
|
|
1038
1049
|
FROM sessions
|
|
1039
1050
|
WHERE remote_session_id IS NOT NULL
|
|
1040
|
-
GROUP BY remote_session_id
|
|
1051
|
+
GROUP BY agent_name, remote_session_id
|
|
1041
1052
|
HAVING count(*) > 1
|
|
1042
|
-
`).all() as Array<{ remote_session_id: string }>;
|
|
1053
|
+
`).all() as Array<{ agent_name: string; remote_session_id: string }>;
|
|
1043
1054
|
if (duplicateIds.length === 0) return;
|
|
1044
1055
|
|
|
1045
1056
|
const reconcile = this.db.transaction(() => {
|
|
1046
|
-
for (const { remote_session_id: remoteSessionId } of duplicateIds) {
|
|
1057
|
+
for (const { agent_name: agentName, remote_session_id: remoteSessionId } of duplicateIds) {
|
|
1047
1058
|
const rows = this.db.prepare(`
|
|
1048
1059
|
SELECT * FROM sessions
|
|
1049
|
-
WHERE remote_session_id = ?
|
|
1060
|
+
WHERE agent_name = ? AND remote_session_id = ?
|
|
1050
1061
|
ORDER BY created_at ASC
|
|
1051
|
-
`).all(remoteSessionId) as SessionRow[];
|
|
1062
|
+
`).all(agentName, remoteSessionId) as SessionRow[];
|
|
1052
1063
|
const canonical = rows[0];
|
|
1053
1064
|
if (!canonical) continue;
|
|
1054
1065
|
const latest = [...rows].sort((left, right) => right.updated_at.localeCompare(left.updated_at))[0] ?? canonical;
|
|
@@ -1086,7 +1097,7 @@ export class StateStore {
|
|
|
1086
1097
|
|
|
1087
1098
|
this.db.prepare(`
|
|
1088
1099
|
UPDATE sessions
|
|
1089
|
-
SET agent_name = ?, cwd = ?, acp_session_id = ?, runtime_kind = ?, title = ?, model = ?,
|
|
1100
|
+
SET agent_name = ?, cwd = ?, acp_session_id = ?, runtime_kind = ?, title = ?, model_provider = ?, model = ?,
|
|
1090
1101
|
reasoning_effort = ?, permission_mode = ?, last_turn_id = ?, last_turn_status = ?,
|
|
1091
1102
|
status = ?, updated_at = ?
|
|
1092
1103
|
WHERE local_session_id = ?
|
|
@@ -1096,6 +1107,7 @@ export class StateStore {
|
|
|
1096
1107
|
latest.acp_session_id,
|
|
1097
1108
|
latest.runtime_kind,
|
|
1098
1109
|
latest.title,
|
|
1110
|
+
latest.model_provider,
|
|
1099
1111
|
latest.model,
|
|
1100
1112
|
latest.reasoning_effort,
|
|
1101
1113
|
latest.permission_mode,
|
|
@@ -1124,6 +1136,7 @@ export class StateStore {
|
|
|
1124
1136
|
["runtime_kind", "TEXT"],
|
|
1125
1137
|
["remote_session_id", "TEXT"],
|
|
1126
1138
|
["title", "TEXT"],
|
|
1139
|
+
["model_provider", "TEXT"],
|
|
1127
1140
|
["model", "TEXT"],
|
|
1128
1141
|
["reasoning_effort", "TEXT"],
|
|
1129
1142
|
["permission_mode", "TEXT"],
|
|
@@ -1206,6 +1219,7 @@ function mapSession(row: SessionRow): SessionRecord {
|
|
|
1206
1219
|
runtimeKind: row.runtime_kind ?? (row.acp_session_id ? "acp" : undefined),
|
|
1207
1220
|
remoteSessionId: row.remote_session_id ?? row.acp_session_id ?? undefined,
|
|
1208
1221
|
title: row.title ?? undefined,
|
|
1222
|
+
modelProvider: row.model_provider ?? undefined,
|
|
1209
1223
|
model: row.model ?? undefined,
|
|
1210
1224
|
reasoningEffort: row.reasoning_effort ?? undefined,
|
|
1211
1225
|
permissionMode: row.permission_mode ?? undefined,
|
|
@@ -64,6 +64,11 @@ export class SafeRestartScheduler {
|
|
|
64
64
|
this.clear();
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
async cancelCurrent(): Promise<boolean> {
|
|
68
|
+
if (!this.reason) return false;
|
|
69
|
+
return this.cancelScheduled(this.scheduleId);
|
|
70
|
+
}
|
|
71
|
+
|
|
67
72
|
async cancelScheduled(expectedScheduleId: number): Promise<boolean> {
|
|
68
73
|
const reason = this.reason;
|
|
69
74
|
if (!reason || this.scheduleId !== expectedScheduleId) return false;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface WindowsEnvironmentSnapshot {
|
|
5
|
+
machine: Record<string, string>;
|
|
6
|
+
user: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface EnvironmentRefreshResult {
|
|
10
|
+
environment: NodeJS.ProcessEnv;
|
|
11
|
+
refreshed: boolean;
|
|
12
|
+
pathChanged: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type WindowsEnvironmentLoader = () => WindowsEnvironmentSnapshot;
|
|
17
|
+
|
|
18
|
+
const PROCESS_LOCAL_ENVIRONMENT_PREFIXES = ["AGENT_BOT_", "FEISHU_"];
|
|
19
|
+
|
|
20
|
+
export function refreshedSystemEnvironment(
|
|
21
|
+
inherited: NodeJS.ProcessEnv = process.env,
|
|
22
|
+
platform: NodeJS.Platform = process.platform,
|
|
23
|
+
loadWindowsEnvironment: WindowsEnvironmentLoader = readWindowsEnvironment,
|
|
24
|
+
): EnvironmentRefreshResult {
|
|
25
|
+
const fallback = { ...inherited };
|
|
26
|
+
if (platform !== "win32") {
|
|
27
|
+
return { environment: fallback, refreshed: false, pathChanged: false };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const snapshot = loadWindowsEnvironment();
|
|
32
|
+
const environment = mergeWindowsEnvironment(inherited, snapshot);
|
|
33
|
+
return {
|
|
34
|
+
environment,
|
|
35
|
+
refreshed: true,
|
|
36
|
+
pathChanged: environmentValue(environment, "Path") !== environmentValue(inherited, "Path"),
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
environment: fallback,
|
|
41
|
+
refreshed: false,
|
|
42
|
+
pathChanged: false,
|
|
43
|
+
error: error instanceof Error ? error.message : String(error),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function mergeWindowsEnvironment(
|
|
49
|
+
inherited: NodeJS.ProcessEnv,
|
|
50
|
+
snapshot: WindowsEnvironmentSnapshot,
|
|
51
|
+
): NodeJS.ProcessEnv {
|
|
52
|
+
const environment = { ...inherited };
|
|
53
|
+
applyEnvironment(environment, snapshot.machine);
|
|
54
|
+
applyEnvironment(environment, snapshot.user);
|
|
55
|
+
|
|
56
|
+
const machinePath = environmentValue(snapshot.machine, "Path")?.trim();
|
|
57
|
+
const userPath = environmentValue(snapshot.user, "Path")?.trim();
|
|
58
|
+
if (machinePath || userPath) {
|
|
59
|
+
const inheritedPath = environmentValue(inherited, "Path")?.trim();
|
|
60
|
+
setEnvironmentValue(
|
|
61
|
+
environment,
|
|
62
|
+
"Path",
|
|
63
|
+
mergePathValues(
|
|
64
|
+
expandEnvironmentReferences(
|
|
65
|
+
[machinePath, userPath, inheritedPath].filter(Boolean).join(";"),
|
|
66
|
+
environment,
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return environment;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function readWindowsEnvironment(): WindowsEnvironmentSnapshot {
|
|
75
|
+
const systemRoot = process.env.SystemRoot ?? "C:\\Windows";
|
|
76
|
+
const executable = path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
77
|
+
const script = [
|
|
78
|
+
"[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)",
|
|
79
|
+
"$machine = @{}",
|
|
80
|
+
"foreach ($entry in [Environment]::GetEnvironmentVariables([EnvironmentVariableTarget]::Machine).GetEnumerator()) { $machine[[string]$entry.Key] = [string]$entry.Value }",
|
|
81
|
+
"$user = @{}",
|
|
82
|
+
"foreach ($entry in [Environment]::GetEnvironmentVariables([EnvironmentVariableTarget]::User).GetEnumerator()) { $user[[string]$entry.Key] = [string]$entry.Value }",
|
|
83
|
+
"[pscustomobject]@{ machine = $machine; user = $user } | ConvertTo-Json -Compress -Depth 3",
|
|
84
|
+
].join("; ");
|
|
85
|
+
const result = spawnSync(executable, [
|
|
86
|
+
"-NoLogo",
|
|
87
|
+
"-NoProfile",
|
|
88
|
+
"-NonInteractive",
|
|
89
|
+
"-ExecutionPolicy",
|
|
90
|
+
"Bypass",
|
|
91
|
+
"-Command",
|
|
92
|
+
script,
|
|
93
|
+
], {
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
windowsHide: true,
|
|
96
|
+
timeout: 5_000,
|
|
97
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
98
|
+
});
|
|
99
|
+
if (result.error) throw result.error;
|
|
100
|
+
if (result.status !== 0) {
|
|
101
|
+
throw new Error(result.stderr.trim() || `PowerShell exited with code ${result.status ?? "unknown"}.`);
|
|
102
|
+
}
|
|
103
|
+
const parsed = JSON.parse(result.stdout) as Partial<WindowsEnvironmentSnapshot>;
|
|
104
|
+
return {
|
|
105
|
+
machine: environmentRecord(parsed.machine),
|
|
106
|
+
user: environmentRecord(parsed.user),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function environmentRecord(value: unknown): Record<string, string> {
|
|
111
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
112
|
+
return Object.fromEntries(
|
|
113
|
+
Object.entries(value).flatMap(([name, entry]) =>
|
|
114
|
+
typeof entry === "string" ? [[name, entry]] : []),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function applyEnvironment(
|
|
119
|
+
target: NodeJS.ProcessEnv,
|
|
120
|
+
source: Record<string, string>,
|
|
121
|
+
): void {
|
|
122
|
+
for (const [name, value] of Object.entries(source)) {
|
|
123
|
+
if (name.toLowerCase() === "path" || isProcessLocalEnvironmentName(name)) continue;
|
|
124
|
+
setEnvironmentValue(target, name, value);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isProcessLocalEnvironmentName(name: string): boolean {
|
|
129
|
+
const normalized = name.toUpperCase();
|
|
130
|
+
return PROCESS_LOCAL_ENVIRONMENT_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function setEnvironmentValue(environment: NodeJS.ProcessEnv, name: string, value: string): void {
|
|
134
|
+
for (const existingName of Object.keys(environment)) {
|
|
135
|
+
if (existingName !== name && existingName.toLowerCase() === name.toLowerCase()) {
|
|
136
|
+
delete environment[existingName];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
environment[name] = value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function environmentValue(
|
|
143
|
+
environment: NodeJS.ProcessEnv | Record<string, string>,
|
|
144
|
+
name: string,
|
|
145
|
+
): string | undefined {
|
|
146
|
+
const key = Object.keys(environment).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
147
|
+
return key === undefined ? undefined : environment[key];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function expandEnvironmentReferences(value: string, environment: NodeJS.ProcessEnv): string {
|
|
151
|
+
let expanded = value;
|
|
152
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
153
|
+
const next = expanded.replace(/%([^%]+)%/g, (match, name: string) =>
|
|
154
|
+
environmentValue(environment, name) ?? match);
|
|
155
|
+
if (next === expanded) break;
|
|
156
|
+
expanded = next;
|
|
157
|
+
}
|
|
158
|
+
return expanded;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function mergePathValues(value: string): string {
|
|
162
|
+
const seen = new Set<string>();
|
|
163
|
+
return value
|
|
164
|
+
.split(";")
|
|
165
|
+
.map((entry) => entry.trim())
|
|
166
|
+
.filter((entry) => {
|
|
167
|
+
if (!entry) return false;
|
|
168
|
+
const normalized = entry.replace(/[\\/]+$/, "").toLowerCase();
|
|
169
|
+
if (seen.has(normalized)) return false;
|
|
170
|
+
seen.add(normalized);
|
|
171
|
+
return true;
|
|
172
|
+
})
|
|
173
|
+
.join(";");
|
|
174
|
+
}
|
package/src/supervisor.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
STOP_EXIT_CODE,
|
|
14
14
|
} from "./supervision/restartPolicy.js";
|
|
15
15
|
import { SupervisorDiagnostics, nodeDiagnosticReportArguments } from "./supervision/SupervisorDiagnostics.js";
|
|
16
|
+
import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
|
|
16
17
|
|
|
17
18
|
const childEntry = fileURLToPath(new URL("./index.js", import.meta.url));
|
|
18
19
|
const config = loadConfig();
|
|
@@ -38,13 +39,23 @@ async function startChild(): Promise<void> {
|
|
|
38
39
|
const restartReason = nextStartReason;
|
|
39
40
|
nextStartReason = "Supervisor 重新拉起进程";
|
|
40
41
|
const workerStderr = diagnostics.openWorkerStderr();
|
|
42
|
+
const environmentRefresh = refreshedSystemEnvironment();
|
|
43
|
+
if (environmentRefresh.error) {
|
|
44
|
+
writeSupervisorLog("environment_refresh_failed", { error: environmentRefresh.error });
|
|
45
|
+
} else if (environmentRefresh.refreshed) {
|
|
46
|
+
writeSupervisorLog("environment_refreshed", { pathChanged: environmentRefresh.pathChanged });
|
|
47
|
+
}
|
|
41
48
|
try {
|
|
42
49
|
child = spawn(process.execPath, [
|
|
43
50
|
...nodeDiagnosticReportArguments(diagnostics.paths.crashReportDirectory),
|
|
44
51
|
childEntry,
|
|
45
52
|
], {
|
|
46
53
|
cwd: process.cwd(),
|
|
47
|
-
env: {
|
|
54
|
+
env: {
|
|
55
|
+
...environmentRefresh.environment,
|
|
56
|
+
AGENT_BOT_SUPERVISED: "1",
|
|
57
|
+
AGENT_BOT_RESTART_REASON: restartReason,
|
|
58
|
+
},
|
|
48
59
|
stdio: ["ignore", "ignore", workerStderr],
|
|
49
60
|
windowsHide: true,
|
|
50
61
|
});
|