pikiclaw 0.2.64 → 0.2.66
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/README.md +4 -12
- package/dist/bot-weixin.js +268 -0
- package/dist/bot.js +25 -1
- package/dist/browser-profile.js +458 -0
- package/dist/channel-weixin.js +189 -0
- package/dist/cli-channels.js +11 -3
- package/dist/cli.js +20 -2
- package/dist/config-validation.js +59 -1
- package/dist/constants.js +31 -4
- package/dist/dashboard-routes-config.js +92 -53
- package/dist/dashboard-ui.js +10 -10
- package/dist/dashboard.js +62 -14
- package/dist/mcp-bridge.js +64 -51
- package/dist/mcp-playwright-proxy.js +197 -0
- package/dist/onboarding.js +25 -1
- package/dist/tools/desktop.js +1 -1
- package/dist/user-config.js +19 -0
- package/dist/weixin-api.js +489 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -98,10 +98,6 @@ npx pikiclaw@latest
|
|
|
98
98
|
|
|
99
99
|
<img src="docs/promo-dashboard-config.png" alt="Config" width="700">
|
|
100
100
|
|
|
101
|
-
**插件中心** — 浏览器操控、桌面自动化
|
|
102
|
-
|
|
103
|
-
<img src="docs/promo-dashboard-extensions.png" alt="Extensions" width="700">
|
|
104
|
-
|
|
105
101
|
**会话管理** — 按 Agent 分组的会话泳道
|
|
106
102
|
|
|
107
103
|
<img src="docs/promo-dashboard-sessions.png" alt="Sessions" width="700">
|
|
@@ -162,7 +158,7 @@ npx pikiclaw@latest --doctor
|
|
|
162
158
|
|
|
163
159
|
可选 GUI 能力:
|
|
164
160
|
|
|
165
|
-
- 浏览器自动化:通过 `@playwright/mcp`
|
|
161
|
+
- 浏览器自动化:通过 `@playwright/mcp` 管理一个专用的持久化 Chrome profile;第一次使用时在这个自动化浏览器里登录需要的网站,后续任务会复用同一个 profile
|
|
166
162
|
- macOS 桌面自动化:通过 Appium Mac2 提供 `desktop_open_app`、`desktop_snapshot`、`desktop_click`、`desktop_type`、`desktop_screenshot` 等工具
|
|
167
163
|
|
|
168
164
|
---
|
|
@@ -196,17 +192,13 @@ npx pikiclaw@latest --doctor
|
|
|
196
192
|
## Config And Setup Notes
|
|
197
193
|
|
|
198
194
|
- 持久化配置在 `~/.pikiclaw/setting.json`
|
|
199
|
-
- Dashboard
|
|
200
|
-
- 浏览器 GUI 相关常用变量:
|
|
201
|
-
- `PIKICLAW_BROWSER_GUI`
|
|
202
|
-
- `PIKICLAW_BROWSER_USE_EXTENSION`
|
|
203
|
-
- `PIKICLAW_BROWSER_HEADLESS`
|
|
204
|
-
- `PIKICLAW_BROWSER_ISOLATED`
|
|
205
|
-
- `PLAYWRIGHT_MCP_EXTENSION_TOKEN`
|
|
195
|
+
- Dashboard 是主配置入口,其他运行时配置仍然可用
|
|
206
196
|
- 桌面 GUI 相关常用变量:
|
|
207
197
|
- `PIKICLAW_DESKTOP_GUI`
|
|
208
198
|
- `PIKICLAW_DESKTOP_APPIUM_URL`
|
|
209
199
|
|
|
200
|
+
浏览器自动化由 dashboard 和本地运行时共同管理,会自动创建并复用专用的 Chrome profile 目录。你只需要在这个专用浏览器里登录需要自动化的网站账号一次。
|
|
201
|
+
|
|
210
202
|
如果要启用 macOS 桌面自动化,需要先准备 Appium Mac2:
|
|
211
203
|
|
|
212
204
|
```bash
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { Bot, buildPrompt, fmtUptime, normalizeAgent, parseAllowedChatIds, } from './bot.js';
|
|
5
|
+
import { BOT_SHUTDOWN_FORCE_EXIT_MS, buildSessionTaskId } from './bot-orchestration.js';
|
|
6
|
+
import { shutdownAllDrivers } from './agent-driver.js';
|
|
7
|
+
import { registerProcessRuntime } from './process-control.js';
|
|
8
|
+
import { WeixinChannel } from './channel-weixin.js';
|
|
9
|
+
import { getActiveUserConfig } from './user-config.js';
|
|
10
|
+
const SHUTDOWN_EXIT_CODE = {
|
|
11
|
+
SIGINT: 130,
|
|
12
|
+
SIGTERM: 143,
|
|
13
|
+
};
|
|
14
|
+
function describeError(error) {
|
|
15
|
+
return error instanceof Error ? error.message : String(error ?? 'unknown error');
|
|
16
|
+
}
|
|
17
|
+
export class WeixinBot extends Bot {
|
|
18
|
+
botToken;
|
|
19
|
+
accountId;
|
|
20
|
+
baseUrl;
|
|
21
|
+
channel;
|
|
22
|
+
nextTaskId = 1;
|
|
23
|
+
shutdownInFlight = false;
|
|
24
|
+
shutdownExitCode = null;
|
|
25
|
+
shutdownForceExitTimer = null;
|
|
26
|
+
signalHandlers = {};
|
|
27
|
+
processRuntimeCleanup = null;
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
const config = getActiveUserConfig();
|
|
31
|
+
if (process.env.WEIXIN_ALLOWED_USER_IDS) {
|
|
32
|
+
for (const id of parseAllowedChatIds(process.env.WEIXIN_ALLOWED_USER_IDS))
|
|
33
|
+
this.allowedChatIds.add(id);
|
|
34
|
+
}
|
|
35
|
+
this.baseUrl = String(config.weixinBaseUrl || process.env.WEIXIN_BASE_URL || '').trim();
|
|
36
|
+
this.botToken = String(config.weixinBotToken || process.env.WEIXIN_BOT_TOKEN || '').trim();
|
|
37
|
+
this.accountId = String(config.weixinAccountId || process.env.WEIXIN_ACCOUNT_ID || '').trim();
|
|
38
|
+
if (!this.baseUrl || !this.botToken || !this.accountId) {
|
|
39
|
+
throw new Error('Missing Weixin credentials. Configure via dashboard QR login first.');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
onManagedConfigChange(config, opts = {}) {
|
|
43
|
+
const nextBaseUrl = String(config.weixinBaseUrl || process.env.WEIXIN_BASE_URL || '').trim();
|
|
44
|
+
const nextBotToken = String(config.weixinBotToken || process.env.WEIXIN_BOT_TOKEN || '').trim();
|
|
45
|
+
const nextAccountId = String(config.weixinAccountId || process.env.WEIXIN_ACCOUNT_ID || '').trim();
|
|
46
|
+
if (nextBaseUrl && nextBaseUrl !== this.baseUrl) {
|
|
47
|
+
this.baseUrl = nextBaseUrl;
|
|
48
|
+
if (!opts.initial)
|
|
49
|
+
this.log('weixin baseUrl reloaded from setting.json');
|
|
50
|
+
}
|
|
51
|
+
if (nextBotToken && nextBotToken !== this.botToken) {
|
|
52
|
+
this.botToken = nextBotToken;
|
|
53
|
+
if (!opts.initial)
|
|
54
|
+
this.log('weixin botToken reloaded from setting.json');
|
|
55
|
+
}
|
|
56
|
+
if (nextAccountId && nextAccountId !== this.accountId) {
|
|
57
|
+
this.accountId = nextAccountId;
|
|
58
|
+
if (!opts.initial)
|
|
59
|
+
this.log('weixin accountId reloaded from setting.json');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
installSignalHandlers() {
|
|
63
|
+
this.removeSignalHandlers();
|
|
64
|
+
const onSigint = () => this.beginShutdown('SIGINT');
|
|
65
|
+
const onSigterm = () => this.beginShutdown('SIGTERM');
|
|
66
|
+
this.signalHandlers = { SIGINT: onSigint, SIGTERM: onSigterm };
|
|
67
|
+
process.once('SIGINT', onSigint);
|
|
68
|
+
process.once('SIGTERM', onSigterm);
|
|
69
|
+
}
|
|
70
|
+
removeSignalHandlers() {
|
|
71
|
+
for (const signal of Object.keys(this.signalHandlers)) {
|
|
72
|
+
const handler = this.signalHandlers[signal];
|
|
73
|
+
if (handler)
|
|
74
|
+
process.off(signal, handler);
|
|
75
|
+
}
|
|
76
|
+
this.signalHandlers = {};
|
|
77
|
+
}
|
|
78
|
+
clearShutdownForceExitTimer() {
|
|
79
|
+
if (!this.shutdownForceExitTimer)
|
|
80
|
+
return;
|
|
81
|
+
clearTimeout(this.shutdownForceExitTimer);
|
|
82
|
+
this.shutdownForceExitTimer = null;
|
|
83
|
+
}
|
|
84
|
+
cleanupRuntimeForExit() {
|
|
85
|
+
try {
|
|
86
|
+
this.channel.disconnect();
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
this.stopKeepAlive();
|
|
90
|
+
shutdownAllDrivers();
|
|
91
|
+
}
|
|
92
|
+
beginShutdown(signal) {
|
|
93
|
+
if (this.shutdownInFlight)
|
|
94
|
+
return;
|
|
95
|
+
this.shutdownInFlight = true;
|
|
96
|
+
this.shutdownExitCode = SHUTDOWN_EXIT_CODE[signal];
|
|
97
|
+
this.log(`${signal}, shutting down...`);
|
|
98
|
+
this.cleanupRuntimeForExit();
|
|
99
|
+
this.clearShutdownForceExitTimer();
|
|
100
|
+
this.shutdownForceExitTimer = setTimeout(() => {
|
|
101
|
+
this.log(`shutdown still pending after ${Math.floor(BOT_SHUTDOWN_FORCE_EXIT_MS / 1000)}s, forcing exit`);
|
|
102
|
+
process.exit(this.shutdownExitCode ?? 1);
|
|
103
|
+
}, BOT_SHUTDOWN_FORCE_EXIT_MS);
|
|
104
|
+
this.shutdownForceExitTimer.unref?.();
|
|
105
|
+
}
|
|
106
|
+
resolveSession(chatId, title, files) {
|
|
107
|
+
return this.ensureSessionForChat(chatId, title, files);
|
|
108
|
+
}
|
|
109
|
+
buildStatusText(chatId) {
|
|
110
|
+
const status = this.getStatusData(chatId);
|
|
111
|
+
return [
|
|
112
|
+
`Agent: ${status.agent}`,
|
|
113
|
+
`Model: ${status.model || '-'}`,
|
|
114
|
+
`Session: ${status.sessionId || 'new'}`,
|
|
115
|
+
`Tasks: ${status.activeTasksCount}`,
|
|
116
|
+
`Workdir: ${status.workdir}`,
|
|
117
|
+
`Uptime: ${fmtUptime(status.uptime)}`,
|
|
118
|
+
].join('\n');
|
|
119
|
+
}
|
|
120
|
+
async handleCommand(text, ctx) {
|
|
121
|
+
const [rawCommand, ...rest] = text.trim().slice(1).split(/\s+/);
|
|
122
|
+
const command = rawCommand?.toLowerCase() || '';
|
|
123
|
+
const args = rest.join(' ').trim();
|
|
124
|
+
switch (command) {
|
|
125
|
+
case 'help':
|
|
126
|
+
await ctx.reply([
|
|
127
|
+
'/help',
|
|
128
|
+
'/new',
|
|
129
|
+
'/status',
|
|
130
|
+
'/agent codex|claude|gemini',
|
|
131
|
+
].join('\n'));
|
|
132
|
+
return true;
|
|
133
|
+
case 'new':
|
|
134
|
+
this.resetConversationForChat(ctx.chatId);
|
|
135
|
+
await ctx.reply('Started a new session.');
|
|
136
|
+
return true;
|
|
137
|
+
case 'status':
|
|
138
|
+
await ctx.reply(this.buildStatusText(ctx.chatId));
|
|
139
|
+
return true;
|
|
140
|
+
case 'agent':
|
|
141
|
+
if (!args) {
|
|
142
|
+
await ctx.reply('Usage: /agent codex|claude|gemini');
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const agent = normalizeAgent(args);
|
|
147
|
+
this.switchAgentForChat(ctx.chatId, agent);
|
|
148
|
+
await ctx.reply(`Agent switched to ${agent}.`);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
await ctx.reply('Usage: /agent codex|claude|gemini');
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
default:
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
createMcpSendFile(chatId) {
|
|
159
|
+
return async (filePath) => {
|
|
160
|
+
try {
|
|
161
|
+
await this.channel.send(chatId, `Artifact ready: ${path.basename(filePath)}\n${filePath}`);
|
|
162
|
+
return { ok: true };
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
return { ok: false, error: describeError(error) };
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async sendResult(chatId, result) {
|
|
170
|
+
const text = result.ok
|
|
171
|
+
? (result.message.trim() || 'Task finished.')
|
|
172
|
+
: ['Task failed.', result.error || result.message || 'Unknown error.'].filter(Boolean).join('\n');
|
|
173
|
+
await this.channel.send(chatId, text);
|
|
174
|
+
}
|
|
175
|
+
async handleMessage(msg, ctx) {
|
|
176
|
+
const text = msg.text.trim();
|
|
177
|
+
if (text.startsWith('/') && await this.handleCommand(text, ctx))
|
|
178
|
+
return;
|
|
179
|
+
if (!text && !msg.files.length) {
|
|
180
|
+
await ctx.reply('This Weixin channel currently supports text input only.');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const session = this.resolveSession(ctx.chatId, text, msg.files);
|
|
184
|
+
const prompt = buildPrompt(text, msg.files);
|
|
185
|
+
const taskId = buildSessionTaskId(session, this.nextTaskId++);
|
|
186
|
+
this.beginTask({
|
|
187
|
+
taskId,
|
|
188
|
+
chatId: ctx.chatId,
|
|
189
|
+
agent: session.agent,
|
|
190
|
+
sessionKey: session.key,
|
|
191
|
+
prompt,
|
|
192
|
+
startedAt: Date.now(),
|
|
193
|
+
sourceMessageId: ctx.messageId,
|
|
194
|
+
});
|
|
195
|
+
void this.queueSessionTask(session, async () => {
|
|
196
|
+
const abortController = new AbortController();
|
|
197
|
+
const task = this.markTaskRunning(taskId, () => abortController.abort());
|
|
198
|
+
if (task?.cancelled) {
|
|
199
|
+
this.finishTask(taskId);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
let typingTimer = null;
|
|
203
|
+
try {
|
|
204
|
+
await ctx.sendTyping().catch(() => { });
|
|
205
|
+
typingTimer = setInterval(() => {
|
|
206
|
+
void ctx.sendTyping().catch(() => { });
|
|
207
|
+
}, 4_000);
|
|
208
|
+
typingTimer.unref?.();
|
|
209
|
+
const result = await this.runStream(prompt, session, msg.files, () => { }, undefined, this.createMcpSendFile(ctx.chatId), abortController.signal);
|
|
210
|
+
await this.sendResult(ctx.chatId, result);
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
await ctx.reply(`Error: ${describeError(error)}`);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
if (typingTimer)
|
|
217
|
+
clearInterval(typingTimer);
|
|
218
|
+
this.finishTask(taskId);
|
|
219
|
+
this.syncSelectedChats(session);
|
|
220
|
+
}
|
|
221
|
+
}).catch(error => {
|
|
222
|
+
this.finishTask(taskId);
|
|
223
|
+
this.log(`weixin queue execution failed: ${describeError(error)}`);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
async run() {
|
|
227
|
+
const tmpDir = path.join(os.tmpdir(), 'pikiclaw');
|
|
228
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
229
|
+
this.channel = new WeixinChannel({
|
|
230
|
+
token: this.botToken,
|
|
231
|
+
accountId: this.accountId,
|
|
232
|
+
baseUrl: this.baseUrl,
|
|
233
|
+
allowedChatIds: this.allowedChatIds.size ? new Set([...this.allowedChatIds].map(value => String(value))) : undefined,
|
|
234
|
+
});
|
|
235
|
+
this.processRuntimeCleanup?.();
|
|
236
|
+
this.processRuntimeCleanup = registerProcessRuntime({
|
|
237
|
+
label: 'weixin',
|
|
238
|
+
getActiveTaskCount: () => this.activeTasks.size,
|
|
239
|
+
prepareForRestart: () => this.cleanupRuntimeForExit(),
|
|
240
|
+
});
|
|
241
|
+
this.installSignalHandlers();
|
|
242
|
+
try {
|
|
243
|
+
const bot = await this.channel.connect();
|
|
244
|
+
this.connected = true;
|
|
245
|
+
this.log(`bot: ${bot.displayName} (id=${bot.id})`);
|
|
246
|
+
for (const agent of this.fetchAgents().agents) {
|
|
247
|
+
this.log(`agent ${agent.agent}: ${agent.path || 'NOT FOUND'}`);
|
|
248
|
+
}
|
|
249
|
+
this.log(`config: agent=${this.defaultAgent} workdir=${this.workdir} timeout=${this.runTimeout}s`);
|
|
250
|
+
this.channel.onMessage((msg, ctx) => this.handleMessage(msg, ctx));
|
|
251
|
+
this.channel.onError(error => this.log(`error: ${describeError(error)}`));
|
|
252
|
+
this.startKeepAlive();
|
|
253
|
+
this.log('✓ Weixin connected, long-polling started — ready to receive messages');
|
|
254
|
+
await this.channel.listen();
|
|
255
|
+
this.stopKeepAlive();
|
|
256
|
+
this.log('stopped');
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
this.stopKeepAlive();
|
|
260
|
+
this.clearShutdownForceExitTimer();
|
|
261
|
+
this.removeSignalHandlers();
|
|
262
|
+
this.processRuntimeCleanup?.();
|
|
263
|
+
this.processRuntimeCleanup = null;
|
|
264
|
+
if (this.shutdownInFlight)
|
|
265
|
+
process.exit(this.shutdownExitCode ?? 1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
package/dist/bot.js
CHANGED
|
@@ -10,6 +10,7 @@ import { execSync, spawn } from 'node:child_process';
|
|
|
10
10
|
import { getActiveUserConfig, onUserConfigChange, resolveUserWorkdir, setUserWorkdir } from './user-config.js';
|
|
11
11
|
import { doStream, getSessions, getSessionTail, getUsage, initializeProjectSkills, listAgents, listModels, listSkills, stageSessionFiles, isPendingSessionId, normalizeClaudeModelId, } from './code-agent.js';
|
|
12
12
|
import { getDriver, hasDriver, allDriverIds } from './agent-driver.js';
|
|
13
|
+
import { resolveGuiIntegrationConfig } from './mcp-bridge.js';
|
|
13
14
|
import { terminateProcessTree } from './process-control.js';
|
|
14
15
|
import { VERSION } from './version.js';
|
|
15
16
|
import { buildHumanLoopResponse, createEmptyHumanLoopAnswer, currentHumanLoopQuestion, isHumanLoopAwaitingText, setHumanLoopOption, setHumanLoopText, skipHumanLoopQuestion, } from './human-loop.js';
|
|
@@ -221,6 +222,23 @@ function buildMcpDeliveryPrompt() {
|
|
|
221
222
|
'This is an IM conversation, so pay attention to the IM tools.',
|
|
222
223
|
].join('\n');
|
|
223
224
|
}
|
|
225
|
+
function buildBrowserAutomationPrompt(browserEnabled) {
|
|
226
|
+
if (!browserEnabled) {
|
|
227
|
+
return [
|
|
228
|
+
'[Browser Automation]',
|
|
229
|
+
'Managed browser automation is disabled by default for this session.',
|
|
230
|
+
process.platform === 'darwin'
|
|
231
|
+
? 'On macOS, operate your main browser directly with native commands such as open, osascript, and screencapture when needed.'
|
|
232
|
+
: 'Use native OS or browser commands directly when browser automation is not enabled.',
|
|
233
|
+
].join('\n');
|
|
234
|
+
}
|
|
235
|
+
return [
|
|
236
|
+
'[Browser Automation]',
|
|
237
|
+
'A Playwright MCP browser server is already configured to use the local Chrome channel with a persistent profile.',
|
|
238
|
+
'Do not call browser_install unless a browser tool explicitly reports that Chrome or the browser is missing.',
|
|
239
|
+
'If you need a new tab, use browser_tabs with action="new".',
|
|
240
|
+
].join('\n');
|
|
241
|
+
}
|
|
224
242
|
function configModelValue(config, agent) {
|
|
225
243
|
switch (agent) {
|
|
226
244
|
case 'claude': return normalizeClaudeModelId(config.claudeModel || process.env.CLAUDE_MODEL || 'claude-opus-4-6');
|
|
@@ -986,6 +1004,7 @@ export class Bot {
|
|
|
986
1004
|
const totalMem = os.totalmem(), freeMem = os.freemem();
|
|
987
1005
|
const memory = getHostMemoryUsageData(totalMem, freeMem);
|
|
988
1006
|
const cpuUsage = getHostCpuUsageData();
|
|
1007
|
+
const [loadOne, loadFive, loadFifteen] = os.loadavg();
|
|
989
1008
|
let disk = null;
|
|
990
1009
|
const battery = getHostBatteryData();
|
|
991
1010
|
try {
|
|
@@ -1004,6 +1023,7 @@ export class Bot {
|
|
|
1004
1023
|
hostName: getHostDisplayName(),
|
|
1005
1024
|
cpuModel: cpus[0]?.model || 'unknown', cpuCount: cpus.length,
|
|
1006
1025
|
cpuUsage,
|
|
1026
|
+
loadAverage: { one: loadOne, five: loadFive, fifteen: loadFifteen },
|
|
1007
1027
|
totalMem, freeMem, memoryUsed: memory.usedBytes, memoryAvailable: memory.availableBytes, memoryPercent: memory.percent, memorySource: memory.source,
|
|
1008
1028
|
disk, battery, topProcs,
|
|
1009
1029
|
selfPid: process.pid, selfRss: mem.rss, selfHeap: mem.heapUsed,
|
|
@@ -1072,11 +1092,15 @@ export class Bot {
|
|
|
1072
1092
|
const resolvedModel = cs.modelId || this.modelForAgent(cs.agent);
|
|
1073
1093
|
const agentConfig = this.agentConfigs[cs.agent] || {};
|
|
1074
1094
|
const extraArgs = agentConfig.extraArgs || [];
|
|
1095
|
+
const browserEnabled = resolveGuiIntegrationConfig(getActiveUserConfig()).browserEnabled;
|
|
1075
1096
|
this.log(`[runStream] agent=${cs.agent} session=${cs.sessionId || '(new)'} workdir=${this.workdir} timeout=${this.runTimeout}s attachments=${attachments.length}`);
|
|
1076
1097
|
this.log(`[runStream] ${cs.agent} config: model=${resolvedModel} extraArgs=[${extraArgs.join(' ')}]`);
|
|
1077
1098
|
const isFirstTurnOfSession = !cs.sessionId || isPendingSessionId(cs.sessionId);
|
|
1099
|
+
const mcpSystemPrompt = mcpSendFile
|
|
1100
|
+
? appendExtraPrompt(buildMcpDeliveryPrompt(), buildBrowserAutomationPrompt(browserEnabled))
|
|
1101
|
+
: '';
|
|
1078
1102
|
const effectiveSystemPrompt = isFirstTurnOfSession
|
|
1079
|
-
?
|
|
1103
|
+
? appendExtraPrompt(systemPrompt, mcpSystemPrompt)
|
|
1080
1104
|
: undefined;
|
|
1081
1105
|
const opts = {
|
|
1082
1106
|
agent: cs.agent, prompt, workdir: this.workdir, timeout: this.runTimeout,
|