Skip to content

Commit 57452fa

Browse files
feat: remember goal agent
1 parent 9196ced commit 57452fa

5 files changed

Lines changed: 236 additions & 8 deletions

File tree

src/dialogs/SendWithGoalDialog.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
#include "SendWithGoalDialog.h"
22

33
#include <QHBoxLayout>
4+
#include <QJsonDocument>
5+
#include <QJsonObject>
46
#include <QLabel>
57
#include <QPushButton>
68
#include <QVBoxLayout>
79

10+
#include "ApplicationSettings.h"
811
#include "GoalConfigWidget.h"
912

1013
SendWithGoalDialog::SendWithGoalDialog(AcpAgentRegistry *registry,
1114
ApplicationSettings *settings,
1215
QWidget *parent)
1316
: QDialog(parent)
17+
, m_settings(settings)
1418
{
1519
setWindowTitle(tr("Send with Goal"));
1620
setMinimumWidth(440);
@@ -42,15 +46,14 @@ SendWithGoalDialog::SendWithGoalDialog(AcpAgentRegistry *registry,
4246

4347
SendWithGoalDialog::~SendWithGoalDialog() = default;
4448

45-
bool SendWithGoalDialog::validate()
49+
bool SendWithGoalDialog::validate(const GoalConfigResult &result)
4650
{
47-
const GoalConfigResult r = m_goalConfig->result();
48-
if (r.criteriaList.isEmpty()) {
51+
if (result.criteriaList.isEmpty()) {
4952
m_errorLabel->setText(tr("At least one criterion is required."));
5053
m_errorLabel->show();
5154
return false;
5255
}
53-
if (r.agentId.isEmpty()) {
56+
if (result.agentId.isEmpty()) {
5457
m_errorLabel->setText(tr("Select a goal-agent."));
5558
m_errorLabel->show();
5659
return false;
@@ -61,8 +64,21 @@ bool SendWithGoalDialog::validate()
6164

6265
void SendWithGoalDialog::onStart()
6366
{
64-
if (!validate())
67+
const GoalConfigResult result = m_goalConfig->result();
68+
if (!validate(result))
6569
return;
70+
71+
if (m_settings) {
72+
const QString settingsJson = m_settings->get("Ai/GoalAgentSettings", QString());
73+
QJsonObject settingsObject = QJsonDocument::fromJson(settingsJson.toUtf8()).object();
74+
if (settingsObject.value(QStringLiteral("agentId")).toString() != result.agentId) {
75+
settingsObject.insert(QStringLiteral("agentId"), result.agentId);
76+
m_settings->setValue(
77+
QStringLiteral("Ai/GoalAgentSettings"),
78+
QString::fromUtf8(QJsonDocument(settingsObject).toJson(QJsonDocument::Compact)));
79+
}
80+
}
81+
6682
accept();
6783
}
6884

src/dialogs/SendWithGoalDialog.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class ApplicationSettings;
99
class GoalConfigWidget;
1010
class QLabel;
1111
class QPushButton;
12+
struct GoalConfigResult;
1213

1314
struct SendWithGoalResult
1415
{
@@ -36,8 +37,9 @@ private slots:
3637
void onStart();
3738

3839
private:
39-
bool validate();
40+
bool validate(const GoalConfigResult &result);
4041

42+
ApplicationSettings *m_settings = nullptr;
4143
GoalConfigWidget *m_goalConfig = nullptr;
4244
QLabel *m_errorLabel = nullptr;
4345
QPushButton *m_startBtn = nullptr;

src/widgets/GoalConfigWidget.cpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@ GoalConfigWidget::GoalConfigWidget(AcpAgentRegistry *registry,
3434
{
3535
buildUi();
3636
populateAgents();
37+
connect(m_agentCombo, qOverload<int>(&QComboBox::currentIndexChanged), this,
38+
[this](int) {
39+
const QString agentId = m_agentCombo->currentData().toString();
40+
if (agentId.isEmpty() || !m_settings)
41+
return;
42+
43+
const QString settingsJson = m_settings->get("Ai/GoalAgentSettings", QString());
44+
GoalAgentSettings goalSettings;
45+
if (!settingsJson.isEmpty()) {
46+
goalSettings = GoalAgentSettings::fromJson(
47+
QJsonDocument::fromJson(settingsJson.toUtf8()).object());
48+
}
49+
if (goalSettings.agentId == agentId)
50+
return;
51+
52+
goalSettings.agentId = agentId;
53+
m_settings->setValue(
54+
QStringLiteral("Ai/GoalAgentSettings"),
55+
QString::fromUtf8(QJsonDocument(goalSettings.toJson()).toJson(QJsonDocument::Compact)));
56+
});
3757
populateTemplates();
3858
populatePresets();
3959
updateRowCount();
@@ -208,8 +228,17 @@ void GoalConfigWidget::populateAgents()
208228
if (a.id == goalSettings.agentId)
209229
selectedIdx = i;
210230
}
211-
if (m_agentCombo->count() > 0)
212-
m_agentCombo->setCurrentIndex(selectedIdx);
231+
if (m_agentCombo->count() == 0)
232+
return;
233+
234+
m_agentCombo->setCurrentIndex(selectedIdx);
235+
const QString effectiveAgentId = m_agentCombo->currentData().toString();
236+
if (!goalSettings.agentId.isEmpty() && goalSettings.agentId != effectiveAgentId) {
237+
goalSettings.agentId = effectiveAgentId;
238+
m_settings->setValue(
239+
QStringLiteral("Ai/GoalAgentSettings"),
240+
QString::fromUtf8(QJsonDocument(goalSettings.toJson()).toJson(QJsonDocument::Compact)));
241+
}
213242
}
214243

215244
void GoalConfigWidget::populateTemplates()

tests/CMakeLists.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,29 @@ target_sources(test_goal_draft_generator PRIVATE
302302
)
303303
add_test(NAME GoalDraftGenerator COMMAND test_goal_draft_generator)
304304

305+
# --- SendWithGoalDialog: persist and restore agent selection across dialog exits ---
306+
qt_add_executable(test_send_with_goal_dialog test_send_with_goal_dialog.cpp)
307+
target_include_directories(test_send_with_goal_dialog PRIVATE
308+
${CMAKE_SOURCE_DIR}/src
309+
${CMAKE_SOURCE_DIR}/src/dialogs
310+
${CMAKE_SOURCE_DIR}/src/widgets
311+
)
312+
target_link_libraries(test_send_with_goal_dialog PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Test)
313+
target_sources(test_send_with_goal_dialog PRIVATE
314+
${CMAKE_SOURCE_DIR}/src/AcpAgentDefinition.h
315+
${CMAKE_SOURCE_DIR}/src/AcpAgentRegistry.cpp
316+
${CMAKE_SOURCE_DIR}/src/AcpAgentRegistry.h
317+
${CMAKE_SOURCE_DIR}/src/ApplicationSettings.cpp
318+
${CMAKE_SOURCE_DIR}/src/ApplicationSettings.h
319+
${CMAKE_SOURCE_DIR}/src/GoalAgentSettings.cpp
320+
${CMAKE_SOURCE_DIR}/src/GoalAgentSettings.h
321+
${CMAKE_SOURCE_DIR}/src/dialogs/SendWithGoalDialog.cpp
322+
${CMAKE_SOURCE_DIR}/src/dialogs/SendWithGoalDialog.h
323+
${CMAKE_SOURCE_DIR}/src/widgets/GoalConfigWidget.cpp
324+
${CMAKE_SOURCE_DIR}/src/widgets/GoalConfigWidget.h
325+
)
326+
add_test(NAME SendWithGoalDialog COMMAND test_send_with_goal_dialog)
327+
305328
# --- AcpAgentManager: lifecycle, history worker-thread placement, shutdown ---
306329
qt_add_executable(test_acp_agent_manager test_acp_agent_manager.cpp)
307330
target_include_directories(test_acp_agent_manager PRIVATE
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#include <QtTest>
2+
3+
#include <QComboBox>
4+
#include <QCoreApplication>
5+
#include <QJsonDocument>
6+
#include <QPlainTextEdit>
7+
#include <QPushButton>
8+
#include <QSettings>
9+
#include <QTemporaryDir>
10+
11+
#include "AcpAgentRegistry.h"
12+
#include "ApplicationSettings.h"
13+
#include "GoalAgentSettings.h"
14+
#include "GoalConfigWidget.h"
15+
#include "SendWithGoalDialog.h"
16+
17+
class TestSendWithGoalDialog : public QObject
18+
{
19+
Q_OBJECT
20+
21+
private slots:
22+
void initTestCase();
23+
void init();
24+
25+
void changedAgent_isRestoredAfterCancel();
26+
void changedAgent_isRestoredAfterEscape();
27+
void changedAgent_isRestoredAfterAccept();
28+
void removedStoredAgent_fallsBackAndNormalizesSetting();
29+
30+
private:
31+
static QComboBox *agentCombo(SendWithGoalDialog &dialog);
32+
static QPushButton *dialogButton(SendWithGoalDialog &dialog, const QString &text);
33+
static QString storedAgentId(ApplicationSettings &settings);
34+
static void selectCodex(SendWithGoalDialog &dialog);
35+
36+
QTemporaryDir m_settingsDir;
37+
};
38+
39+
void TestSendWithGoalDialog::initTestCase()
40+
{
41+
QVERIFY(m_settingsDir.isValid());
42+
QCoreApplication::setOrganizationName(QStringLiteral("NotepadNextTest"));
43+
QCoreApplication::setApplicationName(QStringLiteral("NotepadNextTest_SendWithGoalDialog"));
44+
QSettings::setDefaultFormat(QSettings::IniFormat);
45+
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, m_settingsDir.path());
46+
}
47+
48+
void TestSendWithGoalDialog::init()
49+
{
50+
ApplicationSettings settings;
51+
settings.clear();
52+
settings.sync();
53+
}
54+
55+
QComboBox *TestSendWithGoalDialog::agentCombo(SendWithGoalDialog &dialog)
56+
{
57+
const auto combos = dialog.findChildren<QComboBox *>();
58+
for (QComboBox *combo : combos) {
59+
if (combo->findData(AcpAgentRegistry::builtinClaudeCodeId()) >= 0
60+
&& combo->findData(AcpAgentRegistry::builtinCodexId()) >= 0) {
61+
return combo;
62+
}
63+
}
64+
return nullptr;
65+
}
66+
67+
QPushButton *TestSendWithGoalDialog::dialogButton(SendWithGoalDialog &dialog, const QString &text)
68+
{
69+
for (QPushButton *button : dialog.findChildren<QPushButton *>()) {
70+
if (button->text() == text)
71+
return button;
72+
}
73+
return nullptr;
74+
}
75+
76+
QString TestSendWithGoalDialog::storedAgentId(ApplicationSettings &settings)
77+
{
78+
const QString json = settings.get("Ai/GoalAgentSettings", QString());
79+
return GoalAgentSettings::fromJson(QJsonDocument::fromJson(json.toUtf8()).object()).agentId;
80+
}
81+
82+
void TestSendWithGoalDialog::selectCodex(SendWithGoalDialog &dialog)
83+
{
84+
QComboBox *combo = agentCombo(dialog);
85+
QVERIFY(combo);
86+
const int codexIndex = combo->findData(AcpAgentRegistry::builtinCodexId());
87+
QVERIFY(codexIndex >= 0);
88+
combo->setCurrentIndex(codexIndex);
89+
}
90+
91+
void TestSendWithGoalDialog::changedAgent_isRestoredAfterCancel()
92+
{
93+
ApplicationSettings settings;
94+
AcpAgentRegistry registry(&settings);
95+
96+
SendWithGoalDialog first(&registry, &settings);
97+
selectCodex(first);
98+
QCOMPARE(storedAgentId(settings), AcpAgentRegistry::builtinCodexId());
99+
QVERIFY(dialogButton(first, QStringLiteral("Cancel")));
100+
QTest::mouseClick(dialogButton(first, QStringLiteral("Cancel")), Qt::LeftButton);
101+
QCOMPARE(first.result(), QDialog::Rejected);
102+
103+
SendWithGoalDialog reopened(&registry, &settings);
104+
QCOMPARE(agentCombo(reopened)->currentData().toString(), AcpAgentRegistry::builtinCodexId());
105+
}
106+
107+
void TestSendWithGoalDialog::changedAgent_isRestoredAfterEscape()
108+
{
109+
ApplicationSettings settings;
110+
AcpAgentRegistry registry(&settings);
111+
112+
SendWithGoalDialog first(&registry, &settings);
113+
selectCodex(first);
114+
QCOMPARE(storedAgentId(settings), AcpAgentRegistry::builtinCodexId());
115+
QTest::keyClick(&first, Qt::Key_Escape);
116+
QCOMPARE(first.result(), QDialog::Rejected);
117+
118+
SendWithGoalDialog reopened(&registry, &settings);
119+
QCOMPARE(agentCombo(reopened)->currentData().toString(), AcpAgentRegistry::builtinCodexId());
120+
}
121+
122+
void TestSendWithGoalDialog::changedAgent_isRestoredAfterAccept()
123+
{
124+
ApplicationSettings settings;
125+
AcpAgentRegistry registry(&settings);
126+
127+
SendWithGoalDialog first(&registry, &settings);
128+
selectCodex(first);
129+
QCOMPARE(storedAgentId(settings), AcpAgentRegistry::builtinCodexId());
130+
QVERIFY(dialogButton(first, QStringLiteral("Start Goal")));
131+
const auto criteria = first.findChildren<QPlainTextEdit *>();
132+
QVERIFY(!criteria.isEmpty());
133+
criteria.first()->setPlainText(QStringLiteral("Confirm selected agent"));
134+
QTest::mouseClick(dialogButton(first, QStringLiteral("Start Goal")), Qt::LeftButton);
135+
QCOMPARE(first.result(), QDialog::Accepted);
136+
137+
SendWithGoalDialog reopened(&registry, &settings);
138+
QCOMPARE(agentCombo(reopened)->currentData().toString(), AcpAgentRegistry::builtinCodexId());
139+
}
140+
141+
void TestSendWithGoalDialog::removedStoredAgent_fallsBackAndNormalizesSetting()
142+
{
143+
ApplicationSettings settings;
144+
GoalAgentSettings goalSettings;
145+
goalSettings.agentId = QStringLiteral("removed-agent");
146+
settings.setValue(
147+
QStringLiteral("Ai/GoalAgentSettings"),
148+
QString::fromUtf8(QJsonDocument(goalSettings.toJson()).toJson(QJsonDocument::Compact)));
149+
AcpAgentRegistry registry(&settings);
150+
151+
SendWithGoalDialog dialog(&registry, &settings);
152+
QCOMPARE(agentCombo(dialog)->currentData().toString(), AcpAgentRegistry::builtinClaudeCodeId());
153+
QCOMPARE(storedAgentId(settings), AcpAgentRegistry::builtinClaudeCodeId());
154+
}
155+
156+
QTEST_MAIN(TestSendWithGoalDialog)
157+
158+
#include "test_send_with_goal_dialog.moc"

0 commit comments

Comments
 (0)