@staticpayload/zai-code 1.4.12 → 1.5.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.
@@ -39,15 +39,109 @@ const session_1 = require("./session");
39
39
  const ui_1 = require("./ui");
40
40
  const runtime_1 = require("./runtime");
41
41
  const auth_1 = require("./auth");
42
- const mode_prompts_1 = require("./mode_prompts");
43
42
  const apply_1 = require("./apply");
44
- // Helper to extract meaningful text from API response
43
+ // Analyze task to determine best execution strategy
44
+ function analyzeTask(input) {
45
+ const lower = input.toLowerCase().trim();
46
+ const words = lower.split(/\s+/);
47
+ const wordCount = words.length;
48
+ // CHAT - greetings, small talk
49
+ const chatPatterns = [
50
+ /^h+i+!*$/i, /^he+y+!*$/i, /^y+o+!*$/i,
51
+ /^(hello|hola|howdy|greetings|sup|wassup)[\s!.,]*$/i,
52
+ /^(good\s*(morning|afternoon|evening|night))[\s!.,]*$/i,
53
+ /^(what'?s?\s*up|how\s*are\s*you)[\s!?.,]*$/i,
54
+ /^(thanks|thank\s*you|thx|ty|bye|goodbye|ok|okay|sure|yes|no|yep|nope|yeah|nah|cool|nice|great|awesome|lol)[\s!.,]*$/i,
55
+ ];
56
+ if (chatPatterns.some(p => p.test(lower)) || (wordCount <= 3 && !lower.includes('create') && !lower.includes('make') && !lower.includes('add'))) {
57
+ return { type: 'chat', complexity: 'trivial', needsPlan: false, confidence: 0.95, keywords: [] };
58
+ }
59
+ // QUESTION - explicit questions
60
+ const questionPatterns = [
61
+ /^(what|why|how|when|where|who|which|explain|clarify|describe|tell me|show me)/i,
62
+ /\?$/,
63
+ /^(is|are|can|could|would|should|do|does|did|has|have|will)\s/i,
64
+ ];
65
+ if (questionPatterns.some(p => p.test(lower))) {
66
+ return { type: 'question', complexity: 'simple', needsPlan: false, confidence: 0.9, keywords: [] };
67
+ }
68
+ // Extract action keywords
69
+ const actionKeywords = {
70
+ create: ['create', 'make', 'new', 'add', 'generate', 'build', 'write', 'init', 'setup', 'scaffold'],
71
+ modify: ['update', 'change', 'modify', 'edit', 'fix', 'improve', 'enhance', 'refactor', 'rename', 'move'],
72
+ delete: ['delete', 'remove', 'clean', 'clear', 'drop'],
73
+ debug: ['fix', 'debug', 'error', 'bug', 'issue', 'broken', 'failing', 'crash', 'problem', 'wrong', 'not working'],
74
+ refactor: ['refactor', 'reorganize', 'restructure', 'extract', 'split', 'merge', 'consolidate', 'simplify', 'clean up'],
75
+ };
76
+ const foundKeywords = [];
77
+ let taskType = 'simple_edit';
78
+ for (const [category, keywords] of Object.entries(actionKeywords)) {
79
+ for (const kw of keywords) {
80
+ if (lower.includes(kw)) {
81
+ foundKeywords.push(kw);
82
+ if (category === 'debug')
83
+ taskType = 'debug';
84
+ else if (category === 'refactor')
85
+ taskType = 'refactor';
86
+ }
87
+ }
88
+ }
89
+ // Complexity indicators
90
+ const complexityIndicators = {
91
+ simple: ['file', 'function', 'variable', 'class', 'method', 'component', 'test', 'readme', 'config'],
92
+ medium: ['feature', 'module', 'service', 'api', 'endpoint', 'page', 'route', 'handler', 'middleware'],
93
+ complex: ['system', 'architecture', 'database', 'migration', 'integration', 'authentication', 'authorization', 'deployment', 'infrastructure', 'full', 'complete', 'entire', 'whole', 'all'],
94
+ };
95
+ let complexity = 'simple';
96
+ // Check for complexity indicators
97
+ for (const kw of complexityIndicators.complex) {
98
+ if (lower.includes(kw)) {
99
+ complexity = 'complex';
100
+ break;
101
+ }
102
+ }
103
+ if (complexity === 'simple') {
104
+ for (const kw of complexityIndicators.medium) {
105
+ if (lower.includes(kw)) {
106
+ complexity = 'medium';
107
+ break;
108
+ }
109
+ }
110
+ }
111
+ // Word count affects complexity
112
+ if (wordCount > 30)
113
+ complexity = 'complex';
114
+ else if (wordCount > 15 && complexity === 'simple')
115
+ complexity = 'medium';
116
+ // Multiple files mentioned = more complex
117
+ const filePatterns = /\.(ts|js|py|rs|go|java|cpp|c|h|css|html|json|yaml|yml|md|txt|sql|sh|bash|zsh)/gi;
118
+ const fileMatches = lower.match(filePatterns);
119
+ if (fileMatches && fileMatches.length > 2) {
120
+ complexity = complexity === 'simple' ? 'medium' : 'complex';
121
+ }
122
+ // "and" chains indicate complexity
123
+ const andCount = (lower.match(/\band\b/g) || []).length;
124
+ if (andCount >= 2) {
125
+ complexity = complexity === 'simple' ? 'medium' : 'complex';
126
+ taskType = 'complex_task';
127
+ }
128
+ // Determine if plan is needed
129
+ const needsPlan = complexity === 'complex' || (complexity === 'medium' && wordCount > 20);
130
+ return {
131
+ type: taskType,
132
+ complexity,
133
+ needsPlan,
134
+ confidence: foundKeywords.length > 0 ? 0.85 : 0.7,
135
+ keywords: foundKeywords,
136
+ };
137
+ }
138
+ // ============================================================================
139
+ // RESPONSE PARSING
140
+ // ============================================================================
45
141
  function extractTextFromResponse(response) {
46
142
  if (typeof response === 'string') {
47
- // Strip markdown code blocks
48
143
  let text = response;
49
144
  text = text.replace(/```json\n?/gi, '').replace(/```\n?/g, '');
50
- // Try to parse as JSON and extract explanation
51
145
  try {
52
146
  const parsed = JSON.parse(text);
53
147
  if (parsed.explanation)
@@ -56,14 +150,8 @@ function extractTextFromResponse(response) {
56
150
  return parsed.output;
57
151
  if (parsed.message)
58
152
  return parsed.message;
59
- if (parsed.summary)
60
- return parsed.summary;
61
- if (parsed.status === 'error' && parsed.explanation)
62
- return parsed.explanation;
63
- }
64
- catch {
65
- // Not JSON, return as-is
66
153
  }
154
+ catch { }
67
155
  return text.trim();
68
156
  }
69
157
  if (typeof response === 'object' && response !== null) {
@@ -74,497 +162,304 @@ function extractTextFromResponse(response) {
74
162
  return String(obj.output);
75
163
  if (obj.message)
76
164
  return String(obj.message);
77
- if (obj.summary)
78
- return String(obj.summary);
79
165
  return JSON.stringify(response, null, 2);
80
166
  }
81
167
  return String(response);
82
168
  }
83
- // Try to parse file operations from response
84
169
  function parseFileOperations(response) {
85
170
  if (typeof response === 'string') {
86
171
  let text = response.trim();
87
- // Try to extract JSON from markdown code blocks (handle various formats)
88
172
  const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
89
- if (jsonMatch) {
173
+ if (jsonMatch)
90
174
  text = jsonMatch[1].trim();
91
- }
92
- // Also try to find raw JSON object
93
175
  if (!text.startsWith('{')) {
94
176
  const jsonStart = text.indexOf('{');
95
177
  const jsonEnd = text.lastIndexOf('}');
96
- if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) {
178
+ if (jsonStart !== -1 && jsonEnd > jsonStart) {
97
179
  text = text.substring(jsonStart, jsonEnd + 1);
98
180
  }
99
181
  }
100
182
  try {
101
183
  const parsed = JSON.parse(text);
102
- if (parsed.files && Array.isArray(parsed.files)) {
184
+ if (parsed.files && Array.isArray(parsed.files))
103
185
  return parsed;
104
- }
105
- if (parsed.status) {
186
+ if (parsed.status)
106
187
  return parsed;
107
- }
108
- }
109
- catch (e) {
110
- // JSON parse failed - try to be more lenient
111
- // Sometimes the API returns malformed JSON with extra text
112
- console.log((0, ui_1.dim)('Note: Could not parse response as JSON'));
113
188
  }
189
+ catch { }
114
190
  }
115
191
  if (typeof response === 'object' && response !== null) {
116
192
  const obj = response;
117
- if (obj.files && Array.isArray(obj.files)) {
193
+ if (obj.files && Array.isArray(obj.files))
118
194
  return obj;
119
- }
120
- if (obj.status) {
195
+ if (obj.status)
121
196
  return obj;
122
- }
123
197
  }
124
198
  return null;
125
199
  }
126
- // Check if input is casual chat (greetings, small talk)
127
- function isCasualChat(input) {
128
- const lower = input.toLowerCase().trim();
129
- // Very short inputs (1-4 chars) that look like greetings
130
- if (lower.length <= 4) {
131
- const shortGreetings = ['hi', 'hii', 'hey', 'yo', 'sup', 'ok', 'yes', 'no', 'yep', 'nah', 'bye', 'thx', 'ty'];
132
- if (shortGreetings.some(g => lower.startsWith(g))) {
133
- return true;
134
- }
135
- }
136
- const casualPatterns = [
137
- /^h+i+!*$/i, // hi, hii, hiii, etc
138
- /^he+y+!*$/i, // hey, heyy, heyyy
139
- /^y+o+!*$/i, // yo, yoo, yooo
140
- /^(hello|hola|howdy|greetings)[\s!.,]*$/i,
141
- /^(good\s*(morning|afternoon|evening|night))[\s!.,]*$/i,
142
- /^(what'?s?\s*up|how\s*are\s*you|how'?s?\s*it\s*going)[\s!?.,]*$/i,
143
- /^(thanks|thank\s*you|thx|ty)[\s!.,]*$/i,
144
- /^(bye|goodbye|see\s*ya|later|cya)[\s!.,]*$/i,
145
- /^(ok|okay|sure|yes|no|yep|nope|yeah|nah)[\s!.,]*$/i,
146
- /^(cool|nice|great|awesome|perfect|lol|lmao)[\s!.,]*$/i,
147
- /^(sup|wassup|wazzup)[\s!.,]*$/i,
148
- ];
149
- return casualPatterns.some(pattern => pattern.test(lower));
150
- }
151
- // Check if input is a short simple question
152
- function isSimpleQuestion(input) {
153
- const lower = input.toLowerCase().trim();
154
- // Short questions that don't need code context
155
- if (input.length < 50 && /\?$/.test(input)) {
156
- return true;
200
+ // ============================================================================
201
+ // EXECUTION HANDLERS
202
+ // ============================================================================
203
+ async function getApiKey() {
204
+ try {
205
+ return await (0, auth_1.ensureAuthenticated)();
157
206
  }
158
- // Direct questions
159
- if (/^(what|why|how|when|where|who|can you|could you|would you|will you)\s/i.test(lower)) {
160
- return true;
207
+ catch {
208
+ console.log((0, ui_1.error)('Authentication required. Run: zcode auth'));
209
+ return null;
161
210
  }
162
- return false;
163
211
  }
164
- // Intent classification (rule-based, no model)
165
- function classifyIntent(input) {
166
- const lower = input.toLowerCase().trim();
167
- // CASUAL CHAT - handle separately
168
- if (isCasualChat(input)) {
169
- return 'QUESTION'; // Route to chat handler
170
- }
171
- // QUESTION - explicit questions
172
- if (/^(what|why|how|when|where|who|explain|clarify|describe|tell me)/i.test(lower) ||
173
- /\?$/.test(lower) ||
174
- /^(is|are|can|could|would|should|do|does|did|has|have|will)\s/i.test(lower)) {
175
- return 'QUESTION';
176
- }
177
- // DEBUG
178
- if (/(fix|debug|error|bug|issue|broken|failing|crash|exception|problem|wrong|doesn'?t work|not working)/i.test(lower)) {
179
- return 'DEBUG';
180
- }
181
- // REFACTOR
182
- if (/(refactor|rename|reorganize|restructure|extract|move|split|merge|consolidate|clean up|cleanup|simplify)/i.test(lower)) {
183
- return 'REFACTOR';
184
- }
185
- // REVIEW
186
- if (/(review|analyze|audit|check|inspect|examine|assess|evaluate|look at|understand|read|show me)/i.test(lower)) {
187
- return 'REVIEW';
188
- }
189
- // CODE_EDIT - file operations
190
- if (/(add|create|implement|update|change|modify|write|build|make|generate|new|feature|function|component|edit|insert|append|remove|delete|replace|file|folder|directory)/i.test(lower)) {
191
- return 'CODE_EDIT';
212
+ // Handle casual chat
213
+ async function handleChat(input) {
214
+ const apiKey = await getApiKey();
215
+ if (!apiKey)
216
+ return;
217
+ const chatPrompt = `You are a friendly AI coding assistant. Respond naturally and briefly (1-2 sentences). Be warm and helpful. If the user wants to code, suggest they describe their task.`;
218
+ const result = await (0, runtime_1.execute)({
219
+ instruction: `${chatPrompt}\n\nUser: ${input}\n\nRespond briefly:`,
220
+ enforceSchema: false
221
+ }, apiKey);
222
+ if (result.success && result.output) {
223
+ console.log(extractTextFromResponse(result.output));
192
224
  }
193
- // Default to QUESTION for short inputs, CODE_EDIT for longer ones
194
- if (input.length < 30) {
195
- return 'QUESTION';
225
+ else {
226
+ console.log("Hey! What would you like to build today?");
196
227
  }
197
- return 'CODE_EDIT';
198
228
  }
199
- // Determine workflow based on mode and intent
200
- function determineWorkflow(intent, input) {
201
- const mode = (0, session_1.getMode)();
202
- // Casual chat always goes to chat handler
203
- if (isCasualChat(input)) {
204
- return 'chat';
205
- }
206
- // Simple questions go to ask handler
207
- if (isSimpleQuestion(input) && intent === 'QUESTION') {
208
- return 'ask_question';
209
- }
210
- // Read-only modes route to ask_question workflow
211
- if (mode === 'ask' || mode === 'explain' || mode === 'review') {
212
- return 'ask_question';
213
- }
214
- // Auto mode - execute directly without manual steps
215
- if (mode === 'auto') {
216
- return 'auto_execute';
229
+ // Handle questions
230
+ async function handleQuestion(input) {
231
+ const apiKey = await getApiKey();
232
+ if (!apiKey)
233
+ return;
234
+ const session = (0, session_1.getSession)();
235
+ console.log((0, ui_1.dim)('Thinking...'));
236
+ const result = await (0, runtime_1.execute)({
237
+ instruction: `You are a helpful coding assistant. Answer this question clearly and concisely:\n\n${input}`,
238
+ enforceSchema: false
239
+ }, apiKey);
240
+ if (result.success && result.output) {
241
+ console.log(extractTextFromResponse(result.output));
217
242
  }
218
- // Questions in edit mode still get answered
219
- if (intent === 'QUESTION') {
220
- return 'ask_question';
243
+ else {
244
+ console.log((0, ui_1.error)(result.error || 'Failed to get answer'));
221
245
  }
222
- return 'capture_intent';
223
246
  }
224
- // Handle question/explain/review in read-only modes
225
- async function handleAskQuestion(input) {
226
- try {
227
- let apiKey;
228
- try {
229
- apiKey = await (0, auth_1.ensureAuthenticated)();
230
- }
231
- catch (authError) {
232
- console.log((0, ui_1.error)(`Authentication required: ${authError?.message || 'Run zcode auth'}`));
233
- return { handled: true };
234
- }
235
- if (!apiKey) {
236
- console.log((0, ui_1.error)('No API key configured. Run "zcode auth" to set up.'));
237
- return { handled: true };
238
- }
239
- const session = (0, session_1.getSession)();
240
- const mode = (0, session_1.getMode)();
241
- const modePrompt = (0, mode_prompts_1.buildSystemPrompt)(mode, session.workingDirectory);
242
- const instruction = `${modePrompt}
247
+ // Handle simple edits - direct execution
248
+ async function handleSimpleEdit(input) {
249
+ const apiKey = await getApiKey();
250
+ if (!apiKey)
251
+ return;
252
+ const session = (0, session_1.getSession)();
253
+ console.log((0, ui_1.info)('⚡ Executing...'));
254
+ const { buildContext, formatContextForModel } = await Promise.resolve().then(() => __importStar(require('./context/context_builder')));
255
+ const context = buildContext(session.workingDirectory, input, 'CODE_EDIT', session.openFiles);
256
+ const filesContext = formatContextForModel(context);
257
+ const instruction = `You are an autonomous coding agent. Execute this task completely.
243
258
 
244
- User input: ${input}
259
+ Task: ${input}
260
+ Working directory: ${session.workingDirectory}
261
+ ${filesContext ? `\nRelevant files:\n${filesContext}` : ''}
245
262
 
246
- Respond directly and concisely.`;
247
- console.log((0, ui_1.dim)('Thinking...'));
248
- const result = await (0, runtime_1.execute)({ instruction, enforceSchema: false }, apiKey);
249
- if (result.success && result.output) {
250
- const text = extractTextFromResponse(result.output);
251
- console.log(text);
263
+ RESPOND WITH JSON ONLY:
264
+ {
265
+ "status": "success",
266
+ "files": [{"path": "path/to/file", "operation": "create|modify|delete", "content": "full content"}],
267
+ "output": "Brief explanation"
268
+ }
269
+
270
+ RULES:
271
+ - Create folders by creating files inside them (folders auto-create)
272
+ - Provide COMPLETE file content, never partial
273
+ - Only modify files directly related to the task`;
274
+ const result = await (0, runtime_1.execute)({ instruction }, apiKey);
275
+ if (result.success && result.output) {
276
+ const response = parseFileOperations(result.output) ||
277
+ (typeof result.output === 'object' ? result.output : null);
278
+ if (response?.files?.length) {
279
+ await applyFiles(response.files, session.workingDirectory, input);
280
+ if (response.output)
281
+ console.log('\n' + response.output);
282
+ }
283
+ else if (response?.output) {
284
+ console.log(response.output);
252
285
  }
253
286
  else {
254
- console.log((0, ui_1.error)(`Failed: ${result.error || 'Unknown error'}`));
287
+ const text = extractTextFromResponse(result.output);
288
+ if (text && !text.startsWith('{'))
289
+ console.log(text);
255
290
  }
256
- return { handled: true };
257
291
  }
258
- catch (e) {
259
- console.log((0, ui_1.error)(`Error: ${e?.message || e}`));
260
- return { handled: true };
292
+ else {
293
+ console.log((0, ui_1.error)(result.error || 'Execution failed'));
261
294
  }
262
295
  }
263
- // Handle auto execution - plan, generate, and apply in one go
264
- async function handleAutoExecute(input) {
265
- // SAFETY: Check if this looks like a real coding task
266
- // If input is too short or vague, don't execute file operations
267
- const looksLikeTask = input.length > 10 &&
268
- /(create|add|implement|build|make|write|update|modify|fix|change|delete|remove|refactor)/i.test(input);
269
- if (!looksLikeTask) {
270
- // Treat as a question instead
271
- console.log((0, ui_1.dim)('Input too vague for auto mode. Treating as question...'));
272
- return handleAskQuestion(input);
273
- }
274
- try {
275
- let apiKey;
276
- try {
277
- apiKey = await (0, auth_1.ensureAuthenticated)();
278
- }
279
- catch (authError) {
280
- console.log((0, ui_1.error)(`Authentication required: ${authError?.message || 'Run zcode auth'}`));
281
- return { handled: true };
282
- }
283
- if (!apiKey) {
284
- console.log((0, ui_1.error)('No API key configured. Run "zcode auth" to set up.'));
285
- return { handled: true };
286
- }
287
- const session = (0, session_1.getSession)();
288
- const modePrompt = (0, mode_prompts_1.buildSystemPrompt)('auto', session.workingDirectory);
289
- console.log((0, ui_1.info)('Executing autonomously...'));
290
- // Build context from workspace for better results
291
- const { buildContext, formatContextForModel } = await Promise.resolve().then(() => __importStar(require('./context/context_builder')));
292
- const context = buildContext(session.workingDirectory, input, 'CODE_EDIT', session.openFiles.map(f => require('path').join(session.workingDirectory, f)));
293
- const filesContext = formatContextForModel(context);
294
- const instruction = `${modePrompt}
296
+ // Handle complex tasks - plan then execute
297
+ async function handleComplexTask(input) {
298
+ const apiKey = await getApiKey();
299
+ if (!apiKey)
300
+ return;
301
+ const session = (0, session_1.getSession)();
302
+ (0, session_1.setIntent)(input);
303
+ console.log((0, ui_1.info)('📋 Complex task detected - creating plan...'));
304
+ const { buildContext, formatContextForModel } = await Promise.resolve().then(() => __importStar(require('./context/context_builder')));
305
+ const context = buildContext(session.workingDirectory, input, 'CODE_EDIT', session.openFiles);
306
+ const filesContext = formatContextForModel(context);
307
+ // Step 1: Generate plan
308
+ const planInstruction = `Create a step-by-step plan for this task.
295
309
 
296
310
  Task: ${input}
297
-
298
311
  Working directory: ${session.workingDirectory}
312
+ ${filesContext ? `\nFiles:\n${filesContext}` : ''}
313
+
314
+ Respond with JSON:
315
+ {
316
+ "status": "success",
317
+ "plan": [
318
+ {"id": "1", "description": "Step description", "files": ["affected/files"]}
319
+ ],
320
+ "output": "Plan summary"
321
+ }`;
322
+ console.log((0, ui_1.dim)('Planning...'));
323
+ const planResult = await (0, runtime_1.execute)({ instruction: planInstruction }, apiKey);
324
+ if (!planResult.success) {
325
+ console.log((0, ui_1.error)('Planning failed: ' + planResult.error));
326
+ return;
327
+ }
328
+ const planResponse = parseFileOperations(planResult.output);
329
+ const plan = planResponse?.plan;
330
+ if (!plan || !Array.isArray(plan) || plan.length === 0) {
331
+ console.log((0, ui_1.dim)('No plan generated, executing directly...'));
332
+ return handleSimpleEdit(input);
333
+ }
334
+ // Show plan
335
+ console.log((0, ui_1.success)(`Plan: ${plan.length} steps`));
336
+ plan.forEach((step, i) => {
337
+ console.log(` ${i + 1}. ${step.description}`);
338
+ });
339
+ (0, session_1.setLastPlan)(plan.map((s) => ({ id: s.id, description: s.description, status: 'pending' })));
340
+ // Step 2: Execute plan
341
+ console.log('');
342
+ console.log((0, ui_1.dim)('Executing plan...'));
343
+ const executeInstruction = `Execute this plan and generate all file changes.
344
+
345
+ Task: ${input}
299
346
 
300
- ${filesContext ? `Relevant files:\n${filesContext}` : ''}
347
+ Plan:
348
+ ${plan.map((s, i) => `${i + 1}. ${s.description}`).join('\n')}
301
349
 
302
- CRITICAL INSTRUCTIONS:
303
- 1. You MUST respond with valid JSON containing file operations
304
- 2. To create a folder, create a file inside it (folders are created automatically)
305
- 3. NEVER just describe what you would do - actually provide the file operations
306
- 4. For "create folder X", respond with a file like "X/.gitkeep" or "X/README.md"
350
+ Working directory: ${session.workingDirectory}
351
+ ${filesContext ? `\nFiles:\n${filesContext}` : ''}
307
352
 
308
- REQUIRED JSON FORMAT:
353
+ Respond with JSON containing ALL file operations:
309
354
  {
310
355
  "status": "success",
311
- "files": [
312
- {"path": "folder/file.ext", "operation": "create", "content": "file content"}
313
- ],
314
- "output": "Brief explanation"
356
+ "files": [{"path": "path/to/file", "operation": "create|modify|delete", "content": "COMPLETE content"}],
357
+ "output": "Summary of changes"
315
358
  }
316
359
 
317
- RULES:
318
- - Only create/modify files DIRECTLY related to the task
319
- - NEVER create files in src/ unless explicitly asked
320
- - Use .zai/ folder for internal/temporary files
321
- - Folders are created automatically when you create files inside them
322
-
323
- Respond with JSON only. No markdown, no explanation outside the JSON.`;
324
- const result = await (0, runtime_1.execute)({ instruction }, apiKey);
325
- if (result.success && result.output) {
326
- // Try to parse as ResponseSchema first
327
- let response = parseFileOperations(result.output);
328
- // If direct parsing failed, try the output object
329
- if (!response && typeof result.output === 'object') {
330
- response = result.output;
331
- }
332
- // If no files array, just show the output
333
- if (!response || !response.files || response.files.length === 0) {
334
- if (response && response.output) {
335
- console.log(response.output);
336
- }
337
- else {
338
- const text = extractTextFromResponse(result.output);
339
- if (text && !text.startsWith('{')) {
340
- console.log(text);
341
- }
342
- }
343
- return { handled: true };
344
- }
345
- // Check if there are file operations
346
- if (response && response.files && response.files.length > 0) {
347
- // SAFETY: Filter out suspicious file operations
348
- const safeFiles = response.files.filter(file => {
349
- const filePath = file.path.toLowerCase();
350
- // Block creating files in src/ unless task explicitly mentions it
351
- if (filePath.startsWith('src/') && !input.toLowerCase().includes('src')) {
352
- console.log((0, ui_1.dim)(` Skipped ${file.path} (not in task scope)`));
353
- return false;
354
- }
355
- // Block creating files outside project
356
- if (filePath.startsWith('/') || filePath.startsWith('..')) {
357
- console.log((0, ui_1.dim)(` Skipped ${file.path} (outside project)`));
358
- return false;
359
- }
360
- return true;
361
- });
362
- if (safeFiles.length > 0) {
363
- console.log((0, ui_1.success)(`Applying ${safeFiles.length} file(s)...`));
364
- let applied = 0;
365
- let failed = 0;
366
- for (const file of safeFiles) {
367
- try {
368
- // Normalize the path
369
- let filePath = file.path;
370
- if (filePath.startsWith('./')) {
371
- filePath = filePath.substring(2);
372
- }
373
- const opResult = (0, apply_1.applyFileOperation)(file.operation, filePath, file.content, {
374
- basePath: session.workingDirectory
375
- });
376
- if (opResult.success) {
377
- console.log((0, ui_1.success)(` ${file.operation}: ${filePath}`));
378
- applied++;
379
- }
380
- else {
381
- console.log((0, ui_1.error)(` Failed ${filePath}: ${opResult.error}`));
382
- failed++;
383
- }
384
- }
385
- catch (e) {
386
- console.log((0, ui_1.error)(` Failed ${file.path}: ${e?.message}`));
387
- failed++;
388
- }
389
- }
390
- console.log('');
391
- if (applied > 0) {
392
- console.log((0, ui_1.success)(`Applied ${applied} file(s)`));
393
- }
394
- if (failed > 0) {
395
- console.log((0, ui_1.error)(`Failed ${failed} file(s)`));
396
- }
397
- console.log((0, ui_1.hint)('/undo to rollback'));
398
- }
399
- }
400
- // Show output/explanation
401
- if (response && response.output) {
402
- console.log('');
403
- console.log(response.output);
404
- }
405
- else if (response && response.error) {
406
- console.log((0, ui_1.error)(response.error));
407
- }
408
- else if (!response) {
409
- // No structured response - try to show something useful
410
- const text = extractTextFromResponse(result.output);
411
- // Don't dump raw JSON to the user
412
- if (text && !text.startsWith('{') && !text.startsWith('[')) {
413
- console.log(text);
414
- }
415
- else if (text) {
416
- // It's JSON that we couldn't parse - show a friendly message
417
- console.log((0, ui_1.dim)('Response received but could not be processed.'));
418
- console.log((0, ui_1.dim)('Try rephrasing your request or use /do for step-by-step mode.'));
419
- }
420
- }
421
- }
422
- else {
423
- console.log((0, ui_1.error)(`Failed: ${result.error || 'Unknown error'}`));
424
- }
425
- return { handled: true };
360
+ IMPORTANT: Include COMPLETE file content for every file. Never use placeholders.`;
361
+ const execResult = await (0, runtime_1.execute)({ instruction: executeInstruction }, apiKey);
362
+ if (!execResult.success) {
363
+ console.log((0, ui_1.error)('Execution failed: ' + execResult.error));
364
+ return;
426
365
  }
427
- catch (e) {
428
- console.log((0, ui_1.error)(`Error: ${e?.message || e}`));
429
- return { handled: true };
366
+ const execResponse = parseFileOperations(execResult.output) ||
367
+ (typeof execResult.output === 'object' ? execResult.output : null);
368
+ if (execResponse?.files?.length) {
369
+ await applyFiles(execResponse.files, session.workingDirectory, input);
370
+ if (execResponse.output)
371
+ console.log('\n' + execResponse.output);
430
372
  }
431
- }
432
- // Handle workflow
433
- async function handleWorkflow(workflow, input, parsed, intent) {
434
- switch (workflow) {
435
- case 'slash_command':
436
- await (0, commands_1.executeCommand)(parsed);
437
- return { handled: true };
438
- case 'chat':
439
- return handleChat(input);
440
- case 'ask_question':
441
- return handleAskQuestion(input);
442
- case 'auto_execute':
443
- return handleAutoExecute(input);
444
- case 'capture_intent':
445
- (0, session_1.setIntent)(input);
446
- (0, session_1.setIntentType)(intent);
447
- // Provide clear feedback about what was captured
448
- const intentLabel = intent.toLowerCase().replace('_', ' ');
449
- console.log(`Task captured: "${input.substring(0, 60)}${input.length > 60 ? '...' : ''}"`);
450
- console.log(`Type: ${intentLabel}`);
451
- console.log('');
452
- console.log((0, ui_1.hint)('Type /plan to create execution plan'));
453
- return { handled: true };
454
- case 'append_context':
455
- const existing = (0, session_1.getIntent)();
456
- if (existing) {
457
- (0, session_1.setIntent)(`${existing}\n\nClarification: ${input}`);
458
- console.log((0, ui_1.dim)('Context updated.'));
459
- console.log((0, ui_1.hint)('/plan'));
460
- return { handled: true };
461
- }
462
- (0, session_1.setIntent)(input);
463
- console.log((0, ui_1.dim)('Intent captured.'));
464
- console.log((0, ui_1.hint)('/plan'));
465
- return { handled: true };
466
- case 'confirm_action':
467
- const session = (0, session_1.getSession)();
468
- if (session.pendingActions) {
469
- console.log((0, ui_1.hint)('/diff or /apply'));
470
- return { handled: true };
471
- }
472
- console.log((0, ui_1.dim)('Nothing pending.'));
473
- return { handled: true };
474
- case 'ignore':
475
- return { handled: true };
476
- default:
477
- return { handled: false };
373
+ else if (execResponse?.output) {
374
+ console.log(execResponse.output);
375
+ }
376
+ else {
377
+ console.log((0, ui_1.dim)('No file changes generated.'));
478
378
  }
479
379
  }
480
- // Handle casual chat - greetings, small talk
481
- async function handleChat(input) {
482
- try {
483
- let apiKey;
484
- try {
485
- apiKey = await (0, auth_1.ensureAuthenticated)();
486
- }
487
- catch (authError) {
488
- console.log((0, ui_1.error)(`Authentication required: ${authError?.message || 'Run zcode auth'}`));
489
- return { handled: true };
380
+ // Apply files helper
381
+ async function applyFiles(files, basePath, input) {
382
+ if (!files || files.length === 0)
383
+ return;
384
+ // Safety filter
385
+ const safeFiles = files.filter(file => {
386
+ const p = file.path.toLowerCase();
387
+ if (p.startsWith('src/') && !input.toLowerCase().includes('src')) {
388
+ console.log((0, ui_1.dim)(` Skipped ${file.path} (not in task scope)`));
389
+ return false;
490
390
  }
491
- if (!apiKey) {
492
- console.log((0, ui_1.error)('No API key configured. Run "zcode auth" to set up.'));
493
- return { handled: true };
391
+ if (p.startsWith('/') || p.startsWith('..')) {
392
+ console.log((0, ui_1.dim)(` Skipped ${file.path} (outside project)`));
393
+ return false;
494
394
  }
495
- // Simple chat prompt - no code context needed
496
- const chatPrompt = `You are a friendly AI coding assistant. Respond naturally and briefly to casual conversation.
497
- Keep responses short (1-2 sentences). Be warm and helpful.
498
- If the user seems to want to start coding, suggest they describe their task.`;
499
- const instruction = `${chatPrompt}
500
-
501
- User: ${input}
502
-
503
- Respond briefly and naturally:`;
504
- console.log((0, ui_1.dim)('...'));
505
- const result = await (0, runtime_1.execute)({ instruction, enforceSchema: false }, apiKey);
506
- if (result.success && result.output) {
507
- const text = extractTextFromResponse(result.output);
508
- console.log(text);
395
+ return true;
396
+ });
397
+ if (safeFiles.length === 0)
398
+ return;
399
+ console.log((0, ui_1.success)(`Applying ${safeFiles.length} file(s)...`));
400
+ let applied = 0, failed = 0;
401
+ for (const file of safeFiles) {
402
+ try {
403
+ let filePath = file.path.startsWith('./') ? file.path.substring(2) : file.path;
404
+ const result = (0, apply_1.applyFileOperation)(file.operation, filePath, file.content, { basePath });
405
+ if (result.success) {
406
+ console.log((0, ui_1.success)(` ✓ ${file.operation}: ${filePath}`));
407
+ applied++;
408
+ }
409
+ else {
410
+ console.log((0, ui_1.error)(` ✗ ${filePath}: ${result.error}`));
411
+ failed++;
412
+ }
509
413
  }
510
- else {
511
- // Fallback for chat errors - just be friendly
512
- const greetings = {
513
- 'hi': 'Hey! What would you like to build today?',
514
- 'hello': 'Hello! Ready to code something awesome?',
515
- 'hey': 'Hey there! What can I help you with?',
516
- 'yo': 'Yo! What are we building?',
517
- 'sup': 'Not much! What are you working on?',
518
- 'thanks': 'You\'re welcome! Need anything else?',
519
- 'thank you': 'Happy to help! What\'s next?',
520
- 'bye': 'See you! Happy coding!',
521
- 'ok': 'Great! Let me know if you need anything.',
522
- 'cool': 'Awesome! What\'s next?',
523
- };
524
- const lower = input.toLowerCase().trim().replace(/[!.,?]/g, '');
525
- const response = greetings[lower] || 'Hey! Describe what you\'d like to build.';
526
- console.log(response);
414
+ catch (e) {
415
+ console.log((0, ui_1.error)(` ✗ ${file.path}: ${e?.message}`));
416
+ failed++;
527
417
  }
528
- return { handled: true };
529
- }
530
- catch (e) {
531
- // Fallback on any error
532
- console.log('Hey! What would you like to build today?');
533
- return { handled: true };
534
418
  }
419
+ console.log('');
420
+ if (applied > 0)
421
+ console.log((0, ui_1.success)(`Applied ${applied} file(s)`));
422
+ if (failed > 0)
423
+ console.log((0, ui_1.error)(`Failed ${failed} file(s)`));
424
+ console.log((0, ui_1.hint)('/undo to rollback'));
535
425
  }
536
- // Main orchestration entry
537
426
  async function orchestrate(input) {
538
427
  const trimmed = input.trim();
539
428
  if (!trimmed) {
540
- return {
541
- inputType: 'free_text',
542
- intent: 'COMMAND',
543
- workflow: 'ignore',
544
- handled: true,
545
- };
429
+ return { inputType: 'free_text', intent: 'COMMAND', workflow: 'ignore', handled: true };
546
430
  }
431
+ // Handle slash commands
547
432
  const parsed = (0, commands_1.parseInput)(trimmed);
548
- // Slash commands
549
433
  if (parsed.isSlashCommand) {
550
434
  await (0, commands_1.executeCommand)(parsed);
551
- return {
552
- inputType: 'slash',
553
- intent: 'COMMAND',
554
- workflow: 'slash_command',
555
- handled: true,
556
- };
435
+ return { inputType: 'slash', intent: 'COMMAND', workflow: 'slash_command', handled: true };
436
+ }
437
+ // Analyze the task
438
+ const analysis = analyzeTask(trimmed);
439
+ // Route based on analysis
440
+ switch (analysis.type) {
441
+ case 'chat':
442
+ await handleChat(trimmed);
443
+ return { inputType: 'free_text', intent: 'QUESTION', workflow: 'chat', handled: true };
444
+ case 'question':
445
+ await handleQuestion(trimmed);
446
+ return { inputType: 'free_text', intent: 'QUESTION', workflow: 'question', handled: true };
447
+ case 'debug':
448
+ case 'refactor':
449
+ case 'simple_edit':
450
+ if (analysis.needsPlan) {
451
+ await handleComplexTask(trimmed);
452
+ }
453
+ else {
454
+ await handleSimpleEdit(trimmed);
455
+ }
456
+ return { inputType: 'free_text', intent: 'CODE_EDIT', workflow: 'auto_execute', handled: true };
457
+ case 'complex_task':
458
+ await handleComplexTask(trimmed);
459
+ return { inputType: 'free_text', intent: 'CODE_EDIT', workflow: 'planned_execute', handled: true };
460
+ default:
461
+ await handleSimpleEdit(trimmed);
462
+ return { inputType: 'free_text', intent: 'CODE_EDIT', workflow: 'auto_execute', handled: true };
557
463
  }
558
- // Free text - classify and route
559
- const intent = classifyIntent(trimmed);
560
- const workflow = determineWorkflow(intent, trimmed);
561
- const result = await handleWorkflow(workflow, trimmed, parsed, intent);
562
- return {
563
- inputType: 'free_text',
564
- intent,
565
- workflow,
566
- handled: result.handled,
567
- message: result.message,
568
- };
569
464
  }
570
465
  //# sourceMappingURL=orchestrator.js.map