tuna-agent 0.1.80 → 0.1.81

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/cli/index.js CHANGED
File without changes
@@ -1,7 +1,6 @@
1
1
  import path from 'path';
2
2
  import os from 'os';
3
3
  import { spawn } from 'child_process';
4
- import { StringDecoder } from 'string_decoder';
5
4
  import { runClaude } from '../utils/claude-cli.js';
6
5
  import { validatePath } from '../utils/validate-path.js';
7
6
  const NEEDS_INPUT_MARKER = '"status":"NEEDS_INPUT"';
@@ -23,9 +22,6 @@ export async function runTask(task, onProgress, signal, confirmBeforeEdit) {
23
22
  if (confirmBeforeEdit) {
24
23
  args.push('--permission-mode', 'default');
25
24
  }
26
- else {
27
- args.push('--permission-mode', 'bypassPermissions');
28
- }
29
25
  const env = {
30
26
  ...process.env,
31
27
  HOME: process.env.HOME || '',
@@ -83,9 +79,8 @@ export async function runTask(task, onProgress, signal, confirmBeforeEdit) {
83
79
  let stdout = '';
84
80
  let stderr = '';
85
81
  let buffer = '';
86
- const stdoutDecoder = new StringDecoder('utf8');
87
82
  proc.stdout.on('data', (chunk) => {
88
- const text = stdoutDecoder.write(chunk);
83
+ const text = chunk.toString();
89
84
  stdout += text;
90
85
  buffer += text;
91
86
  const lines = buffer.split('\n');
@@ -102,9 +97,8 @@ export async function runTask(task, onProgress, signal, confirmBeforeEdit) {
102
97
  }
103
98
  }
104
99
  });
105
- const stderrDecoder = new StringDecoder('utf8');
106
100
  proc.stderr.on('data', (chunk) => {
107
- stderr += stderrDecoder.write(chunk);
101
+ stderr += chunk.toString();
108
102
  });
109
103
  proc.on('close', (code) => {
110
104
  clearTimeout(timeoutTimer);
@@ -431,15 +425,8 @@ export async function executeSubtask(subtask, repoPath, contracts, callbacks, si
431
425
  }
432
426
  else {
433
427
  // Fallback: detect if AI just wrote questions as text without using NEEDS_INPUT
434
- // Only trigger for very short outputs (< 500 chars) that completed quickly (< 15s)
435
- // This avoids false positives when a completed task output contains sentences with "?"
436
- const isShortOutput = result.result.length < 500;
437
- const isQuickRun = result.durationMs != null && result.durationMs < 15000;
438
- const fallbackQuestion = (isShortOutput && isQuickRun)
439
- ? parseQuestionFromOutput(result.result, subtask.id)
440
- : null;
441
- const seemsLikeQuestion = !!fallbackQuestion;
442
- if (seemsLikeQuestion && fallbackQuestion && callbacks?.onSubtaskNeedsInput) {
428
+ const fallbackQuestion = parseQuestionFromOutput(result.result, subtask.id);
429
+ if (fallbackQuestion && callbacks?.onSubtaskNeedsInput) {
443
430
  info.status = 'waiting_input';
444
431
  info.pendingQuestion = fallbackQuestion;
445
432
  log(info, 'thinking', `Fallback: detected question in output (no NEEDS_INPUT marker): ${fallbackQuestion.question}`);
@@ -490,6 +477,15 @@ export async function executeSubtask(subtask, repoPath, contracts, callbacks, si
490
477
  log(info, 'thinking', 'No answer received — task paused');
491
478
  }
492
479
  }
480
+ else if (looksIncomplete(result.result)) {
481
+ info.status = 'failed';
482
+ info.result = result.result;
483
+ log(info, 'error', `Detected incomplete output — marking as failed`);
484
+ callbacks?.onSubtaskLog?.(subtask.id, {
485
+ type: 'error',
486
+ message: `❌ Task did not complete: AI could not fulfill the request`,
487
+ });
488
+ }
493
489
  else {
494
490
  info.status = 'done';
495
491
  info.result = result.result;
@@ -586,6 +582,26 @@ export async function executeTaskWithPlan(task, plan, onProgress, callbacks, sig
586
582
  console.log(`[Executor] Total time: ${(totalTime / 1000).toFixed(1)}s`);
587
583
  return { sessions: allSessions, status: 'done' };
588
584
  }
585
+ /**
586
+ * Detect if Claude's output indicates it could NOT complete the task.
587
+ * Checks the last ~500 chars for common failure/inability patterns.
588
+ */
589
+ function looksIncomplete(output) {
590
+ // Check only the tail — that's where the conclusion lives
591
+ const tail = output.slice(-500).toLowerCase();
592
+ const patterns = [
593
+ /i (?:was |am )?(?:unable|not able) to/,
594
+ /(?:could|can)(?:n't|not) (?:find|locate|access|complete|proceed|continue)/,
595
+ /(?:doesn't|does not|don't|do not) (?:exist|have access)/,
596
+ /(?:no (?:such|matching) (?:file|directory|path|repo))/,
597
+ /(?:not found|file not found|directory not found)/,
598
+ /(?:i need (?:you to|more information|the .* path|access))/,
599
+ /(?:please (?:provide|specify|confirm|check))/,
600
+ /(?:unfortunately|i apologize).{0,50}(?:cannot|couldn't|unable|can't)/,
601
+ /(?:blocked|stuck).{0,30}(?:because|due to|cannot)/,
602
+ ];
603
+ return patterns.some((p) => p.test(tail));
604
+ }
589
605
  /**
590
606
  * Parse question from Claude output when question.json is missing.
591
607
  * Fallback for when Claude outputs NEEDS_INPUT but didn't create the file.
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.80",
3
+ "version": "0.1.81",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"