@zds-ai/cli 0.1.4 → 0.1.6
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/agent/grok-agent.d.ts +13 -1
- package/dist/agent/grok-agent.js +374 -64
- package/dist/agent/grok-agent.js.map +1 -1
- package/dist/agent/llm-agent.d.ts +276 -0
- package/dist/agent/llm-agent.js +2839 -0
- package/dist/agent/llm-agent.js.map +1 -0
- package/dist/agent/prompt-variables.d.ts +127 -0
- package/dist/agent/prompt-variables.js +325 -0
- package/dist/agent/prompt-variables.js.map +1 -0
- package/dist/bin/fastcaption.sh +56 -0
- package/dist/bin/generate_image_sd.sh +2 -2
- package/dist/grok/client.d.ts +10 -9
- package/dist/grok/client.js +26 -10
- package/dist/grok/client.js.map +1 -1
- package/dist/grok/tools.d.ts +5 -5
- package/dist/grok/tools.js +16 -15
- package/dist/grok/tools.js.map +1 -1
- package/dist/hooks/use-input-handler.d.ts +3 -3
- package/dist/hooks/use-input-handler.js +47 -6
- package/dist/hooks/use-input-handler.js.map +1 -1
- package/dist/index.js +17 -10
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.js +10 -2
- package/dist/mcp/client.js.map +1 -1
- package/dist/tools/character-tool.js +1 -1
- package/dist/tools/character-tool.js.map +1 -1
- package/dist/tools/clear-cache-tool.js +1 -1
- package/dist/tools/clear-cache-tool.js.map +1 -1
- package/dist/tools/file-conversion-tool.js +1 -1
- package/dist/tools/file-conversion-tool.js.map +1 -1
- package/dist/tools/image-tool.d.ts +4 -4
- package/dist/tools/image-tool.js +11 -19
- package/dist/tools/image-tool.js.map +1 -1
- package/dist/tools/internet-tool.js +1 -1
- package/dist/tools/internet-tool.js.map +1 -1
- package/dist/tools/introspect-tool.js +7 -12
- package/dist/tools/introspect-tool.js.map +1 -1
- package/dist/tools/task-tool.js +1 -1
- package/dist/tools/task-tool.js.map +1 -1
- package/dist/tools/text-editor.js +1 -1
- package/dist/tools/text-editor.js.map +1 -1
- package/dist/tools/zsh.js +1 -1
- package/dist/tools/zsh.js.map +1 -1
- package/dist/ui/components/active-task-status.d.ts +2 -2
- package/dist/ui/components/api-key-input.d.ts +2 -2
- package/dist/ui/components/api-key-input.js +2 -2
- package/dist/ui/components/api-key-input.js.map +1 -1
- package/dist/ui/components/backend-status.d.ts +2 -2
- package/dist/ui/components/chat-history.d.ts +1 -1
- package/dist/ui/components/chat-history.js +1 -1
- package/dist/ui/components/chat-history.js.map +1 -1
- package/dist/ui/components/chat-interface.d.ts +2 -2
- package/dist/ui/components/chat-interface.js +4 -0
- package/dist/ui/components/chat-interface.js.map +1 -1
- package/dist/ui/components/context-status.d.ts +2 -2
- package/dist/ui/components/model-selection.js +1 -1
- package/dist/ui/components/model-selection.js.map +1 -1
- package/dist/ui/components/mood-status.d.ts +2 -2
- package/dist/ui/components/persona-status.d.ts +2 -2
- package/dist/utils/chat-history-manager.d.ts +2 -1
- package/dist/utils/chat-history-manager.js.map +1 -1
- package/dist/utils/hook-executor.d.ts +8 -2
- package/dist/utils/hook-executor.js +138 -13
- package/dist/utils/hook-executor.js.map +1 -1
- package/dist/utils/image-encoder.js +4 -3
- package/dist/utils/image-encoder.js.map +1 -1
- package/dist/utils/rephrase-handler.d.ts +2 -2
- package/dist/utils/rephrase-handler.js.map +1 -1
- package/dist/utils/settings-manager.d.ts +5 -0
- package/dist/utils/settings-manager.js +6 -0
- package/dist/utils/settings-manager.js.map +1 -1
- package/dist/utils/slash-commands.d.ts +7 -3
- package/dist/utils/slash-commands.js +42 -11
- package/dist/utils/slash-commands.js.map +1 -1
- package/dist/utils/startup-hook.d.ts +3 -3
- package/dist/utils/startup-hook.js +30 -16
- package/dist/utils/startup-hook.js.map +1 -1
- package/package.json +2 -1
package/dist/agent/grok-agent.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ChatHistoryManager } from "../utils/chat-history-manager.js";
|
|
|
5
5
|
import { logApiError } from "../utils/error-logger.js";
|
|
6
6
|
import { parseImagesFromMessage, hasImageReferences } from "../utils/image-encoder.js";
|
|
7
7
|
import { getTextContent } from "../utils/content-utils.js";
|
|
8
|
+
import { Variable } from "./prompt-variables.js";
|
|
8
9
|
import fs from "fs";
|
|
9
10
|
import { TextEditorTool, MorphEditorTool, ZshTool, ConfirmationTool, SearchTool, EnvTool, IntrospectTool, ClearCacheTool, CharacterTool, TaskTool, InternetTool, ImageTool, FileConversionTool, RestartTool } from "../tools/index.js";
|
|
10
11
|
import { EventEmitter } from "events";
|
|
@@ -109,6 +110,7 @@ export class GrokAgent extends EventEmitter {
|
|
|
109
110
|
apiKeyEnvVar = "GROK_API_KEY";
|
|
110
111
|
pendingContextEditSession = null;
|
|
111
112
|
rephraseState = null;
|
|
113
|
+
hookPrefillText = null;
|
|
112
114
|
constructor(apiKey, baseURL, model, maxToolRounds, debugLogFile, startupHookOutput, temperature, maxTokens) {
|
|
113
115
|
super();
|
|
114
116
|
const manager = getSettingsManager();
|
|
@@ -160,6 +162,7 @@ export class GrokAgent extends EventEmitter {
|
|
|
160
162
|
}
|
|
161
163
|
startupHookOutput;
|
|
162
164
|
systemPrompt = "Initializing..."; // THE system prompt (always at messages[0])
|
|
165
|
+
hasRunInstanceHook = false;
|
|
163
166
|
/**
|
|
164
167
|
* Initialize the agent with dynamic system prompt
|
|
165
168
|
* Must be called after construction
|
|
@@ -167,35 +170,19 @@ export class GrokAgent extends EventEmitter {
|
|
|
167
170
|
async initialize() {
|
|
168
171
|
// Build system message
|
|
169
172
|
await this.buildSystemMessage();
|
|
170
|
-
// Execute instance hook on every startup (fresh or not)
|
|
171
|
-
const settings = getSettingsManager();
|
|
172
|
-
const instanceHookPath = settings.getInstanceHook();
|
|
173
|
-
if (instanceHookPath) {
|
|
174
|
-
const hookResult = await executeOperationHook(instanceHookPath, "instance", {}, 30000, false, // Instance hook is not mandatory
|
|
175
|
-
this.getCurrentTokenCount(), this.getMaxContextSize());
|
|
176
|
-
if (hookResult.approved && hookResult.commands && hookResult.commands.length > 0) {
|
|
177
|
-
// Apply hook commands (ENV, TOOL_RESULT, MODEL, SYSTEM)
|
|
178
|
-
await this.processHookResult(hookResult);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
173
|
}
|
|
182
174
|
/**
|
|
183
175
|
* Build/rebuild the system message with current tool availability
|
|
184
176
|
* Updates this.systemPrompt which is always used for messages[0]
|
|
185
177
|
*/
|
|
186
178
|
async buildSystemMessage() {
|
|
187
|
-
// Add startup hook output if provided
|
|
188
|
-
const startupHookSection = this.startupHookOutput
|
|
189
|
-
? `${this.startupHookOutput}\n`
|
|
190
|
-
: "";
|
|
191
179
|
// Generate dynamic tool list using introspect tool
|
|
192
180
|
const toolsResult = await this.introspect.introspect("tools");
|
|
193
181
|
const toolsSection = toolsResult.success ? toolsResult.output : "Tools: Unknown";
|
|
182
|
+
// Set APP:TOOLS variable
|
|
183
|
+
Variable.set("APP:TOOLS", toolsSection);
|
|
194
184
|
// Build THE system prompt
|
|
195
|
-
this.systemPrompt =
|
|
196
|
-
${toolsSection}
|
|
197
|
-
|
|
198
|
-
Current working directory: ${process.cwd()}`;
|
|
185
|
+
this.systemPrompt = Variable.renderFull('SYSTEM');
|
|
199
186
|
// Update messages[0] with the system prompt
|
|
200
187
|
this.messages[0] = {
|
|
201
188
|
role: "system",
|
|
@@ -355,16 +342,27 @@ Current working directory: ${process.cwd()}`;
|
|
|
355
342
|
let isSystemRephrase = false;
|
|
356
343
|
let messageToSend = message;
|
|
357
344
|
let messageType = "user";
|
|
345
|
+
let prefillText;
|
|
358
346
|
if (message.startsWith("/system rephrase")) {
|
|
359
347
|
isRephraseCommand = true;
|
|
360
348
|
isSystemRephrase = true;
|
|
361
349
|
messageToSend = message.substring(8).trim(); // Strip "/system " (8 chars including space)
|
|
362
350
|
messageType = "system";
|
|
351
|
+
// Extract prefill text after "/system rephrase "
|
|
352
|
+
const prefillMatch = message.match(/^\/system rephrase\s+(.+)$/);
|
|
353
|
+
if (prefillMatch) {
|
|
354
|
+
prefillText = prefillMatch[1];
|
|
355
|
+
}
|
|
363
356
|
}
|
|
364
357
|
else if (message.startsWith("/rephrase")) {
|
|
365
358
|
isRephraseCommand = true;
|
|
366
359
|
messageToSend = message; // Keep full text including "/rephrase"
|
|
367
360
|
messageType = "user";
|
|
361
|
+
// Extract prefill text after "/rephrase "
|
|
362
|
+
const prefillMatch = message.match(/^\/rephrase\s+(.+)$/);
|
|
363
|
+
if (prefillMatch) {
|
|
364
|
+
prefillText = prefillMatch[1];
|
|
365
|
+
}
|
|
368
366
|
}
|
|
369
367
|
// If this is a rephrase command, find the last assistant message
|
|
370
368
|
if (isRephraseCommand) {
|
|
@@ -381,7 +379,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
381
379
|
}
|
|
382
380
|
// Store rephrase state (will be updated with newResponseIndex after response)
|
|
383
381
|
// For now, just mark that we're in rephrase mode
|
|
384
|
-
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType);
|
|
382
|
+
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType, prefillText);
|
|
385
383
|
}
|
|
386
384
|
// Before adding the new user message, check if there are incomplete tool calls
|
|
387
385
|
// from a previous interrupted turn. This prevents malformed message sequences
|
|
@@ -413,29 +411,83 @@ Current working directory: ${process.cwd()}`;
|
|
|
413
411
|
}
|
|
414
412
|
}
|
|
415
413
|
}
|
|
414
|
+
// Clear one-shot variables
|
|
415
|
+
Variable.clearOneShot();
|
|
416
|
+
// Execute instance hook once per session (after first clearOneShot)
|
|
417
|
+
if (!this.hasRunInstanceHook) {
|
|
418
|
+
this.hasRunInstanceHook = true;
|
|
419
|
+
const settings = getSettingsManager();
|
|
420
|
+
const instanceHookPath = settings.getInstanceHook();
|
|
421
|
+
if (instanceHookPath) {
|
|
422
|
+
const hookResult = await executeOperationHook(instanceHookPath, "instance", {}, 30000, false, // Instance hook is not mandatory
|
|
423
|
+
this.getCurrentTokenCount(), this.getMaxContextSize());
|
|
424
|
+
if (hookResult.approved && hookResult.commands && hookResult.commands.length > 0) {
|
|
425
|
+
// Apply hook commands (ENV, TOOL_RESULT, MODEL, SYSTEM, SET*)
|
|
426
|
+
const results = applyHookCommands(hookResult.commands);
|
|
427
|
+
// Apply prompt variables from SET* commands
|
|
428
|
+
for (const [varName, value] of results.promptVars.entries()) {
|
|
429
|
+
Variable.set(varName, value);
|
|
430
|
+
}
|
|
431
|
+
// Process other hook commands (MODEL, BACKEND, ENV)
|
|
432
|
+
await this.processHookCommands(results);
|
|
433
|
+
// Add SYSTEM message to messages array if present
|
|
434
|
+
if (results.system) {
|
|
435
|
+
this.messages.push({
|
|
436
|
+
role: 'system',
|
|
437
|
+
content: results.system
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// Store prefill text from hook if present
|
|
441
|
+
if (results.prefill) {
|
|
442
|
+
this.hookPrefillText = results.prefill;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Parse images once if present (for both text extraction and later assembly)
|
|
448
|
+
const parsed = hasImageReferences(messageToSend)
|
|
449
|
+
? parseImagesFromMessage(messageToSend)
|
|
450
|
+
: { text: messageToSend, images: [] };
|
|
451
|
+
// Set USER:PROMPT variable (text only, images stripped)
|
|
452
|
+
Variable.set("USER:PROMPT", parsed.text);
|
|
453
|
+
// Execute prePrompt hook if configured
|
|
454
|
+
const hookPath = getSettingsManager().getPrePromptHook();
|
|
455
|
+
if (hookPath) {
|
|
456
|
+
const hookResult = await executeOperationHook(hookPath, "prePrompt", { USER_MESSAGE: parsed.text }, 30000, false, // prePrompt hook is never mandatory
|
|
457
|
+
this.getCurrentTokenCount(), this.getMaxContextSize());
|
|
458
|
+
if (hookResult.approved && hookResult.commands) {
|
|
459
|
+
const results = applyHookCommands(hookResult.commands);
|
|
460
|
+
// Set prompt variables from hook output (SET, SET_FILE, SET_TEMP_FILE)
|
|
461
|
+
for (const [varName, value] of results.promptVars.entries()) {
|
|
462
|
+
Variable.set(varName, value);
|
|
463
|
+
}
|
|
464
|
+
// Process other hook commands (MODEL, BACKEND, SYSTEM, etc.)
|
|
465
|
+
await this.processHookCommands(results);
|
|
466
|
+
// Store prefill text from hook if present
|
|
467
|
+
if (results.prefill) {
|
|
468
|
+
this.hookPrefillText = results.prefill;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// Assemble final message from variables
|
|
473
|
+
const assembledMessage = Variable.renderFull("USER");
|
|
416
474
|
// Add user/system message to conversation
|
|
417
|
-
// Check for image references and parse if present
|
|
418
475
|
// Note: System messages can only have string content, so images are only supported for user messages
|
|
419
476
|
const supportsVision = this.grokClient.getSupportsVision();
|
|
420
|
-
let messageContent =
|
|
421
|
-
if (messageType === "user" &&
|
|
422
|
-
//
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
{ type: "text", text: parsed.text },
|
|
428
|
-
...parsed.images
|
|
429
|
-
];
|
|
430
|
-
}
|
|
431
|
-
else {
|
|
432
|
-
// No valid images found (errors will be in parsed.text)
|
|
433
|
-
messageContent = parsed.text;
|
|
434
|
-
}
|
|
477
|
+
let messageContent = assembledMessage;
|
|
478
|
+
if (messageType === "user" && parsed.images.length > 0 && supportsVision) {
|
|
479
|
+
// Construct content array with assembled text and images
|
|
480
|
+
messageContent = [
|
|
481
|
+
{ type: "text", text: assembledMessage },
|
|
482
|
+
...parsed.images
|
|
483
|
+
];
|
|
435
484
|
}
|
|
436
485
|
const userEntry = {
|
|
437
486
|
type: messageType,
|
|
438
487
|
content: messageContent,
|
|
488
|
+
originalContent: messageType === "user" ? (parsed.images.length > 0 && supportsVision
|
|
489
|
+
? [{ type: "text", text: parsed.text }, ...parsed.images]
|
|
490
|
+
: parsed.text) : undefined,
|
|
439
491
|
timestamp: new Date(),
|
|
440
492
|
};
|
|
441
493
|
this.chatHistory.push(userEntry);
|
|
@@ -453,6 +505,20 @@ Current working directory: ${process.cwd()}`;
|
|
|
453
505
|
let toolRounds = 0;
|
|
454
506
|
let consecutiveNonToolResponses = 0;
|
|
455
507
|
try {
|
|
508
|
+
// If this is a rephrase with prefill text, add the assistant message now
|
|
509
|
+
if (this.rephraseState?.prefillText) {
|
|
510
|
+
this.messages.push({
|
|
511
|
+
role: "assistant",
|
|
512
|
+
content: this.rephraseState.prefillText
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
// If a hook returned prefill text, add the assistant message now
|
|
516
|
+
if (this.hookPrefillText) {
|
|
517
|
+
this.messages.push({
|
|
518
|
+
role: "assistant",
|
|
519
|
+
content: this.hookPrefillText
|
|
520
|
+
});
|
|
521
|
+
}
|
|
456
522
|
// Always fetch tools fresh - getAllGrokTools() handles lazy refresh internally
|
|
457
523
|
const supportsTools = this.grokClient.getSupportsTools();
|
|
458
524
|
let currentResponse = await this.grokClient.chat(this.messages, supportsTools ? await getAllGrokTools() : [], undefined, this.isGrokModel() && this.shouldUseSearchFor(message)
|
|
@@ -626,7 +692,16 @@ Current working directory: ${process.cwd()}`;
|
|
|
626
692
|
}
|
|
627
693
|
else {
|
|
628
694
|
// No tool calls in this response - only add it if there's actual content
|
|
629
|
-
|
|
695
|
+
let trimmedContent = assistantMessage.content?.trim();
|
|
696
|
+
// If this was a rephrase with prefill, prepend the prefill text to the response
|
|
697
|
+
if (trimmedContent && this.rephraseState?.prefillText) {
|
|
698
|
+
trimmedContent = this.rephraseState.prefillText + trimmedContent;
|
|
699
|
+
}
|
|
700
|
+
// If a hook provided prefill, prepend it to the response
|
|
701
|
+
if (trimmedContent && this.hookPrefillText) {
|
|
702
|
+
trimmedContent = this.hookPrefillText + trimmedContent;
|
|
703
|
+
this.hookPrefillText = null; // Clear after use
|
|
704
|
+
}
|
|
630
705
|
if (trimmedContent) {
|
|
631
706
|
const responseEntry = {
|
|
632
707
|
type: "assistant",
|
|
@@ -642,7 +717,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
642
717
|
// Update rephrase state with the new response index
|
|
643
718
|
if (this.rephraseState && this.rephraseState.newResponseIndex === -1) {
|
|
644
719
|
const newResponseIndex = this.chatHistory.length - 1;
|
|
645
|
-
this.setRephraseState(this.rephraseState.originalAssistantMessageIndex, this.rephraseState.rephraseRequestIndex, newResponseIndex, this.rephraseState.messageType);
|
|
720
|
+
this.setRephraseState(this.rephraseState.originalAssistantMessageIndex, this.rephraseState.rephraseRequestIndex, newResponseIndex, this.rephraseState.messageType, this.rephraseState.prefillText);
|
|
646
721
|
}
|
|
647
722
|
}
|
|
648
723
|
// TODO: HACK - This is a temporary fix to prevent duplicate responses.
|
|
@@ -691,6 +766,22 @@ Current working directory: ${process.cwd()}`;
|
|
|
691
766
|
return newEntries;
|
|
692
767
|
}
|
|
693
768
|
catch (error) {
|
|
769
|
+
// Check if context is too large (413 error when vision already disabled)
|
|
770
|
+
if (error.message && error.message.startsWith('CONTEXT_TOO_LARGE:')) {
|
|
771
|
+
const beforeCount = this.chatHistory.length;
|
|
772
|
+
this.compactContext(20);
|
|
773
|
+
const afterCount = this.chatHistory.length;
|
|
774
|
+
const removedCount = beforeCount - afterCount;
|
|
775
|
+
const compactEntry = {
|
|
776
|
+
type: "system",
|
|
777
|
+
content: `Context was too large for backend. Automatically compacted: removed ${removedCount} older messages, keeping last 20 messages. Please retry your request.`,
|
|
778
|
+
timestamp: new Date(),
|
|
779
|
+
};
|
|
780
|
+
this.chatHistory.push(compactEntry);
|
|
781
|
+
// Mark first message as processed
|
|
782
|
+
this.firstMessageProcessed = true;
|
|
783
|
+
return [userEntry, compactEntry];
|
|
784
|
+
}
|
|
694
785
|
const errorEntry = {
|
|
695
786
|
type: "assistant",
|
|
696
787
|
content: `Sorry, I encountered an error: ${error.message}`,
|
|
@@ -804,16 +895,27 @@ Current working directory: ${process.cwd()}`;
|
|
|
804
895
|
let isSystemRephrase = false;
|
|
805
896
|
let messageToSend = message;
|
|
806
897
|
let messageType = "user";
|
|
898
|
+
let prefillText;
|
|
807
899
|
if (message.startsWith("/system rephrase")) {
|
|
808
900
|
isRephraseCommand = true;
|
|
809
901
|
isSystemRephrase = true;
|
|
810
902
|
messageToSend = message.substring(8).trim(); // Strip "/system " (8 chars including space)
|
|
811
903
|
messageType = "system";
|
|
904
|
+
// Extract prefill text after "/system rephrase "
|
|
905
|
+
const prefillMatch = message.match(/^\/system rephrase\s+(.+)$/);
|
|
906
|
+
if (prefillMatch) {
|
|
907
|
+
prefillText = prefillMatch[1];
|
|
908
|
+
}
|
|
812
909
|
}
|
|
813
910
|
else if (message.startsWith("/rephrase")) {
|
|
814
911
|
isRephraseCommand = true;
|
|
815
912
|
messageToSend = message; // Keep full text including "/rephrase"
|
|
816
913
|
messageType = "user";
|
|
914
|
+
// Extract prefill text after "/rephrase "
|
|
915
|
+
const prefillMatch = message.match(/^\/rephrase\s+(.+)$/);
|
|
916
|
+
if (prefillMatch) {
|
|
917
|
+
prefillText = prefillMatch[1];
|
|
918
|
+
}
|
|
817
919
|
}
|
|
818
920
|
// If this is a rephrase command, find the last assistant message
|
|
819
921
|
if (isRephraseCommand) {
|
|
@@ -830,7 +932,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
830
932
|
}
|
|
831
933
|
// Store rephrase state (will be updated with newResponseIndex after response)
|
|
832
934
|
// For now, just mark that we're in rephrase mode
|
|
833
|
-
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType);
|
|
935
|
+
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType, prefillText);
|
|
834
936
|
}
|
|
835
937
|
// Before adding the new user message, check if there are incomplete tool calls
|
|
836
938
|
// from a previous interrupted turn. This prevents malformed message sequences
|
|
@@ -862,29 +964,83 @@ Current working directory: ${process.cwd()}`;
|
|
|
862
964
|
}
|
|
863
965
|
}
|
|
864
966
|
}
|
|
967
|
+
// Clear one-shot variables
|
|
968
|
+
Variable.clearOneShot();
|
|
969
|
+
// Execute instance hook once per session (after first clearOneShot)
|
|
970
|
+
if (!this.hasRunInstanceHook) {
|
|
971
|
+
this.hasRunInstanceHook = true;
|
|
972
|
+
const settings = getSettingsManager();
|
|
973
|
+
const instanceHookPath = settings.getInstanceHook();
|
|
974
|
+
if (instanceHookPath) {
|
|
975
|
+
const hookResult = await executeOperationHook(instanceHookPath, "instance", {}, 30000, false, // Instance hook is not mandatory
|
|
976
|
+
this.getCurrentTokenCount(), this.getMaxContextSize());
|
|
977
|
+
if (hookResult.approved && hookResult.commands && hookResult.commands.length > 0) {
|
|
978
|
+
// Apply hook commands (ENV, TOOL_RESULT, MODEL, SYSTEM, SET*)
|
|
979
|
+
const results = applyHookCommands(hookResult.commands);
|
|
980
|
+
// Apply prompt variables from SET* commands
|
|
981
|
+
for (const [varName, value] of results.promptVars.entries()) {
|
|
982
|
+
Variable.set(varName, value);
|
|
983
|
+
}
|
|
984
|
+
// Process other hook commands (MODEL, BACKEND, ENV)
|
|
985
|
+
await this.processHookCommands(results);
|
|
986
|
+
// Add SYSTEM message to messages array if present
|
|
987
|
+
if (results.system) {
|
|
988
|
+
this.messages.push({
|
|
989
|
+
role: 'system',
|
|
990
|
+
content: results.system
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
// Store prefill text from hook if present
|
|
994
|
+
if (results.prefill) {
|
|
995
|
+
this.hookPrefillText = results.prefill;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
// Parse images once if present (for both text extraction and later assembly)
|
|
1001
|
+
const parsed = hasImageReferences(messageToSend)
|
|
1002
|
+
? parseImagesFromMessage(messageToSend)
|
|
1003
|
+
: { text: messageToSend, images: [] };
|
|
1004
|
+
// Set USER:PROMPT variable (text only, images stripped)
|
|
1005
|
+
Variable.set("USER:PROMPT", parsed.text);
|
|
1006
|
+
// Execute prePrompt hook if configured
|
|
1007
|
+
const hookPath = getSettingsManager().getPrePromptHook();
|
|
1008
|
+
if (hookPath) {
|
|
1009
|
+
const hookResult = await executeOperationHook(hookPath, "prePrompt", { USER_MESSAGE: parsed.text }, 30000, false, // prePrompt hook is never mandatory
|
|
1010
|
+
this.getCurrentTokenCount(), this.getMaxContextSize());
|
|
1011
|
+
if (hookResult.approved && hookResult.commands) {
|
|
1012
|
+
const results = applyHookCommands(hookResult.commands);
|
|
1013
|
+
// Set prompt variables from hook output (SET, SET_FILE, SET_TEMP_FILE)
|
|
1014
|
+
for (const [varName, value] of results.promptVars.entries()) {
|
|
1015
|
+
Variable.set(varName, value);
|
|
1016
|
+
}
|
|
1017
|
+
// Process other hook commands (MODEL, BACKEND, SYSTEM, etc.)
|
|
1018
|
+
await this.processHookCommands(results);
|
|
1019
|
+
// Store prefill text from hook if present
|
|
1020
|
+
if (results.prefill) {
|
|
1021
|
+
this.hookPrefillText = results.prefill;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
// Assemble final message from variables
|
|
1026
|
+
const assembledMessage = Variable.renderFull("USER");
|
|
865
1027
|
// Add user/system message to both API conversation and chat history
|
|
866
|
-
// Check for image references and parse if present
|
|
867
1028
|
// Note: System messages can only have string content, so images are only supported for user messages
|
|
868
1029
|
const supportsVision = this.grokClient.getSupportsVision();
|
|
869
|
-
let messageContent =
|
|
870
|
-
if (messageType === "user" &&
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
{ type: "text", text: parsed.text },
|
|
877
|
-
...parsed.images
|
|
878
|
-
];
|
|
879
|
-
}
|
|
880
|
-
else {
|
|
881
|
-
// No valid images found (errors will be in parsed.text)
|
|
882
|
-
messageContent = parsed.text;
|
|
883
|
-
}
|
|
1030
|
+
let messageContent = assembledMessage;
|
|
1031
|
+
if (messageType === "user" && parsed.images.length > 0 && supportsVision) {
|
|
1032
|
+
// Construct content array with assembled text and images
|
|
1033
|
+
messageContent = [
|
|
1034
|
+
{ type: "text", text: assembledMessage },
|
|
1035
|
+
...parsed.images
|
|
1036
|
+
];
|
|
884
1037
|
}
|
|
885
1038
|
const userEntry = {
|
|
886
1039
|
type: messageType,
|
|
887
1040
|
content: messageContent,
|
|
1041
|
+
originalContent: messageType === "user" ? (parsed.images.length > 0 && supportsVision
|
|
1042
|
+
? [{ type: "text", text: parsed.text }, ...parsed.images]
|
|
1043
|
+
: parsed.text) : undefined,
|
|
888
1044
|
timestamp: new Date(),
|
|
889
1045
|
};
|
|
890
1046
|
this.chatHistory.push(userEntry);
|
|
@@ -894,7 +1050,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
894
1050
|
}
|
|
895
1051
|
else {
|
|
896
1052
|
// System messages must have string content only
|
|
897
|
-
this.messages.push({ role: "system", content: typeof messageContent === "string" ? messageContent :
|
|
1053
|
+
this.messages.push({ role: "system", content: typeof messageContent === "string" ? messageContent : assembledMessage });
|
|
898
1054
|
}
|
|
899
1055
|
await this.emitContextChange();
|
|
900
1056
|
// Yield user message so UI can display it immediately
|
|
@@ -902,6 +1058,20 @@ Current working directory: ${process.cwd()}`;
|
|
|
902
1058
|
type: "user_message",
|
|
903
1059
|
userEntry: userEntry,
|
|
904
1060
|
};
|
|
1061
|
+
// If this is a rephrase with prefill text, add the assistant message now
|
|
1062
|
+
if (this.rephraseState?.prefillText) {
|
|
1063
|
+
this.messages.push({
|
|
1064
|
+
role: "assistant",
|
|
1065
|
+
content: this.rephraseState.prefillText
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
// If a hook returned prefill text, add the assistant message now
|
|
1069
|
+
if (this.hookPrefillText) {
|
|
1070
|
+
this.messages.push({
|
|
1071
|
+
role: "assistant",
|
|
1072
|
+
content: this.hookPrefillText
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
905
1075
|
// Calculate input tokens
|
|
906
1076
|
let inputTokens = this.tokenCounter.countMessageTokens(this.messages);
|
|
907
1077
|
yield {
|
|
@@ -940,6 +1110,23 @@ Current working directory: ${process.cwd()}`;
|
|
|
940
1110
|
let tool_calls_yielded = false;
|
|
941
1111
|
let streamFinished = false;
|
|
942
1112
|
let insideThinkTag = false;
|
|
1113
|
+
// If this is a rephrase with prefill, yield the prefill text first and add to accumulated content
|
|
1114
|
+
if (this.rephraseState?.prefillText) {
|
|
1115
|
+
yield {
|
|
1116
|
+
type: "content",
|
|
1117
|
+
content: this.rephraseState.prefillText,
|
|
1118
|
+
};
|
|
1119
|
+
accumulatedContent = this.rephraseState.prefillText;
|
|
1120
|
+
}
|
|
1121
|
+
// If a hook provided prefill, yield it first and add to accumulated content
|
|
1122
|
+
if (this.hookPrefillText) {
|
|
1123
|
+
yield {
|
|
1124
|
+
type: "content",
|
|
1125
|
+
content: this.hookPrefillText,
|
|
1126
|
+
};
|
|
1127
|
+
accumulatedContent = this.hookPrefillText;
|
|
1128
|
+
this.hookPrefillText = null; // Clear after use
|
|
1129
|
+
}
|
|
943
1130
|
try {
|
|
944
1131
|
for await (const chunk of stream) {
|
|
945
1132
|
// Check for cancellation in the streaming loop
|
|
@@ -1237,6 +1424,25 @@ Current working directory: ${process.cwd()}`;
|
|
|
1237
1424
|
yield { type: "done" };
|
|
1238
1425
|
return;
|
|
1239
1426
|
}
|
|
1427
|
+
// Check if context is too large (413 error when vision already disabled)
|
|
1428
|
+
if (error.message && error.message.startsWith('CONTEXT_TOO_LARGE:')) {
|
|
1429
|
+
const beforeCount = this.chatHistory.length;
|
|
1430
|
+
this.compactContext(20);
|
|
1431
|
+
const afterCount = this.chatHistory.length;
|
|
1432
|
+
const removedCount = beforeCount - afterCount;
|
|
1433
|
+
const compactEntry = {
|
|
1434
|
+
type: "system",
|
|
1435
|
+
content: `Context was too large for backend. Automatically compacted: removed ${removedCount} older messages, keeping last 20 messages. Please retry your request.`,
|
|
1436
|
+
timestamp: new Date(),
|
|
1437
|
+
};
|
|
1438
|
+
this.chatHistory.push(compactEntry);
|
|
1439
|
+
yield {
|
|
1440
|
+
type: "content",
|
|
1441
|
+
content: getTextContent(compactEntry.content),
|
|
1442
|
+
};
|
|
1443
|
+
yield { type: "done" };
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1240
1446
|
const errorEntry = {
|
|
1241
1447
|
type: "assistant",
|
|
1242
1448
|
content: `Sorry, I encountered an error: ${error.message}`,
|
|
@@ -1496,7 +1702,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
1496
1702
|
case "generateImage":
|
|
1497
1703
|
return await this.imageTool.generateImage(args.prompt, args.negativePrompt, args.width, args.height, args.model, args.sampler, args.configScale, args.numSteps, args.nsfw, args.name, args.move, args.seed);
|
|
1498
1704
|
case "captionImage":
|
|
1499
|
-
return await this.imageTool.captionImage(args.filename, args.
|
|
1705
|
+
return await this.imageTool.captionImage(args.filename, args.backend);
|
|
1500
1706
|
case "pngInfo":
|
|
1501
1707
|
return await this.imageTool.pngInfo(args.filename);
|
|
1502
1708
|
case "listImageModels":
|
|
@@ -1683,8 +1889,8 @@ Current working directory: ${process.cwd()}`;
|
|
|
1683
1889
|
clearPendingContextEditSession() {
|
|
1684
1890
|
this.pendingContextEditSession = null;
|
|
1685
1891
|
}
|
|
1686
|
-
setRephraseState(originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType) {
|
|
1687
|
-
this.rephraseState = { originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType };
|
|
1892
|
+
setRephraseState(originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType, prefillText) {
|
|
1893
|
+
this.rephraseState = { originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType, prefillText };
|
|
1688
1894
|
}
|
|
1689
1895
|
getRephraseState() {
|
|
1690
1896
|
return this.rephraseState;
|
|
@@ -2038,6 +2244,8 @@ Current working directory: ${process.cwd()}`;
|
|
|
2038
2244
|
}
|
|
2039
2245
|
setModel(model) {
|
|
2040
2246
|
this.grokClient.setModel(model);
|
|
2247
|
+
// Reset supportsVision flag for new model
|
|
2248
|
+
this.grokClient.setSupportsVision(true);
|
|
2041
2249
|
// Update token counter for new model
|
|
2042
2250
|
this.tokenCounter.dispose();
|
|
2043
2251
|
this.tokenCounter = createTokenCounter(model);
|
|
@@ -2412,6 +2620,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
2412
2620
|
baseUrl: this.grokClient.getBaseURL(),
|
|
2413
2621
|
apiKeyEnvVar: this.apiKeyEnvVar,
|
|
2414
2622
|
model: this.getCurrentModel(),
|
|
2623
|
+
supportsVision: this.grokClient.getSupportsVision(),
|
|
2415
2624
|
};
|
|
2416
2625
|
}
|
|
2417
2626
|
/**
|
|
@@ -2425,10 +2634,15 @@ Current working directory: ${process.cwd()}`;
|
|
|
2425
2634
|
// Restore cwd early (hooks may need correct working directory)
|
|
2426
2635
|
if (state.cwd) {
|
|
2427
2636
|
try {
|
|
2428
|
-
|
|
2637
|
+
const fs = await import('fs');
|
|
2638
|
+
// Only attempt to change directory if it exists
|
|
2639
|
+
if (fs.existsSync(state.cwd)) {
|
|
2640
|
+
process.chdir(state.cwd);
|
|
2641
|
+
}
|
|
2642
|
+
// Silently skip if directory doesn't exist (common in containerized environments)
|
|
2429
2643
|
}
|
|
2430
2644
|
catch (error) {
|
|
2431
|
-
|
|
2645
|
+
// Silently skip on any error - working directory restoration is non-critical
|
|
2432
2646
|
}
|
|
2433
2647
|
}
|
|
2434
2648
|
// Restore backend/baseUrl/apiKeyEnvVar/model if present (creates initial client)
|
|
@@ -2441,6 +2655,10 @@ Current working directory: ${process.cwd()}`;
|
|
|
2441
2655
|
const model = state.model || this.getCurrentModel();
|
|
2442
2656
|
this.grokClient = new GrokClient(apiKey, model, state.baseUrl, state.backend);
|
|
2443
2657
|
this.apiKeyEnvVar = state.apiKeyEnvVar;
|
|
2658
|
+
// Restore supportsVision flag if present
|
|
2659
|
+
if (state.supportsVision !== undefined) {
|
|
2660
|
+
this.grokClient.setSupportsVision(state.supportsVision);
|
|
2661
|
+
}
|
|
2444
2662
|
// Reinitialize MCP servers when restoring session
|
|
2445
2663
|
try {
|
|
2446
2664
|
const config = loadMCPConfig();
|
|
@@ -2469,30 +2687,122 @@ Current working directory: ${process.cwd()}`;
|
|
|
2469
2687
|
// Restore persona (hook may change backend/model and sets env vars)
|
|
2470
2688
|
if (state.persona) {
|
|
2471
2689
|
try {
|
|
2472
|
-
await this.setPersona(state.persona, state.personaColor);
|
|
2690
|
+
const result = await this.setPersona(state.persona, state.personaColor);
|
|
2691
|
+
if (!result.success) {
|
|
2692
|
+
// If persona hook failed (e.g., backend test failed), still set the persona values
|
|
2693
|
+
// but don't change backend/model. This prevents losing persona state on transitory errors.
|
|
2694
|
+
console.warn(`Persona hook failed, setting persona without backend change: ${result.error}`);
|
|
2695
|
+
this.persona = state.persona;
|
|
2696
|
+
this.personaColor = state.personaColor;
|
|
2697
|
+
process.env.ZDS_AI_AGENT_PERSONA = state.persona;
|
|
2698
|
+
}
|
|
2473
2699
|
}
|
|
2474
2700
|
catch (error) {
|
|
2475
2701
|
console.warn(`Failed to restore persona "${state.persona}":`, error);
|
|
2702
|
+
// Still set persona values even if hook crashed
|
|
2703
|
+
this.persona = state.persona;
|
|
2704
|
+
this.personaColor = state.personaColor;
|
|
2705
|
+
process.env.ZDS_AI_AGENT_PERSONA = state.persona;
|
|
2476
2706
|
}
|
|
2477
2707
|
}
|
|
2478
2708
|
// Restore mood (hook sets env vars)
|
|
2479
2709
|
if (state.mood) {
|
|
2480
2710
|
try {
|
|
2481
|
-
await this.setMood(state.mood, state.moodColor);
|
|
2711
|
+
const result = await this.setMood(state.mood, state.moodColor);
|
|
2712
|
+
if (!result.success) {
|
|
2713
|
+
// If mood hook failed (e.g., backend test failed), still set the mood values
|
|
2714
|
+
// but don't change backend/model. This prevents losing mood state on transitory errors.
|
|
2715
|
+
console.warn(`Mood hook failed, setting mood without backend change: ${result.error}`);
|
|
2716
|
+
this.mood = state.mood;
|
|
2717
|
+
this.moodColor = state.moodColor;
|
|
2718
|
+
process.env.ZDS_AI_AGENT_MOOD = state.mood;
|
|
2719
|
+
}
|
|
2482
2720
|
}
|
|
2483
2721
|
catch (error) {
|
|
2484
2722
|
console.warn(`Failed to restore mood "${state.mood}":`, error);
|
|
2723
|
+
// Still set mood values even if hook crashed
|
|
2724
|
+
this.mood = state.mood;
|
|
2725
|
+
this.moodColor = state.moodColor;
|
|
2726
|
+
process.env.ZDS_AI_AGENT_MOOD = state.mood;
|
|
2485
2727
|
}
|
|
2486
2728
|
}
|
|
2487
2729
|
// Restore active task (hook sets env vars)
|
|
2488
2730
|
if (state.activeTask) {
|
|
2489
2731
|
try {
|
|
2490
|
-
await this.startActiveTask(state.activeTask, state.activeTaskAction, state.activeTaskColor);
|
|
2732
|
+
const result = await this.startActiveTask(state.activeTask, state.activeTaskAction, state.activeTaskColor);
|
|
2733
|
+
if (!result.success) {
|
|
2734
|
+
// If task hook failed (e.g., backend test failed), still set the task values
|
|
2735
|
+
// but don't change backend/model. This prevents losing task state on transitory errors.
|
|
2736
|
+
console.warn(`Task hook failed, setting active task without backend change: ${result.error}`);
|
|
2737
|
+
this.activeTask = state.activeTask;
|
|
2738
|
+
this.activeTaskAction = state.activeTaskAction;
|
|
2739
|
+
this.activeTaskColor = state.activeTaskColor;
|
|
2740
|
+
process.env.ZDS_AI_AGENT_ACTIVE_TASK = state.activeTask;
|
|
2741
|
+
process.env.ZDS_AI_AGENT_ACTIVE_TASK_ACTION = state.activeTaskAction;
|
|
2742
|
+
}
|
|
2491
2743
|
}
|
|
2492
2744
|
catch (error) {
|
|
2493
2745
|
console.warn(`Failed to restore active task "${state.activeTask}":`, error);
|
|
2746
|
+
// Still set task values even if hook crashed
|
|
2747
|
+
this.activeTask = state.activeTask;
|
|
2748
|
+
this.activeTaskAction = state.activeTaskAction;
|
|
2749
|
+
this.activeTaskColor = state.activeTaskColor;
|
|
2750
|
+
process.env.ZDS_AI_AGENT_ACTIVE_TASK = state.activeTask;
|
|
2751
|
+
process.env.ZDS_AI_AGENT_ACTIVE_TASK_ACTION = state.activeTaskAction;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
/**
|
|
2756
|
+
* Compact conversation context by keeping system prompt and last N messages
|
|
2757
|
+
* Reduces context size when it grows too large for backend to handle
|
|
2758
|
+
* @returns Number of messages removed
|
|
2759
|
+
*/
|
|
2760
|
+
compactContext(keepLastMessages = 20) {
|
|
2761
|
+
if (this.chatHistory.length <= keepLastMessages) {
|
|
2762
|
+
// Nothing to compact
|
|
2763
|
+
return 0;
|
|
2764
|
+
}
|
|
2765
|
+
const removedCount = this.chatHistory.length - keepLastMessages;
|
|
2766
|
+
const keptMessages = this.chatHistory.slice(-keepLastMessages);
|
|
2767
|
+
// Clear both arrays
|
|
2768
|
+
this.chatHistory = keptMessages;
|
|
2769
|
+
this.messages = [];
|
|
2770
|
+
// Add system message noting the compaction
|
|
2771
|
+
const compactionNote = {
|
|
2772
|
+
type: 'system',
|
|
2773
|
+
content: `Context compacted: removed ${removedCount} older messages, keeping last ${keepLastMessages} messages.`,
|
|
2774
|
+
timestamp: new Date()
|
|
2775
|
+
};
|
|
2776
|
+
this.chatHistory.push(compactionNote);
|
|
2777
|
+
// Rebuild this.messages from compacted chatHistory
|
|
2778
|
+
for (const entry of this.chatHistory) {
|
|
2779
|
+
if (entry.type === 'system') {
|
|
2780
|
+
this.messages.push({
|
|
2781
|
+
role: 'system',
|
|
2782
|
+
content: entry.content
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2785
|
+
else if (entry.type === 'user') {
|
|
2786
|
+
this.messages.push({
|
|
2787
|
+
role: 'user',
|
|
2788
|
+
content: entry.content
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
else if (entry.type === 'assistant') {
|
|
2792
|
+
this.messages.push({
|
|
2793
|
+
role: 'assistant',
|
|
2794
|
+
content: entry.content
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
else if (entry.type === 'tool_result') {
|
|
2798
|
+
this.messages.push({
|
|
2799
|
+
role: 'tool',
|
|
2800
|
+
tool_call_id: entry.toolResult.output || '',
|
|
2801
|
+
content: JSON.stringify(entry.toolResult)
|
|
2802
|
+
});
|
|
2494
2803
|
}
|
|
2495
2804
|
}
|
|
2805
|
+
return removedCount;
|
|
2496
2806
|
}
|
|
2497
2807
|
/**
|
|
2498
2808
|
* Get all tool instances and their class names for display purposes
|