@zds-ai/cli 0.1.5 → 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.
Files changed (75) hide show
  1. package/dist/agent/grok-agent.d.ts +5 -1
  2. package/dist/agent/grok-agent.js +243 -30
  3. package/dist/agent/grok-agent.js.map +1 -1
  4. package/dist/agent/llm-agent.d.ts +276 -0
  5. package/dist/agent/llm-agent.js +2839 -0
  6. package/dist/agent/llm-agent.js.map +1 -0
  7. package/dist/agent/prompt-variables.d.ts +33 -5
  8. package/dist/agent/prompt-variables.js +162 -28
  9. package/dist/agent/prompt-variables.js.map +1 -1
  10. package/dist/bin/fastcaption.sh +3 -2
  11. package/dist/bin/generate_image_sd.sh +1 -1
  12. package/dist/grok/client.d.ts +9 -9
  13. package/dist/grok/client.js +3 -4
  14. package/dist/grok/client.js.map +1 -1
  15. package/dist/grok/tools.d.ts +5 -5
  16. package/dist/grok/tools.js +12 -12
  17. package/dist/grok/tools.js.map +1 -1
  18. package/dist/hooks/use-input-handler.d.ts +3 -3
  19. package/dist/hooks/use-input-handler.js +19 -12
  20. package/dist/hooks/use-input-handler.js.map +1 -1
  21. package/dist/index.js +17 -10
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp/client.js +10 -2
  24. package/dist/mcp/client.js.map +1 -1
  25. package/dist/tools/character-tool.js +1 -1
  26. package/dist/tools/character-tool.js.map +1 -1
  27. package/dist/tools/clear-cache-tool.js +1 -1
  28. package/dist/tools/clear-cache-tool.js.map +1 -1
  29. package/dist/tools/file-conversion-tool.js +1 -1
  30. package/dist/tools/file-conversion-tool.js.map +1 -1
  31. package/dist/tools/image-tool.d.ts +2 -2
  32. package/dist/tools/image-tool.js +5 -3
  33. package/dist/tools/image-tool.js.map +1 -1
  34. package/dist/tools/internet-tool.js +1 -1
  35. package/dist/tools/internet-tool.js.map +1 -1
  36. package/dist/tools/introspect-tool.js +7 -12
  37. package/dist/tools/introspect-tool.js.map +1 -1
  38. package/dist/tools/task-tool.js +1 -1
  39. package/dist/tools/task-tool.js.map +1 -1
  40. package/dist/tools/text-editor.js +1 -1
  41. package/dist/tools/text-editor.js.map +1 -1
  42. package/dist/tools/zsh.js +1 -1
  43. package/dist/tools/zsh.js.map +1 -1
  44. package/dist/ui/components/active-task-status.d.ts +2 -2
  45. package/dist/ui/components/api-key-input.d.ts +2 -2
  46. package/dist/ui/components/api-key-input.js +2 -2
  47. package/dist/ui/components/api-key-input.js.map +1 -1
  48. package/dist/ui/components/backend-status.d.ts +2 -2
  49. package/dist/ui/components/chat-history.d.ts +1 -1
  50. package/dist/ui/components/chat-history.js +1 -1
  51. package/dist/ui/components/chat-history.js.map +1 -1
  52. package/dist/ui/components/chat-interface.d.ts +2 -2
  53. package/dist/ui/components/chat-interface.js +4 -0
  54. package/dist/ui/components/chat-interface.js.map +1 -1
  55. package/dist/ui/components/context-status.d.ts +2 -2
  56. package/dist/ui/components/model-selection.js +1 -1
  57. package/dist/ui/components/model-selection.js.map +1 -1
  58. package/dist/ui/components/mood-status.d.ts +2 -2
  59. package/dist/ui/components/persona-status.d.ts +2 -2
  60. package/dist/utils/chat-history-manager.d.ts +1 -1
  61. package/dist/utils/hook-executor.d.ts +8 -2
  62. package/dist/utils/hook-executor.js +138 -13
  63. package/dist/utils/hook-executor.js.map +1 -1
  64. package/dist/utils/rephrase-handler.d.ts +2 -2
  65. package/dist/utils/rephrase-handler.js.map +1 -1
  66. package/dist/utils/settings-manager.d.ts +5 -0
  67. package/dist/utils/settings-manager.js +6 -0
  68. package/dist/utils/settings-manager.js.map +1 -1
  69. package/dist/utils/slash-commands.d.ts +7 -3
  70. package/dist/utils/slash-commands.js +17 -17
  71. package/dist/utils/slash-commands.js.map +1 -1
  72. package/dist/utils/startup-hook.d.ts +3 -3
  73. package/dist/utils/startup-hook.js +9 -4
  74. package/dist/utils/startup-hook.js.map +1 -1
  75. 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<{
@@ -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 = `${startupHookSection}
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
- const trimmedContent = assistantMessage.content?.trim();
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
- process.chdir(state.cwd);
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
- console.warn(`Failed to restore working directory to ${state.cwd}:`, error);
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
  }