@yeaft/webchat-agent 0.0.11 → 0.0.13
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/claude.js +10 -0
- package/conversation.js +1 -0
- package/history.js +6 -2
- package/package.json +3 -2
- package/scripts/agent-tray.ps1 +134 -0
- package/service.js +13 -2
package/claude.js
CHANGED
|
@@ -34,6 +34,7 @@ export async function startClaudeQuery(conversationId, workDir, resumeSessionId)
|
|
|
34
34
|
createdAt: Date.now(),
|
|
35
35
|
abortController,
|
|
36
36
|
turnActive: false, // 是否有 turn 正在处理中
|
|
37
|
+
turnResultReceived: false, // 当前 turn 是否已收到 result(用于抑制重复 result)
|
|
37
38
|
// Metadata from system init message
|
|
38
39
|
tools: [],
|
|
39
40
|
slashCommands: [],
|
|
@@ -324,6 +325,7 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
324
325
|
|
|
325
326
|
// 捕获 result 消息中的 usage 信息
|
|
326
327
|
if (message.type === 'result') {
|
|
328
|
+
// 累计 usage(无论是否重复,始终统计)
|
|
327
329
|
if (message.usage) {
|
|
328
330
|
state.usage.inputTokens += message.usage.input_tokens || 0;
|
|
329
331
|
state.usage.outputTokens += message.usage.output_tokens || 0;
|
|
@@ -333,9 +335,17 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
333
335
|
state.usage.totalCostUsd += message.total_cost_usd || 0;
|
|
334
336
|
console.log(`[SDK] Query completed for ${conversationId}, cost: $${state.usage.totalCostUsd.toFixed(4)}`);
|
|
335
337
|
|
|
338
|
+
// ★ Guard:当前 turn 已收到过 result,抑制 SDK 发出的重复 result
|
|
339
|
+
// (长任务场景下 SDK 可能先发 result/success 再发 result/error_during_execution)
|
|
340
|
+
if (state.turnResultReceived) {
|
|
341
|
+
console.warn(`[SDK] Suppressing duplicate result for ${conversationId} (subtype: ${message.subtype || 'unknown'})`);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
336
345
|
// ★ Turn 完成:发送 turn_completed,进程继续运行等待下一条消息
|
|
337
346
|
// stream-json 模式下 Claude 进程是持久运行的,for-await 在 result 后继续等待
|
|
338
347
|
// 不清空 state.query 和 state.inputStream,下次用户消息直接通过同一个 inputStream 发送
|
|
348
|
+
state.turnResultReceived = true;
|
|
339
349
|
sendOutput(conversationId, message);
|
|
340
350
|
|
|
341
351
|
resultHandled = true;
|
package/conversation.js
CHANGED
|
@@ -360,6 +360,7 @@ export async function handleUserInput(msg) {
|
|
|
360
360
|
|
|
361
361
|
console.log(`[${conversationId}] Sending: ${prompt.substring(0, 100)}...`);
|
|
362
362
|
state.turnActive = true;
|
|
363
|
+
state.turnResultReceived = false; // 重置 per-turn 去重标志
|
|
363
364
|
sendConversationList(); // 在 turnActive=true 后通知 server,确保 processing 状态正确
|
|
364
365
|
state.inputStream.enqueue(userMessage);
|
|
365
366
|
}
|
package/history.js
CHANGED
|
@@ -168,7 +168,7 @@ export function loadSessionHistory(workDir, claudeSessionId, limit = 500) {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
export async function handleListHistorySessions(msg) {
|
|
171
|
-
const { workDir, requestId } = msg;
|
|
171
|
+
const { workDir, requestId, _requestClientId } = msg;
|
|
172
172
|
const effectiveWorkDir = workDir || ctx.CONFIG.workDir;
|
|
173
173
|
|
|
174
174
|
console.log(`Listing history sessions for: ${effectiveWorkDir}`);
|
|
@@ -178,6 +178,7 @@ export async function handleListHistorySessions(msg) {
|
|
|
178
178
|
ctx.sendToServer({
|
|
179
179
|
type: 'history_sessions_list',
|
|
180
180
|
requestId,
|
|
181
|
+
_requestClientId,
|
|
181
182
|
workDir: effectiveWorkDir,
|
|
182
183
|
sessions
|
|
183
184
|
});
|
|
@@ -186,6 +187,7 @@ export async function handleListHistorySessions(msg) {
|
|
|
186
187
|
ctx.sendToServer({
|
|
187
188
|
type: 'history_sessions_list',
|
|
188
189
|
requestId,
|
|
190
|
+
_requestClientId,
|
|
189
191
|
workDir: effectiveWorkDir,
|
|
190
192
|
sessions: [],
|
|
191
193
|
error: e.message
|
|
@@ -195,7 +197,7 @@ export async function handleListHistorySessions(msg) {
|
|
|
195
197
|
|
|
196
198
|
// 列出 Claude projects 目录下的所有 folder (工作目录)
|
|
197
199
|
export async function handleListFolders(msg) {
|
|
198
|
-
const { requestId } = msg;
|
|
200
|
+
const { requestId, _requestClientId } = msg;
|
|
199
201
|
const projectsDir = getClaudeProjectsDir();
|
|
200
202
|
|
|
201
203
|
console.log(`Listing folders from: ${projectsDir}`);
|
|
@@ -268,6 +270,7 @@ export async function handleListFolders(msg) {
|
|
|
268
270
|
ctx.sendToServer({
|
|
269
271
|
type: 'folders_list',
|
|
270
272
|
requestId,
|
|
273
|
+
_requestClientId,
|
|
271
274
|
folders
|
|
272
275
|
});
|
|
273
276
|
console.log(`folders_list sent with ${folders.length} folders`);
|
|
@@ -276,6 +279,7 @@ export async function handleListFolders(msg) {
|
|
|
276
279
|
ctx.sendToServer({
|
|
277
280
|
type: 'folders_list',
|
|
278
281
|
requestId,
|
|
282
|
+
_requestClientId,
|
|
279
283
|
folders: [],
|
|
280
284
|
error: e.message
|
|
281
285
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeaft/webchat-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"workbench.js",
|
|
45
45
|
"history.js",
|
|
46
46
|
"encryption.js",
|
|
47
|
-
"sdk/"
|
|
47
|
+
"sdk/",
|
|
48
|
+
"scripts/agent-tray.ps1"
|
|
48
49
|
],
|
|
49
50
|
"license": "MIT",
|
|
50
51
|
"author": "Yeaft",
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Claude Agent 托盘管理器
|
|
2
|
+
# 右键托盘图标可以查看日志、重启、停止等
|
|
3
|
+
|
|
4
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
5
|
+
Add-Type -AssemblyName System.Drawing
|
|
6
|
+
|
|
7
|
+
$PM2AppName = "yeaft-agent"
|
|
8
|
+
|
|
9
|
+
# 创建托盘图标
|
|
10
|
+
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
11
|
+
$notifyIcon.Text = "Yeaft Agent"
|
|
12
|
+
$notifyIcon.Visible = $true
|
|
13
|
+
|
|
14
|
+
# 创建 Claude 风格图标(橙色圆形 + 白色 C)
|
|
15
|
+
function Create-ClaudeIcon {
|
|
16
|
+
$size = 32
|
|
17
|
+
$bitmap = New-Object System.Drawing.Bitmap($size, $size)
|
|
18
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
19
|
+
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
|
|
20
|
+
$graphics.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
|
|
21
|
+
|
|
22
|
+
# 橙色背景圆形 (Claude 的品牌色)
|
|
23
|
+
$orangeBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(255, 204, 102, 51))
|
|
24
|
+
$graphics.FillEllipse($orangeBrush, 1, 1, $size - 2, $size - 2)
|
|
25
|
+
|
|
26
|
+
# 白色 "C" 字母
|
|
27
|
+
$whiteBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
|
|
28
|
+
$font = New-Object System.Drawing.Font("Arial", 16, [System.Drawing.FontStyle]::Bold)
|
|
29
|
+
# 测量文字大小并精确居中
|
|
30
|
+
$textSize = $graphics.MeasureString("C", $font)
|
|
31
|
+
$x = ($size - $textSize.Width) / 2
|
|
32
|
+
$y = ($size - $textSize.Height) / 2
|
|
33
|
+
$graphics.DrawString("C", $font, $whiteBrush, $x, $y)
|
|
34
|
+
|
|
35
|
+
$graphics.Dispose()
|
|
36
|
+
|
|
37
|
+
# 转换为图标
|
|
38
|
+
$hIcon = $bitmap.GetHicon()
|
|
39
|
+
$icon = [System.Drawing.Icon]::FromHandle($hIcon)
|
|
40
|
+
return $icon
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
$notifyIcon.Icon = Create-ClaudeIcon
|
|
44
|
+
|
|
45
|
+
# 创建右键菜单
|
|
46
|
+
$contextMenu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
47
|
+
|
|
48
|
+
# 查看状态
|
|
49
|
+
$menuStatus = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
50
|
+
$menuStatus.Text = "View Status"
|
|
51
|
+
$menuStatus.Add_Click({
|
|
52
|
+
Start-Process "powershell" -ArgumentList "-NoExit -Command `"pm2 status`""
|
|
53
|
+
})
|
|
54
|
+
$contextMenu.Items.Add($menuStatus)
|
|
55
|
+
|
|
56
|
+
# 查看日志
|
|
57
|
+
$menuLogs = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
58
|
+
$menuLogs.Text = "View Logs"
|
|
59
|
+
$menuLogs.Add_Click({
|
|
60
|
+
Start-Process "powershell" -ArgumentList "-NoExit -Command `"pm2 logs $PM2AppName --lines 100`""
|
|
61
|
+
})
|
|
62
|
+
$contextMenu.Items.Add($menuLogs)
|
|
63
|
+
|
|
64
|
+
# 分隔线
|
|
65
|
+
$contextMenu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
|
|
66
|
+
|
|
67
|
+
# 重启
|
|
68
|
+
$menuRestart = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
69
|
+
$menuRestart.Text = "Restart Agent"
|
|
70
|
+
$menuRestart.Add_Click({
|
|
71
|
+
pm2 restart $PM2AppName
|
|
72
|
+
$notifyIcon.ShowBalloonTip(2000, "Yeaft Agent", "Agent restarted", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
73
|
+
})
|
|
74
|
+
$contextMenu.Items.Add($menuRestart)
|
|
75
|
+
|
|
76
|
+
# 停止
|
|
77
|
+
$menuStop = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
78
|
+
$menuStop.Text = "Stop Agent"
|
|
79
|
+
$menuStop.Add_Click({
|
|
80
|
+
pm2 stop $PM2AppName
|
|
81
|
+
$notifyIcon.ShowBalloonTip(2000, "Yeaft Agent", "Agent stopped", [System.Windows.Forms.ToolTipIcon]::Warning)
|
|
82
|
+
})
|
|
83
|
+
$contextMenu.Items.Add($menuStop)
|
|
84
|
+
|
|
85
|
+
# 启动
|
|
86
|
+
$menuStart = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
87
|
+
$menuStart.Text = "Start Agent"
|
|
88
|
+
$menuStart.Add_Click({
|
|
89
|
+
pm2 start $PM2AppName
|
|
90
|
+
$notifyIcon.ShowBalloonTip(2000, "Yeaft Agent", "Agent started", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
91
|
+
})
|
|
92
|
+
$contextMenu.Items.Add($menuStart)
|
|
93
|
+
|
|
94
|
+
# 分隔线
|
|
95
|
+
$contextMenu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
|
|
96
|
+
|
|
97
|
+
# 打开日志文件夹
|
|
98
|
+
$menuOpenLogs = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
99
|
+
$menuOpenLogs.Text = "Open Logs Folder"
|
|
100
|
+
$menuOpenLogs.Add_Click({
|
|
101
|
+
$logDir = Join-Path $env:APPDATA "yeaft-agent\logs"
|
|
102
|
+
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
|
|
103
|
+
Start-Process "explorer" -ArgumentList $logDir
|
|
104
|
+
})
|
|
105
|
+
$contextMenu.Items.Add($menuOpenLogs)
|
|
106
|
+
|
|
107
|
+
# 分隔线
|
|
108
|
+
$contextMenu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
|
|
109
|
+
|
|
110
|
+
# 退出(停止 agent + 关闭托盘)
|
|
111
|
+
$menuExit = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
112
|
+
$menuExit.Text = "Exit"
|
|
113
|
+
$menuExit.Add_Click({
|
|
114
|
+
pm2 stop $PM2AppName 2>$null
|
|
115
|
+
$notifyIcon.ShowBalloonTip(1000, "Yeaft Agent", "Agent stopped", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
116
|
+
Start-Sleep -Milliseconds 500
|
|
117
|
+
$notifyIcon.Visible = $false
|
|
118
|
+
$notifyIcon.Dispose()
|
|
119
|
+
[System.Windows.Forms.Application]::Exit()
|
|
120
|
+
})
|
|
121
|
+
$contextMenu.Items.Add($menuExit)
|
|
122
|
+
|
|
123
|
+
$notifyIcon.ContextMenuStrip = $contextMenu
|
|
124
|
+
|
|
125
|
+
# 双击打开日志
|
|
126
|
+
$notifyIcon.Add_DoubleClick({
|
|
127
|
+
Start-Process "powershell" -ArgumentList "-NoExit -Command `"pm2 logs $PM2AppName`""
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
# 显示启动提示
|
|
131
|
+
$notifyIcon.ShowBalloonTip(2000, "Yeaft Agent", "Tray manager started. Right-click for options.", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
132
|
+
|
|
133
|
+
# 保持运行
|
|
134
|
+
[System.Windows.Forms.Application]::Run()
|
package/service.js
CHANGED
|
@@ -443,12 +443,23 @@ function winInstall(config) {
|
|
|
443
443
|
|
|
444
444
|
// Setup auto-start: create startup script in Windows Startup folder
|
|
445
445
|
// pm2-startup doesn't work well on Windows, use Startup folder approach
|
|
446
|
+
const trayScript = join(__dirname, 'scripts', 'agent-tray.ps1');
|
|
446
447
|
const startupDir = join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
447
448
|
const startupBat = join(startupDir, `${PM2_APP_NAME}.bat`);
|
|
448
|
-
// pm2
|
|
449
|
-
|
|
449
|
+
// Resurrect pm2 processes + launch tray icon
|
|
450
|
+
let batContent = `@echo off\r\npm2 resurrect\r\n`;
|
|
451
|
+
if (existsSync(trayScript)) {
|
|
452
|
+
batContent += `start "" powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File "${trayScript}"\r\n`;
|
|
453
|
+
}
|
|
450
454
|
writeFileSync(startupBat, batContent);
|
|
451
455
|
|
|
456
|
+
// Launch tray now
|
|
457
|
+
if (existsSync(trayScript)) {
|
|
458
|
+
spawn('powershell', ['-WindowStyle', 'Hidden', '-ExecutionPolicy', 'Bypass', '-File', trayScript], {
|
|
459
|
+
detached: true, stdio: 'ignore'
|
|
460
|
+
}).unref();
|
|
461
|
+
}
|
|
462
|
+
|
|
452
463
|
console.log(`\nService installed and started.`);
|
|
453
464
|
console.log(` Ecosystem: ${ecoPath}`);
|
|
454
465
|
console.log(` Startup: ${startupBat}`);
|