@yeaft/webchat-agent 0.1.224 → 0.1.226
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/conductor/actor.js +478 -0
- package/conductor/claudemd-generator.js +194 -0
- package/conductor/conductor-claude.js +472 -0
- package/conductor/index.js +65 -0
- package/conductor/orchestrator-flows.js +498 -0
- package/conductor/orchestrator.js +888 -0
- package/conductor/persistence.js +297 -0
- package/conductor/personas/dev.js +97 -0
- package/conductor/personas/index.js +77 -0
- package/conductor/personas/trading.js +92 -0
- package/conductor/personas/video.js +75 -0
- package/conductor/personas/writing.js +82 -0
- package/conductor/semaphore.js +73 -0
- package/conductor/session.js +208 -0
- package/conductor/specialties/common.js +350 -0
- package/conductor/specialties/index.js +135 -0
- package/conductor/specialties/trading.js +165 -0
- package/conductor/specialties/video.js +175 -0
- package/conductor/specialties/writing.js +120 -0
- package/conductor/task.js +333 -0
- package/conductor/ui-messages.js +170 -0
- package/conductor/worktree.js +182 -0
- package/conductor.js +21 -32
- package/connection/message-router.js +10 -32
- package/package.json +2 -8
- package/scripts/agent-tray.ps1 +0 -134
- package/scripts/create-startup-shortcut.ps1 +0 -18
- package/scripts/ecosystem.config.cjs +0 -46
- package/scripts/pm2-start.ps1 +0 -59
- package/scripts/start-with-tray.ps1 +0 -36
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — UI Message Helpers (V5)
|
|
3
|
+
*
|
|
4
|
+
* Sends WebSocket messages to server/frontend.
|
|
5
|
+
* Maintains uiMessages persistence list on the conductor instance.
|
|
6
|
+
*
|
|
7
|
+
* No sessionId — Conductor is a singleton per Agent.
|
|
8
|
+
*/
|
|
9
|
+
import ctx from '../context.js';
|
|
10
|
+
import { saveConductorMeta } from './persistence.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Send conductor message to server (forwarded to Web frontend)
|
|
14
|
+
*/
|
|
15
|
+
export function sendConductorMessage(msg) {
|
|
16
|
+
if (ctx.sendToServer) {
|
|
17
|
+
ctx.sendToServer(msg);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Send Conductor Claude output to frontend
|
|
23
|
+
* outputType: 'text' | 'tool_use' | 'tool_result' | 'system' | 'task_created' | 'task_forwarded'
|
|
24
|
+
*/
|
|
25
|
+
export function sendConductorOutput(conductor, outputType, rawMessage, extra = {}) {
|
|
26
|
+
sendConductorMessage({
|
|
27
|
+
type: 'conductor_output',
|
|
28
|
+
outputType,
|
|
29
|
+
data: rawMessage,
|
|
30
|
+
...extra
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Record trimmed UI messages
|
|
34
|
+
if (outputType === 'text') {
|
|
35
|
+
const content = rawMessage?.message?.content;
|
|
36
|
+
let text = '';
|
|
37
|
+
if (typeof content === 'string') {
|
|
38
|
+
text = content;
|
|
39
|
+
} else if (Array.isArray(content)) {
|
|
40
|
+
text = content.filter(b => b.type === 'text').map(b => b.text).join('');
|
|
41
|
+
}
|
|
42
|
+
if (!text) return;
|
|
43
|
+
let found = false;
|
|
44
|
+
for (let i = conductor.uiMessages.length - 1; i >= 0; i--) {
|
|
45
|
+
const msg = conductor.uiMessages[i];
|
|
46
|
+
if (msg.source === 'conductor' && msg.type === 'text' && msg._streaming) {
|
|
47
|
+
msg.content += text;
|
|
48
|
+
found = true;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!found) {
|
|
53
|
+
conductor.uiMessages.push({
|
|
54
|
+
source: 'conductor', type: 'text', content: text,
|
|
55
|
+
_streaming: true, timestamp: Date.now()
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
} else if (outputType === 'system') {
|
|
59
|
+
const content = rawMessage?.message?.content;
|
|
60
|
+
let text = '';
|
|
61
|
+
if (typeof content === 'string') text = content;
|
|
62
|
+
else if (Array.isArray(content)) text = content.filter(b => b.type === 'text').map(b => b.text).join('');
|
|
63
|
+
if (!text) return;
|
|
64
|
+
conductor.uiMessages.push({
|
|
65
|
+
source: 'conductor', type: 'system', content: text, timestamp: Date.now()
|
|
66
|
+
});
|
|
67
|
+
} else if (outputType === 'task_created') {
|
|
68
|
+
conductor.uiMessages.push({
|
|
69
|
+
source: 'conductor', type: 'task_created',
|
|
70
|
+
taskId: extra.taskId, taskTitle: extra.taskTitle,
|
|
71
|
+
content: `Created task: ${extra.taskTitle}`,
|
|
72
|
+
timestamp: Date.now()
|
|
73
|
+
});
|
|
74
|
+
} else if (outputType === 'task_forwarded') {
|
|
75
|
+
conductor.uiMessages.push({
|
|
76
|
+
source: 'conductor', type: 'task_forwarded',
|
|
77
|
+
taskId: extra.taskId,
|
|
78
|
+
content: `Forwarded message to task: ${extra.taskId}`,
|
|
79
|
+
timestamp: Date.now()
|
|
80
|
+
});
|
|
81
|
+
} else if (outputType === 'tool_use') {
|
|
82
|
+
endConductorStreaming(conductor);
|
|
83
|
+
const content = rawMessage?.message?.content;
|
|
84
|
+
if (Array.isArray(content)) {
|
|
85
|
+
for (const block of content) {
|
|
86
|
+
if (block.type === 'tool_use') {
|
|
87
|
+
const input = block.input || {};
|
|
88
|
+
const trimmedInput = {};
|
|
89
|
+
if (input.file_path) trimmedInput.file_path = input.file_path;
|
|
90
|
+
if (input.command) trimmedInput.command = input.command.substring(0, 200);
|
|
91
|
+
if (input.pattern) trimmedInput.pattern = input.pattern;
|
|
92
|
+
conductor.uiMessages.push({
|
|
93
|
+
source: 'conductor', type: 'tool',
|
|
94
|
+
toolName: block.name, toolId: block.id,
|
|
95
|
+
toolInput: Object.keys(trimmedInput).length > 0 ? trimmedInput : null,
|
|
96
|
+
content: `${block.name} ${input.file_path || input.command?.substring(0, 60) || ''}`,
|
|
97
|
+
hasResult: false,
|
|
98
|
+
timestamp: Date.now()
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} else if (outputType === 'tool_result') {
|
|
104
|
+
const toolId = rawMessage?.message?.tool_use_id;
|
|
105
|
+
if (toolId) {
|
|
106
|
+
for (let i = conductor.uiMessages.length - 1; i >= 0; i--) {
|
|
107
|
+
if (conductor.uiMessages[i].type === 'tool' && conductor.uiMessages[i].toolId === toolId) {
|
|
108
|
+
conductor.uiMessages[i].hasResult = true;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* End Conductor streaming message
|
|
118
|
+
*/
|
|
119
|
+
export function endConductorStreaming(conductor) {
|
|
120
|
+
for (let i = conductor.uiMessages.length - 1; i >= 0; i--) {
|
|
121
|
+
if (conductor.uiMessages[i].source === 'conductor' && conductor.uiMessages[i]._streaming) {
|
|
122
|
+
delete conductor.uiMessages[i]._streaming;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Record user message to uiMessages
|
|
130
|
+
*/
|
|
131
|
+
export function recordUserMessage(conductor, content) {
|
|
132
|
+
conductor.uiMessages.push({
|
|
133
|
+
source: 'user', type: 'text', content,
|
|
134
|
+
timestamp: Date.now()
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Send conductor status update to frontend
|
|
140
|
+
*/
|
|
141
|
+
export function sendStatusUpdate(conductor) {
|
|
142
|
+
const tasks = {};
|
|
143
|
+
for (const [taskId, t] of conductor.tasks) {
|
|
144
|
+
tasks[taskId] = {
|
|
145
|
+
taskId: t.taskId,
|
|
146
|
+
title: t.title,
|
|
147
|
+
workDir: t.workDir,
|
|
148
|
+
scenario: t.scenario,
|
|
149
|
+
status: t.status,
|
|
150
|
+
activeActors: t.activeActors || [],
|
|
151
|
+
currentStep: t.currentStep || '',
|
|
152
|
+
lastUpdate: t.lastUpdate || t.updatedAt
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
sendConductorMessage({
|
|
157
|
+
type: 'conductor_status',
|
|
158
|
+
status: conductor.status,
|
|
159
|
+
tasks,
|
|
160
|
+
costUsd: conductor.costUsd,
|
|
161
|
+
totalInputTokens: conductor.totalInputTokens,
|
|
162
|
+
totalOutputTokens: conductor.totalOutputTokens,
|
|
163
|
+
activeClaudes: conductor.activeClaudes || 0
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Async persist
|
|
167
|
+
saveConductorMeta(conductor).catch(e =>
|
|
168
|
+
console.warn('[Conductor] Failed to save meta:', e.message)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor — Worktree Management (V5)
|
|
3
|
+
*
|
|
4
|
+
* V5 strategy: each task gets ONE worktree at creation time.
|
|
5
|
+
* Path: {workDir}/.conductor/task-N/worktree/
|
|
6
|
+
*
|
|
7
|
+
* This prevents file conflicts when multiple tasks work on the same project.
|
|
8
|
+
* - Read-write actors (coding/testing/review) → cwd = worktree
|
|
9
|
+
* - Read-only actors (planning/discussion/design) → cwd = {workDir}
|
|
10
|
+
*/
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
import { join, resolve } from 'path';
|
|
13
|
+
import { execFile as execFileCb } from 'child_process';
|
|
14
|
+
import { promisify } from 'util';
|
|
15
|
+
|
|
16
|
+
const execFile = promisify(execFileCb);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a worktree for a task.
|
|
20
|
+
* Called at task creation time.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} workDir - Project root directory (git repo root)
|
|
23
|
+
* @param {string} taskId - Task ID (e.g. 'task-m1234-abcd')
|
|
24
|
+
* @param {string} taskDir - Full task directory path ({workDir}/.conductor/{taskId})
|
|
25
|
+
* @param {object} [options]
|
|
26
|
+
* @param {string} [options.baseBranch='HEAD'] - Base ref for the worktree
|
|
27
|
+
* @returns {Promise<string>} worktree path
|
|
28
|
+
*/
|
|
29
|
+
export async function createTaskWorktree(workDir, taskId, taskDir, options = {}) {
|
|
30
|
+
const { baseBranch = 'HEAD' } = options;
|
|
31
|
+
|
|
32
|
+
const wtDir = join(taskDir, 'worktree');
|
|
33
|
+
const branch = `conductor/${taskId}`;
|
|
34
|
+
|
|
35
|
+
// Check if worktree already exists
|
|
36
|
+
const exists = await directoryExists(wtDir);
|
|
37
|
+
if (exists) {
|
|
38
|
+
const known = await isKnownWorktree(workDir, wtDir);
|
|
39
|
+
if (known) {
|
|
40
|
+
console.log(`[Conductor/Worktree] Already exists: ${wtDir}`);
|
|
41
|
+
return wtDir;
|
|
42
|
+
}
|
|
43
|
+
// Orphaned directory, clean up and recreate
|
|
44
|
+
console.warn(`[Conductor/Worktree] Orphaned dir, removing: ${wtDir}`);
|
|
45
|
+
await fs.rm(wtDir, { recursive: true, force: true }).catch(() => {});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if this is a git repo
|
|
49
|
+
const isGitRepo = await directoryExists(join(workDir, '.git'));
|
|
50
|
+
if (!isGitRepo) {
|
|
51
|
+
// Not a git repo — just create a regular directory (no worktree isolation possible)
|
|
52
|
+
console.warn(`[Conductor/Worktree] ${workDir} is not a git repo, creating plain directory`);
|
|
53
|
+
await fs.mkdir(wtDir, { recursive: true });
|
|
54
|
+
return wtDir;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
// Create branch (ignore if already exists)
|
|
59
|
+
try {
|
|
60
|
+
await execFile('git', ['branch', branch, baseBranch], { cwd: workDir });
|
|
61
|
+
} catch {
|
|
62
|
+
// Branch already exists, ignore
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Create worktree
|
|
66
|
+
await execFile('git', ['worktree', 'add', wtDir, branch], { cwd: workDir });
|
|
67
|
+
console.log(`[Conductor/Worktree] Created: ${wtDir} on branch ${branch}`);
|
|
68
|
+
return wtDir;
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error(`[Conductor/Worktree] Failed to create worktree for ${taskId}:`, e.message);
|
|
71
|
+
// Fallback: create plain directory so task can still function
|
|
72
|
+
await fs.mkdir(wtDir, { recursive: true }).catch(() => {});
|
|
73
|
+
return wtDir;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Clean up a task's worktree
|
|
79
|
+
*
|
|
80
|
+
* @param {string} workDir - Project root directory
|
|
81
|
+
* @param {string} taskId - Task ID
|
|
82
|
+
* @param {string} taskDir - Full task directory path
|
|
83
|
+
*/
|
|
84
|
+
export async function cleanupTaskWorktree(workDir, taskId, taskDir) {
|
|
85
|
+
const wtDir = join(taskDir, 'worktree');
|
|
86
|
+
const branch = `conductor/${taskId}`;
|
|
87
|
+
|
|
88
|
+
if (!(await directoryExists(wtDir))) return;
|
|
89
|
+
|
|
90
|
+
const isGitRepo = await directoryExists(join(workDir, '.git'));
|
|
91
|
+
if (!isGitRepo) {
|
|
92
|
+
// Plain directory, just remove
|
|
93
|
+
await fs.rm(wtDir, { recursive: true, force: true }).catch(() => {});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await execFile('git', ['worktree', 'remove', wtDir, '--force'], { cwd: workDir });
|
|
99
|
+
console.log(`[Conductor/Worktree] Removed: ${wtDir}`);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.warn(`[Conductor/Worktree] Failed to remove ${wtDir}:`, e.message);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await execFile('git', ['branch', '-D', branch], { cwd: workDir });
|
|
106
|
+
console.log(`[Conductor/Worktree] Deleted branch: ${branch}`);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.warn(`[Conductor/Worktree] Failed to delete branch ${branch}:`, e.message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Clean up all conductor worktrees under a project
|
|
114
|
+
*
|
|
115
|
+
* @param {string} workDir - Project root directory
|
|
116
|
+
*/
|
|
117
|
+
export async function cleanupAllConductorWorktrees(workDir) {
|
|
118
|
+
const conductorDir = join(workDir, '.conductor');
|
|
119
|
+
|
|
120
|
+
if (!(await directoryExists(conductorDir))) return;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const entries = await fs.readdir(conductorDir);
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
if (!entry.startsWith('task-')) continue;
|
|
126
|
+
const taskDir = join(conductorDir, entry);
|
|
127
|
+
const stat = await fs.stat(taskDir).catch(() => null);
|
|
128
|
+
if (stat?.isDirectory()) {
|
|
129
|
+
await cleanupTaskWorktree(workDir, entry, taskDir);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch (e) {
|
|
133
|
+
console.error(`[Conductor/Worktree] Failed to cleanup all:`, e.message);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the worktree path for a task (for cwd routing)
|
|
139
|
+
*/
|
|
140
|
+
export function getTaskWorktreePath(taskDir) {
|
|
141
|
+
return join(taskDir, 'worktree');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Determine actor cwd based on specialty:
|
|
146
|
+
* - Read-write actors → worktree
|
|
147
|
+
* - Read-only actors → workDir (main project directory)
|
|
148
|
+
*/
|
|
149
|
+
export function getActorCwd(workDir, taskDir, specialty) {
|
|
150
|
+
const readWriteSpecialties = new Set(['coding', 'testing', 'review']);
|
|
151
|
+
if (readWriteSpecialties.has(specialty)) {
|
|
152
|
+
return getTaskWorktreePath(taskDir);
|
|
153
|
+
}
|
|
154
|
+
return workDir;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// =====================================================================
|
|
158
|
+
// Helpers
|
|
159
|
+
// =====================================================================
|
|
160
|
+
|
|
161
|
+
async function directoryExists(dir) {
|
|
162
|
+
try {
|
|
163
|
+
await fs.access(dir);
|
|
164
|
+
return true;
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function isKnownWorktree(projectDir, wtDir) {
|
|
171
|
+
const normalizedWtDir = resolve(wtDir);
|
|
172
|
+
try {
|
|
173
|
+
const { stdout } = await execFile('git', ['worktree', 'list', '--porcelain'], { cwd: projectDir });
|
|
174
|
+
for (const line of stdout.split('\n')) {
|
|
175
|
+
if (line.startsWith('worktree ')) {
|
|
176
|
+
const knownPath = resolve(line.slice('worktree '.length).trim());
|
|
177
|
+
if (knownPath === normalizedWtDir) return true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} catch { /* ignore */ }
|
|
181
|
+
return false;
|
|
182
|
+
}
|
package/conductor.js
CHANGED
|
@@ -1,46 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Conductor Mode —
|
|
2
|
+
* Conductor Mode — V5 Entry Point
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 本文件聚合并重新导出 conductor 子模块的公共 API,
|
|
7
|
-
* 供 message-router.js 消费。
|
|
8
|
-
*
|
|
9
|
-
* hideConductorSession / handleLoadConductorHistory 需要跨模块依赖,
|
|
10
|
-
* 在这里注入,避免 persistence.js ↔ session.js 循环引用。
|
|
4
|
+
* Aggregates and re-exports conductor sub-module public API.
|
|
5
|
+
* Singleton model: one Conductor per Agent.
|
|
11
6
|
*/
|
|
12
7
|
|
|
13
|
-
// Session
|
|
8
|
+
// Session core (singleton)
|
|
14
9
|
export {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
handleListConductorSessions,
|
|
18
|
-
resumeConductorSession,
|
|
10
|
+
getConductor,
|
|
11
|
+
initConductor,
|
|
19
12
|
handleConductorUserInput,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
stopConductorSession,
|
|
23
|
-
clearConductorSession
|
|
13
|
+
stopConductor,
|
|
14
|
+
clearConductor
|
|
24
15
|
} from './conductor/session.js';
|
|
25
16
|
|
|
26
|
-
//
|
|
27
|
-
export {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import { sendConductorMessage } from './conductor/ui-messages.js';
|
|
32
|
-
import {
|
|
33
|
-
hideConductorSession as _hideConductorSession,
|
|
34
|
-
handleLoadConductorHistory as _handleLoadConductorHistory
|
|
17
|
+
// Persistence
|
|
18
|
+
export {
|
|
19
|
+
handleLoadConductorHistory,
|
|
20
|
+
loadState as loadConductorState,
|
|
21
|
+
ensureConductorHome
|
|
35
22
|
} from './conductor/persistence.js';
|
|
36
23
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
24
|
+
// UI messages
|
|
25
|
+
import { sendConductorMessage } from './conductor/ui-messages.js';
|
|
26
|
+
import { getConductor } from './conductor/session.js';
|
|
27
|
+
import { handleLoadConductorHistory as _handleLoadConductorHistory } from './conductor/persistence.js';
|
|
40
28
|
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
// Dependency injection wrapper for history loading
|
|
30
|
+
export async function handleConductorHistory(msg) {
|
|
31
|
+
return _handleLoadConductorHistory(msg, getConductor(), sendConductorMessage);
|
|
43
32
|
}
|
|
44
33
|
|
|
45
|
-
//
|
|
34
|
+
// Semaphore
|
|
46
35
|
export { globalSemaphore } from './conductor/semaphore.js';
|
|
@@ -21,11 +21,9 @@ import {
|
|
|
21
21
|
handleLoadCrewHistory, handleCheckCrewContext
|
|
22
22
|
} from '../crew.js';
|
|
23
23
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
stopConductorSession, clearConductorSession,
|
|
28
|
-
hideConductorSession, handleLoadConductorHistory
|
|
24
|
+
initConductor, handleConductorUserInput,
|
|
25
|
+
stopConductor, clearConductor,
|
|
26
|
+
handleConductorHistory
|
|
29
27
|
} from '../conductor.js';
|
|
30
28
|
import { sendToServer, flushMessageBuffer } from './buffer.js';
|
|
31
29
|
import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
|
|
@@ -260,44 +258,24 @@ export async function handleMessage(msg) {
|
|
|
260
258
|
break;
|
|
261
259
|
|
|
262
260
|
// Conductor (V2) messages
|
|
263
|
-
case '
|
|
264
|
-
await
|
|
265
|
-
break;
|
|
266
|
-
|
|
267
|
-
case 'list_conductor_sessions':
|
|
268
|
-
await handleListConductorSessions(msg);
|
|
269
|
-
break;
|
|
270
|
-
|
|
271
|
-
case 'resume_conductor_session':
|
|
272
|
-
await resumeConductorSession(msg);
|
|
261
|
+
case 'open_conductor':
|
|
262
|
+
await initConductor(msg);
|
|
273
263
|
break;
|
|
274
264
|
|
|
275
265
|
case 'conductor_user_input':
|
|
276
266
|
await handleConductorUserInput(msg);
|
|
277
267
|
break;
|
|
278
268
|
|
|
279
|
-
case '
|
|
280
|
-
await
|
|
281
|
-
break;
|
|
282
|
-
|
|
283
|
-
case 'update_conductor_session':
|
|
284
|
-
await handleUpdateConductorSession(msg);
|
|
285
|
-
break;
|
|
286
|
-
|
|
287
|
-
case 'stop_conductor_session':
|
|
288
|
-
await stopConductorSession(msg.sessionId);
|
|
289
|
-
break;
|
|
290
|
-
|
|
291
|
-
case 'clear_conductor_session':
|
|
292
|
-
await clearConductorSession(msg.sessionId);
|
|
269
|
+
case 'stop_conductor':
|
|
270
|
+
await stopConductor();
|
|
293
271
|
break;
|
|
294
272
|
|
|
295
|
-
case '
|
|
296
|
-
await
|
|
273
|
+
case 'clear_conductor':
|
|
274
|
+
await clearConductor();
|
|
297
275
|
break;
|
|
298
276
|
|
|
299
277
|
case 'conductor_load_history':
|
|
300
|
-
await
|
|
278
|
+
await handleConductorHistory(msg);
|
|
301
279
|
break;
|
|
302
280
|
|
|
303
281
|
// Port proxy
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeaft/webchat-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.226",
|
|
4
4
|
"description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -32,13 +32,7 @@
|
|
|
32
32
|
"url": "https://github.com/yeaft/claude-web-chat/issues"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
-
"
|
|
36
|
-
"connection/",
|
|
37
|
-
"crew/",
|
|
38
|
-
"sdk/",
|
|
39
|
-
"scripts/",
|
|
40
|
-
"service/",
|
|
41
|
-
"workbench/"
|
|
35
|
+
"**/*.js"
|
|
42
36
|
],
|
|
43
37
|
"license": "MIT",
|
|
44
38
|
"author": "Yeaft",
|
package/scripts/agent-tray.ps1
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
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()
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# 创建开机自启动快捷方式
|
|
2
|
-
$AgentDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
3
|
-
$StartupFolder = [Environment]::GetFolderPath('Startup')
|
|
4
|
-
$ShortcutPath = Join-Path $StartupFolder "Claude Agent.lnk"
|
|
5
|
-
|
|
6
|
-
$WshShell = New-Object -ComObject WScript.Shell
|
|
7
|
-
$Shortcut = $WshShell.CreateShortcut($ShortcutPath)
|
|
8
|
-
$Shortcut.TargetPath = "powershell.exe"
|
|
9
|
-
$Shortcut.Arguments = "-WindowStyle Hidden -ExecutionPolicy Bypass -File `"$AgentDir\start-with-tray.ps1`""
|
|
10
|
-
$Shortcut.WorkingDirectory = $AgentDir
|
|
11
|
-
$Shortcut.Description = "Claude Agent with Tray"
|
|
12
|
-
$Shortcut.WindowStyle = 7 # Minimized
|
|
13
|
-
$Shortcut.Save()
|
|
14
|
-
|
|
15
|
-
Write-Host "Startup shortcut created at:" -ForegroundColor Green
|
|
16
|
-
Write-Host " $ShortcutPath" -ForegroundColor Cyan
|
|
17
|
-
Write-Host ""
|
|
18
|
-
Write-Host "The agent will now start automatically when you log in." -ForegroundColor Green
|