orquesta-cli 0.2.45 → 0.2.47

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 (40) hide show
  1. package/dist/agents/planner/index.js +2 -1
  2. package/dist/cli.js +17 -16
  3. package/dist/constants.d.ts +1 -1
  4. package/dist/constants.js +1 -1
  5. package/dist/core/commands/clear.d.ts +3 -0
  6. package/dist/core/commands/clear.js +22 -0
  7. package/dist/core/commands/compact.d.ts +3 -0
  8. package/dist/core/commands/compact.js +45 -0
  9. package/dist/core/commands/help.d.ts +3 -0
  10. package/dist/core/commands/help.js +50 -0
  11. package/dist/core/commands/index.d.ts +3 -0
  12. package/dist/core/commands/index.js +11 -0
  13. package/dist/core/commands/memory.d.ts +3 -0
  14. package/dist/core/commands/memory.js +40 -0
  15. package/dist/core/commands/registry.d.ts +11 -0
  16. package/dist/core/commands/registry.js +25 -0
  17. package/dist/core/commands/types.d.ts +10 -0
  18. package/dist/core/commands/types.js +2 -0
  19. package/dist/core/event-bus.d.ts +20 -0
  20. package/dist/core/event-bus.js +35 -0
  21. package/dist/core/git-context.d.ts +11 -0
  22. package/dist/core/git-context.js +62 -0
  23. package/dist/core/ignore-filter.d.ts +4 -0
  24. package/dist/core/ignore-filter.js +50 -0
  25. package/dist/core/llm/llm-client.d.ts +1 -0
  26. package/dist/core/llm/llm-client.js +118 -40
  27. package/dist/core/onboarding.d.ts +3 -0
  28. package/dist/core/onboarding.js +48 -0
  29. package/dist/core/slash-command-handler.js +8 -135
  30. package/dist/eval/eval-runner.d.ts +1 -0
  31. package/dist/eval/eval-runner.js +22 -0
  32. package/dist/orchestration/plan-executor.js +77 -71
  33. package/dist/prompts/shared/tool-usage.js +0 -1
  34. package/dist/prompts/system/plan-execute.js +50 -57
  35. package/dist/tools/llm/simple/file-tools.js +12 -1
  36. package/dist/tools/llm/simple/final-response-tool.js +7 -11
  37. package/dist/tools/registry.js +63 -10
  38. package/dist/ui/components/PlanExecuteApp.d.ts +1 -0
  39. package/dist/ui/components/PlanExecuteApp.js +59 -22
  40. package/package.json +8 -4
