@keyou007/agent-bot 0.1.9 → 0.1.11
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 +23 -0
- package/README.md +22 -10
- package/README.zh.md +22 -10
- package/assets/agent-bot-logo.png +0 -0
- package/dist/acp/AcpSessionManager.js +78 -46
- package/dist/acp/AcpSessionManager.js.map +1 -1
- package/dist/cli/FeishuAppConfiguration.js +84 -22
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuAppRegistration.js +12 -9
- package/dist/cli/FeishuAppRegistration.js.map +1 -1
- package/dist/cli/Initializer.js +7 -6
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/LocalControlClient.js +3 -2
- package/dist/cli/LocalControlClient.js.map +1 -1
- package/dist/cli/LocalControlServer.js +2 -1
- package/dist/cli/LocalControlServer.js.map +1 -1
- package/dist/cli/OptionalAuthorizationSkip.js +27 -6
- package/dist/cli/OptionalAuthorizationSkip.js.map +1 -1
- package/dist/cli/ServerStarter.js +12 -3
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/SkillRegistry.js +5 -4
- package/dist/cli/SkillRegistry.js.map +1 -1
- package/dist/cli/VerificationOutput.js +2 -1
- package/dist/cli/VerificationOutput.js.map +1 -1
- package/dist/cli/help.js +48 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/i18n.js +27 -0
- package/dist/cli/i18n.js.map +1 -0
- package/dist/cli/initOptions.js +5 -4
- package/dist/cli/initOptions.js.map +1 -1
- package/dist/cli/packageVersion.js +2 -1
- package/dist/cli/packageVersion.js.map +1 -1
- package/dist/cli/profile.js +4 -3
- package/dist/cli/profile.js.map +1 -1
- package/dist/cli/serverStatus.js +15 -14
- package/dist/cli/serverStatus.js.map +1 -1
- package/dist/cli/taskChatRoute.js +3 -2
- package/dist/cli/taskChatRoute.js.map +1 -1
- package/dist/cli.js +165 -107
- 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 +77 -2
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +108 -47
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +1 -1
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +345 -11
- 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 +8 -3
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/index.js +18 -17
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +595 -205
- 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/docs/technical-reference.md +27 -6
- package/docs/technical-reference.zh.md +27 -6
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/skills/agent-bot/SKILL.md +19 -9
- package/src/acp/AcpSessionManager.ts +73 -54
- package/src/cli/FeishuAppConfiguration.ts +130 -18
- package/src/cli/FeishuAppRegistration.ts +24 -9
- package/src/cli/Initializer.ts +25 -6
- package/src/cli/LocalControlClient.ts +9 -2
- package/src/cli/LocalControlServer.ts +2 -1
- package/src/cli/OptionalAuthorizationSkip.ts +51 -6
- package/src/cli/ServerStarter.ts +20 -3
- package/src/cli/SkillRegistry.ts +17 -4
- package/src/cli/VerificationOutput.ts +4 -1
- package/src/cli/help.ts +50 -1
- package/src/cli/i18n.ts +38 -0
- package/src/cli/initOptions.ts +17 -4
- package/src/cli/packageVersion.ts +5 -1
- package/src/cli/profile.ts +13 -3
- package/src/cli/serverStatus.ts +19 -14
- package/src/cli/taskChatRoute.ts +9 -2
- package/src/cli.ts +290 -103
- 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 +91 -2
- package/src/commands/CommandRouter.ts +123 -46
- package/src/commands/commandTypes.ts +6 -11
- package/src/console/ConsoleTurnPresenter.ts +1 -1
- package/src/feishu/CardRenderer.ts +418 -11
- package/src/feishu/FeishuMessageClient.ts +66 -3
- package/src/feishu/FeishuTurnPresenter.ts +7 -2
- package/src/index.ts +21 -22
- package/src/proxy/ProxySessionController.ts +712 -208
- 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
|
@@ -5,6 +5,7 @@ import type { RuntimeSession } from "../acp/AcpSessionManager.js";
|
|
|
5
5
|
import type {
|
|
6
6
|
ApprovalDecision,
|
|
7
7
|
ModelOption,
|
|
8
|
+
ModelProviderOption,
|
|
8
9
|
PermissionMode,
|
|
9
10
|
ReasoningEffortOption,
|
|
10
11
|
ToolState,
|
|
@@ -24,6 +25,7 @@ export interface StartupStatusView {
|
|
|
24
25
|
currentTask?: {
|
|
25
26
|
id: string;
|
|
26
27
|
title?: string;
|
|
28
|
+
modelProvider?: string;
|
|
27
29
|
model?: string;
|
|
28
30
|
reasoningEffort?: string;
|
|
29
31
|
permissionMode?: PermissionMode;
|
|
@@ -60,6 +62,9 @@ export interface ModelSelectorCardView {
|
|
|
60
62
|
currentModel?: string;
|
|
61
63
|
reasoningEffort?: string;
|
|
62
64
|
models: ModelOption[];
|
|
65
|
+
modelProvider?: string;
|
|
66
|
+
permissionMode?: PermissionMode;
|
|
67
|
+
unifiedSettings?: boolean;
|
|
63
68
|
notice?: string;
|
|
64
69
|
}
|
|
65
70
|
|
|
@@ -69,6 +74,55 @@ export interface ReasoningSelectorCardView {
|
|
|
69
74
|
model: string;
|
|
70
75
|
currentEffort?: string;
|
|
71
76
|
options: ReasoningEffortOption[];
|
|
77
|
+
modelProvider?: string;
|
|
78
|
+
permissionMode?: PermissionMode;
|
|
79
|
+
unifiedSettings?: boolean;
|
|
80
|
+
notice?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ProviderSelectorCardView {
|
|
84
|
+
sessionId: string;
|
|
85
|
+
contextKey: string;
|
|
86
|
+
currentProvider?: string;
|
|
87
|
+
currentModel?: string;
|
|
88
|
+
reasoningEffort?: string;
|
|
89
|
+
permissionMode: PermissionMode;
|
|
90
|
+
providers: ModelProviderOption[];
|
|
91
|
+
notice?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface PermissionSelectorCardView {
|
|
95
|
+
sessionId: string;
|
|
96
|
+
contextKey: string;
|
|
97
|
+
modelProvider: string;
|
|
98
|
+
model: string;
|
|
99
|
+
reasoningEffort: string;
|
|
100
|
+
currentMode: PermissionMode;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type ExecutionSettingsTab = "agent" | "provider" | "model" | "thinking" | "permission";
|
|
104
|
+
|
|
105
|
+
export interface ExecutionSettingsAgentOption {
|
|
106
|
+
name: string;
|
|
107
|
+
title: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ExecutionSettingsCardView {
|
|
111
|
+
sessionId?: string;
|
|
112
|
+
contextKey: string;
|
|
113
|
+
activeTab: ExecutionSettingsTab;
|
|
114
|
+
currentAgent: string;
|
|
115
|
+
taskAgent?: string;
|
|
116
|
+
agents: ExecutionSettingsAgentOption[];
|
|
117
|
+
runtimeSettingsAvailable: boolean;
|
|
118
|
+
currentProvider?: string;
|
|
119
|
+
currentModel?: string;
|
|
120
|
+
currentEffort?: string;
|
|
121
|
+
currentPermissionMode: PermissionMode;
|
|
122
|
+
providers: ModelProviderOption[];
|
|
123
|
+
providerSupported: boolean;
|
|
124
|
+
models: ModelOption[];
|
|
125
|
+
reasoningOptions: ReasoningEffortOption[];
|
|
72
126
|
notice?: string;
|
|
73
127
|
}
|
|
74
128
|
|
|
@@ -91,9 +145,183 @@ export interface TaskListCardEntry {
|
|
|
91
145
|
}
|
|
92
146
|
|
|
93
147
|
export class CardRenderer {
|
|
148
|
+
renderExecutionSettings(view: ExecutionSettingsCardView): Record<string, unknown> {
|
|
149
|
+
const baseAction: Record<string, string> = {
|
|
150
|
+
contextKey: view.contextKey,
|
|
151
|
+
...(view.sessionId ? { sessionId: view.sessionId } : {}),
|
|
152
|
+
};
|
|
153
|
+
const tabRow = settingsTabRow(
|
|
154
|
+
view.activeTab,
|
|
155
|
+
baseAction,
|
|
156
|
+
view.agents.length > 1,
|
|
157
|
+
view.runtimeSettingsAvailable,
|
|
158
|
+
);
|
|
159
|
+
const elements: Record<string, unknown>[] = [
|
|
160
|
+
markdown([
|
|
161
|
+
view.taskAgent
|
|
162
|
+
? `**默认 Agent / 当前任务 Agent**:${inlineCode(view.currentAgent)} / ${inlineCode(view.taskAgent)}`
|
|
163
|
+
: `**默认 Agent**:${inlineCode(view.currentAgent)}`,
|
|
164
|
+
...(view.runtimeSettingsAvailable
|
|
165
|
+
? [
|
|
166
|
+
`**Provider / 模型**:${inlineCode(view.currentProvider ?? "Codex 默认")} / ${inlineCode(view.currentModel ?? "默认")}`,
|
|
167
|
+
`**思考强度 / 权限**:${inlineCode(view.currentEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.currentPermissionMode))}`,
|
|
168
|
+
]
|
|
169
|
+
: []),
|
|
170
|
+
...(view.notice ? [view.notice] : []),
|
|
171
|
+
].join("\n")),
|
|
172
|
+
...(tabRow ? [tabRow] : []),
|
|
173
|
+
{ tag: "hr" },
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
if (view.activeTab === "agent") {
|
|
177
|
+
elements.push(...view.agents.map((agent) => settingsOptionRow({
|
|
178
|
+
label: `${inlineCode(agent.name)} · ${escapeCardHtml(agent.title)}`,
|
|
179
|
+
current: agent.name === view.currentAgent,
|
|
180
|
+
action: {
|
|
181
|
+
text: "Switch",
|
|
182
|
+
value: {
|
|
183
|
+
action: "settings_agent_select",
|
|
184
|
+
...baseAction,
|
|
185
|
+
agent: agent.name,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
})));
|
|
189
|
+
} else if (view.activeTab === "provider") {
|
|
190
|
+
if (!view.providerSupported) {
|
|
191
|
+
elements.push(markdown("当前运行时不支持 Provider 切换;其他设置仍可通过上方 tab 修改。"));
|
|
192
|
+
} else if (view.providers.length === 0) {
|
|
193
|
+
elements.push(markdown("Codex 配置中没有可用的 Provider。"));
|
|
194
|
+
} else {
|
|
195
|
+
elements.push(...view.providers.map((provider) => settingsOptionRow({
|
|
196
|
+
label: [
|
|
197
|
+
inlineCode(provider.id),
|
|
198
|
+
provider.displayName && provider.displayName !== provider.id
|
|
199
|
+
? ` · ${escapeCardHtml(provider.displayName)}`
|
|
200
|
+
: "",
|
|
201
|
+
provider.isDefault ? " · 默认" : "",
|
|
202
|
+
].join(""),
|
|
203
|
+
current: provider.id === view.currentProvider,
|
|
204
|
+
action: {
|
|
205
|
+
text: "Switch",
|
|
206
|
+
value: {
|
|
207
|
+
action: "settings_provider_select",
|
|
208
|
+
...baseAction,
|
|
209
|
+
provider: provider.id,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
})));
|
|
213
|
+
}
|
|
214
|
+
} else if (view.activeTab === "model") {
|
|
215
|
+
if (view.models.length === 0) {
|
|
216
|
+
elements.push(markdown("当前运行时未返回可用模型。"));
|
|
217
|
+
} else {
|
|
218
|
+
elements.push(...view.models.map((model) => settingsOptionRow({
|
|
219
|
+
label: `${inlineCode(model.id)}${model.isDefault ? " · 默认" : ""}`,
|
|
220
|
+
current: model.id === view.currentModel,
|
|
221
|
+
action: {
|
|
222
|
+
text: "Switch",
|
|
223
|
+
value: {
|
|
224
|
+
action: "settings_model_select",
|
|
225
|
+
...baseAction,
|
|
226
|
+
model: model.id,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
})));
|
|
230
|
+
}
|
|
231
|
+
} else if (view.activeTab === "thinking") {
|
|
232
|
+
if (!view.currentModel) {
|
|
233
|
+
elements.push(markdown("请先选择模型。"));
|
|
234
|
+
} else if (view.reasoningOptions.length === 0) {
|
|
235
|
+
elements.push(markdown("当前模型没有可配置的思考强度。"));
|
|
236
|
+
} else {
|
|
237
|
+
const currentModel = view.currentModel;
|
|
238
|
+
elements.push(...view.reasoningOptions.map((option) => settingsOptionRow({
|
|
239
|
+
label: inlineCode(option.value),
|
|
240
|
+
current: option.value === view.currentEffort,
|
|
241
|
+
action: {
|
|
242
|
+
text: "Switch",
|
|
243
|
+
value: {
|
|
244
|
+
action: "settings_thinking_select",
|
|
245
|
+
...baseAction,
|
|
246
|
+
model: currentModel,
|
|
247
|
+
effort: option.value,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
})));
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
elements.push(...(["auto", "confirm"] as PermissionMode[]).map((mode) => settingsOptionRow({
|
|
254
|
+
label: `${inlineCode(mode)} · ${permissionModeLabel(mode)}`,
|
|
255
|
+
current: mode === view.currentPermissionMode,
|
|
256
|
+
action: {
|
|
257
|
+
text: "Switch",
|
|
258
|
+
value: {
|
|
259
|
+
action: "settings_permission_select",
|
|
260
|
+
...baseAction,
|
|
261
|
+
permissionMode: mode,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
})));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return sectionCard("运行设置", elements, view.notice ? "green" : "blue");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
renderProviderSelector(view: ProviderSelectorCardView): Record<string, unknown> {
|
|
271
|
+
const elements: Record<string, unknown>[] = [
|
|
272
|
+
markdown([
|
|
273
|
+
`**当前 Provider**:${inlineCode(view.currentProvider ?? "Codex 默认")}`,
|
|
274
|
+
`**模型 / 思考强度 / 权限**:${inlineCode(view.currentModel ?? "默认")} / ${inlineCode(view.reasoningEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.permissionMode))}`,
|
|
275
|
+
...(view.notice ? [view.notice] : []),
|
|
276
|
+
].join("\n")),
|
|
277
|
+
{ tag: "hr" },
|
|
278
|
+
];
|
|
279
|
+
if (view.providers.length === 0) {
|
|
280
|
+
elements.push(markdown("Codex 配置中没有可用的 Provider。"));
|
|
281
|
+
} else {
|
|
282
|
+
elements.push(...view.providers.map((provider) => ({
|
|
283
|
+
tag: "column_set",
|
|
284
|
+
flex_mode: "none",
|
|
285
|
+
horizontal_spacing: "8px",
|
|
286
|
+
vertical_align: "center",
|
|
287
|
+
columns: [
|
|
288
|
+
{
|
|
289
|
+
tag: "column",
|
|
290
|
+
width: "weighted",
|
|
291
|
+
weight: 1,
|
|
292
|
+
vertical_align: "center",
|
|
293
|
+
elements: [markdown([
|
|
294
|
+
inlineCode(provider.id),
|
|
295
|
+
provider.displayName && provider.displayName !== provider.id ? ` · ${escapeCardHtml(provider.displayName)}` : "",
|
|
296
|
+
provider.isDefault ? " · 默认" : "",
|
|
297
|
+
provider.id === view.currentProvider ? " · ✅ 当前" : "",
|
|
298
|
+
].join(""))],
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
tag: "column",
|
|
302
|
+
width: "auto",
|
|
303
|
+
vertical_align: "center",
|
|
304
|
+
elements: [taskActionElement({
|
|
305
|
+
text: provider.id === view.currentProvider ? "Configure" : "Select",
|
|
306
|
+
value: {
|
|
307
|
+
action: "provider_select",
|
|
308
|
+
sessionId: view.sessionId,
|
|
309
|
+
contextKey: view.contextKey,
|
|
310
|
+
provider: provider.id,
|
|
311
|
+
permissionMode: view.permissionMode,
|
|
312
|
+
},
|
|
313
|
+
})],
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
})));
|
|
317
|
+
}
|
|
318
|
+
return sectionCard("Provider 设置", elements, view.notice ? "green" : "blue");
|
|
319
|
+
}
|
|
320
|
+
|
|
94
321
|
renderReasoningSelector(view: ReasoningSelectorCardView): Record<string, unknown> {
|
|
95
322
|
const elements: Record<string, unknown>[] = [
|
|
96
323
|
markdown([
|
|
324
|
+
...(view.modelProvider ? [`**Provider**:${inlineCode(view.modelProvider)}`] : []),
|
|
97
325
|
`**模型**:${inlineCode(view.model)}`,
|
|
98
326
|
`**当前思考模式**:${inlineCode(view.currentEffort ?? "默认")}`,
|
|
99
327
|
...(view.notice ? [view.notice] : []),
|
|
@@ -122,16 +350,18 @@ export class CardRenderer {
|
|
|
122
350
|
tag: "column",
|
|
123
351
|
width: "auto",
|
|
124
352
|
vertical_align: "center",
|
|
125
|
-
elements: isCurrent
|
|
353
|
+
elements: isCurrent && !view.unifiedSettings
|
|
126
354
|
? [markdown("✅ 当前")]
|
|
127
355
|
: [taskActionElement({
|
|
128
|
-
text: "Switch",
|
|
356
|
+
text: view.unifiedSettings ? (isCurrent ? "Continue" : "Select") : "Switch",
|
|
129
357
|
value: {
|
|
130
|
-
action: "reasoning_select",
|
|
358
|
+
action: view.unifiedSettings ? "provider_reasoning_select" : "reasoning_select",
|
|
131
359
|
sessionId: view.sessionId,
|
|
132
360
|
contextKey: view.contextKey,
|
|
361
|
+
...(view.modelProvider ? { provider: view.modelProvider } : {}),
|
|
133
362
|
model: view.model,
|
|
134
363
|
effort: option.value,
|
|
364
|
+
...(view.permissionMode ? { permissionMode: view.permissionMode } : {}),
|
|
135
365
|
},
|
|
136
366
|
})],
|
|
137
367
|
},
|
|
@@ -144,9 +374,11 @@ export class CardRenderer {
|
|
|
144
374
|
taskActionRow([{
|
|
145
375
|
text: "Back",
|
|
146
376
|
value: {
|
|
147
|
-
action: "model_open",
|
|
377
|
+
action: view.unifiedSettings ? "provider_model_open" : "model_open",
|
|
148
378
|
sessionId: view.sessionId,
|
|
149
379
|
contextKey: view.contextKey,
|
|
380
|
+
...(view.modelProvider ? { provider: view.modelProvider } : {}),
|
|
381
|
+
...(view.permissionMode ? { permissionMode: view.permissionMode } : {}),
|
|
150
382
|
},
|
|
151
383
|
}]),
|
|
152
384
|
);
|
|
@@ -156,6 +388,7 @@ export class CardRenderer {
|
|
|
156
388
|
renderModelSelector(view: ModelSelectorCardView): Record<string, unknown> {
|
|
157
389
|
const elements: Record<string, unknown>[] = [
|
|
158
390
|
markdown([
|
|
391
|
+
...(view.modelProvider ? [`**Provider**:${inlineCode(view.modelProvider)}`] : []),
|
|
159
392
|
`**当前模型**:${inlineCode(view.currentModel ?? "默认")}`,
|
|
160
393
|
`**思考强度**:${inlineCode(view.reasoningEffort ?? "默认")}`,
|
|
161
394
|
...(view.notice ? [view.notice] : []),
|
|
@@ -186,15 +419,17 @@ export class CardRenderer {
|
|
|
186
419
|
tag: "column",
|
|
187
420
|
width: "auto",
|
|
188
421
|
vertical_align: "center",
|
|
189
|
-
elements: isCurrent
|
|
422
|
+
elements: isCurrent && !view.unifiedSettings
|
|
190
423
|
? [markdown("✅ 当前")]
|
|
191
424
|
: [taskActionElement({
|
|
192
|
-
text: "Switch",
|
|
425
|
+
text: view.unifiedSettings ? (isCurrent ? "Continue" : "Select") : "Switch",
|
|
193
426
|
value: {
|
|
194
|
-
action: "model_select",
|
|
427
|
+
action: view.unifiedSettings ? "provider_model_select" : "model_select",
|
|
195
428
|
sessionId: view.sessionId,
|
|
196
429
|
contextKey: view.contextKey,
|
|
430
|
+
...(view.modelProvider ? { provider: view.modelProvider } : {}),
|
|
197
431
|
model: model.id,
|
|
432
|
+
...(view.permissionMode ? { permissionMode: view.permissionMode } : {}),
|
|
198
433
|
},
|
|
199
434
|
})],
|
|
200
435
|
},
|
|
@@ -202,7 +437,77 @@ export class CardRenderer {
|
|
|
202
437
|
};
|
|
203
438
|
}));
|
|
204
439
|
}
|
|
205
|
-
|
|
440
|
+
if (view.unifiedSettings) {
|
|
441
|
+
elements.push(
|
|
442
|
+
{ tag: "hr" },
|
|
443
|
+
taskActionRow([{
|
|
444
|
+
text: "Back",
|
|
445
|
+
value: {
|
|
446
|
+
action: "provider_open",
|
|
447
|
+
sessionId: view.sessionId,
|
|
448
|
+
contextKey: view.contextKey,
|
|
449
|
+
},
|
|
450
|
+
}]),
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
return sectionCard(view.unifiedSettings ? "Provider 设置 · 模型" : "模型", elements, view.notice ? "green" : "blue");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
renderPermissionSelector(view: PermissionSelectorCardView): Record<string, unknown> {
|
|
457
|
+
const elements: Record<string, unknown>[] = [
|
|
458
|
+
markdown([
|
|
459
|
+
`**Provider**:${inlineCode(view.modelProvider)}`,
|
|
460
|
+
`**模型**:${inlineCode(view.model)}`,
|
|
461
|
+
`**思考强度**:${inlineCode(view.reasoningEffort)}`,
|
|
462
|
+
`**当前权限**:${inlineCode(permissionModeLabel(view.currentMode))}`,
|
|
463
|
+
].join("\n")),
|
|
464
|
+
{ tag: "hr" },
|
|
465
|
+
...(["auto", "confirm"] as PermissionMode[]).map((mode) => ({
|
|
466
|
+
tag: "column_set",
|
|
467
|
+
flex_mode: "none",
|
|
468
|
+
horizontal_spacing: "8px",
|
|
469
|
+
vertical_align: "center",
|
|
470
|
+
columns: [
|
|
471
|
+
{
|
|
472
|
+
tag: "column",
|
|
473
|
+
width: "weighted",
|
|
474
|
+
weight: 1,
|
|
475
|
+
vertical_align: "center",
|
|
476
|
+
elements: [markdown(`${inlineCode(mode)} · ${permissionModeLabel(mode)}`)],
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
tag: "column",
|
|
480
|
+
width: "auto",
|
|
481
|
+
vertical_align: "center",
|
|
482
|
+
elements: [taskActionElement({
|
|
483
|
+
text: mode === view.currentMode ? "Apply" : "Select",
|
|
484
|
+
value: {
|
|
485
|
+
action: "provider_permission_select",
|
|
486
|
+
sessionId: view.sessionId,
|
|
487
|
+
contextKey: view.contextKey,
|
|
488
|
+
provider: view.modelProvider,
|
|
489
|
+
model: view.model,
|
|
490
|
+
effort: view.reasoningEffort,
|
|
491
|
+
permissionMode: mode,
|
|
492
|
+
},
|
|
493
|
+
})],
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
})),
|
|
497
|
+
{ tag: "hr" },
|
|
498
|
+
taskActionRow([{
|
|
499
|
+
text: "Back",
|
|
500
|
+
value: {
|
|
501
|
+
action: "provider_reasoning_open",
|
|
502
|
+
sessionId: view.sessionId,
|
|
503
|
+
contextKey: view.contextKey,
|
|
504
|
+
provider: view.modelProvider,
|
|
505
|
+
model: view.model,
|
|
506
|
+
permissionMode: view.currentMode,
|
|
507
|
+
},
|
|
508
|
+
}]),
|
|
509
|
+
];
|
|
510
|
+
return sectionCard("Provider 设置 · 权限", elements);
|
|
206
511
|
}
|
|
207
512
|
|
|
208
513
|
renderPromptQueue(view: PromptQueueCardView): Record<string, unknown> {
|
|
@@ -330,14 +635,14 @@ export class CardRenderer {
|
|
|
330
635
|
? [
|
|
331
636
|
`**当前任务**:${inlineCode(view.currentTask.title ?? view.currentTask.id)}`,
|
|
332
637
|
workspaceLine,
|
|
333
|
-
|
|
638
|
+
`**Provider / 模型 / 思考强度 / 权限**:${inlineCode(view.currentTask.modelProvider ?? "Codex 默认")} / ${inlineCode(view.currentTask.model ?? "默认")} / ${inlineCode(view.currentTask.reasoningEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.currentTask.permissionMode))}`,
|
|
334
639
|
`**任务状态 / Agent**:${persistedTaskStatus(view.currentTask.sessionStatus, view.currentTask.lastTurnStatus)} / ${inlineCode(view.currentTask.agentName)}`,
|
|
335
640
|
`**任务 ID**:${inlineCode(view.currentTask.id)}`,
|
|
336
641
|
]
|
|
337
642
|
: [
|
|
338
643
|
"**当前任务**:无,下一条普通消息会创建新任务",
|
|
339
644
|
workspaceLine,
|
|
340
|
-
|
|
645
|
+
`**Provider / 模型 / 思考强度 / 权限**:${inlineCode("Codex 默认")} / ${inlineCode("默认")} / ${inlineCode("自动")} / ${inlineCode(permissionModeLabel())}`,
|
|
341
646
|
`**默认 Agent**:${view.defaultAgentTitle} (${inlineCode(view.defaultAgentName)})`,
|
|
342
647
|
];
|
|
343
648
|
lines.push(
|
|
@@ -646,7 +951,7 @@ function fileSummaryPanel(state: TurnViewState): Record<string, unknown> {
|
|
|
646
951
|
return collapsiblePanel(
|
|
647
952
|
`文件变更 · ${state.fileSummary.length}`,
|
|
648
953
|
state.fileSummary
|
|
649
|
-
.map((file) => `- ${displayFilePath(file.path, state.projectCwd)} +${file.additions ?? 0} -${file.deletions ?? 0}`)
|
|
954
|
+
.map((file) => `- ${escapeMarkdownFilePath(displayFilePath(file.path, state.projectCwd))} +${file.additions ?? 0} -${file.deletions ?? 0}`)
|
|
650
955
|
.join("\n"),
|
|
651
956
|
{ elementId: "turn_files" },
|
|
652
957
|
);
|
|
@@ -859,6 +1164,10 @@ function displayFilePath(filePath: string, projectCwd?: string): string {
|
|
|
859
1164
|
return isInsideProject ? relativePath || "." : absolutePath;
|
|
860
1165
|
}
|
|
861
1166
|
|
|
1167
|
+
function escapeMarkdownFilePath(filePath: string): string {
|
|
1168
|
+
return filePath.replaceAll("\\.", "\\\\.");
|
|
1169
|
+
}
|
|
1170
|
+
|
|
862
1171
|
function usesWindowsPaths(...values: string[]): boolean {
|
|
863
1172
|
return values.some((value) => /^[A-Za-z]:[\\/]/.test(value) || /^\\\\/.test(value));
|
|
864
1173
|
}
|
|
@@ -1062,6 +1371,104 @@ function acpPermissionOptionLabel(option: { name: string; kind: string }): strin
|
|
|
1062
1371
|
return labels[option.kind] ?? (/^[\x20-\x7E]+$/.test(option.name) ? option.name : "Select");
|
|
1063
1372
|
}
|
|
1064
1373
|
|
|
1374
|
+
function settingsTabRow(
|
|
1375
|
+
activeTab: ExecutionSettingsTab,
|
|
1376
|
+
baseAction: Record<string, string>,
|
|
1377
|
+
showAgentTab: boolean,
|
|
1378
|
+
showRuntimeTabs: boolean,
|
|
1379
|
+
): Record<string, unknown> | undefined {
|
|
1380
|
+
const tabs: Array<{ id: ExecutionSettingsTab; label: string }> = [
|
|
1381
|
+
...(showAgentTab ? [{ id: "agent" as const, label: "Agent" }] : []),
|
|
1382
|
+
...(showRuntimeTabs
|
|
1383
|
+
? [
|
|
1384
|
+
{ id: "provider" as const, label: "Provider" },
|
|
1385
|
+
{ id: "model" as const, label: "Model" },
|
|
1386
|
+
{ id: "thinking" as const, label: "Thinking" },
|
|
1387
|
+
{ id: "permission" as const, label: "Permission" },
|
|
1388
|
+
]
|
|
1389
|
+
: []),
|
|
1390
|
+
];
|
|
1391
|
+
if (tabs.length === 0) return undefined;
|
|
1392
|
+
return {
|
|
1393
|
+
tag: "column_set",
|
|
1394
|
+
flex_mode: "none",
|
|
1395
|
+
horizontal_spacing: "2px",
|
|
1396
|
+
margin: "8px 0 0 0",
|
|
1397
|
+
columns: tabs.flatMap((tab, index) => [
|
|
1398
|
+
...(index > 0
|
|
1399
|
+
? [{
|
|
1400
|
+
tag: "column",
|
|
1401
|
+
width: "auto",
|
|
1402
|
+
vertical_align: "center",
|
|
1403
|
+
elements: [{ ...markdown("·"), text_align: "center", text_size: "notation" }],
|
|
1404
|
+
}]
|
|
1405
|
+
: []),
|
|
1406
|
+
{
|
|
1407
|
+
tag: "column",
|
|
1408
|
+
width: "auto",
|
|
1409
|
+
vertical_align: "center",
|
|
1410
|
+
elements: [settingsTabElement(tab.label, tab.id === activeTab, {
|
|
1411
|
+
action: "settings_tab_open",
|
|
1412
|
+
...baseAction,
|
|
1413
|
+
tab: tab.id,
|
|
1414
|
+
})],
|
|
1415
|
+
},
|
|
1416
|
+
]),
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
function settingsTabElement(
|
|
1421
|
+
label: string,
|
|
1422
|
+
active: boolean,
|
|
1423
|
+
value: Record<string, string>,
|
|
1424
|
+
): Record<string, unknown> {
|
|
1425
|
+
if (active) {
|
|
1426
|
+
return {
|
|
1427
|
+
...markdown(escapeCardHtml(label)),
|
|
1428
|
+
text_align: "center",
|
|
1429
|
+
text_size: "notation",
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
const action = taskActionElement({ text: label, value });
|
|
1433
|
+
return {
|
|
1434
|
+
...action,
|
|
1435
|
+
padding: "6px 0px",
|
|
1436
|
+
elements: (action.elements as Array<Record<string, unknown>>).map((element) => ({
|
|
1437
|
+
...element,
|
|
1438
|
+
text_align: "center",
|
|
1439
|
+
text_size: "notation",
|
|
1440
|
+
})),
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
function settingsOptionRow(input: {
|
|
1445
|
+
label: string;
|
|
1446
|
+
current: boolean;
|
|
1447
|
+
action: TaskListCardAction;
|
|
1448
|
+
}): Record<string, unknown> {
|
|
1449
|
+
return {
|
|
1450
|
+
tag: "column_set",
|
|
1451
|
+
flex_mode: "none",
|
|
1452
|
+
horizontal_spacing: "8px",
|
|
1453
|
+
vertical_align: "center",
|
|
1454
|
+
columns: [
|
|
1455
|
+
{
|
|
1456
|
+
tag: "column",
|
|
1457
|
+
width: "weighted",
|
|
1458
|
+
weight: 1,
|
|
1459
|
+
vertical_align: "center",
|
|
1460
|
+
elements: [markdown(input.label)],
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
tag: "column",
|
|
1464
|
+
width: "auto",
|
|
1465
|
+
vertical_align: "center",
|
|
1466
|
+
elements: input.current ? [markdown("✅ 当前")] : [taskActionElement(input.action)],
|
|
1467
|
+
},
|
|
1468
|
+
],
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1065
1472
|
function taskActionRow(actions: TaskListCardAction[]): Record<string, unknown> {
|
|
1066
1473
|
return {
|
|
1067
1474
|
tag: "column_set",
|
|
@@ -233,8 +233,16 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
async updateInteractiveCard(messageId: string, card: Record<string, unknown>): Promise<void> {
|
|
236
|
+
const preparedCard = await this.prepareInteractiveCard(card);
|
|
237
|
+
await this.updateInteractiveCardNow(messageId, preparedCard);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private async updateInteractiveCardNow(
|
|
241
|
+
messageId: string,
|
|
242
|
+
card: Record<string, unknown>,
|
|
243
|
+
allowAuditFallback = true,
|
|
244
|
+
): Promise<void> {
|
|
236
245
|
try {
|
|
237
|
-
const preparedCard = await this.prepareInteractiveCard(card);
|
|
238
246
|
const token = await this.getTenantAccessToken();
|
|
239
247
|
const response = await fetch(`https://open.feishu.cn/open-apis/im/v1/messages/${messageId}`, {
|
|
240
248
|
method: "PATCH",
|
|
@@ -242,14 +250,26 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
242
250
|
Authorization: `Bearer ${token}`,
|
|
243
251
|
"Content-Type": "application/json; charset=utf-8",
|
|
244
252
|
},
|
|
245
|
-
body: JSON.stringify({ content: JSON.stringify(
|
|
253
|
+
body: JSON.stringify({ content: JSON.stringify(card) }),
|
|
246
254
|
});
|
|
247
255
|
const payload = (await response.json()) as SendMessageResponse;
|
|
248
256
|
if (!response.ok || payload.code !== 0) {
|
|
249
257
|
throw new FeishuApiError(payload.msg || response.statusText, payload.code, payload, "update", response.status);
|
|
250
258
|
}
|
|
251
259
|
} catch (error) {
|
|
252
|
-
|
|
260
|
+
const normalized = normalizeTransportError(error, "update");
|
|
261
|
+
const fallback = allowAuditFallback && normalized.isEmailAddressAuditFailure
|
|
262
|
+
? sanitizeEmailAddressesInContent(card)
|
|
263
|
+
: undefined;
|
|
264
|
+
if (fallback) {
|
|
265
|
+
this.logger.warn(
|
|
266
|
+
{ code: normalized.code, messageId },
|
|
267
|
+
"Feishu rejected a card update as email-sensitive; retrying with audit-safe text.",
|
|
268
|
+
);
|
|
269
|
+
await this.updateInteractiveCardNow(messageId, fallback, false);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
throw normalized;
|
|
253
273
|
}
|
|
254
274
|
}
|
|
255
275
|
|
|
@@ -278,6 +298,7 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
278
298
|
msgType: string,
|
|
279
299
|
content: Record<string, unknown>,
|
|
280
300
|
idempotencyKey?: string,
|
|
301
|
+
allowAuditFallback = true,
|
|
281
302
|
): Promise<string | undefined> {
|
|
282
303
|
let token: string;
|
|
283
304
|
try {
|
|
@@ -317,6 +338,16 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
317
338
|
lastError = error;
|
|
318
339
|
|
|
319
340
|
if (!error.isRetryable || attempt === 2) {
|
|
341
|
+
const fallback = allowAuditFallback && error.isEmailAddressAuditFailure
|
|
342
|
+
? sanitizeEmailAddressesInContent(content)
|
|
343
|
+
: undefined;
|
|
344
|
+
if (fallback) {
|
|
345
|
+
this.logger.warn(
|
|
346
|
+
{ code: error.code, messageId: target.messageId, msgType },
|
|
347
|
+
"Feishu rejected a reply as email-sensitive; retrying with audit-safe text.",
|
|
348
|
+
);
|
|
349
|
+
return this.replyMessageNow(target, msgType, fallback, idempotencyKey, false);
|
|
350
|
+
}
|
|
320
351
|
this.logger.error({ error, messageId: target.messageId, msgType }, "Failed to reply to Feishu message.");
|
|
321
352
|
throw error;
|
|
322
353
|
}
|
|
@@ -334,6 +365,7 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
334
365
|
msgType: string,
|
|
335
366
|
content: Record<string, unknown>,
|
|
336
367
|
idempotencyKey?: string,
|
|
368
|
+
allowAuditFallback = true,
|
|
337
369
|
): Promise<string | undefined> {
|
|
338
370
|
let token: string;
|
|
339
371
|
try {
|
|
@@ -376,6 +408,16 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
376
408
|
lastError = error;
|
|
377
409
|
|
|
378
410
|
if (!error.isRetryable || attempt === 2) {
|
|
411
|
+
const fallback = allowAuditFallback && error.isEmailAddressAuditFailure
|
|
412
|
+
? sanitizeEmailAddressesInContent(content)
|
|
413
|
+
: undefined;
|
|
414
|
+
if (fallback) {
|
|
415
|
+
this.logger.warn(
|
|
416
|
+
{ code: error.code, contextKey, msgType },
|
|
417
|
+
"Feishu rejected a message as email-sensitive; retrying with audit-safe text.",
|
|
418
|
+
);
|
|
419
|
+
return this.sendMessageNow(contextKey, msgType, fallback, idempotencyKey, false);
|
|
420
|
+
}
|
|
379
421
|
this.logger.error({ error, contextKey, msgType }, "Failed to send Feishu message.");
|
|
380
422
|
throw error;
|
|
381
423
|
}
|
|
@@ -573,6 +615,7 @@ export class FeishuMessageClient implements FeishuOutbound {
|
|
|
573
615
|
export class FeishuApiError extends Error {
|
|
574
616
|
readonly isRateLimit: boolean;
|
|
575
617
|
readonly isRetryable: boolean;
|
|
618
|
+
readonly isEmailAddressAuditFailure: boolean;
|
|
576
619
|
|
|
577
620
|
constructor(
|
|
578
621
|
message: string,
|
|
@@ -591,6 +634,7 @@ export class FeishuApiError extends Error {
|
|
|
591
634
|
this.name = "FeishuApiError";
|
|
592
635
|
this.isRateLimit = code === 230020 || message.toLowerCase().includes("frequency limit");
|
|
593
636
|
this.isRetryable = forceRetryable || this.isRateLimit || (httpStatus !== undefined && httpStatus >= 500);
|
|
637
|
+
this.isEmailAddressAuditFailure = code === 230028 && message.includes("EMAIL_ADDRESS");
|
|
594
638
|
}
|
|
595
639
|
}
|
|
596
640
|
|
|
@@ -642,6 +686,25 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
642
686
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
643
687
|
}
|
|
644
688
|
|
|
689
|
+
function sanitizeEmailAddressesInContent(content: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
690
|
+
let changed = false;
|
|
691
|
+
const sanitize = (value: unknown): unknown => {
|
|
692
|
+
if (typeof value === "string") {
|
|
693
|
+
const sanitized = value.replace(
|
|
694
|
+
/\b([a-z0-9._%+-]+)@([a-z0-9.-]+\.[a-z]{2,})\b/gi,
|
|
695
|
+
"$1 [at] $2",
|
|
696
|
+
);
|
|
697
|
+
if (sanitized !== value) changed = true;
|
|
698
|
+
return sanitized;
|
|
699
|
+
}
|
|
700
|
+
if (Array.isArray(value)) return value.map(sanitize);
|
|
701
|
+
if (!isRecord(value)) return value;
|
|
702
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, sanitize(child)]));
|
|
703
|
+
};
|
|
704
|
+
const sanitized = sanitize(content) as Record<string, unknown>;
|
|
705
|
+
return changed ? sanitized : undefined;
|
|
706
|
+
}
|
|
707
|
+
|
|
645
708
|
function cardImageLabel(image: Record<string, unknown>): string {
|
|
646
709
|
const alt = image.alt;
|
|
647
710
|
if (isRecord(alt) && typeof alt.content === "string" && alt.content.trim()) return alt.content;
|