evolclaw 3.1.4 → 3.1.6
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 +60 -0
- package/dist/agents/claude-runner.js +398 -161
- package/dist/agents/kit-renderer.js +191 -25
- package/dist/aun/aid/agentmd.js +75 -103
- package/dist/aun/aid/client.js +1 -29
- package/dist/aun/aid/identity.js +105 -64
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/store.js +74 -0
- package/dist/aun/msg/group.js +2 -2
- package/dist/aun/msg/p2p.js +26 -2
- package/dist/aun/rpc/connection.js +23 -30
- package/dist/channels/aun.js +174 -99
- package/dist/channels/dingtalk.js +2 -1
- package/dist/channels/feishu.js +301 -199
- package/dist/channels/qqbot.js +2 -1
- package/dist/channels/wechat.js +2 -1
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent.js +21 -16
- package/dist/cli/bench.js +41 -28
- package/dist/cli/help.js +8 -0
- package/dist/cli/index.js +176 -87
- package/dist/cli/init-channel.js +5 -1
- package/dist/cli/init.js +37 -21
- package/dist/cli/link-rules.js +1 -7
- package/dist/cli/model.js +549 -0
- package/dist/cli/net-check.js +133 -50
- package/dist/cli/watch-msg.js +7 -7
- package/dist/cli/watch-web/debug-log.js +18 -0
- package/dist/cli/watch-web/server.js +306 -0
- package/dist/cli/watch-web/sources/aid.js +63 -0
- package/dist/cli/watch-web/sources/msg.js +70 -0
- package/dist/cli/watch-web/sources/session.js +638 -0
- package/dist/cli/watch-web/sources/types.js +10 -0
- package/dist/cli/watch-web/static/app.js +546 -0
- package/dist/cli/watch-web/static/index.html +54 -0
- package/dist/cli/watch-web/static/style.css +247 -0
- package/dist/config-store.js +1 -22
- package/dist/core/channel-loader.js +7 -4
- package/dist/core/command-handler.js +261 -133
- package/dist/core/evolagent-registry.js +1 -1
- package/dist/core/evolagent.js +4 -22
- package/dist/core/interaction-router.js +59 -0
- package/dist/core/message/im-renderer.js +9 -20
- package/dist/core/message/message-bridge.js +13 -9
- package/dist/core/message/message-log.js +2 -2
- package/dist/core/message/message-processor.js +211 -123
- package/dist/core/message/stream-idle-monitor.js +21 -0
- package/dist/core/model/model-catalog.js +215 -0
- package/dist/core/model/model-scope.js +250 -0
- package/dist/core/relation/peer-identity.js +58 -55
- package/dist/core/relation/peer-key.js +16 -0
- package/dist/core/session/session-fs-store.js +34 -55
- package/dist/core/session/session-key.js +24 -0
- package/dist/core/session/session-manager.js +308 -251
- package/dist/core/session/session-mapper.js +9 -4
- package/dist/core/trigger/manager.js +3 -3
- package/dist/core/trigger/parser.js +4 -4
- package/dist/core/trigger/scheduler.js +22 -7
- package/dist/index.js +61 -7
- package/dist/ipc.js +23 -1
- package/dist/utils/error-utils.js +6 -0
- package/dist/utils/process-introspect.js +7 -5
- package/kits/docs/GUIDE.md +2 -2
- package/kits/docs/INDEX.md +8 -8
- package/kits/docs/channels/aun.md +56 -17
- package/kits/docs/channels/feishu.md +41 -12
- package/kits/docs/context-assembly.md +182 -0
- package/kits/docs/evolclaw/INDEX.md +43 -0
- package/kits/docs/evolclaw/agent.md +49 -0
- package/kits/docs/evolclaw/aid.md +49 -0
- package/kits/docs/evolclaw/ctl.md +46 -0
- package/kits/docs/evolclaw/group.md +89 -0
- package/kits/docs/evolclaw/model.md +51 -0
- package/kits/docs/evolclaw/msg.md +91 -0
- package/kits/docs/evolclaw/rpc.md +35 -0
- package/kits/docs/evolclaw/storage.md +49 -0
- package/kits/docs/venues/aun-group.md +10 -0
- package/kits/docs/venues/aun-private.md +10 -0
- package/kits/docs/venues/client-desktop.md +10 -0
- package/kits/docs/venues/client-mobile.md +10 -0
- package/kits/docs/venues/feishu-group.md +13 -0
- package/kits/docs/venues/feishu-private.md +9 -0
- package/kits/docs/venues/group.md +23 -0
- package/kits/docs/venues/private.md +10 -0
- package/kits/eck_manifest.json +81 -36
- package/kits/rules/01-overview.md +20 -10
- package/kits/rules/06-channel.md +34 -27
- package/kits/templates/system-fragments/baseagent.md +7 -1
- package/kits/templates/system-fragments/channel.md +7 -5
- package/kits/templates/system-fragments/commands.md +19 -0
- package/kits/templates/system-fragments/session.md +19 -3
- package/kits/templates/system-fragments/venue.md +24 -0
- package/package.json +10 -5
- package/dist/aun/aid/lifecycle-log.js +0 -33
- package/dist/utils/aid-lifecycle-log.js +0 -33
- package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
- package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
- package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
- package/kits/docs/evolclaw/tools.md +0 -25
|
@@ -10,6 +10,114 @@ import os from 'os';
|
|
|
10
10
|
import { logger } from '../utils/logger.js';
|
|
11
11
|
import { checkBlacklist, checkReadonly, summarizeToolInput } from '../core/permission.js';
|
|
12
12
|
import { encodePath } from '../utils/cross-platform.js';
|
|
13
|
+
// ── 模型别名解析 ──
|
|
14
|
+
// SDK 内置的别名表可能落后于代理实际可用的最新模型,
|
|
15
|
+
// 因此优先从 {baseUrl}/models 动态获取各系列最新版本,失败则回退静态表。
|
|
16
|
+
// 已验证可用但尚未出现在 /models 列表中的模型 ID 会被注入候选列表,
|
|
17
|
+
// 等列表更新后注入自动变为 no-op。
|
|
18
|
+
const MODEL_FAMILIES = ['opus', 'sonnet', 'haiku'];
|
|
19
|
+
/** 已验证可用但可能尚未出现在 /models 列表中的模型 ID(注入候选) */
|
|
20
|
+
const INJECTED_MODELS = [];
|
|
21
|
+
/** 静态回退表:动态获取失败时使用 */
|
|
22
|
+
const STATIC_MODEL_ALIASES = {
|
|
23
|
+
'opus': 'claude-opus-4-8',
|
|
24
|
+
'sonnet': 'claude-sonnet-4-6',
|
|
25
|
+
'haiku': 'claude-haiku-4-5-20251001',
|
|
26
|
+
};
|
|
27
|
+
const MODEL_ALIAS_TTL_MS = 5 * 60 * 1000; // 5min
|
|
28
|
+
const modelAliasCache = new Map(); // key: baseUrl
|
|
29
|
+
const modelAliasInFlight = new Set(); // 去重并发刷新
|
|
30
|
+
/** 从模型 ID 列表中提取各 claude 系列的最新版本(按 major.minor 取最高) */
|
|
31
|
+
function deriveAliasesFromModelIds(ids) {
|
|
32
|
+
// 注入已验证可用的模型(如果列表中已有则去重无影响)
|
|
33
|
+
const allIds = [...new Set([...ids, ...INJECTED_MODELS])];
|
|
34
|
+
const best = {};
|
|
35
|
+
for (const id of allIds) {
|
|
36
|
+
const m = id.match(/^claude-(opus|sonnet|haiku)-(\d+)-(\d+)/);
|
|
37
|
+
if (!m)
|
|
38
|
+
continue;
|
|
39
|
+
const [, family, majorStr, minorStr] = m;
|
|
40
|
+
const major = parseInt(majorStr, 10);
|
|
41
|
+
const minor = parseInt(minorStr, 10);
|
|
42
|
+
const cur = best[family];
|
|
43
|
+
if (!cur || major > cur.major || (major === cur.major && minor > cur.minor)) {
|
|
44
|
+
best[family] = { id, major, minor };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const aliases = {};
|
|
48
|
+
for (const [family, info] of Object.entries(best))
|
|
49
|
+
aliases[family] = info.id;
|
|
50
|
+
return aliases;
|
|
51
|
+
}
|
|
52
|
+
/** 异步刷新某 baseUrl 的别名缓存(失败静默,不抛出) */
|
|
53
|
+
async function refreshModelAliases(baseUrl, apiKey) {
|
|
54
|
+
if (modelAliasInFlight.has(baseUrl))
|
|
55
|
+
return;
|
|
56
|
+
modelAliasInFlight.add(baseUrl);
|
|
57
|
+
try {
|
|
58
|
+
const url = `${baseUrl.replace(/\/$/, '')}/models`;
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timer = setTimeout(() => controller.abort(), 5000);
|
|
61
|
+
const resp = await fetch(url, {
|
|
62
|
+
signal: controller.signal,
|
|
63
|
+
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
|
64
|
+
});
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
if (!resp.ok)
|
|
67
|
+
return;
|
|
68
|
+
const json = await resp.json();
|
|
69
|
+
const ids = Array.isArray(json?.data)
|
|
70
|
+
? json.data.map((m) => m?.id).filter((x) => typeof x === 'string')
|
|
71
|
+
: [];
|
|
72
|
+
const aliases = deriveAliasesFromModelIds(ids);
|
|
73
|
+
if (ids.length > 0 || Object.keys(aliases).length > 0) {
|
|
74
|
+
modelAliasCache.set(baseUrl, { aliases, ids, fetchedAt: Date.now() });
|
|
75
|
+
logger.info(`[AgentRunner] Refreshed models from ${url}: ${ids.length} ids, aliases ${JSON.stringify(aliases)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// 网络/解析失败:保持静态回退,不打断查询
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
modelAliasInFlight.delete(baseUrl);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** 将短别名展开为完整 model ID,已是完整 ID 则原样返回 */
|
|
86
|
+
function resolveModelAlias(model, baseUrl) {
|
|
87
|
+
// 非短别名(已经是完整 ID)直接返回
|
|
88
|
+
if (!MODEL_FAMILIES.includes(model))
|
|
89
|
+
return model;
|
|
90
|
+
// 优先使用动态缓存
|
|
91
|
+
if (baseUrl) {
|
|
92
|
+
const cached = modelAliasCache.get(baseUrl);
|
|
93
|
+
if (cached && (Date.now() - cached.fetchedAt < MODEL_ALIAS_TTL_MS)) {
|
|
94
|
+
return cached.aliases[model] || STATIC_MODEL_ALIASES[model] || model;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 回退静态表
|
|
98
|
+
return STATIC_MODEL_ALIASES[model] || model;
|
|
99
|
+
}
|
|
100
|
+
/** 支持 1M 上下文窗口的模型 ID 前缀(SDK 通过 `[1m]` 后缀启用)。 */
|
|
101
|
+
const ONE_M_CONTEXT_PREFIXES = ['claude-opus-4-8', 'claude-sonnet-4-6'];
|
|
102
|
+
/**
|
|
103
|
+
* 为支持 1M 上下文的模型追加 `[1m]` 后缀——仅在交给 SDK query() 时调用。
|
|
104
|
+
* 目录与校验层始终使用不带后缀的基础 ID,避免与网关 /models 返回值(无 `[1m]`)冲突。
|
|
105
|
+
*/
|
|
106
|
+
function applyContextWindow(modelId) {
|
|
107
|
+
if (/\[1m\]$/.test(modelId))
|
|
108
|
+
return modelId; // 已带后缀
|
|
109
|
+
if (ONE_M_CONTEXT_PREFIXES.some(p => modelId === p))
|
|
110
|
+
return `${modelId}[1m]`;
|
|
111
|
+
return modelId;
|
|
112
|
+
}
|
|
113
|
+
/** 根据 SDK model 串(含 [1m] 后缀)返回合适的 autoCompactWindow 值。 */
|
|
114
|
+
function contextWindowFor(sdkModel) {
|
|
115
|
+
return /\[1m\]$/.test(sdkModel) ? 900000 : 200000;
|
|
116
|
+
}
|
|
117
|
+
/** 解析别名 + 追加 1M 后缀,得到最终交给 SDK 的 model 串。 */
|
|
118
|
+
function resolveSdkModel(model, baseUrl) {
|
|
119
|
+
return applyContextWindow(resolveModelAlias(model, baseUrl));
|
|
120
|
+
}
|
|
13
121
|
class MessageStream {
|
|
14
122
|
queue = [];
|
|
15
123
|
waiting = null;
|
|
@@ -89,6 +197,9 @@ export class AgentRunner {
|
|
|
89
197
|
permissionContexts = new Map();
|
|
90
198
|
currentEvolclawSessionId;
|
|
91
199
|
claudeExecutablePath;
|
|
200
|
+
/** 每个 session 最近的子进程 stderr 行(环形缓冲),用于子进程崩溃时还原真正原因 */
|
|
201
|
+
recentStderr = new Map();
|
|
202
|
+
static STDERR_BUFFER_MAX = 80;
|
|
92
203
|
constructor(apiKey, model, onSessionIdUpdate, baseUrl, config) {
|
|
93
204
|
this.apiKey = apiKey;
|
|
94
205
|
this.model = model || 'sonnet';
|
|
@@ -102,11 +213,17 @@ export class AgentRunner {
|
|
|
102
213
|
}
|
|
103
214
|
}
|
|
104
215
|
getAgentEnv() {
|
|
216
|
+
// SDK 0.3.x 起,CLI 在以 root 运行时会拒绝 --dangerously-skip-permissions
|
|
217
|
+
// (bypassPermissions 模式映射而来),报错 "cannot be used with root/sudo privileges"
|
|
218
|
+
// 并以 code 1 退出。IS_SANDBOX=1 是 CLI 提供的 root 守卫豁免开关。
|
|
219
|
+
// 仅在以 root 运行时注入,非 root 部署行为不变。
|
|
220
|
+
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
105
221
|
return {
|
|
106
222
|
...process.env,
|
|
107
223
|
ANTHROPIC_AUTH_TOKEN: this.apiKey,
|
|
108
224
|
PATH: process.env.PATH,
|
|
109
225
|
DISABLE_AUTOUPDATER: '1',
|
|
226
|
+
...(isRoot ? { IS_SANDBOX: '1' } : {}),
|
|
110
227
|
...(this.baseUrl ? { ANTHROPIC_BASE_URL: this.baseUrl } : {}),
|
|
111
228
|
...(this.currentEvolclawSessionId ? { EVOLCLAW_SESSION_ID: this.currentEvolclawSessionId } : {}),
|
|
112
229
|
};
|
|
@@ -117,8 +234,28 @@ export class AgentRunner {
|
|
|
117
234
|
getModel() {
|
|
118
235
|
return this.model;
|
|
119
236
|
}
|
|
120
|
-
listModels() {
|
|
121
|
-
|
|
237
|
+
async listModels() {
|
|
238
|
+
if (this.baseUrl) {
|
|
239
|
+
let cached = modelAliasCache.get(this.baseUrl);
|
|
240
|
+
const stale = !cached || (Date.now() - cached.fetchedAt > MODEL_ALIAS_TTL_MS);
|
|
241
|
+
// 缓存为空(首次打开)→ 等待刷新;缓存仅过期 → 后台刷新不阻塞
|
|
242
|
+
if (!cached) {
|
|
243
|
+
await refreshModelAliases(this.baseUrl, this.apiKey);
|
|
244
|
+
cached = modelAliasCache.get(this.baseUrl);
|
|
245
|
+
}
|
|
246
|
+
else if (stale) {
|
|
247
|
+
refreshModelAliases(this.baseUrl, this.apiKey);
|
|
248
|
+
}
|
|
249
|
+
// 有缓存时返回网关 /models 的全量原始 ID
|
|
250
|
+
if (cached && cached.ids.length > 0)
|
|
251
|
+
return cached.ids;
|
|
252
|
+
}
|
|
253
|
+
// 无 baseUrl / 刷新超时或失败 → 回退短别名
|
|
254
|
+
return Object.values(STATIC_MODEL_ALIASES);
|
|
255
|
+
}
|
|
256
|
+
/** 将短别名解析为当前代理实际使用的完整 model ID(仅用于展示,不改变持久化值) */
|
|
257
|
+
resolveModelId(model) {
|
|
258
|
+
return resolveModelAlias(model, this.baseUrl);
|
|
122
259
|
}
|
|
123
260
|
setEffort(effort) {
|
|
124
261
|
this.effort = effort;
|
|
@@ -157,7 +294,7 @@ export class AgentRunner {
|
|
|
157
294
|
toSdkPermissionMode() {
|
|
158
295
|
const map = {
|
|
159
296
|
'auto': 'auto', // AI 分类器自动判断
|
|
160
|
-
'bypass': '
|
|
297
|
+
'bypass': 'bypassPermissions', // 全部自动放行(SDK 跳过分类器,canUseTool 仍保留 hook 安全检查)
|
|
161
298
|
'request': 'default', // 部分自动,部分询问
|
|
162
299
|
'edit': 'acceptEdits',
|
|
163
300
|
'plan': 'plan',
|
|
@@ -213,49 +350,70 @@ export class AgentRunner {
|
|
|
213
350
|
if (!adapterHasInteractionPath) {
|
|
214
351
|
return this.handleAskUserQuestionFallback(sessionId, input, questions);
|
|
215
352
|
}
|
|
353
|
+
// 立即暂停 idle 监控,不等卡片发完再 register
|
|
354
|
+
permCtx.interactionRouter?.markWaiting(sessionId);
|
|
355
|
+
let waitMarked = true;
|
|
216
356
|
const answers = {};
|
|
217
|
-
// 从 permCtx 构造 per-session 的发送函数,避免全局 sendPromptFn 被其他 channel 实例覆盖
|
|
218
|
-
// 注意:sendPromptFn 是全局单例,多 channel 并发时会被覆盖,导致提示发到错误 channel
|
|
219
357
|
const sendPrompt = permCtx.adapter && permCtx.channelId
|
|
220
358
|
? async (text) => permCtx.adapter.send(buildEnvelope({ channel: permCtx.adapter.channelName, channelId: permCtx.channelId, replyContext: permCtx.replyContext }), { kind: 'result.text', text, isFinal: true })
|
|
221
359
|
: this.sendPromptFn;
|
|
222
|
-
// 逐个 question 发送卡片并等待用户选择
|
|
223
360
|
for (let i = 0; i < questions.length; i++) {
|
|
224
361
|
const q = questions[i];
|
|
225
362
|
const requestId = `ask-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
226
363
|
const cardTitle = q.header ? `💬 ${q.header}` : `💬 问题 ${i + 1}/${questions.length}`;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
364
|
+
let interaction;
|
|
365
|
+
if (q.multiSelect) {
|
|
366
|
+
// 多选:使用 checkers + form 提交(JSON 2.0 CardKit 路径)
|
|
367
|
+
interaction = {
|
|
368
|
+
type: 'interaction',
|
|
369
|
+
id: requestId,
|
|
370
|
+
kind: {
|
|
371
|
+
kind: 'action',
|
|
372
|
+
title: cardTitle,
|
|
373
|
+
body: q.question,
|
|
374
|
+
checkers: q.options.map(opt => ({
|
|
375
|
+
key: opt.label,
|
|
376
|
+
label: opt.label,
|
|
377
|
+
description: opt.description,
|
|
378
|
+
})),
|
|
379
|
+
buttons: [
|
|
380
|
+
{ key: 'submit', label: '✅ 确认选择', style: 'primary' },
|
|
381
|
+
],
|
|
382
|
+
allowCustomInput: true,
|
|
383
|
+
},
|
|
384
|
+
channelId: permCtx.channelId,
|
|
385
|
+
sessionId,
|
|
386
|
+
expiresAt: Date.now() + 5 * 60 * 1000,
|
|
387
|
+
};
|
|
234
388
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
389
|
+
else {
|
|
390
|
+
// 单选:保持按钮模式
|
|
391
|
+
const bodyLines = [q.question];
|
|
392
|
+
if (q.options.some(opt => opt.description)) {
|
|
393
|
+
bodyLines.push('');
|
|
394
|
+
q.options.forEach((opt, idx) => {
|
|
395
|
+
bodyLines.push(`${idx + 1}. **${opt.label}**${opt.description ? ` — ${opt.description}` : ''}`);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
interaction = {
|
|
399
|
+
type: 'interaction',
|
|
400
|
+
id: requestId,
|
|
401
|
+
kind: {
|
|
402
|
+
kind: 'action',
|
|
403
|
+
title: cardTitle,
|
|
404
|
+
body: bodyLines.join('\n'),
|
|
405
|
+
buttons: q.options.map(opt => ({
|
|
244
406
|
key: opt.label,
|
|
245
407
|
label: opt.label,
|
|
246
408
|
style: 'default',
|
|
247
409
|
})),
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
channelId: permCtx.channelId,
|
|
256
|
-
sessionId,
|
|
257
|
-
expiresAt: Date.now() + 5 * 60 * 1000,
|
|
258
|
-
};
|
|
410
|
+
allowCustomInput: true,
|
|
411
|
+
},
|
|
412
|
+
channelId: permCtx.channelId,
|
|
413
|
+
sessionId,
|
|
414
|
+
expiresAt: Date.now() + 5 * 60 * 1000,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
259
417
|
let cardSent = false;
|
|
260
418
|
try {
|
|
261
419
|
const envelope = buildEnvelope({
|
|
@@ -275,7 +433,6 @@ export class AgentRunner {
|
|
|
275
433
|
logger.warn(`[AgentRunner] AskUserQuestion card send failed for q${i}:`, err);
|
|
276
434
|
}
|
|
277
435
|
if (!cardSent) {
|
|
278
|
-
// 卡片发送失败,以纯文本展示选项并自动选推荐项
|
|
279
436
|
const firstLabel = q.options[0]?.label || '';
|
|
280
437
|
answers[q.question] = q.multiSelect ? [firstLabel] : firstLabel;
|
|
281
438
|
if (sendPrompt) {
|
|
@@ -284,35 +441,39 @@ export class AgentRunner {
|
|
|
284
441
|
}
|
|
285
442
|
continue;
|
|
286
443
|
}
|
|
287
|
-
//
|
|
444
|
+
// 等待用户交互:先 register 接管计数,再 unmark 占位,消除空窗期
|
|
445
|
+
// (unmark 必须在 register 之后,否则计数短暂降为 0 触发 onWaitEnd→resume,idle 时钟被重置)
|
|
288
446
|
const answer = await new Promise((resolve) => {
|
|
289
447
|
permCtx?.interactionRouter?.register(requestId, sessionId, (action, values) => {
|
|
290
448
|
if (action === 'cancel') {
|
|
291
449
|
resolve(null);
|
|
292
450
|
}
|
|
293
|
-
else if (action === '_custom_input'
|
|
294
|
-
//
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
451
|
+
else if (action === '_custom_input') {
|
|
452
|
+
// 用户通过追加的 input 提交了自定义文本
|
|
453
|
+
const customText = values?.custom_text;
|
|
454
|
+
resolve(typeof customText === 'string' && customText.trim() ? customText.trim() : null);
|
|
455
|
+
}
|
|
456
|
+
else if (action === 'submit' && q.multiSelect && values) {
|
|
457
|
+
// checker 多选提交:从 form_value 收集 checked 选项
|
|
458
|
+
const selected = [];
|
|
459
|
+
q.options.forEach((opt, idx) => {
|
|
460
|
+
if (values[`opt_${idx}`] === true) {
|
|
461
|
+
selected.push(opt.label);
|
|
298
462
|
}
|
|
299
|
-
};
|
|
300
|
-
sendHint().catch(() => { });
|
|
301
|
-
permCtx.interceptNextMessage(sessionId, (msg) => {
|
|
302
|
-
resolve(msg.content || null);
|
|
303
463
|
});
|
|
304
|
-
|
|
305
|
-
else if (q.multiSelect) {
|
|
306
|
-
// multiSelect 按钮点击:包装为数组
|
|
307
|
-
resolve([action]);
|
|
464
|
+
resolve(selected.length > 0 ? selected : null);
|
|
308
465
|
}
|
|
309
466
|
else {
|
|
310
|
-
resolve(action);
|
|
467
|
+
resolve(action);
|
|
311
468
|
}
|
|
312
469
|
});
|
|
470
|
+
// register 已接管计数(计数 +1),现在才能安全释放 markWaiting 占位(计数 -1),避免空窗
|
|
471
|
+
if (waitMarked) {
|
|
472
|
+
permCtx?.interactionRouter?.unmarkWaiting(sessionId);
|
|
473
|
+
waitMarked = false;
|
|
474
|
+
}
|
|
313
475
|
});
|
|
314
476
|
if (answer === null) {
|
|
315
|
-
// 取消,自动选第一项
|
|
316
477
|
const firstLabel = q.options[0]?.label || '';
|
|
317
478
|
answers[q.question] = q.multiSelect ? [firstLabel] : firstLabel;
|
|
318
479
|
}
|
|
@@ -320,6 +481,9 @@ export class AgentRunner {
|
|
|
320
481
|
answers[q.question] = answer;
|
|
321
482
|
}
|
|
322
483
|
}
|
|
484
|
+
if (waitMarked) {
|
|
485
|
+
permCtx?.interactionRouter?.unmarkWaiting(sessionId);
|
|
486
|
+
}
|
|
323
487
|
const updatedInput = { ...input, answers };
|
|
324
488
|
return { behavior: 'allow', updatedInput, decisionClassification: 'user_temporary' };
|
|
325
489
|
}
|
|
@@ -395,6 +559,8 @@ export class AgentRunner {
|
|
|
395
559
|
if (!permCtx?.channelId || !sendPrompt) {
|
|
396
560
|
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_temporary' };
|
|
397
561
|
}
|
|
562
|
+
// 立即暂停 idle 监控,不等卡片发完再 register
|
|
563
|
+
permCtx.interactionRouter?.markWaiting(sessionId);
|
|
398
564
|
// 尝试发送交互卡片
|
|
399
565
|
let cardSent = false;
|
|
400
566
|
if (permCtx.adapter?.send) {
|
|
@@ -429,6 +595,7 @@ export class AgentRunner {
|
|
|
429
595
|
{ key: 'approve', label: '✅ 批准执行', style: 'primary' },
|
|
430
596
|
{ key: 'reject', label: '❌ 拒绝', style: 'danger' },
|
|
431
597
|
],
|
|
598
|
+
allowCustomInput: true,
|
|
432
599
|
},
|
|
433
600
|
channelId: permCtx.channelId,
|
|
434
601
|
sessionId,
|
|
@@ -455,10 +622,15 @@ export class AgentRunner {
|
|
|
455
622
|
logger.warn('[AgentRunner] ExitPlanMode card send failed:', err);
|
|
456
623
|
}
|
|
457
624
|
if (cardSent) {
|
|
625
|
+
permCtx.interactionRouter?.unmarkWaiting(sessionId);
|
|
458
626
|
return new Promise((resolve) => {
|
|
459
|
-
permCtx.interactionRouter?.register(requestId, sessionId, (action) => {
|
|
627
|
+
permCtx.interactionRouter?.register(requestId, sessionId, (action, values) => {
|
|
460
628
|
const trimmed = action.trim();
|
|
461
|
-
if (trimmed === '
|
|
629
|
+
if (trimmed === '_custom_input') {
|
|
630
|
+
const feedback = typeof values?.custom_text === 'string' ? values.custom_text.trim() : '';
|
|
631
|
+
resolve({ behavior: 'deny', message: feedback || '用户提交了反馈', decisionClassification: 'user_reject' });
|
|
632
|
+
}
|
|
633
|
+
else if (trimmed === '2' || trimmed.toLowerCase() === 'reject' || trimmed === '拒绝') {
|
|
462
634
|
resolve({ behavior: 'deny', message: '用户拒绝了计划', decisionClassification: 'user_reject' });
|
|
463
635
|
}
|
|
464
636
|
else {
|
|
@@ -492,6 +664,7 @@ export class AgentRunner {
|
|
|
492
664
|
},
|
|
493
665
|
};
|
|
494
666
|
await sendPrompt(renderActionAsText(fallbackInteraction));
|
|
667
|
+
permCtx.interactionRouter.unmarkWaiting(sessionId);
|
|
495
668
|
return new Promise((resolve) => {
|
|
496
669
|
permCtx.interactionRouter.register(fallbackRequestId, sessionId, (action) => {
|
|
497
670
|
const trimmed = action.trim();
|
|
@@ -505,6 +678,7 @@ export class AgentRunner {
|
|
|
505
678
|
});
|
|
506
679
|
}
|
|
507
680
|
// 无交互能力,发提示后直接 allow
|
|
681
|
+
permCtx?.interactionRouter?.unmarkWaiting(sessionId);
|
|
508
682
|
await sendPrompt('📋 计划审批\nAI 已完成规划,自动批准执行。');
|
|
509
683
|
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_temporary' };
|
|
510
684
|
}
|
|
@@ -512,127 +686,171 @@ export class AgentRunner {
|
|
|
512
686
|
* SDK 原始事件 → 标准 AgentEvent 转换
|
|
513
687
|
* 所有 SDK 特有的事件类型引用封装在此方法内
|
|
514
688
|
*/
|
|
515
|
-
async *transformStream(sdkStream, sessionId) {
|
|
689
|
+
async *transformStream(sdkStream, sessionId, callModel, callEffort, sdkModel) {
|
|
516
690
|
let lastSessionId;
|
|
517
691
|
// tool_use_id → tool_name 映射,用于从 SDKUserMessage 的 tool_result 块中还原工具名
|
|
518
692
|
const toolUseNames = new Map();
|
|
519
693
|
let turnCount = 0;
|
|
520
694
|
const seenMessageIds = new Set();
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
// system: compact_boundary → compact
|
|
529
|
-
if (event.type === 'system' && event.subtype === 'compact_boundary') {
|
|
530
|
-
yield { type: 'compact', preTokens: event.compact_metadata?.pre_tokens || 0 };
|
|
531
|
-
}
|
|
532
|
-
// system: task_progress → task_progress
|
|
533
|
-
if (event.type === 'system' && event.subtype === 'task_progress') {
|
|
534
|
-
yield {
|
|
535
|
-
type: 'task_progress',
|
|
536
|
-
summary: event.summary,
|
|
537
|
-
toolUses: event.tool_uses,
|
|
538
|
-
durationMs: event.duration_ms,
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
// system: session_state_changed → state_changed
|
|
542
|
-
if (event.type === 'system' && event.subtype === 'session_state_changed') {
|
|
543
|
-
yield { type: 'state_changed', state: event.state };
|
|
544
|
-
}
|
|
545
|
-
// assistant: 提取 tool_use 和文本(仅无 text_delta 时提取文本)
|
|
546
|
-
if (event.type === 'assistant' && event.message?.content) {
|
|
547
|
-
const msgId = event.message.id;
|
|
548
|
-
if (!msgId || !seenMessageIds.has(msgId)) {
|
|
549
|
-
if (msgId)
|
|
550
|
-
seenMessageIds.add(msgId);
|
|
551
|
-
turnCount++;
|
|
695
|
+
try {
|
|
696
|
+
for await (const event of sdkStream) {
|
|
697
|
+
// 提取 session_id(任意 SDK 事件都可能携带)
|
|
698
|
+
if (event.session_id && event.session_id !== lastSessionId) {
|
|
699
|
+
lastSessionId = event.session_id;
|
|
700
|
+
this.updateSessionId(sessionId, event.session_id);
|
|
701
|
+
yield { type: 'session_id', sessionId: event.session_id };
|
|
552
702
|
}
|
|
553
|
-
//
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
turnOutputChars += content.text.length;
|
|
562
|
-
}
|
|
703
|
+
// system: compact_boundary → compact
|
|
704
|
+
if (event.type === 'system' && event.subtype === 'compact_boundary') {
|
|
705
|
+
yield {
|
|
706
|
+
type: 'compact',
|
|
707
|
+
preTokens: event.compact_metadata?.pre_tokens || 0,
|
|
708
|
+
postTokens: event.compact_metadata?.post_tokens,
|
|
709
|
+
durationMs: event.compact_metadata?.duration_ms,
|
|
710
|
+
};
|
|
563
711
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
712
|
+
// system: task_progress → task_progress
|
|
713
|
+
if (event.type === 'system' && event.subtype === 'task_progress') {
|
|
714
|
+
yield {
|
|
715
|
+
type: 'task_progress',
|
|
716
|
+
summary: event.summary,
|
|
717
|
+
toolUses: event.tool_uses,
|
|
718
|
+
durationMs: event.duration_ms,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
// system: session_state_changed → state_changed
|
|
722
|
+
if (event.type === 'system' && event.subtype === 'session_state_changed') {
|
|
723
|
+
yield { type: 'state_changed', state: event.state };
|
|
724
|
+
}
|
|
725
|
+
// assistant: 提取 tool_use 和文本(仅无 text_delta 时提取文本)
|
|
726
|
+
if (event.type === 'assistant' && event.message?.content) {
|
|
727
|
+
const msgId = event.message.id;
|
|
728
|
+
if (!msgId || !seenMessageIds.has(msgId)) {
|
|
729
|
+
if (msgId)
|
|
730
|
+
seenMessageIds.add(msgId);
|
|
731
|
+
turnCount++;
|
|
569
732
|
}
|
|
570
|
-
|
|
571
|
-
|
|
733
|
+
// 统计本轮 base agent 全部输出字符数(text + tool_use input)
|
|
734
|
+
let turnOutputChars = 0;
|
|
735
|
+
for (const content of event.message.content) {
|
|
736
|
+
if (content.type === 'tool_use') {
|
|
737
|
+
const inputStr = typeof content.input === 'string' ? content.input : JSON.stringify(content.input || '');
|
|
738
|
+
turnOutputChars += inputStr.length;
|
|
739
|
+
}
|
|
740
|
+
else if (content.type === 'text' && content.text) {
|
|
741
|
+
turnOutputChars += content.text.length;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
for (const content of event.message.content) {
|
|
745
|
+
if (content.type === 'tool_use') {
|
|
746
|
+
if (content.id)
|
|
747
|
+
toolUseNames.set(content.id, content.name);
|
|
748
|
+
yield { type: 'tool_use', name: content.name, input: content.input, callId: content.id, turn: turnCount, outputTokens: turnOutputChars };
|
|
749
|
+
}
|
|
750
|
+
else if (content.type === 'text' && content.text) {
|
|
751
|
+
yield { type: 'text', text: content.text, outputTokens: turnOutputChars, turn: turnCount };
|
|
752
|
+
}
|
|
572
753
|
}
|
|
573
754
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
755
|
+
// user: 提取 tool_result 块(SDK 将工具结果嵌套在 SDKUserMessage 中)
|
|
756
|
+
if (event.type === 'user' && event.message?.content) {
|
|
757
|
+
const contentArray = Array.isArray(event.message.content) ? event.message.content : [];
|
|
758
|
+
for (const block of contentArray) {
|
|
759
|
+
if (typeof block === 'object' && block !== null && block.type === 'tool_result') {
|
|
760
|
+
const toolName = toolUseNames.get(block.tool_use_id) || '';
|
|
761
|
+
const resultContent = typeof block.content === 'string'
|
|
762
|
+
? block.content
|
|
763
|
+
: block.content != null ? JSON.stringify(block.content) : '';
|
|
764
|
+
yield {
|
|
765
|
+
type: 'tool_result',
|
|
766
|
+
name: toolName,
|
|
767
|
+
result: resultContent,
|
|
768
|
+
isError: block.is_error === true,
|
|
769
|
+
error: block.is_error === true ? resultContent : undefined,
|
|
770
|
+
callId: block.tool_use_id,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
592
773
|
}
|
|
593
774
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
775
|
+
// result → complete(含 permission_denials 提取)
|
|
776
|
+
if (event.type === 'result') {
|
|
777
|
+
// 先发出被拒绝的权限事件
|
|
778
|
+
if (Array.isArray(event.permission_denials)) {
|
|
779
|
+
for (const denial of event.permission_denials) {
|
|
780
|
+
yield {
|
|
781
|
+
type: 'tool_result',
|
|
782
|
+
name: denial.tool_name || '',
|
|
783
|
+
result: '',
|
|
784
|
+
isError: true,
|
|
785
|
+
error: `权限被拒绝: ${denial.tool_name}`,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
607
788
|
}
|
|
789
|
+
// 剥离 SDK result 中混入的 <thinking>...</thinking> 块
|
|
790
|
+
const cleanResult = typeof event.result === 'string'
|
|
791
|
+
? event.result.replace(/<thinking>[\s\S]*?<\/thinking>\s*/g, '').trim()
|
|
792
|
+
: event.result;
|
|
793
|
+
// 从 usage 三项求和得到当前上下文占用(与 claude-hud getTotalTokens 相同算法)
|
|
794
|
+
const u = event.usage;
|
|
795
|
+
const totalTokens = u
|
|
796
|
+
? (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0)
|
|
797
|
+
: 0;
|
|
798
|
+
const maxTokens = sdkModel ? contextWindowFor(sdkModel) : 200000;
|
|
799
|
+
const contextUsage = totalTokens > 0 ? {
|
|
800
|
+
totalTokens,
|
|
801
|
+
maxTokens,
|
|
802
|
+
percentage: Math.round((totalTokens / maxTokens) * 100),
|
|
803
|
+
model: callModel ?? this.model,
|
|
804
|
+
effort: callEffort ?? this.effort,
|
|
805
|
+
} : undefined;
|
|
806
|
+
yield {
|
|
807
|
+
type: 'complete',
|
|
808
|
+
result: cleanResult,
|
|
809
|
+
subtype: event.subtype,
|
|
810
|
+
isError: event.is_error,
|
|
811
|
+
errors: event.errors,
|
|
812
|
+
durationMs: event.duration_ms,
|
|
813
|
+
ttftMs: event.ttft_ms,
|
|
814
|
+
costUsd: event.total_cost_usd,
|
|
815
|
+
terminalReason: event.terminal_reason,
|
|
816
|
+
sessionTitle: event.session_title,
|
|
817
|
+
numTurns: event.num_turns,
|
|
818
|
+
tokenUsage: event.usage,
|
|
819
|
+
contextUsage,
|
|
820
|
+
};
|
|
821
|
+
// result 是 SDK 流的终结事件,不再等待后续(防止 interrupt 后流不关闭导致挂起)
|
|
822
|
+
return;
|
|
608
823
|
}
|
|
609
|
-
// 剥离 SDK result 中混入的 <thinking>...</thinking> 块
|
|
610
|
-
const cleanResult = typeof event.result === 'string'
|
|
611
|
-
? event.result.replace(/<thinking>[\s\S]*?<\/thinking>\s*/g, '').trim()
|
|
612
|
-
: event.result;
|
|
613
|
-
yield {
|
|
614
|
-
type: 'complete',
|
|
615
|
-
result: cleanResult,
|
|
616
|
-
subtype: event.subtype,
|
|
617
|
-
isError: event.is_error,
|
|
618
|
-
errors: event.errors,
|
|
619
|
-
durationMs: event.duration_ms,
|
|
620
|
-
costUsd: event.total_cost_usd,
|
|
621
|
-
terminalReason: event.terminal_reason,
|
|
622
|
-
sessionTitle: event.session_title,
|
|
623
|
-
numTurns: event.num_turns,
|
|
624
|
-
usage: event.usage,
|
|
625
|
-
};
|
|
626
|
-
// result 是 SDK 流的终结事件,不再等待后续(防止 interrupt 后流不关闭导致挂起)
|
|
627
|
-
return;
|
|
628
824
|
}
|
|
629
825
|
}
|
|
826
|
+
catch (err) {
|
|
827
|
+
// 子进程崩溃(如 exited with code 1)时,把缓冲的 stderr 打出来还原真实原因。
|
|
828
|
+
// SDK 包装后的错误信息不含子进程实际报错,缓冲区才是根因所在。
|
|
829
|
+
const buf = this.recentStderr.get(sessionId);
|
|
830
|
+
if (buf && buf.length > 0) {
|
|
831
|
+
logger.error(`[AgentRunner] Subprocess stream failed (session=${sessionId}). Last ${buf.length} stderr line(s):\n${buf.join('\n')}`);
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
logger.error(`[AgentRunner] Subprocess stream failed (session=${sessionId}) with no captured stderr.`);
|
|
835
|
+
}
|
|
836
|
+
throw err;
|
|
837
|
+
}
|
|
838
|
+
finally {
|
|
839
|
+
this.recentStderr.delete(sessionId);
|
|
840
|
+
}
|
|
630
841
|
}
|
|
631
|
-
async runQuery(sessionId, prompt, projectPath, initialClaudeSessionId, images, systemPromptAppend, sessionManager) {
|
|
842
|
+
async runQuery(sessionId, prompt, projectPath, initialClaudeSessionId, images, systemPromptAppend, sessionManager, modelOverride) {
|
|
632
843
|
// 记录当前 evolclaw session ID,用于 Agent ctl 环境变量注入
|
|
633
844
|
this.currentEvolclawSessionId = sessionId;
|
|
634
845
|
// 同步用户级配置到内存
|
|
635
846
|
this.syncFromUserSettings();
|
|
847
|
+
// 异步刷新模型别名缓存(fire-and-forget,不阻塞查询)
|
|
848
|
+
if (this.baseUrl) {
|
|
849
|
+
const cached = modelAliasCache.get(this.baseUrl);
|
|
850
|
+
if (!cached || (Date.now() - cached.fetchedAt > MODEL_ALIAS_TTL_MS)) {
|
|
851
|
+
refreshModelAliases(this.baseUrl, this.apiKey);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
636
854
|
ensureDir(projectPath);
|
|
637
855
|
ensureDir(path.join(projectPath, '.claude'));
|
|
638
856
|
// 优先使用传入的 agentSessionId(从数据库恢复),否则使用内存中的
|
|
@@ -795,19 +1013,24 @@ export class AgentRunner {
|
|
|
795
1013
|
const excludeDynamic = this.config?.agents?.claude?.excludeDynamicSections === true;
|
|
796
1014
|
// 公共 options(新旧模式共用)
|
|
797
1015
|
const sdkPermissionMode = this.toSdkPermissionMode();
|
|
798
|
-
|
|
1016
|
+
// 本次调用使用的模型/强度:优先 modelOverride(message-processor 按 关系>agent>全局 解析后传入),
|
|
1017
|
+
// 缺省回落 agent 级 this.model。作为 per-call 入参传入,无共享状态,多对端并发互不污染。
|
|
1018
|
+
const callModel = modelOverride?.model || this.model;
|
|
1019
|
+
const callEffort = (modelOverride?.effort ?? this.effort);
|
|
1020
|
+
logger.info(`[AgentRunner] runQuery model=${callModel} effort=${callEffort ?? 'auto'} permMode=${this.permissionMode} sdkMode=${sdkPermissionMode}`);
|
|
799
1021
|
if (systemPromptAppend) {
|
|
800
1022
|
logger.info(`[AgentRunner] systemPromptAppend: ${systemPromptAppend.length} chars`);
|
|
801
1023
|
}
|
|
802
1024
|
else {
|
|
803
1025
|
logger.info(`[AgentRunner] systemPromptAppend: none`);
|
|
804
1026
|
}
|
|
1027
|
+
const sdkModel = resolveSdkModel(callModel, this.baseUrl);
|
|
805
1028
|
const commonOptions = {
|
|
806
1029
|
cwd: projectPath,
|
|
807
|
-
model:
|
|
808
|
-
...(
|
|
1030
|
+
model: sdkModel,
|
|
1031
|
+
...(callEffort ? { effort: callEffort } : {}),
|
|
809
1032
|
...(this.claudeExecutablePath ? { pathToClaudeCodeExecutable: this.claudeExecutablePath } : {}),
|
|
810
|
-
autoCompactWindow:
|
|
1033
|
+
autoCompactWindow: contextWindowFor(sdkModel),
|
|
811
1034
|
advisorModel: 'haiku',
|
|
812
1035
|
canUseTool: canUseToolCallback,
|
|
813
1036
|
permissionMode: sdkPermissionMode,
|
|
@@ -820,11 +1043,23 @@ export class AgentRunner {
|
|
|
820
1043
|
},
|
|
821
1044
|
...(enableSummaries ? { agentProgressSummaries: true } : {}),
|
|
822
1045
|
stderr: (msg) => {
|
|
1046
|
+
const trimmed = msg.trim();
|
|
1047
|
+
if (trimmed) {
|
|
1048
|
+
// 环形缓冲:保留最近 N 行,供子进程崩溃时还原真实原因
|
|
1049
|
+
let buf = this.recentStderr.get(sessionId);
|
|
1050
|
+
if (!buf) {
|
|
1051
|
+
buf = [];
|
|
1052
|
+
this.recentStderr.set(sessionId, buf);
|
|
1053
|
+
}
|
|
1054
|
+
buf.push(trimmed);
|
|
1055
|
+
if (buf.length > AgentRunner.STDERR_BUFFER_MAX)
|
|
1056
|
+
buf.shift();
|
|
1057
|
+
}
|
|
823
1058
|
if (msg.includes('[ERROR]') || msg.includes('[WARN]') || msg.includes('Stream started')) {
|
|
824
|
-
logger.info(`[Claude-stderr] ${
|
|
1059
|
+
logger.info(`[Claude-stderr] ${trimmed}`);
|
|
825
1060
|
}
|
|
826
1061
|
else {
|
|
827
|
-
logger.debug(`[Claude-stderr] ${
|
|
1062
|
+
logger.debug(`[Claude-stderr] ${trimmed}`);
|
|
828
1063
|
}
|
|
829
1064
|
},
|
|
830
1065
|
env: this.getAgentEnv()
|
|
@@ -920,7 +1155,7 @@ export class AgentRunner {
|
|
|
920
1155
|
}
|
|
921
1156
|
let sdkStream;
|
|
922
1157
|
if (images && images.length > 0) {
|
|
923
|
-
logger.
|
|
1158
|
+
logger.info('[AgentRunner] Creating query with images:', images.length, 'first image size:', images[0]?.data?.length ?? 0);
|
|
924
1159
|
logger.debug('[AgentRunner] Skipping resume for image message to avoid history conflict');
|
|
925
1160
|
const stream = new MessageStream();
|
|
926
1161
|
stream.push(prompt, images);
|
|
@@ -936,7 +1171,7 @@ export class AgentRunner {
|
|
|
936
1171
|
this.interruptFns.set(sessionId, () => sdkStream.interrupt());
|
|
937
1172
|
}
|
|
938
1173
|
// 返回标准 AgentEvent 流(重试由 MessageProcessor 层负责)
|
|
939
|
-
return this.transformStream(sdkStream, sessionId);
|
|
1174
|
+
return this.transformStream(sdkStream, sessionId, callModel, callEffort, sdkModel);
|
|
940
1175
|
}
|
|
941
1176
|
async interrupt(sessionId) {
|
|
942
1177
|
const fn = this.interruptFns.get(sessionId);
|
|
@@ -961,6 +1196,7 @@ export class AgentRunner {
|
|
|
961
1196
|
cleanupStream(sessionId) {
|
|
962
1197
|
this.activeStreams.delete(sessionId);
|
|
963
1198
|
this.interruptFns.delete(sessionId);
|
|
1199
|
+
this.recentStderr.delete(sessionId);
|
|
964
1200
|
}
|
|
965
1201
|
updateSessionId(sessionId, agentSessionId) {
|
|
966
1202
|
logger.info(`[AgentRunner] updateSessionId called: sessionId=${sessionId}, agentSessionId=${agentSessionId}`);
|
|
@@ -974,7 +1210,7 @@ export class AgentRunner {
|
|
|
974
1210
|
prompt,
|
|
975
1211
|
options: {
|
|
976
1212
|
cwd: projectPath,
|
|
977
|
-
model: this.model,
|
|
1213
|
+
model: resolveSdkModel(this.model, this.baseUrl),
|
|
978
1214
|
resume: agentSessionId,
|
|
979
1215
|
maxTurns: 1,
|
|
980
1216
|
permissionMode: this.toSdkPermissionMode(),
|
|
@@ -1060,6 +1296,7 @@ export class AgentRunner {
|
|
|
1060
1296
|
enableFileCheckpointing: true,
|
|
1061
1297
|
permissionMode: this.toSdkPermissionMode(),
|
|
1062
1298
|
stderr: (data) => { stderrChunks.push(data); },
|
|
1299
|
+
env: this.getAgentEnv(),
|
|
1063
1300
|
}
|
|
1064
1301
|
});
|
|
1065
1302
|
try {
|