gekto 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/dist/agents/ClaudeAgent.js +72 -0
- package/dist/agents/HeadlessAgent.js +141 -0
- package/dist/agents/agentPool.js +211 -0
- package/dist/agents/agentWebSocket.js +264 -0
- package/dist/agents/gektoOrchestrator.js +195 -0
- package/dist/agents/gektoPersistent.js +239 -0
- package/dist/agents/gektoSimple.js +137 -0
- package/dist/agents/gektoTools.js +223 -0
- package/dist/proxy.js +318 -0
- package/dist/store.js +53 -0
- package/dist/terminal.js +106 -0
- package/dist/widget/gekto-widget.iife.js +4077 -0
- package/dist/widget/logo.svg +32 -0
- package/dist/widget/vite.svg +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
// === Gekto System Prompt ===
|
|
3
|
+
const GEKTO_SYSTEM_PROMPT = `You are Gekto, a friendly task orchestration assistant. You help users with coding tasks by delegating work to specialized agents.
|
|
4
|
+
|
|
5
|
+
IMPORTANT: Analyze each message and decide how to respond:
|
|
6
|
+
|
|
7
|
+
1. If the message is a GREETING, QUESTION, or CONVERSATION (not a coding task):
|
|
8
|
+
- Respond naturally and friendly
|
|
9
|
+
- Your response will be shown directly to the user
|
|
10
|
+
|
|
11
|
+
2. If the message is a CODING TASK (feature request, bug fix, refactoring, file changes):
|
|
12
|
+
- Start your response with exactly: [PLAN_MODE]
|
|
13
|
+
- Then provide a JSON execution plan:
|
|
14
|
+
{
|
|
15
|
+
"tasks": [
|
|
16
|
+
{
|
|
17
|
+
"id": "task_1",
|
|
18
|
+
"description": "Brief description",
|
|
19
|
+
"prompt": "Detailed prompt for the worker agent",
|
|
20
|
+
"files": ["path/to/file.ts"],
|
|
21
|
+
"dependencies": []
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
- "hey" -> "Hey! How can I help you today?"
|
|
28
|
+
- "what's up?" -> "Not much! Ready to help with any coding tasks you have."
|
|
29
|
+
- "add dark mode" -> [PLAN_MODE]{"tasks":[...]}
|
|
30
|
+
- "fix the login bug" -> [PLAN_MODE]{"tasks":[...]}`;
|
|
31
|
+
// Process a message to Gekto - returns either a chat response or an execution plan
|
|
32
|
+
export async function processGektoMessage(prompt, planId, workingDir) {
|
|
33
|
+
console.log('[Orchestrator] Processing message:', prompt.substring(0, 100));
|
|
34
|
+
const result = await runClaudeOnce(prompt, GEKTO_SYSTEM_PROMPT, workingDir);
|
|
35
|
+
console.log('[Orchestrator] Gekto response:', result.substring(0, 200));
|
|
36
|
+
// Check if response indicates plan mode
|
|
37
|
+
if (result.includes('[PLAN_MODE]')) {
|
|
38
|
+
// Extract the JSON plan after [PLAN_MODE]
|
|
39
|
+
const planPart = result.split('[PLAN_MODE]')[1]?.trim() || '';
|
|
40
|
+
return {
|
|
41
|
+
type: 'plan',
|
|
42
|
+
plan: parsePlanFromResponse(planPart, planId, prompt),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// Regular chat response
|
|
46
|
+
return {
|
|
47
|
+
type: 'chat',
|
|
48
|
+
message: result.trim(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Parse plan JSON from response
|
|
52
|
+
function parsePlanFromResponse(result, planId, originalPrompt) {
|
|
53
|
+
let parsedTasks = [];
|
|
54
|
+
try {
|
|
55
|
+
const jsonMatch = result.match(/\{[\s\S]*\}/);
|
|
56
|
+
if (jsonMatch) {
|
|
57
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
58
|
+
if (parsed.tasks && Array.isArray(parsed.tasks)) {
|
|
59
|
+
parsedTasks = parsed.tasks.map((t, i) => ({
|
|
60
|
+
id: t.id || `task_${i + 1}`,
|
|
61
|
+
description: t.description || 'Task',
|
|
62
|
+
prompt: t.prompt || originalPrompt,
|
|
63
|
+
files: t.files || [],
|
|
64
|
+
status: 'pending',
|
|
65
|
+
dependencies: t.dependencies || [],
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error('[Orchestrator] Failed to parse plan JSON:', err);
|
|
72
|
+
}
|
|
73
|
+
// Fallback to single task if parsing failed
|
|
74
|
+
if (parsedTasks.length === 0) {
|
|
75
|
+
parsedTasks = [{
|
|
76
|
+
id: 'task_1',
|
|
77
|
+
description: 'Execute task',
|
|
78
|
+
prompt: originalPrompt,
|
|
79
|
+
files: [],
|
|
80
|
+
status: 'pending',
|
|
81
|
+
dependencies: [],
|
|
82
|
+
}];
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
id: planId,
|
|
86
|
+
status: 'ready',
|
|
87
|
+
originalPrompt,
|
|
88
|
+
tasks: parsedTasks,
|
|
89
|
+
createdAt: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Legacy function for backwards compatibility
|
|
93
|
+
export async function generatePlan(prompt, planId, workingDir) {
|
|
94
|
+
const response = await processGektoMessage(prompt, planId, workingDir);
|
|
95
|
+
if (response.type === 'plan') {
|
|
96
|
+
return response.plan;
|
|
97
|
+
}
|
|
98
|
+
// If it returned chat, wrap in a single task (shouldn't happen with proper prompts)
|
|
99
|
+
return {
|
|
100
|
+
id: planId,
|
|
101
|
+
status: 'ready',
|
|
102
|
+
originalPrompt: prompt,
|
|
103
|
+
tasks: [{
|
|
104
|
+
id: 'task_1',
|
|
105
|
+
description: 'Execute task',
|
|
106
|
+
prompt: prompt,
|
|
107
|
+
files: [],
|
|
108
|
+
status: 'pending',
|
|
109
|
+
dependencies: [],
|
|
110
|
+
}],
|
|
111
|
+
createdAt: new Date().toISOString(),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// === Helper: Run Claude once without session ===
|
|
115
|
+
function runClaudeOnce(prompt, systemPrompt, workingDir) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const args = [
|
|
118
|
+
'-p', prompt,
|
|
119
|
+
'--output-format', 'stream-json',
|
|
120
|
+
'--verbose',
|
|
121
|
+
'--model', 'claude-haiku-4-5-20251001',
|
|
122
|
+
'--system-prompt', systemPrompt,
|
|
123
|
+
'--dangerously-skip-permissions',
|
|
124
|
+
];
|
|
125
|
+
console.log('[Orchestrator] Running claude (haiku) for orchestration');
|
|
126
|
+
const proc = spawn('claude', args, {
|
|
127
|
+
cwd: workingDir,
|
|
128
|
+
env: process.env,
|
|
129
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
130
|
+
});
|
|
131
|
+
proc.stdin?.end();
|
|
132
|
+
let buffer = '';
|
|
133
|
+
let resultText = '';
|
|
134
|
+
proc.stdout.on('data', (data) => {
|
|
135
|
+
buffer += data.toString();
|
|
136
|
+
const lines = buffer.split('\n');
|
|
137
|
+
buffer = lines.pop() || '';
|
|
138
|
+
for (const line of lines) {
|
|
139
|
+
if (!line.trim())
|
|
140
|
+
continue;
|
|
141
|
+
try {
|
|
142
|
+
const event = JSON.parse(line);
|
|
143
|
+
if (event.type === 'result' && event.result) {
|
|
144
|
+
resultText = event.result;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Ignore parse errors
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
proc.stderr.on('data', (data) => {
|
|
153
|
+
console.error('[Orchestrator] stderr:', data.toString());
|
|
154
|
+
});
|
|
155
|
+
proc.on('close', (code) => {
|
|
156
|
+
console.log('[Orchestrator] Plan generation complete, code:', code);
|
|
157
|
+
// Try to parse any remaining buffer
|
|
158
|
+
if (buffer.trim()) {
|
|
159
|
+
try {
|
|
160
|
+
const event = JSON.parse(buffer);
|
|
161
|
+
if (event.type === 'result' && event.result) {
|
|
162
|
+
resultText = event.result;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Ignore
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (resultText) {
|
|
170
|
+
resolve(resultText);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
reject(new Error('No result from plan generation'));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
proc.on('error', (err) => {
|
|
177
|
+
console.error('[Orchestrator] Spawn error:', err);
|
|
178
|
+
reject(err);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// === Delegation Helper ===
|
|
183
|
+
export function shouldDelegateToTask(prompt, tasks) {
|
|
184
|
+
// Simple heuristic: check if prompt mentions any file from a task
|
|
185
|
+
const lowerPrompt = prompt.toLowerCase();
|
|
186
|
+
for (const task of tasks) {
|
|
187
|
+
for (const file of task.files) {
|
|
188
|
+
const fileName = file.split('/').pop()?.toLowerCase() || '';
|
|
189
|
+
if (fileName && lowerPrompt.includes(fileName)) {
|
|
190
|
+
return task;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
// === Opus Worker (persistent, for direct mode) ===
|
|
4
|
+
const OPUS_SYSTEM_PROMPT = `You are Gekto, a friendly and capable coding assistant. You help users with their coding tasks directly.
|
|
5
|
+
|
|
6
|
+
Be concise and helpful. When you need to make changes, use the available tools. Explain what you're doing briefly.
|
|
7
|
+
|
|
8
|
+
For greetings and questions, just respond naturally without using tools.
|
|
9
|
+
|
|
10
|
+
IMPORTANT: You can ONLY use Read, Write, Edit, Glob, and Grep tools. You CANNOT use Bash or Task tools - they are disabled.`;
|
|
11
|
+
let opusProcess = null;
|
|
12
|
+
let opusReady = false;
|
|
13
|
+
let opusLoading = false;
|
|
14
|
+
let opusPendingResolve = null;
|
|
15
|
+
let opusBuffer = '';
|
|
16
|
+
let opusCallbacks = null;
|
|
17
|
+
let opusCurrentTool = null;
|
|
18
|
+
// Session ID for persistent history - generated once and shared across all Gekto calls
|
|
19
|
+
// This allows both direct mode (persistent process) and plan mode (one-shot calls) to share history
|
|
20
|
+
let gektoSessionId = randomUUID();
|
|
21
|
+
let workingDir = process.cwd();
|
|
22
|
+
let stateChangeCallback = null;
|
|
23
|
+
// === Initialization ===
|
|
24
|
+
export function initGekto(cwd, onStateChange) {
|
|
25
|
+
workingDir = cwd;
|
|
26
|
+
stateChangeCallback = onStateChange || null;
|
|
27
|
+
// Start Opus process
|
|
28
|
+
spawnOpus();
|
|
29
|
+
}
|
|
30
|
+
export function getGektoState() {
|
|
31
|
+
if (opusLoading)
|
|
32
|
+
return 'loading';
|
|
33
|
+
if (opusReady)
|
|
34
|
+
return 'ready';
|
|
35
|
+
return 'loading';
|
|
36
|
+
}
|
|
37
|
+
// === Opus Process ===
|
|
38
|
+
function spawnOpus() {
|
|
39
|
+
if (opusProcess)
|
|
40
|
+
return;
|
|
41
|
+
opusLoading = true;
|
|
42
|
+
opusReady = false;
|
|
43
|
+
updateState();
|
|
44
|
+
const args = [
|
|
45
|
+
'--input-format', 'stream-json',
|
|
46
|
+
'--output-format', 'stream-json',
|
|
47
|
+
'--verbose',
|
|
48
|
+
'--model', 'claude-opus-4-5-20251101',
|
|
49
|
+
'--system-prompt', OPUS_SYSTEM_PROMPT,
|
|
50
|
+
'--dangerously-skip-permissions',
|
|
51
|
+
'--disallowed-tools', 'Bash', 'Task',
|
|
52
|
+
'--session-id', gektoSessionId,
|
|
53
|
+
];
|
|
54
|
+
opusProcess = spawn('claude', args, {
|
|
55
|
+
cwd: workingDir,
|
|
56
|
+
env: process.env,
|
|
57
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
58
|
+
});
|
|
59
|
+
opusProcess.stdout.on('data', (data) => {
|
|
60
|
+
opusBuffer += data.toString();
|
|
61
|
+
const lines = opusBuffer.split('\n');
|
|
62
|
+
opusBuffer = lines.pop() || '';
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
if (!line.trim())
|
|
65
|
+
continue;
|
|
66
|
+
try {
|
|
67
|
+
const event = JSON.parse(line);
|
|
68
|
+
handleOpusEvent(event);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Ignore non-JSON lines
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
opusProcess.stderr.on('data', () => {
|
|
76
|
+
// Ignore stderr
|
|
77
|
+
});
|
|
78
|
+
opusProcess.on('close', () => {
|
|
79
|
+
opusProcess = null;
|
|
80
|
+
opusReady = false;
|
|
81
|
+
opusLoading = true;
|
|
82
|
+
updateState();
|
|
83
|
+
if (opusPendingResolve) {
|
|
84
|
+
opusPendingResolve('Process restarting, please try again.');
|
|
85
|
+
opusPendingResolve = null;
|
|
86
|
+
}
|
|
87
|
+
// Auto-restart
|
|
88
|
+
setTimeout(spawnOpus, 1000);
|
|
89
|
+
});
|
|
90
|
+
opusProcess.on('error', () => {
|
|
91
|
+
opusProcess = null;
|
|
92
|
+
opusReady = false;
|
|
93
|
+
opusLoading = true;
|
|
94
|
+
updateState();
|
|
95
|
+
});
|
|
96
|
+
// Warm up: send a quick message to trigger ready state
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
if (opusProcess && !opusReady) {
|
|
99
|
+
const warmup = { type: 'user', message: { role: 'user', content: 'hi' } };
|
|
100
|
+
opusProcess.stdin.write(JSON.stringify(warmup) + '\n');
|
|
101
|
+
}
|
|
102
|
+
}, 500);
|
|
103
|
+
}
|
|
104
|
+
function handleOpusEvent(event) {
|
|
105
|
+
// Result event means process is working
|
|
106
|
+
if (event.type === 'result') {
|
|
107
|
+
if (!opusReady) {
|
|
108
|
+
opusReady = true;
|
|
109
|
+
opusLoading = false;
|
|
110
|
+
updateState();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Tool use detection from assistant message
|
|
114
|
+
if (event.type === 'assistant' && event.message) {
|
|
115
|
+
const message = event.message;
|
|
116
|
+
if (message.content) {
|
|
117
|
+
for (const block of message.content) {
|
|
118
|
+
if (block.type === 'tool_use' && block.name) {
|
|
119
|
+
opusCurrentTool = block.name;
|
|
120
|
+
opusCallbacks?.onToolStart?.(block.name, block.input);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Tool result (tool completed)
|
|
126
|
+
if (event.type === 'user' && event.message) {
|
|
127
|
+
const message = event.message;
|
|
128
|
+
if (message.content) {
|
|
129
|
+
for (const block of message.content) {
|
|
130
|
+
if (block.type === 'tool_result' && opusCurrentTool) {
|
|
131
|
+
opusCallbacks?.onToolEnd?.(opusCurrentTool);
|
|
132
|
+
opusCurrentTool = null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Text streaming
|
|
138
|
+
if (event.type === 'content_block_delta') {
|
|
139
|
+
const delta = event.delta;
|
|
140
|
+
if (delta?.type === 'text_delta' && delta.text) {
|
|
141
|
+
opusCallbacks?.onText?.(delta.text);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Final result
|
|
145
|
+
if (event.type === 'result' && event.result) {
|
|
146
|
+
if (opusPendingResolve) {
|
|
147
|
+
opusPendingResolve(event.result);
|
|
148
|
+
opusPendingResolve = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function sendToOpus(prompt, callbacks) {
|
|
153
|
+
if (!opusProcess || !opusReady) {
|
|
154
|
+
spawnOpus();
|
|
155
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
156
|
+
}
|
|
157
|
+
if (!opusProcess) {
|
|
158
|
+
return 'Gekto is starting up, please try again.';
|
|
159
|
+
}
|
|
160
|
+
opusCallbacks = callbacks;
|
|
161
|
+
opusCurrentTool = null;
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
opusPendingResolve = (result) => {
|
|
164
|
+
opusCallbacks = null;
|
|
165
|
+
resolve(result);
|
|
166
|
+
};
|
|
167
|
+
const inputMessage = {
|
|
168
|
+
type: 'user',
|
|
169
|
+
message: { role: 'user', content: prompt },
|
|
170
|
+
};
|
|
171
|
+
opusProcess.stdin.write(JSON.stringify(inputMessage) + '\n');
|
|
172
|
+
// Timeout after 5 min for complex tasks
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
if (opusPendingResolve) {
|
|
175
|
+
opusPendingResolve('Task timed out. Please try breaking it into smaller steps.');
|
|
176
|
+
opusPendingResolve = null;
|
|
177
|
+
}
|
|
178
|
+
}, 300000);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// Mode is now passed as parameter - default is 'plan', UI can toggle to 'direct'
|
|
182
|
+
export async function sendToGekto(prompt, mode = 'plan', callbacks) {
|
|
183
|
+
const startTime = Date.now();
|
|
184
|
+
// Plan mode - return immediately, caller will use gektoTools.ts for planning
|
|
185
|
+
if (mode === 'plan') {
|
|
186
|
+
return {
|
|
187
|
+
mode: 'plan',
|
|
188
|
+
message: 'Creating plan...',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// Direct mode - use Opus
|
|
192
|
+
const result = await sendToOpus(prompt, callbacks || {});
|
|
193
|
+
const workMs = Date.now() - startTime;
|
|
194
|
+
callbacks?.onResult?.(result);
|
|
195
|
+
return {
|
|
196
|
+
mode: 'direct',
|
|
197
|
+
message: result,
|
|
198
|
+
workMs,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// === State Management ===
|
|
202
|
+
function updateState() {
|
|
203
|
+
const state = getGektoState();
|
|
204
|
+
stateChangeCallback?.(state);
|
|
205
|
+
}
|
|
206
|
+
export function isGektoReady() {
|
|
207
|
+
return opusReady;
|
|
208
|
+
}
|
|
209
|
+
// Set/update state change callback (for reconnections)
|
|
210
|
+
export function setStateCallback(callback) {
|
|
211
|
+
stateChangeCallback = callback;
|
|
212
|
+
}
|
|
213
|
+
// Get current session ID (shared between direct and plan modes)
|
|
214
|
+
export function getGektoSessionId() {
|
|
215
|
+
return gektoSessionId;
|
|
216
|
+
}
|
|
217
|
+
// Reset session to start fresh (clears history)
|
|
218
|
+
export function resetGektoSession() {
|
|
219
|
+
gektoSessionId = randomUUID();
|
|
220
|
+
// Kill current process to force restart with new session
|
|
221
|
+
if (opusProcess) {
|
|
222
|
+
opusProcess.kill('SIGTERM');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// === Abort current task (like pressing ESC in CLI) ===
|
|
226
|
+
export function abortGekto() {
|
|
227
|
+
let aborted = false;
|
|
228
|
+
// Send SIGINT to interrupt current Opus task (like Ctrl+C / ESC)
|
|
229
|
+
if (opusProcess && opusPendingResolve) {
|
|
230
|
+
opusProcess.kill('SIGINT');
|
|
231
|
+
// Resolve the pending promise so caller doesn't hang
|
|
232
|
+
opusPendingResolve('Task was stopped.');
|
|
233
|
+
opusPendingResolve = null;
|
|
234
|
+
opusCallbacks = null;
|
|
235
|
+
opusCurrentTool = null;
|
|
236
|
+
aborted = true;
|
|
237
|
+
}
|
|
238
|
+
return aborted;
|
|
239
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
// Simple Gekto - just chat with Haiku and return text response
|
|
3
|
+
const GEKTO_SYSTEM_PROMPT = `You are Gekto, a friendly coding assistant. Decide what to do with the user's message.
|
|
4
|
+
|
|
5
|
+
Options:
|
|
6
|
+
1. answer - For greetings, questions, conversation. Just reply naturally.
|
|
7
|
+
2. write_code - For coding tasks that need implementation.
|
|
8
|
+
3. plan - For complex tasks that need breaking into steps.
|
|
9
|
+
|
|
10
|
+
Respond with JSON: { "action": "answer|write_code|plan", "response": "your message" }
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
- "yo" -> { "action": "answer", "response": "Hey! What can I help you with?" }
|
|
14
|
+
- "add dark mode" -> { "action": "write_code", "response": "I'll spawn an agent to add dark mode." }
|
|
15
|
+
- "refactor the auth system" -> { "action": "plan", "response": "Let me break this down into steps..." }
|
|
16
|
+
|
|
17
|
+
Always respond with valid JSON.`;
|
|
18
|
+
export async function processSimple(prompt, workingDir) {
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
try {
|
|
21
|
+
const rawResponse = await runHaiku(prompt, GEKTO_SYSTEM_PROMPT, workingDir);
|
|
22
|
+
const durationMs = Date.now() - startTime;
|
|
23
|
+
// Parse JSON response
|
|
24
|
+
const jsonMatch = rawResponse.match(/\{[\s\S]*\}/);
|
|
25
|
+
if (jsonMatch) {
|
|
26
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
27
|
+
return {
|
|
28
|
+
type: 'chat',
|
|
29
|
+
action: parsed.action || 'answer',
|
|
30
|
+
message: parsed.response || rawResponse,
|
|
31
|
+
durationMs,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// Fallback: use raw response as message
|
|
35
|
+
return {
|
|
36
|
+
type: 'chat',
|
|
37
|
+
action: 'answer',
|
|
38
|
+
message: rawResponse,
|
|
39
|
+
durationMs,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
const durationMs = Date.now() - startTime;
|
|
44
|
+
// Always return a friendly message, never an error
|
|
45
|
+
return {
|
|
46
|
+
type: 'chat',
|
|
47
|
+
action: 'answer',
|
|
48
|
+
message: "Hey! Something went wrong on my end. Could you try again?",
|
|
49
|
+
durationMs,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function runHaiku(prompt, systemPrompt, workingDir) {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const args = [
|
|
56
|
+
'-p', prompt,
|
|
57
|
+
'--output-format', 'stream-json',
|
|
58
|
+
'--model', 'claude-haiku-4-5-20251001',
|
|
59
|
+
'--system-prompt', systemPrompt,
|
|
60
|
+
'--dangerously-skip-permissions',
|
|
61
|
+
];
|
|
62
|
+
const proc = spawn('claude', args, {
|
|
63
|
+
cwd: workingDir,
|
|
64
|
+
env: process.env,
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
+
});
|
|
67
|
+
proc.stdin?.end();
|
|
68
|
+
let buffer = '';
|
|
69
|
+
let resultText = '';
|
|
70
|
+
let allOutput = '';
|
|
71
|
+
proc.stdout.on('data', (data) => {
|
|
72
|
+
const chunk = data.toString();
|
|
73
|
+
buffer += chunk;
|
|
74
|
+
allOutput += chunk;
|
|
75
|
+
const lines = buffer.split('\n');
|
|
76
|
+
buffer = lines.pop() || '';
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
if (!line.trim())
|
|
79
|
+
continue;
|
|
80
|
+
try {
|
|
81
|
+
const event = JSON.parse(line);
|
|
82
|
+
// Capture any text content
|
|
83
|
+
if (event.type === 'result' && event.result) {
|
|
84
|
+
resultText = event.result;
|
|
85
|
+
}
|
|
86
|
+
else if (event.type === 'assistant' && event.message?.content) {
|
|
87
|
+
// Also check for assistant message format
|
|
88
|
+
for (const block of event.message.content) {
|
|
89
|
+
if (block.type === 'text') {
|
|
90
|
+
resultText = block.text;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (event.type === 'content_block_delta' && event.delta?.text) {
|
|
95
|
+
resultText += event.delta.text;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Ignore parse errors
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
proc.stderr.on('data', (data) => {
|
|
104
|
+
console.error('[GektoSimple] stderr:', data.toString());
|
|
105
|
+
});
|
|
106
|
+
proc.on('close', (code) => {
|
|
107
|
+
console.log('[GektoSimple] Exit code:', code);
|
|
108
|
+
// Try to parse remaining buffer
|
|
109
|
+
if (buffer.trim()) {
|
|
110
|
+
try {
|
|
111
|
+
const event = JSON.parse(buffer);
|
|
112
|
+
if (event.type === 'result' && event.result) {
|
|
113
|
+
resultText = event.result;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Ignore
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Always resolve with something
|
|
121
|
+
if (resultText) {
|
|
122
|
+
console.log('[GektoSimple] Result:', resultText.substring(0, 100));
|
|
123
|
+
resolve(resultText);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Log all output for debugging
|
|
127
|
+
console.log('[GektoSimple] No result found. All output:', allOutput.substring(0, 500));
|
|
128
|
+
resolve('{ "action": "answer", "response": "Hey there! How can I help?" }');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
proc.on('error', (err) => {
|
|
132
|
+
console.error('[GektoSimple] Spawn error:', err);
|
|
133
|
+
// Still resolve with a default response
|
|
134
|
+
resolve('{ "action": "answer", "response": "Hey! How can I help you today?" }');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|