@@ -108,6 +108,7 @@ export class LLMClient {
108
108
  modelName;
109
109
  currentAbortController = null;
110
110
  isInterrupted = false;
111
+ onStreamingContent = null;
111
112
  static DEFAULT_MAX_RETRIES = 3;
112
113
  constructor() {
113
114
  const endpoint = configManager.getCurrentEndpoint();
@@ -183,11 +184,11 @@ export class LLMClient {
183
184
  const modelId = options.model || this.model;
184
185
  const processedMessages = options.messages ?
185
186
  this.preprocessMessages(options.messages, modelId) : [];
186
- logger.vars({ name: 'modelId', value: modelId }, { name: 'originalMessages', value: options.messages?.length || 0 }, { name: 'processedMessages', value: processedMessages.length }, { name: 'temperature', value: options.temperature ?? 0.7 });
187
+ logger.vars({ name: 'modelId', value: modelId }, { name: 'originalMessages', value: options.messages?.length || 0 }, { name: 'processedMessages', value: processedMessages.length }, { name: 'temperature', value: options.temperature ?? 0 });
187
188
  const requestBody = {
188
189
  model: modelId,
189
190
  messages: processedMessages,
190
- temperature: options.temperature ?? 0.7,
191
+ temperature: options.temperature ?? 0,
191
192
  max_tokens: options.max_tokens,
192
193
  stream: false,
193
194
  ...(options.tools && {
@@ -210,13 +211,112 @@ export class LLMClient {
210
211
  }
211
212
  logger.startTimer('llm-api-call');
212
213
  this.currentAbortController = new AbortController();
213
- const response = await this.axiosInstance.post(url, requestBody, {
214
- signal: this.currentAbortController.signal,
215
- headers: buildPerRequestHeaders(),
216
- });
217
- this.currentAbortController = null;
214
+ let response;
215
+ if (this.onStreamingContent) {
216
+ const streamRequestBody = { ...requestBody, stream: true };
217
+ const streamResp = await this.axiosInstance.post(url, streamRequestBody, {
218
+ responseType: 'stream',
219
+ signal: this.currentAbortController.signal,
220
+ headers: buildPerRequestHeaders(),
221
+ });
222
+ captureBatutaHeaders(streamResp.headers);
223
+ const stream = streamResp.data;
224
+ let buffer = '';
225
+ let contentAccum = '';
226
+ let reasoningAccum = '';
227
+ let role = 'assistant';
228
+ let finishReason = null;
229
+ const toolCallsMap = new Map();
230
+ let responseId = '';
231
+ let responseModel = '';
232
+ for await (const chunk of stream) {
233
+ if (this.isInterrupted) {
234
+ throw new Error('INTERRUPTED');
235
+ }
236
+ buffer += chunk.toString();
237
+ const lines = buffer.split('\n');
238
+ buffer = lines.pop() || '';
239
+ for (const line of lines) {
240
+ const trimmed = line.trim();
241
+ if (!trimmed || trimmed === 'data: [DONE]')
242
+ continue;
243
+ if (!trimmed.startsWith('data: '))
244
+ continue;
245
+ try {
246
+ const data = JSON.parse(trimmed.slice(6));
247
+ if (data.id)
248
+ responseId = data.id;
249
+ if (data.model)
250
+ responseModel = data.model;
251
+ const choice = data.choices?.[0];
252
+ if (!choice)
253
+ continue;
254
+ if (choice.finish_reason)
255
+ finishReason = choice.finish_reason;
256
+ const delta = choice.delta;
257
+ if (!delta)
258
+ continue;
259
+ if (delta.role)
260
+ role = delta.role;
261
+ if (delta.content) {
262
+ contentAccum += delta.content;
263
+ this.onStreamingContent(delta.content);
264
+ }
265
+ if (delta.reasoning) {
266
+ reasoningAccum += delta.reasoning;
267
+ }
268
+ if (delta.tool_calls) {
269
+ for (const tc of delta.tool_calls) {
270
+ const idx = tc.index ?? 0;
271
+ if (!toolCallsMap.has(idx)) {
272
+ toolCallsMap.set(idx, { id: tc.id || '', type: 'function', function: { name: '', arguments: '' } });
273
+ }
274
+ const existing = toolCallsMap.get(idx);
275
+ if (tc.id)
276
+ existing.id = tc.id;
277
+ if (tc.function?.name)
278
+ existing.function.name += tc.function.name;
279
+ if (tc.function?.arguments)
280
+ existing.function.arguments += tc.function.arguments;
281
+ }
282
+ }
283
+ }
284
+ catch { }
285
+ }
286
+ }
287
+ this.currentAbortController = null;
288
+ const toolCalls = Array.from(toolCallsMap.values())
289
+ .filter(tc => tc.id && tc.function.name)
290
+ .map(tc => ({ id: tc.id, type: 'function', function: { name: tc.function.name, arguments: tc.function.arguments } }));
291
+ const reassembledMessage = {
292
+ role: role,
293
+ content: contentAccum,
294
+ ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
295
+ ...(reasoningAccum ? { reasoning: reasoningAccum } : {}),
296
+ };
297
+ response = {
298
+ data: {
299
+ id: responseId,
300
+ object: 'chat.completion',
301
+ created: Math.floor(Date.now() / 1000),
302
+ model: responseModel || modelId,
303
+ choices: [{ index: 0, message: reassembledMessage, finish_reason: finishReason }],
304
+ },
305
+ status: streamResp.status,
306
+ statusText: streamResp.statusText,
307
+ headers: streamResp.headers,
308
+ };
309
+ }
310
+ else {
311
+ const httpResp = await this.axiosInstance.post(url, requestBody, {
312
+ signal: this.currentAbortController.signal,
313
+ headers: buildPerRequestHeaders(),
314
+ });
315
+ this.currentAbortController = null;
316
+ response = { data: httpResp.data, status: httpResp.status, statusText: httpResp.statusText, headers: httpResp.headers };
317
+ captureBatutaHeaders(response.headers);
318
+ }
218
319
  const elapsed = logger.endTimer('llm-api-call');
219
- captureBatutaHeaders(response.headers);
220
320
  logger.flow('API response received');
221
321
  if (!response.data.choices || !Array.isArray(response.data.choices)) {
222
322
  logger.error('Invalid response structure - missing choices array', response.data);
@@ -354,7 +454,7 @@ export class LLMClient {
354
454
  const requestBody = {
355
455
  model: modelId,
356
456
  messages: processedMessages,
357
- temperature: options.temperature ?? 0.7,
457
+ temperature: options.temperature ?? 0,
358
458
  max_tokens: options.max_tokens,
359
459
  stream: true,
360
460
  ...(options.tools && {
@@ -495,9 +595,7 @@ export class LLMClient {
495
595
  const toolCallHistory = [];
496
596
  let iterations = 0;
497
597
  let contextLengthRecoveryAttempted = false;
498
- let noToolCallRetries = 0;
499
598
  let finalResponseFailures = 0;
500
- const MAX_NO_TOOL_CALL_RETRIES = 3;
501
599
  const MAX_FINAL_RESPONSE_FAILURES = 3;
502
600
  const recentToolSignatures = [];
503
601
  const recentNormalizedSignatures = [];
@@ -524,7 +622,7 @@ export class LLMClient {
524
622
  response = await this.chatCompletion({
525
623
  messages: workingMessages,
526
624
  tools,
527
- tool_choice: 'required',
625
+ tool_choice: 'auto',
528
626
  ...(roleModel ? { model: roleModel } : {}),
529
627
  });
530
628
  }
@@ -733,34 +831,14 @@ export class LLMClient {
733
831
  continue;
734
832
  }
735
833
  else {
736
- noToolCallRetries++;
737
- logger.flow(`No tool call - enforcing tool usage (attempt ${noToolCallRetries}/${MAX_NO_TOOL_CALL_RETRIES})`);
738
- if (noToolCallRetries > MAX_NO_TOOL_CALL_RETRIES) {
739
- logger.warn('Max no-tool-call retries exceeded - returning content as final response');
740
- const fallbackContent = assistantMessage.content || 'Task completed.';
741
- const { emitAssistantResponse } = await import('../../tools/llm/simple/file-tools.js');
742
- emitAssistantResponse(fallbackContent);
743
- return {
744
- message: { role: 'assistant', content: fallbackContent },
745
- toolCalls: toolCallHistory,
746
- allMessages: workingMessages,
747
- };
748
- }
749
- const hasMalformedToolCall = assistantMessage.content &&
750
- (/<tool_call>/i.test(assistantMessage.content) ||
751
- /<arg_key>/i.test(assistantMessage.content) ||
752
- /<arg_value>/i.test(assistantMessage.content) ||
753
- /<\/tool_call>/i.test(assistantMessage.content) ||
754
- /bash<arg_key>/i.test(assistantMessage.content));
755
- const retryMessage = hasMalformedToolCall
756
- ? 'Your previous response contained a malformed tool call (XML tags in content). You MUST use the proper tool_calls API format. Use final_response tool to deliver your message to the user.'
757
- : 'You must use tools for all actions. Use final_response tool to deliver your final message to the user after completing all tasks.';
758
- workingMessages.push({
759
- role: 'user',
760
- content: retryMessage,
761
- });
762
- logger.debug('Enforcing tool call - added retry message');
763
- continue;
834
+ const finalContent = assistantMessage.content || 'Task completed.';
835
+ const { emitAssistantResponse } = await import('../../tools/llm/simple/file-tools.js');
836
+ emitAssistantResponse(finalContent);
837
+ return {
838
+ message: { role: 'assistant', content: finalContent },
839
+ toolCalls: toolCallHistory,
840
+ allMessages: workingMessages,
841
+ };
764
842
  }
765
843
  }
766
844
  }
@@ -0,0 +1,3 @@
1
+ export declare function shouldShowOnboarding(): boolean;
2
+ export declare function runOnboarding(): Promise<boolean>;
3
+ //# sourceMappingURL=onboarding.d.ts.map
@@ -0,0 +1,48 @@
1
+ import chalk from 'chalk';
2
+ import { configManager } from './config/config-manager.js';
3
+ import { scanProviders, toEndpointConfig } from './config/auto-detect.js';
4
+ import { CONFIG_FILE_PATH } from '../constants.js';
5
+ import * as fs from 'fs';
6
+ export function shouldShowOnboarding() {
7
+ if (!fs.existsSync(CONFIG_FILE_PATH))
8
+ return true;
9
+ return !configManager.hasEndpoints();
10
+ }
11
+ export async function runOnboarding() {
12
+ console.log();
13
+ console.log(chalk.cyan(' ╔══════════════════════════════════════════╗'));
14
+ console.log(chalk.cyan(' ║') + chalk.bold(' Welcome to Orquesta CLI! 🎵 ') + chalk.cyan('║'));
15
+ console.log(chalk.cyan(' ╚══════════════════════════════════════════╝'));
16
+ console.log();
17
+ console.log(chalk.dim(' Scanning for LLM providers...'));
18
+ console.log();
19
+ const result = await scanProviders();
20
+ if (result.detected.length === 0) {
21
+ console.log(chalk.yellow(' No LLM providers detected.'));
22
+ console.log();
23
+ console.log(chalk.dim(' To get started, do one of the following:'));
24
+ console.log(chalk.dim(' • Set an env var: OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.'));
25
+ console.log(chalk.dim(' • Start a local provider (Ollama on port 11434)'));
26
+ console.log(chalk.dim(' • Run: orquesta --add-provider <provider-id>'));
27
+ console.log();
28
+ return false;
29
+ }
30
+ let addedCount = 0;
31
+ for (const detected of result.detected) {
32
+ const endpoint = toEndpointConfig(detected);
33
+ await configManager.addEndpoint(endpoint);
34
+ addedCount++;
35
+ if (addedCount === 1 && endpoint.models.length > 0) {
36
+ await configManager.setCurrentEndpoint(endpoint.id);
37
+ await configManager.setCurrentModel(endpoint.models[0].id);
38
+ }
39
+ }
40
+ console.log(chalk.green(` ✓ Auto-configured ${addedCount} provider(s):`));
41
+ for (const d of result.detected) {
42
+ const modelCount = d.discoveredModels.length;
43
+ console.log(chalk.white(` • ${d.provider.name}`) + chalk.dim(` (${modelCount} model${modelCount !== 1 ? 's' : ''})`));
44
+ }
45
+ console.log();
46
+ return true;
47
+ }
48
+ //# sourceMappingURL=onboarding.js.map
@@ -1,3 +1,4 @@
1
+ import { commandRegistry } from './commands/index.js';
1
2
  import { sessionManager } from './session/session-manager.js';
2
3
  import { usageTracker } from './usage-tracker.js';
3
4
  import { logger } from '../utils/logger.js';
@@ -6,72 +7,24 @@ import { readHookConfig, writeHookFiles, disableHooks } from '../orquesta/hook-i
6
7
  import { checkForCliUpdate, runCliUpdate, setSkippedVersion } from '../utils/update-checker.js';
7
8
  import { createRequire } from 'module';
8
9
  import { configManager } from './config/config-manager.js';
9
- import { getForcedTier, setForcedTier, resetBatutaSession } from './routing-state.js';
10
+ import { getForcedTier, setForcedTier } from './routing-state.js';
10
11
  import { auditLog } from '../orchestration/audit-log.js';
11
12
  import { remotePhone } from '../orquesta/remote-phone.js';
12
13
  export async function executeSlashCommand(command, context) {
13
14
  const trimmedCommand = command.trim();
14
15
  logger.enter('executeSlashCommand', { command: trimmedCommand });
16
+ const commandName = trimmedCommand.split(/\s/)[0];
17
+ const registryResult = await commandRegistry.execute(commandName, context, trimmedCommand);
18
+ if (registryResult) {
19
+ logger.exit('executeSlashCommand', { handled: true, command: commandName, source: 'registry' });
20
+ return registryResult;
21
+ }
15
22
  if (trimmedCommand === '/exit' || trimmedCommand === '/quit') {
16
23
  logger.flow('Exit command received');
17
24
  context.exit();
18
25
  logger.exit('executeSlashCommand', { handled: true, command: 'exit' });
19
26
  return { handled: true, shouldContinue: false };
20
27
  }
21
- if (trimmedCommand === '/clear') {
22
- logger.flow('Clear command - resetting messages and todos');
23
- context.setMessages([]);
24
- context.setTodos([]);
25
- resetBatutaSession();
26
- logger.exit('executeSlashCommand', { handled: true, command: 'clear' });
27
- return {
28
- handled: true,
29
- shouldContinue: false,
30
- updatedContext: {
31
- messages: [],
32
- todos: [],
33
- },
34
- };
35
- }
36
- if (trimmedCommand === '/compact') {
37
- logger.flow('Compact command received');
38
- if (context.onCompact) {
39
- logger.flow('Executing compact callback');
40
- const result = await context.onCompact();
41
- logger.vars({ name: 'compactSuccess', value: result.success }, { name: 'originalCount', value: result.originalMessageCount }, { name: 'newCount', value: result.newMessageCount });
42
- const compactMessage = result.success
43
- ? `✅ Conversation compacted successfully. (${result.originalMessageCount} → ${result.newMessageCount} messages)`
44
- : `❌ Compact failed: ${result.error}`;
45
- const baseMessages = (result.success && result.compactedMessages)
46
- ? result.compactedMessages
47
- : context.messages;
48
- const updatedMessages = [
49
- ...baseMessages,
50
- { role: 'assistant', content: compactMessage },
51
- ];
52
- context.setMessages(updatedMessages);
53
- return {
54
- handled: true,
55
- shouldContinue: false,
56
- updatedContext: {
57
- messages: updatedMessages,
58
- },
59
- };
60
- }
61
- const fallbackMessage = '/compact is only available in interactive mode.';
62
- const updatedMessages = [
63
- ...context.messages,
64
- { role: 'assistant', content: fallbackMessage },
65
- ];
66
- context.setMessages(updatedMessages);
67
- return {
68
- handled: true,
69
- shouldContinue: false,
70
- updatedContext: {
71
- messages: updatedMessages,
72
- },
73
- };
74
- }
75
28
  if (trimmedCommand === '/settings') {
76
29
  if (context.onShowSettings) {
77
30
  context.onShowSettings();
@@ -524,41 +477,6 @@ ${executorLines}
524
477
  context.setMessages(updatedMessages);
525
478
  return { handled: true, shouldContinue: false, updatedContext: { messages: updatedMessages } };
526
479
  }
527
- if (trimmedCommand.startsWith('/memory')) {
528
- const sub = trimmedCommand.slice(7).trim();
529
- const { addMemory, removeMemory, clearMemory, listMemory } = await import('./memory.js');
530
- const reply = (content) => {
531
- const updatedMessages = [...context.messages, { role: 'assistant', content }];
532
- context.setMessages(updatedMessages);
533
- return { handled: true, shouldContinue: false, updatedContext: { messages: updatedMessages } };
534
- };
535
- if (sub.startsWith('add ')) {
536
- const note = sub.slice(4).trim();
537
- if (!note)
538
- return reply('Usage: /memory add <note>');
539
- addMemory(note);
540
- return reply(`✓ Saved to memory: "${note}"`);
541
- }
542
- if (sub === 'list' || sub === '') {
543
- const entries = listMemory();
544
- if (entries.length === 0)
545
- return reply('Memory is empty. Use `/memory add <note>` to save preferences.');
546
- const list = entries.map((e, i) => ` ${i + 1}. ${e}`).join('\n');
547
- return reply(`📝 User memory (${entries.length} entries):\n${list}\n\nCommands: /memory add <note> | remove <n> | clear`);
548
- }
549
- if (sub.startsWith('remove ')) {
550
- const idx = parseInt(sub.slice(7).trim(), 10);
551
- if (isNaN(idx))
552
- return reply('Usage: /memory remove <number>');
553
- const ok = removeMemory(idx);
554
- return reply(ok ? `✓ Removed entry #${idx}` : `Entry #${idx} not found`);
555
- }
556
- if (sub === 'clear') {
557
- clearMemory();
558
- return reply('✓ Memory cleared');
559
- }
560
- return reply('Usage: /memory add <note> | list | remove <n> | clear');
561
- }
562
480
  if (trimmedCommand === '/update') {
563
481
  logger.flow('Update command received');
564
482
  const reply = (content) => {
@@ -661,51 +579,6 @@ ${executorLines}
661
579
  return reply(`❌ Could not open the remote phone channel: ${e.message}`);
662
580
  }
663
581
  }
664
- if (trimmedCommand === '/help') {
665
- const helpMessage = `
666
- Available commands:
667
- /exit, /quit - Exit the application
668
- /clear - Clear conversation and TODOs
669
- /compact - Compact conversation to free up context
670
- /memory - Persistent memory: /memory add <note> | list | remove <n> | clear
671
- /settings - Open settings menu
672
- /model - Switch between LLM models
673
- /project - Switch between Orquesta projects
674
- /tool - Enable/disable optional tools (Browser, Background)
675
- /load - Load a saved session
676
- /usage - Show token usage statistics
677
- /cost - Estimated USD spend this process (by model)
678
- /route - Pin Batuta Auto tier (fast/balanced/premium/auto)
679
- /sync - Bidirectional sync with Orquesta dashboard (pull & push LLM configs)
680
- /login - Sign in to Orquesta via browser (opens getorquesta.com)
681
- /logout - Sign out of Orquesta (clears token, keeps local LLM configs)
682
- /whoami - Show current Orquesta connection
683
- /hook - Claude Code hook here: /hook status | enable | disable
684
- /remote-phone - Drive this session from your phone: on | off | status
685
- /update - Update orquesta-cli to the latest version
686
-
687
- Keyboard shortcuts:
688
- Ctrl+C - Exit
689
- Ctrl+T - Toggle TODO details
690
- ESC - Interrupt current execution
691
- @ - File browser
692
- / - Command autocomplete
693
-
694
- Note: All conversations are automatically saved.
695
- `;
696
- const updatedMessages = [
697
- ...context.messages,
698
- { role: 'assistant', content: helpMessage },
699
- ];
700
- context.setMessages(updatedMessages);
701
- return {
702
- handled: true,
703
- shouldContinue: false,
704
- updatedContext: {
705
- messages: updatedMessages,
706
- },
707
- };
708
- }
709
582
  if (trimmedCommand.startsWith('/load')) {
710
583
  logger.flow('Load command received');
711
584
  const parts = trimmedCommand.split(' ');
@@ -7,6 +7,7 @@ export declare class EvalRunner {
7
7
  private filesModified;
8
8
  private lastResponse;
9
9
  private todos;
10
+ private followUpFile;
10
11
  constructor();
11
12
  run(input: EvalInput): Promise<void>;
12
13
  private execute;
@@ -1,6 +1,8 @@
1
1
  import { createLLMClient } from '../core/llm/llm-client.js';
2
2
  import { configManager } from '../core/config/config-manager.js';
3
3
  import { PlanExecutor } from '../orchestration/plan-executor.js';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
4
6
  function emitEvent(event) {
5
7
  console.log(JSON.stringify(event));
6
8
  }
@@ -15,8 +17,11 @@ export class EvalRunner {
15
17
  filesModified = new Set();
16
18
  lastResponse = '';
17
19
  todos = [];
20
+ followUpFile;
18
21
  constructor() {
19
22
  this.planExecutor = new PlanExecutor();
23
+ const tmpDir = process.env['ORQUESTA_IPC_DIR'] || path.join(process.env['HOME'] || '/tmp', '.orquesta-cli');
24
+ this.followUpFile = path.join(tmpDir, 'follow-up.msg');
20
25
  }
21
26
  async run(input) {
22
27
  this.startTime = Date.now();
@@ -126,6 +131,23 @@ export class EvalRunner {
126
131
  },
127
132
  setAskUserRequest: () => {
128
133
  },
134
+ getPendingMessage: () => {
135
+ try {
136
+ if (fs.existsSync(this.followUpFile)) {
137
+ const msg = fs.readFileSync(this.followUpFile, 'utf-8').trim();
138
+ if (msg)
139
+ return msg;
140
+ }
141
+ }
142
+ catch { }
143
+ return null;
144
+ },
145
+ clearPendingMessage: () => {
146
+ try {
147
+ fs.unlinkSync(this.followUpFile);
148
+ }
149
+ catch { }
150
+ },
129
151
  };
130
152
  }
131
153
  emitTodo(action, todo) {