tuna-agent 0.1.172 → 0.1.174
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.
|
@@ -603,91 +603,89 @@ export class ClaudeCodeAdapter {
|
|
|
603
603
|
status: s.status,
|
|
604
604
|
})),
|
|
605
605
|
});
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
description: s.description,
|
|
622
|
-
dependencies: s.dependencies,
|
|
623
|
-
}))),
|
|
624
|
-
});
|
|
625
|
-
const { text: planApproval } = await waitForInput(task.id, pendingInputResolvers);
|
|
626
|
-
console.log(`[ClaudeCode] Plan approval response: ${planApproval.substring(0, 80)}`);
|
|
627
|
-
// If user requests changes, re-plan with feedback and loop
|
|
628
|
-
if (planApproval.toLowerCase() !== 'approve') {
|
|
629
|
-
console.log(`[ClaudeCode] User requested changes — re-planning with feedback`);
|
|
630
|
-
ws.sendPMMessage(task.id, {
|
|
631
|
-
sender: 'pm',
|
|
632
|
-
content: `Got it, adjusting the plan based on your feedback...`,
|
|
606
|
+
// Auto-approve single-subtask ('self') plans — no plan-review friction for
|
|
607
|
+
// simple own-domain work. Only complex multi-subtask builds ask for approval.
|
|
608
|
+
if (plan.subtasks.length > 1) {
|
|
609
|
+
// Step 2: Wait for user to approve the plan
|
|
610
|
+
console.log(`[ClaudeCode] Waiting for plan approval for task ${task.id}`);
|
|
611
|
+
ws.sendNeedsInput(task.id, {
|
|
612
|
+
subtaskId: 'plan',
|
|
613
|
+
question: plan.summary,
|
|
614
|
+
options: ['Approve', 'Request Changes'],
|
|
615
|
+
context: JSON.stringify(plan.subtasks.map((s) => ({
|
|
616
|
+
id: s.id,
|
|
617
|
+
role: s.role,
|
|
618
|
+
description: s.description,
|
|
619
|
+
dependencies: s.dependencies,
|
|
620
|
+
}))),
|
|
633
621
|
});
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (
|
|
638
|
-
|
|
639
|
-
plan.summary = replanResult.plan.summary;
|
|
640
|
-
plan.subtasks = replanResult.plan.subtasks;
|
|
641
|
-
plan.contracts = replanResult.plan.contracts;
|
|
642
|
-
if (replanResult.sessionId) {
|
|
643
|
-
planResult.pmSessionId = replanResult.sessionId;
|
|
644
|
-
}
|
|
645
|
-
// Send updated plan_ready and recurse approval
|
|
646
|
-
ws.sendPlanReady(task.id, {
|
|
647
|
-
summary: plan.summary,
|
|
648
|
-
contracts: plan.contracts,
|
|
649
|
-
subtasks: plan.subtasks.map((s) => ({
|
|
650
|
-
id: s.id, role: s.role, description: s.description,
|
|
651
|
-
cwd: s.cwd, dependencies: s.dependencies, status: s.status,
|
|
652
|
-
})),
|
|
653
|
-
});
|
|
654
|
-
const updatedRoles = plan.subtasks.map((s) => s.role).join(', ');
|
|
622
|
+
const { text: planApproval } = await waitForInput(task.id, pendingInputResolvers);
|
|
623
|
+
console.log(`[ClaudeCode] Plan approval response: ${planApproval.substring(0, 80)}`);
|
|
624
|
+
// If user requests changes, re-plan with feedback and loop
|
|
625
|
+
if (planApproval.toLowerCase() !== 'approve') {
|
|
626
|
+
console.log(`[ClaudeCode] User requested changes — re-planning with feedback`);
|
|
655
627
|
ws.sendPMMessage(task.id, {
|
|
656
628
|
sender: 'pm',
|
|
657
|
-
content: `
|
|
629
|
+
content: `Got it, adjusting the plan based on your feedback...`,
|
|
658
630
|
});
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
ws.
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
631
|
+
ws.sendProgress(task.id, 'planning', { startedAt: new Date().toISOString() });
|
|
632
|
+
const replanResult = await chatWithPM(planResult.pmSessionId || '', task.repoPath, `The user wants changes to the plan. Their feedback: "${planApproval}"\n\nPlease create an updated execution plan as JSON.`, signal, (chunk) => { ws.sendPMStream(task.id, chunk); });
|
|
633
|
+
ws.sendPMStreamEnd(task.id, `pm-replan-${Date.now()}`);
|
|
634
|
+
if (replanResult.plan && replanResult.plan.subtasks.length > 0) {
|
|
635
|
+
// Replace plan with updated version
|
|
636
|
+
plan.summary = replanResult.plan.summary;
|
|
637
|
+
plan.subtasks = replanResult.plan.subtasks;
|
|
638
|
+
plan.contracts = replanResult.plan.contracts;
|
|
639
|
+
if (replanResult.sessionId) {
|
|
640
|
+
planResult.pmSessionId = replanResult.sessionId;
|
|
641
|
+
}
|
|
642
|
+
// Send updated plan_ready and recurse approval
|
|
643
|
+
ws.sendPlanReady(task.id, {
|
|
644
|
+
summary: plan.summary,
|
|
645
|
+
contracts: plan.contracts,
|
|
646
|
+
subtasks: plan.subtasks.map((s) => ({
|
|
647
|
+
id: s.id, role: s.role, description: s.description,
|
|
648
|
+
cwd: s.cwd, dependencies: s.dependencies, status: s.status,
|
|
649
|
+
})),
|
|
650
|
+
});
|
|
651
|
+
const updatedRoles = plan.subtasks.map((s) => s.role).join(', ');
|
|
652
|
+
ws.sendPMMessage(task.id, {
|
|
653
|
+
sender: 'pm',
|
|
654
|
+
content: `Updated plan: ${plan.summary}\n\n${plan.subtasks.length} session${plan.subtasks.length > 1 ? 's' : ''}: ${updatedRoles}.`,
|
|
655
|
+
});
|
|
656
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
657
|
+
// Ask for approval again
|
|
658
|
+
ws.sendNeedsInput(task.id, {
|
|
659
|
+
subtaskId: 'plan',
|
|
660
|
+
question: plan.summary,
|
|
661
|
+
options: ['Approve', 'Request Changes'],
|
|
662
|
+
context: JSON.stringify(plan.subtasks.map((s) => ({
|
|
663
|
+
id: s.id, role: s.role, description: s.description, dependencies: s.dependencies,
|
|
664
|
+
}))),
|
|
665
|
+
});
|
|
666
|
+
const { text: secondApproval } = await waitForInput(task.id, pendingInputResolvers);
|
|
667
|
+
console.log(`[ClaudeCode] Second plan approval: ${secondApproval.substring(0, 80)}`);
|
|
668
|
+
if (secondApproval.toLowerCase() !== 'approve') {
|
|
669
|
+
ws.sendPMMessage(task.id, {
|
|
670
|
+
sender: 'pm',
|
|
671
|
+
content: `Understood. I'll cancel this task. You can create a new one when ready.`,
|
|
672
|
+
});
|
|
673
|
+
ws.sendTaskFailed(task.id, 'Plan not approved by user');
|
|
674
|
+
console.log(`[ClaudeCode] Task ${task.id} — plan rejected twice, closing`);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
// Re-plan didn't produce subtasks — close
|
|
672
680
|
ws.sendPMMessage(task.id, {
|
|
673
681
|
sender: 'pm',
|
|
674
|
-
content:
|
|
682
|
+
content: replanResult.response || `I couldn't create an updated plan. Please try creating a new task.`,
|
|
675
683
|
});
|
|
676
|
-
ws.sendTaskFailed(task.id, '
|
|
677
|
-
console.log(`[ClaudeCode] Task ${task.id} — plan rejected twice, closing`);
|
|
684
|
+
ws.sendTaskFailed(task.id, 'Re-planning failed');
|
|
678
685
|
return;
|
|
679
686
|
}
|
|
680
687
|
}
|
|
681
|
-
|
|
682
|
-
// Re-plan didn't produce subtasks — close
|
|
683
|
-
ws.sendPMMessage(task.id, {
|
|
684
|
-
sender: 'pm',
|
|
685
|
-
content: replanResult.response || `I couldn't create an updated plan. Please try creating a new task.`,
|
|
686
|
-
});
|
|
687
|
-
ws.sendTaskFailed(task.id, 'Re-planning failed');
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
688
|
+
} // end approval gate (multi-subtask plans only)
|
|
691
689
|
console.log(`[ClaudeCode] Plan approved — proceeding to execution`);
|
|
692
690
|
// Step 3: Handle plan questions if any — collect answers to pass to subtasks
|
|
693
691
|
const pmAnswers = [];
|
|
@@ -732,22 +730,12 @@ export class ClaudeCodeAdapter {
|
|
|
732
730
|
// Step 4: Execute subtasks
|
|
733
731
|
console.log(`[ClaudeCode] Starting execution for task ${task.id}`);
|
|
734
732
|
ws.sendProgress(task.id, 'executing', { startedAt: new Date().toISOString() });
|
|
735
|
-
ws.sendPMMessage(task.id, {
|
|
736
|
-
sender: 'pm',
|
|
737
|
-
content: "Starting execution now. I'll keep you updated as each session progresses.",
|
|
738
|
-
});
|
|
739
733
|
// Step 4-6: Execute plan with follow-up loop
|
|
740
734
|
const allSessions = [];
|
|
741
735
|
let lastStatus = 'done';
|
|
742
736
|
const MAX_FOLLOW_UP_ROUNDS = 10;
|
|
743
737
|
for (let followUpRound = 0; followUpRound <= MAX_FOLLOW_UP_ROUNDS; followUpRound++) {
|
|
744
738
|
ws.sendProgress(task.id, 'executing', { startedAt: new Date().toISOString() });
|
|
745
|
-
if (followUpRound === 0) {
|
|
746
|
-
ws.sendPMMessage(task.id, {
|
|
747
|
-
sender: 'pm',
|
|
748
|
-
content: "Starting execution now. I'll keep you updated as each session progresses.",
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
739
|
const result = await executePlanAndReport(task, plan, ws, pendingInputResolvers, signal, confirmBeforeEdit, onPermissionRequest);
|
|
752
740
|
allSessions.push(...result.sessions);
|
|
753
741
|
lastStatus = result.status;
|
|
@@ -65,11 +65,15 @@ export function buildExecutionCallbacks(taskId, plan, ws, resolvers, onPermissio
|
|
|
65
65
|
role: subtask.role,
|
|
66
66
|
description: subtask.description,
|
|
67
67
|
});
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
// Only announce a "session started" for real multi-role dev teams.
|
|
69
|
+
// A single 'self' agent doing its own work needs no orchestration noise.
|
|
70
|
+
if (subtask.role !== 'self') {
|
|
71
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
72
|
+
ws.sendPMMessage(taskId, {
|
|
73
|
+
sender: 'system',
|
|
74
|
+
content: `${capitalize(subtask.role)} session started`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
73
77
|
}
|
|
74
78
|
},
|
|
75
79
|
onSubtaskLog: async (subtaskId, logData) => {
|
|
@@ -92,9 +96,13 @@ export function buildExecutionCallbacks(taskId, plan, ws, resolvers, onPermissio
|
|
|
92
96
|
const durationStr = durationMs ? `${(durationMs / 1000).toFixed(1)}s` : '';
|
|
93
97
|
const resultPreview = rawResult ? simplifyMarkdown(rawResult) : '';
|
|
94
98
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
99
|
+
const isSelf = subtask?.role === 'self';
|
|
95
100
|
ws.sendPMMessage(taskId, {
|
|
96
101
|
sender: 'pm',
|
|
97
|
-
|
|
102
|
+
// Single 'self' agent: just deliver the result, no "X completed successfully" scaffolding.
|
|
103
|
+
content: isSelf
|
|
104
|
+
? (resultPreview || 'Xong ạ.')
|
|
105
|
+
: `${roleName} completed successfully${durationStr ? ` in ${durationStr}` : ''}${resultPreview ? `\n\n${resultPreview}` : ''}`,
|
|
98
106
|
});
|
|
99
107
|
}
|
|
100
108
|
else if (session.status === 'failed') {
|
|
@@ -109,9 +117,10 @@ export function buildExecutionCallbacks(taskId, plan, ws, resolvers, onPermissio
|
|
|
109
117
|
onSubtaskNeedsInput: async (subtaskId, question) => {
|
|
110
118
|
const subtask = plan.subtasks.find((s) => s.id === subtaskId);
|
|
111
119
|
const roleName = subtask ? capitalize(subtask.role) : 'A session';
|
|
120
|
+
const askSelf = subtask?.role === 'self';
|
|
112
121
|
ws.sendPMMessage(taskId, {
|
|
113
122
|
sender: 'pm',
|
|
114
|
-
content: `${roleName} is asking: ${question.question}`,
|
|
123
|
+
content: askSelf ? question.question : `${roleName} is asking: ${question.question}`,
|
|
115
124
|
options: question.options,
|
|
116
125
|
context: question.context,
|
|
117
126
|
});
|
|
@@ -122,10 +131,13 @@ export function buildExecutionCallbacks(taskId, plan, ws, resolvers, onPermissio
|
|
|
122
131
|
context: question.context,
|
|
123
132
|
});
|
|
124
133
|
const { text: answer } = await waitForInput(taskId, resolvers);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
// No "passing this to X" scaffolding for a single self agent.
|
|
135
|
+
if (!askSelf) {
|
|
136
|
+
ws.sendPMMessage(taskId, {
|
|
137
|
+
sender: 'pm',
|
|
138
|
+
content: `Got it! Passing this to ${roleName}...`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
129
141
|
return answer;
|
|
130
142
|
},
|
|
131
143
|
onLayerStart: async (layerIndex, totalLayers, subtaskIds) => {
|