@zds-ai/cli 0.1.5 → 0.1.7
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 +5 -1
- package/dist/agent/grok-agent.js +243 -30
- 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 +33 -5
- package/dist/agent/prompt-variables.js +162 -28
- package/dist/agent/prompt-variables.js.map +1 -1
- package/dist/bin/fastcaption.sh +3 -2
- package/dist/bin/generate_image_sd.sh +1 -1
- package/dist/grok/client.d.ts +9 -9
- package/dist/grok/client.js +3 -4
- package/dist/grok/client.js.map +1 -1
- package/dist/grok/tools.d.ts +5 -5
- package/dist/grok/tools.js +12 -12
- 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 +19 -12
- 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 +2 -2
- package/dist/tools/image-tool.js +5 -3
- 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 +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/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 +17 -17
- package/dist/utils/slash-commands.js.map +1 -1
- package/dist/utils/startup-hook.d.ts +3 -3
- package/dist/utils/startup-hook.js +9 -4
- package/dist/utils/startup-hook.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import { EventEmitter } from "events";
|
|
|
5
5
|
export interface ChatEntry {
|
|
6
6
|
type: "user" | "assistant" | "tool_result" | "tool_call" | "system";
|
|
7
7
|
content?: string | ChatCompletionContentPart[];
|
|
8
|
+
originalContent?: string | ChatCompletionContentPart[];
|
|
8
9
|
timestamp: Date;
|
|
9
10
|
tool_calls?: GrokToolCall[];
|
|
10
11
|
toolCall?: GrokToolCall;
|
|
@@ -68,9 +69,11 @@ export declare class GrokAgent extends EventEmitter {
|
|
|
68
69
|
private apiKeyEnvVar;
|
|
69
70
|
private pendingContextEditSession;
|
|
70
71
|
private rephraseState;
|
|
72
|
+
private hookPrefillText;
|
|
71
73
|
constructor(apiKey: string, baseURL?: string, model?: string, maxToolRounds?: number, debugLogFile?: string, startupHookOutput?: string, temperature?: number, maxTokens?: number);
|
|
72
74
|
private startupHookOutput?;
|
|
73
75
|
private systemPrompt;
|
|
76
|
+
private hasRunInstanceHook;
|
|
74
77
|
/**
|
|
75
78
|
* Initialize the agent with dynamic system prompt
|
|
76
79
|
* Must be called after construction
|
|
@@ -131,12 +134,13 @@ export declare class GrokAgent extends EventEmitter {
|
|
|
131
134
|
contextFilePath: string;
|
|
132
135
|
} | null;
|
|
133
136
|
clearPendingContextEditSession(): void;
|
|
134
|
-
setRephraseState(originalAssistantMessageIndex: number, rephraseRequestIndex: number, newResponseIndex: number, messageType: "user" | "system"): void;
|
|
137
|
+
setRephraseState(originalAssistantMessageIndex: number, rephraseRequestIndex: number, newResponseIndex: number, messageType: "user" | "system", prefillText?: string): void;
|
|
135
138
|
getRephraseState(): {
|
|
136
139
|
originalAssistantMessageIndex: number;
|
|
137
140
|
rephraseRequestIndex: number;
|
|
138
141
|
newResponseIndex: number;
|
|
139
142
|
messageType: "user" | "system";
|
|
143
|
+
prefillText?: string;
|
|
140
144
|
} | null;
|
|
141
145
|
clearRephraseState(): void;
|
|
142
146
|
setPersona(persona: string, color?: string): Promise<{
|
package/dist/agent/grok-agent.js
CHANGED
|
@@ -110,6 +110,7 @@ export class GrokAgent extends EventEmitter {
|
|
|
110
110
|
apiKeyEnvVar = "GROK_API_KEY";
|
|
111
111
|
pendingContextEditSession = null;
|
|
112
112
|
rephraseState = null;
|
|
113
|
+
hookPrefillText = null;
|
|
113
114
|
constructor(apiKey, baseURL, model, maxToolRounds, debugLogFile, startupHookOutput, temperature, maxTokens) {
|
|
114
115
|
super();
|
|
115
116
|
const manager = getSettingsManager();
|
|
@@ -161,6 +162,7 @@ export class GrokAgent extends EventEmitter {
|
|
|
161
162
|
}
|
|
162
163
|
startupHookOutput;
|
|
163
164
|
systemPrompt = "Initializing..."; // THE system prompt (always at messages[0])
|
|
165
|
+
hasRunInstanceHook = false;
|
|
164
166
|
/**
|
|
165
167
|
* Initialize the agent with dynamic system prompt
|
|
166
168
|
* Must be called after construction
|
|
@@ -168,35 +170,19 @@ export class GrokAgent extends EventEmitter {
|
|
|
168
170
|
async initialize() {
|
|
169
171
|
// Build system message
|
|
170
172
|
await this.buildSystemMessage();
|
|
171
|
-
// Execute instance hook on every startup (fresh or not)
|
|
172
|
-
const settings = getSettingsManager();
|
|
173
|
-
const instanceHookPath = settings.getInstanceHook();
|
|
174
|
-
if (instanceHookPath) {
|
|
175
|
-
const hookResult = await executeOperationHook(instanceHookPath, "instance", {}, 30000, false, // Instance hook is not mandatory
|
|
176
|
-
this.getCurrentTokenCount(), this.getMaxContextSize());
|
|
177
|
-
if (hookResult.approved && hookResult.commands && hookResult.commands.length > 0) {
|
|
178
|
-
// Apply hook commands (ENV, TOOL_RESULT, MODEL, SYSTEM)
|
|
179
|
-
await this.processHookResult(hookResult);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
173
|
}
|
|
183
174
|
/**
|
|
184
175
|
* Build/rebuild the system message with current tool availability
|
|
185
176
|
* Updates this.systemPrompt which is always used for messages[0]
|
|
186
177
|
*/
|
|
187
178
|
async buildSystemMessage() {
|
|
188
|
-
// Add startup hook output if provided
|
|
189
|
-
const startupHookSection = this.startupHookOutput
|
|
190
|
-
? `${this.startupHookOutput}\n`
|
|
191
|
-
: "";
|
|
192
179
|
// Generate dynamic tool list using introspect tool
|
|
193
180
|
const toolsResult = await this.introspect.introspect("tools");
|
|
194
181
|
const toolsSection = toolsResult.success ? toolsResult.output : "Tools: Unknown";
|
|
182
|
+
// Set APP:TOOLS variable
|
|
183
|
+
Variable.set("APP:TOOLS", toolsSection);
|
|
195
184
|
// Build THE system prompt
|
|
196
|
-
this.systemPrompt =
|
|
197
|
-
${toolsSection}
|
|
198
|
-
|
|
199
|
-
Current working directory: ${process.cwd()}`;
|
|
185
|
+
this.systemPrompt = Variable.renderFull('SYSTEM');
|
|
200
186
|
// Update messages[0] with the system prompt
|
|
201
187
|
this.messages[0] = {
|
|
202
188
|
role: "system",
|
|
@@ -356,16 +342,27 @@ Current working directory: ${process.cwd()}`;
|
|
|
356
342
|
let isSystemRephrase = false;
|
|
357
343
|
let messageToSend = message;
|
|
358
344
|
let messageType = "user";
|
|
345
|
+
let prefillText;
|
|
359
346
|
if (message.startsWith("/system rephrase")) {
|
|
360
347
|
isRephraseCommand = true;
|
|
361
348
|
isSystemRephrase = true;
|
|
362
349
|
messageToSend = message.substring(8).trim(); // Strip "/system " (8 chars including space)
|
|
363
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
|
+
}
|
|
364
356
|
}
|
|
365
357
|
else if (message.startsWith("/rephrase")) {
|
|
366
358
|
isRephraseCommand = true;
|
|
367
359
|
messageToSend = message; // Keep full text including "/rephrase"
|
|
368
360
|
messageType = "user";
|
|
361
|
+
// Extract prefill text after "/rephrase "
|
|
362
|
+
const prefillMatch = message.match(/^\/rephrase\s+(.+)$/);
|
|
363
|
+
if (prefillMatch) {
|
|
364
|
+
prefillText = prefillMatch[1];
|
|
365
|
+
}
|
|
369
366
|
}
|
|
370
367
|
// If this is a rephrase command, find the last assistant message
|
|
371
368
|
if (isRephraseCommand) {
|
|
@@ -382,7 +379,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
382
379
|
}
|
|
383
380
|
// Store rephrase state (will be updated with newResponseIndex after response)
|
|
384
381
|
// For now, just mark that we're in rephrase mode
|
|
385
|
-
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType);
|
|
382
|
+
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType, prefillText);
|
|
386
383
|
}
|
|
387
384
|
// Before adding the new user message, check if there are incomplete tool calls
|
|
388
385
|
// from a previous interrupted turn. This prevents malformed message sequences
|
|
@@ -416,12 +413,62 @@ Current working directory: ${process.cwd()}`;
|
|
|
416
413
|
}
|
|
417
414
|
// Clear one-shot variables
|
|
418
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
|
+
}
|
|
419
447
|
// Parse images once if present (for both text extraction and later assembly)
|
|
420
448
|
const parsed = hasImageReferences(messageToSend)
|
|
421
449
|
? parseImagesFromMessage(messageToSend)
|
|
422
450
|
: { text: messageToSend, images: [] };
|
|
423
451
|
// Set USER:PROMPT variable (text only, images stripped)
|
|
424
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
|
+
}
|
|
425
472
|
// Assemble final message from variables
|
|
426
473
|
const assembledMessage = Variable.renderFull("USER");
|
|
427
474
|
// Add user/system message to conversation
|
|
@@ -438,6 +485,9 @@ Current working directory: ${process.cwd()}`;
|
|
|
438
485
|
const userEntry = {
|
|
439
486
|
type: messageType,
|
|
440
487
|
content: messageContent,
|
|
488
|
+
originalContent: messageType === "user" ? (parsed.images.length > 0 && supportsVision
|
|
489
|
+
? [{ type: "text", text: parsed.text }, ...parsed.images]
|
|
490
|
+
: parsed.text) : undefined,
|
|
441
491
|
timestamp: new Date(),
|
|
442
492
|
};
|
|
443
493
|
this.chatHistory.push(userEntry);
|
|
@@ -455,6 +505,20 @@ Current working directory: ${process.cwd()}`;
|
|
|
455
505
|
let toolRounds = 0;
|
|
456
506
|
let consecutiveNonToolResponses = 0;
|
|
457
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
|
+
}
|
|
458
522
|
// Always fetch tools fresh - getAllGrokTools() handles lazy refresh internally
|
|
459
523
|
const supportsTools = this.grokClient.getSupportsTools();
|
|
460
524
|
let currentResponse = await this.grokClient.chat(this.messages, supportsTools ? await getAllGrokTools() : [], undefined, this.isGrokModel() && this.shouldUseSearchFor(message)
|
|
@@ -628,7 +692,16 @@ Current working directory: ${process.cwd()}`;
|
|
|
628
692
|
}
|
|
629
693
|
else {
|
|
630
694
|
// No tool calls in this response - only add it if there's actual content
|
|
631
|
-
|
|
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
|
+
}
|
|
632
705
|
if (trimmedContent) {
|
|
633
706
|
const responseEntry = {
|
|
634
707
|
type: "assistant",
|
|
@@ -644,7 +717,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
644
717
|
// Update rephrase state with the new response index
|
|
645
718
|
if (this.rephraseState && this.rephraseState.newResponseIndex === -1) {
|
|
646
719
|
const newResponseIndex = this.chatHistory.length - 1;
|
|
647
|
-
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);
|
|
648
721
|
}
|
|
649
722
|
}
|
|
650
723
|
// TODO: HACK - This is a temporary fix to prevent duplicate responses.
|
|
@@ -822,16 +895,27 @@ Current working directory: ${process.cwd()}`;
|
|
|
822
895
|
let isSystemRephrase = false;
|
|
823
896
|
let messageToSend = message;
|
|
824
897
|
let messageType = "user";
|
|
898
|
+
let prefillText;
|
|
825
899
|
if (message.startsWith("/system rephrase")) {
|
|
826
900
|
isRephraseCommand = true;
|
|
827
901
|
isSystemRephrase = true;
|
|
828
902
|
messageToSend = message.substring(8).trim(); // Strip "/system " (8 chars including space)
|
|
829
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
|
+
}
|
|
830
909
|
}
|
|
831
910
|
else if (message.startsWith("/rephrase")) {
|
|
832
911
|
isRephraseCommand = true;
|
|
833
912
|
messageToSend = message; // Keep full text including "/rephrase"
|
|
834
913
|
messageType = "user";
|
|
914
|
+
// Extract prefill text after "/rephrase "
|
|
915
|
+
const prefillMatch = message.match(/^\/rephrase\s+(.+)$/);
|
|
916
|
+
if (prefillMatch) {
|
|
917
|
+
prefillText = prefillMatch[1];
|
|
918
|
+
}
|
|
835
919
|
}
|
|
836
920
|
// If this is a rephrase command, find the last assistant message
|
|
837
921
|
if (isRephraseCommand) {
|
|
@@ -848,7 +932,7 @@ Current working directory: ${process.cwd()}`;
|
|
|
848
932
|
}
|
|
849
933
|
// Store rephrase state (will be updated with newResponseIndex after response)
|
|
850
934
|
// For now, just mark that we're in rephrase mode
|
|
851
|
-
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType);
|
|
935
|
+
this.setRephraseState(lastAssistantIndex, this.chatHistory.length, -1, messageType, prefillText);
|
|
852
936
|
}
|
|
853
937
|
// Before adding the new user message, check if there are incomplete tool calls
|
|
854
938
|
// from a previous interrupted turn. This prevents malformed message sequences
|
|
@@ -882,12 +966,62 @@ Current working directory: ${process.cwd()}`;
|
|
|
882
966
|
}
|
|
883
967
|
// Clear one-shot variables
|
|
884
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
|
+
}
|
|
885
1000
|
// Parse images once if present (for both text extraction and later assembly)
|
|
886
1001
|
const parsed = hasImageReferences(messageToSend)
|
|
887
1002
|
? parseImagesFromMessage(messageToSend)
|
|
888
1003
|
: { text: messageToSend, images: [] };
|
|
889
1004
|
// Set USER:PROMPT variable (text only, images stripped)
|
|
890
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
|
+
}
|
|
891
1025
|
// Assemble final message from variables
|
|
892
1026
|
const assembledMessage = Variable.renderFull("USER");
|
|
893
1027
|
// Add user/system message to both API conversation and chat history
|
|
@@ -904,6 +1038,9 @@ Current working directory: ${process.cwd()}`;
|
|
|
904
1038
|
const userEntry = {
|
|
905
1039
|
type: messageType,
|
|
906
1040
|
content: messageContent,
|
|
1041
|
+
originalContent: messageType === "user" ? (parsed.images.length > 0 && supportsVision
|
|
1042
|
+
? [{ type: "text", text: parsed.text }, ...parsed.images]
|
|
1043
|
+
: parsed.text) : undefined,
|
|
907
1044
|
timestamp: new Date(),
|
|
908
1045
|
};
|
|
909
1046
|
this.chatHistory.push(userEntry);
|
|
@@ -921,6 +1058,20 @@ Current working directory: ${process.cwd()}`;
|
|
|
921
1058
|
type: "user_message",
|
|
922
1059
|
userEntry: userEntry,
|
|
923
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
|
+
}
|
|
924
1075
|
// Calculate input tokens
|
|
925
1076
|
let inputTokens = this.tokenCounter.countMessageTokens(this.messages);
|
|
926
1077
|
yield {
|
|
@@ -959,6 +1110,23 @@ Current working directory: ${process.cwd()}`;
|
|
|
959
1110
|
let tool_calls_yielded = false;
|
|
960
1111
|
let streamFinished = false;
|
|
961
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
|
+
}
|
|
962
1130
|
try {
|
|
963
1131
|
for await (const chunk of stream) {
|
|
964
1132
|
// Check for cancellation in the streaming loop
|
|
@@ -1721,8 +1889,8 @@ Current working directory: ${process.cwd()}`;
|
|
|
1721
1889
|
clearPendingContextEditSession() {
|
|
1722
1890
|
this.pendingContextEditSession = null;
|
|
1723
1891
|
}
|
|
1724
|
-
setRephraseState(originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType) {
|
|
1725
|
-
this.rephraseState = { originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType };
|
|
1892
|
+
setRephraseState(originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType, prefillText) {
|
|
1893
|
+
this.rephraseState = { originalAssistantMessageIndex, rephraseRequestIndex, newResponseIndex, messageType, prefillText };
|
|
1726
1894
|
}
|
|
1727
1895
|
getRephraseState() {
|
|
1728
1896
|
return this.rephraseState;
|
|
@@ -2466,10 +2634,15 @@ Current working directory: ${process.cwd()}`;
|
|
|
2466
2634
|
// Restore cwd early (hooks may need correct working directory)
|
|
2467
2635
|
if (state.cwd) {
|
|
2468
2636
|
try {
|
|
2469
|
-
|
|
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)
|
|
2470
2643
|
}
|
|
2471
2644
|
catch (error) {
|
|
2472
|
-
|
|
2645
|
+
// Silently skip on any error - working directory restoration is non-critical
|
|
2473
2646
|
}
|
|
2474
2647
|
}
|
|
2475
2648
|
// Restore backend/baseUrl/apiKeyEnvVar/model if present (creates initial client)
|
|
@@ -2514,28 +2687,68 @@ Current working directory: ${process.cwd()}`;
|
|
|
2514
2687
|
// Restore persona (hook may change backend/model and sets env vars)
|
|
2515
2688
|
if (state.persona) {
|
|
2516
2689
|
try {
|
|
2517
|
-
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
|
+
}
|
|
2518
2699
|
}
|
|
2519
2700
|
catch (error) {
|
|
2520
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;
|
|
2521
2706
|
}
|
|
2522
2707
|
}
|
|
2523
2708
|
// Restore mood (hook sets env vars)
|
|
2524
2709
|
if (state.mood) {
|
|
2525
2710
|
try {
|
|
2526
|
-
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
|
+
}
|
|
2527
2720
|
}
|
|
2528
2721
|
catch (error) {
|
|
2529
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;
|
|
2530
2727
|
}
|
|
2531
2728
|
}
|
|
2532
2729
|
// Restore active task (hook sets env vars)
|
|
2533
2730
|
if (state.activeTask) {
|
|
2534
2731
|
try {
|
|
2535
|
-
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
|
+
}
|
|
2536
2743
|
}
|
|
2537
2744
|
catch (error) {
|
|
2538
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;
|
|
2539
2752
|
}
|
|
2540
2753
|
}
|
|
2541
2754
|
}
|