@codewithdan/zingit 0.0.1
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/AGENTS.md +214 -0
- package/LICENSE +21 -0
- package/README.md +301 -0
- package/bin/cli.js +90 -0
- package/client/dist/zingit-client.js +2974 -0
- package/package.json +69 -0
- package/server/dist/agents/base.d.ts +20 -0
- package/server/dist/agents/base.js +136 -0
- package/server/dist/agents/claude.d.ts +18 -0
- package/server/dist/agents/claude.js +141 -0
- package/server/dist/agents/codex.d.ts +12 -0
- package/server/dist/agents/codex.js +194 -0
- package/server/dist/agents/copilot.d.ts +12 -0
- package/server/dist/agents/copilot.js +168 -0
- package/server/dist/handlers/messageHandlers.d.ts +57 -0
- package/server/dist/handlers/messageHandlers.js +329 -0
- package/server/dist/index.d.ts +1 -0
- package/server/dist/index.js +244 -0
- package/server/dist/services/git-manager.d.ts +104 -0
- package/server/dist/services/git-manager.js +317 -0
- package/server/dist/services/index.d.ts +2 -0
- package/server/dist/services/index.js +2 -0
- package/server/dist/types.d.ts +74 -0
- package/server/dist/types.js +2 -0
- package/server/dist/utils/agent-detection.d.ts +17 -0
- package/server/dist/utils/agent-detection.js +91 -0
- package/server/dist/validation/payload.d.ts +12 -0
- package/server/dist/validation/payload.js +64 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// server/src/agents/copilot.ts
|
|
2
|
+
// Agent that uses GitHub Copilot SDK
|
|
3
|
+
import { CopilotClient } from '@github/copilot-sdk';
|
|
4
|
+
import { BaseAgent } from './base.js';
|
|
5
|
+
import { promises as fs } from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import { randomUUID } from 'crypto';
|
|
9
|
+
export class CopilotAgent extends BaseAgent {
|
|
10
|
+
name = 'copilot';
|
|
11
|
+
model;
|
|
12
|
+
client = null;
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.model = process.env.COPILOT_MODEL || 'claude-sonnet-4-20250514';
|
|
16
|
+
}
|
|
17
|
+
async start() {
|
|
18
|
+
// Initialize the Copilot client
|
|
19
|
+
this.client = new CopilotClient({
|
|
20
|
+
logLevel: 'info',
|
|
21
|
+
autoRestart: true,
|
|
22
|
+
});
|
|
23
|
+
await this.client.start();
|
|
24
|
+
console.log(`✓ Copilot SDK initialized (model: ${this.model})`);
|
|
25
|
+
}
|
|
26
|
+
async stop() {
|
|
27
|
+
if (this.client) {
|
|
28
|
+
await this.client.stop();
|
|
29
|
+
this.client = null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async createSession(ws, projectDir) {
|
|
33
|
+
if (!this.client) {
|
|
34
|
+
throw new Error('Copilot client not initialized');
|
|
35
|
+
}
|
|
36
|
+
const send = (data) => {
|
|
37
|
+
if (ws.readyState === ws.OPEN) {
|
|
38
|
+
ws.send(JSON.stringify(data));
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
// Create a Copilot session with streaming enabled
|
|
42
|
+
// Note: Copilot SDK doesn't support cwd in session config, so we include it in the system message
|
|
43
|
+
const session = await this.client.createSession({
|
|
44
|
+
model: this.model,
|
|
45
|
+
streaming: true,
|
|
46
|
+
systemMessage: {
|
|
47
|
+
mode: 'append',
|
|
48
|
+
content: `
|
|
49
|
+
<context>
|
|
50
|
+
You are a UI debugging assistant working in the project directory: ${projectDir}
|
|
51
|
+
|
|
52
|
+
When given annotations about UI elements:
|
|
53
|
+
1. Search for the corresponding code using the selectors and HTML context provided
|
|
54
|
+
2. Make the requested changes in the project at ${projectDir}
|
|
55
|
+
3. Be thorough in finding the right files and making precise edits
|
|
56
|
+
|
|
57
|
+
When screenshots are provided, use them to:
|
|
58
|
+
- Better understand the visual context and styling of the elements
|
|
59
|
+
- Identify the exact appearance that needs to be changed
|
|
60
|
+
- Verify you're targeting the correct element based on its visual representation
|
|
61
|
+
|
|
62
|
+
IMPORTANT: Format all responses using markdown:
|
|
63
|
+
- Use **bold** for emphasis on important points
|
|
64
|
+
- Use numbered lists for sequential steps (1. 2. 3.)
|
|
65
|
+
- Use bullet points for non-sequential items
|
|
66
|
+
- Use code blocks with \`\`\`language syntax for code examples
|
|
67
|
+
- Use inline \`code\` for file paths, selectors, and technical terms
|
|
68
|
+
</context>
|
|
69
|
+
`
|
|
70
|
+
},
|
|
71
|
+
onPermissionRequest: async (request) => {
|
|
72
|
+
// Auto-approve read/write operations for file edits
|
|
73
|
+
if (request.kind === 'read' || request.kind === 'write') {
|
|
74
|
+
return { kind: 'approved' };
|
|
75
|
+
}
|
|
76
|
+
return { kind: 'approved' };
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
// Track temp files for cleanup on session destroy (prevents race condition)
|
|
80
|
+
const sessionTempFiles = [];
|
|
81
|
+
// Subscribe to streaming events and capture unsubscribe function
|
|
82
|
+
const unsubscribe = session.on((event) => {
|
|
83
|
+
switch (event.type) {
|
|
84
|
+
case 'assistant.message_delta':
|
|
85
|
+
// Streaming chunk
|
|
86
|
+
send({ type: 'delta', content: event.data.deltaContent });
|
|
87
|
+
break;
|
|
88
|
+
case 'assistant.message':
|
|
89
|
+
// Final message (we already sent deltas, so just log)
|
|
90
|
+
break;
|
|
91
|
+
case 'tool.execution_start':
|
|
92
|
+
send({ type: 'tool_start', tool: event.data.toolName });
|
|
93
|
+
break;
|
|
94
|
+
case 'tool.execution_complete':
|
|
95
|
+
send({ type: 'tool_end', tool: event.data.toolCallId });
|
|
96
|
+
break;
|
|
97
|
+
case 'session.idle':
|
|
98
|
+
send({ type: 'idle' });
|
|
99
|
+
break;
|
|
100
|
+
case 'session.error':
|
|
101
|
+
send({ type: 'error', message: event.data.message });
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
send: async (msg) => {
|
|
107
|
+
try {
|
|
108
|
+
// If images are provided, save them as temp files and attach them
|
|
109
|
+
// Copilot SDK supports file attachments for images
|
|
110
|
+
const attachments = [];
|
|
111
|
+
if (msg.images && msg.images.length > 0) {
|
|
112
|
+
const tempDir = os.tmpdir();
|
|
113
|
+
for (let i = 0; i < msg.images.length; i++) {
|
|
114
|
+
const img = msg.images[i];
|
|
115
|
+
// Use UUID to avoid filename collisions
|
|
116
|
+
const ext = img.mediaType.split('/')[1] || 'png';
|
|
117
|
+
const tempPath = path.join(tempDir, `zingit-screenshot-${randomUUID()}.${ext}`);
|
|
118
|
+
// Decode base64 to buffer with error handling
|
|
119
|
+
let buffer;
|
|
120
|
+
try {
|
|
121
|
+
buffer = Buffer.from(img.base64, 'base64');
|
|
122
|
+
}
|
|
123
|
+
catch (decodeErr) {
|
|
124
|
+
console.warn(`ZingIt: Failed to decode base64 for image ${i + 1}:`, decodeErr);
|
|
125
|
+
continue; // Skip this image
|
|
126
|
+
}
|
|
127
|
+
// Save with restrictive permissions (owner read/write only)
|
|
128
|
+
await fs.writeFile(tempPath, buffer, { mode: 0o600 });
|
|
129
|
+
sessionTempFiles.push(tempPath);
|
|
130
|
+
attachments.push({
|
|
131
|
+
type: 'file',
|
|
132
|
+
path: tempPath,
|
|
133
|
+
displayName: img.label || `Screenshot ${i + 1}`
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
await session.sendAndWait({
|
|
138
|
+
prompt: msg.prompt,
|
|
139
|
+
attachments: attachments.length > 0 ? attachments : undefined
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
send({ type: 'error', message: err.message });
|
|
144
|
+
}
|
|
145
|
+
// Note: Temp files cleaned up on session destroy to avoid race condition
|
|
146
|
+
},
|
|
147
|
+
destroy: async () => {
|
|
148
|
+
try {
|
|
149
|
+
unsubscribe();
|
|
150
|
+
await session.destroy();
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
// Clean up all temp files even if destroy() fails
|
|
154
|
+
for (const tempPath of sessionTempFiles) {
|
|
155
|
+
try {
|
|
156
|
+
await fs.unlink(tempPath);
|
|
157
|
+
}
|
|
158
|
+
catch (cleanupErr) {
|
|
159
|
+
// Ignore errors (file may already be deleted)
|
|
160
|
+
console.warn(`ZingIt: Failed to clean up temp file ${tempPath}:`, cleanupErr.message);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
sessionTempFiles.length = 0; // Clear the array
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { WebSocket } from 'ws';
|
|
2
|
+
import type { Agent, WSIncomingMessage, WSOutgoingMessage } from '../types.js';
|
|
3
|
+
import type { GitManager, GitManagerError as GitManagerErrorType } from '../services/git-manager.js';
|
|
4
|
+
export { GitManagerError } from '../services/git-manager.js';
|
|
5
|
+
export interface ConnectionState {
|
|
6
|
+
session: any | null;
|
|
7
|
+
agentName: string | null;
|
|
8
|
+
agent: Agent | null;
|
|
9
|
+
gitManager: GitManager | null;
|
|
10
|
+
currentCheckpointId: string | null;
|
|
11
|
+
}
|
|
12
|
+
export interface MessageHandlerDeps {
|
|
13
|
+
projectDir: string;
|
|
14
|
+
detectAgents: () => Promise<any[]>;
|
|
15
|
+
getAgent: (name: string) => Promise<Agent>;
|
|
16
|
+
}
|
|
17
|
+
export declare function sendMessage(ws: WebSocket, msg: WSOutgoingMessage): void;
|
|
18
|
+
/**
|
|
19
|
+
* Handle get_agents message
|
|
20
|
+
*/
|
|
21
|
+
export declare function handleGetAgents(ws: WebSocket, deps: MessageHandlerDeps): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Handle select_agent message
|
|
24
|
+
*/
|
|
25
|
+
export declare function handleSelectAgent(ws: WebSocket, state: ConnectionState, msg: WSIncomingMessage, deps: MessageHandlerDeps): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Handle batch message
|
|
28
|
+
*/
|
|
29
|
+
export declare function handleBatch(ws: WebSocket, state: ConnectionState, msg: WSIncomingMessage, deps: MessageHandlerDeps): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Handle message (follow-up message to agent)
|
|
32
|
+
*/
|
|
33
|
+
export declare function handleMessage(ws: WebSocket, state: ConnectionState, msg: WSIncomingMessage): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Handle reset message
|
|
36
|
+
*/
|
|
37
|
+
export declare function handleReset(ws: WebSocket, state: ConnectionState): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Handle stop message
|
|
40
|
+
*/
|
|
41
|
+
export declare function handleStop(ws: WebSocket, state: ConnectionState): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Handle get_history message
|
|
44
|
+
*/
|
|
45
|
+
export declare function handleGetHistory(ws: WebSocket, state: ConnectionState): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Handle undo message
|
|
48
|
+
*/
|
|
49
|
+
export declare function handleUndo(ws: WebSocket, state: ConnectionState, GitManagerError: typeof GitManagerErrorType): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Handle revert_to message
|
|
52
|
+
*/
|
|
53
|
+
export declare function handleRevertTo(ws: WebSocket, state: ConnectionState, msg: WSIncomingMessage, GitManagerError: typeof GitManagerErrorType): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Handle clear_history message
|
|
56
|
+
*/
|
|
57
|
+
export declare function handleClearHistory(ws: WebSocket, state: ConnectionState): Promise<void>;
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// server/src/handlers/messageHandlers.ts
|
|
2
|
+
import { validateBatchData } from '../validation/payload.js';
|
|
3
|
+
// Re-export GitManagerError for instanceof checks
|
|
4
|
+
export { GitManagerError } from '../services/git-manager.js';
|
|
5
|
+
// Helper to send messages
|
|
6
|
+
export function sendMessage(ws, msg) {
|
|
7
|
+
if (ws.readyState === ws.OPEN) {
|
|
8
|
+
ws.send(JSON.stringify(msg));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Handle get_agents message
|
|
13
|
+
*/
|
|
14
|
+
export async function handleGetAgents(ws, deps) {
|
|
15
|
+
const agents = await deps.detectAgents();
|
|
16
|
+
sendMessage(ws, { type: 'agents', agents });
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Handle select_agent message
|
|
20
|
+
*/
|
|
21
|
+
export async function handleSelectAgent(ws, state, msg, deps) {
|
|
22
|
+
if (!msg.agent) {
|
|
23
|
+
sendMessage(ws, { type: 'agent_error', message: 'No agent specified' });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Check if agent is available
|
|
27
|
+
const agentInfo = (await deps.detectAgents()).find(a => a.name === msg.agent);
|
|
28
|
+
if (!agentInfo) {
|
|
29
|
+
sendMessage(ws, { type: 'agent_error', message: `Unknown agent: ${msg.agent}` });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!agentInfo.available) {
|
|
33
|
+
sendMessage(ws, {
|
|
34
|
+
type: 'agent_error',
|
|
35
|
+
message: agentInfo.reason || `Agent ${msg.agent} is not available`,
|
|
36
|
+
agent: msg.agent
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Destroy existing session if switching agents
|
|
41
|
+
if (state.session && state.agentName !== msg.agent) {
|
|
42
|
+
try {
|
|
43
|
+
await state.session.destroy();
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.error('Error destroying session during agent switch:', err.message);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
state.session = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Initialize the agent
|
|
53
|
+
try {
|
|
54
|
+
state.agent = await deps.getAgent(msg.agent);
|
|
55
|
+
state.agentName = msg.agent;
|
|
56
|
+
sendMessage(ws, {
|
|
57
|
+
type: 'agent_selected',
|
|
58
|
+
agent: msg.agent,
|
|
59
|
+
model: state.agent.model,
|
|
60
|
+
projectDir: deps.projectDir
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
sendMessage(ws, {
|
|
65
|
+
type: 'agent_error',
|
|
66
|
+
message: `Failed to initialize ${msg.agent}: ${err.message}`,
|
|
67
|
+
agent: msg.agent
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Handle batch message
|
|
73
|
+
*/
|
|
74
|
+
export async function handleBatch(ws, state, msg, deps) {
|
|
75
|
+
if (!msg.data)
|
|
76
|
+
return;
|
|
77
|
+
// Validate batch data
|
|
78
|
+
const validation = validateBatchData(msg.data);
|
|
79
|
+
if (!validation.valid) {
|
|
80
|
+
sendMessage(ws, { type: 'error', message: validation.error || 'Invalid batch data' });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Use sanitized data
|
|
84
|
+
const batchData = validation.sanitizedData;
|
|
85
|
+
// Check if agent is selected
|
|
86
|
+
if (!state.agentName || !state.agent) {
|
|
87
|
+
// If agent specified in batch message, try to select it
|
|
88
|
+
if (msg.agent) {
|
|
89
|
+
const agentInfo = (await deps.detectAgents()).find(a => a.name === msg.agent);
|
|
90
|
+
if (!agentInfo?.available) {
|
|
91
|
+
sendMessage(ws, {
|
|
92
|
+
type: 'agent_error',
|
|
93
|
+
message: `Agent ${msg.agent} is not available. Please select a different agent.`
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
state.agent = await deps.getAgent(msg.agent);
|
|
99
|
+
state.agentName = msg.agent;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
sendMessage(ws, { type: 'error', message: err.message });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
sendMessage(ws, { type: 'error', message: 'No agent selected. Please select an agent first.' });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Use client-specified projectDir, or fall back to server default
|
|
112
|
+
const projectDir = batchData.projectDir || deps.projectDir;
|
|
113
|
+
// Create a checkpoint before AI modifications (if git manager available)
|
|
114
|
+
if (state.gitManager) {
|
|
115
|
+
try {
|
|
116
|
+
const checkpoint = await state.gitManager.createCheckpoint({
|
|
117
|
+
annotations: batchData.annotations,
|
|
118
|
+
pageUrl: batchData.pageUrl,
|
|
119
|
+
pageTitle: batchData.pageTitle,
|
|
120
|
+
agentName: state.agentName,
|
|
121
|
+
});
|
|
122
|
+
state.currentCheckpointId = checkpoint.id;
|
|
123
|
+
sendMessage(ws, {
|
|
124
|
+
type: 'checkpoint_created',
|
|
125
|
+
checkpoint: {
|
|
126
|
+
id: checkpoint.id,
|
|
127
|
+
timestamp: checkpoint.timestamp,
|
|
128
|
+
annotations: checkpoint.annotations,
|
|
129
|
+
filesModified: 0,
|
|
130
|
+
linesChanged: 0,
|
|
131
|
+
agentName: checkpoint.agentName,
|
|
132
|
+
pageUrl: checkpoint.pageUrl,
|
|
133
|
+
status: 'pending',
|
|
134
|
+
canUndo: false,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
// Log but don't block - checkpoint is optional
|
|
140
|
+
console.warn('Failed to create checkpoint:', err.message);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (!state.session) {
|
|
144
|
+
state.session = await state.agent.createSession(ws, projectDir);
|
|
145
|
+
}
|
|
146
|
+
const prompt = state.agent.formatPrompt(batchData, projectDir);
|
|
147
|
+
const images = state.agent.extractImages(batchData);
|
|
148
|
+
sendMessage(ws, { type: 'processing' });
|
|
149
|
+
await state.session.send({ prompt, images: images.length > 0 ? images : undefined });
|
|
150
|
+
// Finalize checkpoint after processing
|
|
151
|
+
if (state.gitManager && state.currentCheckpointId) {
|
|
152
|
+
try {
|
|
153
|
+
await state.gitManager.finalizeCheckpoint(state.currentCheckpointId);
|
|
154
|
+
// Send updated checkpoint info
|
|
155
|
+
const checkpoints = await state.gitManager.getHistory();
|
|
156
|
+
const updatedCheckpoint = checkpoints.find((c) => c.id === state.currentCheckpointId);
|
|
157
|
+
if (updatedCheckpoint) {
|
|
158
|
+
sendMessage(ws, {
|
|
159
|
+
type: 'checkpoint_created',
|
|
160
|
+
checkpoint: updatedCheckpoint,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
console.warn('Failed to finalize checkpoint:', err.message);
|
|
166
|
+
}
|
|
167
|
+
state.currentCheckpointId = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Handle message (follow-up message to agent)
|
|
172
|
+
*/
|
|
173
|
+
export async function handleMessage(ws, state, msg) {
|
|
174
|
+
if (state.session && msg.content) {
|
|
175
|
+
try {
|
|
176
|
+
console.log(`[ZingIt] Sending follow-up message: "${msg.content.substring(0, 50)}..."`);
|
|
177
|
+
sendMessage(ws, { type: 'processing' });
|
|
178
|
+
// Add timeout to detect if SDK hangs
|
|
179
|
+
const timeoutMs = 120000; // 2 minutes
|
|
180
|
+
const sendPromise = state.session.send({ prompt: msg.content });
|
|
181
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Agent response timeout')), timeoutMs));
|
|
182
|
+
await Promise.race([sendPromise, timeoutPromise]);
|
|
183
|
+
console.log('[ZingIt] Follow-up message sent to agent');
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
console.error('[ZingIt] Error sending follow-up message:', err.message);
|
|
187
|
+
sendMessage(ws, { type: 'error', message: `Failed to send message: ${err.message}` });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (!state.session) {
|
|
191
|
+
console.warn('[ZingIt] No active session for follow-up message');
|
|
192
|
+
sendMessage(ws, { type: 'error', message: 'No active session. Please create annotations first.' });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Handle reset message
|
|
197
|
+
*/
|
|
198
|
+
export async function handleReset(ws, state) {
|
|
199
|
+
if (state.session) {
|
|
200
|
+
try {
|
|
201
|
+
await state.session.destroy();
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
console.error('Error destroying session during reset:', err.message);
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
state.session = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
sendMessage(ws, { type: 'reset_complete' });
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Handle stop message
|
|
214
|
+
*/
|
|
215
|
+
export async function handleStop(ws, state) {
|
|
216
|
+
// Stop current agent execution
|
|
217
|
+
if (state.session) {
|
|
218
|
+
console.log('Stopping agent execution...');
|
|
219
|
+
try {
|
|
220
|
+
await state.session.destroy();
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
console.error('Error destroying session during stop:', err.message);
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
state.session = null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
sendMessage(ws, { type: 'idle' });
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Handle get_history message
|
|
233
|
+
*/
|
|
234
|
+
export async function handleGetHistory(ws, state) {
|
|
235
|
+
if (!state.gitManager) {
|
|
236
|
+
sendMessage(ws, { type: 'error', message: 'Git manager not initialized' });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const checkpoints = await state.gitManager.getHistory();
|
|
241
|
+
sendMessage(ws, { type: 'history', checkpoints });
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
sendMessage(ws, {
|
|
245
|
+
type: 'error',
|
|
246
|
+
message: `Failed to get history: ${err.message}`,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Handle undo message
|
|
252
|
+
*/
|
|
253
|
+
export async function handleUndo(ws, state, GitManagerError) {
|
|
254
|
+
if (!state.gitManager) {
|
|
255
|
+
sendMessage(ws, { type: 'error', message: 'Git manager not initialized' });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const result = await state.gitManager.undoLastCheckpoint();
|
|
260
|
+
state.currentCheckpointId = null;
|
|
261
|
+
sendMessage(ws, {
|
|
262
|
+
type: 'undo_complete',
|
|
263
|
+
checkpointId: result.checkpointId,
|
|
264
|
+
filesReverted: result.filesReverted,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
if (err instanceof GitManagerError) {
|
|
269
|
+
sendMessage(ws, { type: 'error', message: err.message });
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
sendMessage(ws, {
|
|
273
|
+
type: 'error',
|
|
274
|
+
message: `Undo failed: ${err.message}`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Handle revert_to message
|
|
281
|
+
*/
|
|
282
|
+
export async function handleRevertTo(ws, state, msg, GitManagerError) {
|
|
283
|
+
if (!state.gitManager) {
|
|
284
|
+
sendMessage(ws, { type: 'error', message: 'Git manager not initialized' });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!msg.checkpointId) {
|
|
288
|
+
sendMessage(ws, { type: 'error', message: 'No checkpoint ID specified' });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
const result = await state.gitManager.revertToCheckpoint(msg.checkpointId);
|
|
293
|
+
sendMessage(ws, {
|
|
294
|
+
type: 'revert_complete',
|
|
295
|
+
checkpointId: msg.checkpointId,
|
|
296
|
+
filesReverted: result.filesReverted,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
if (err instanceof GitManagerError) {
|
|
301
|
+
sendMessage(ws, { type: 'error', message: err.message });
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
sendMessage(ws, {
|
|
305
|
+
type: 'error',
|
|
306
|
+
message: `Revert failed: ${err.message}`,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Handle clear_history message
|
|
313
|
+
*/
|
|
314
|
+
export async function handleClearHistory(ws, state) {
|
|
315
|
+
if (!state.gitManager) {
|
|
316
|
+
sendMessage(ws, { type: 'error', message: 'Git manager not initialized' });
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
await state.gitManager.clearHistory();
|
|
321
|
+
sendMessage(ws, { type: 'history_cleared' });
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
sendMessage(ws, {
|
|
325
|
+
type: 'error',
|
|
326
|
+
message: `Failed to clear history: ${err.message}`,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|