-
Notifications
You must be signed in to change notification settings - Fork 1
Claude/route logging diagnostics #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
05b460e
574d454
1162a97
602a676
9a56a3f
cb8df9a
02d9a03
ef200c4
3dfc900
5e89b95
cd0a9a4
4b6ba45
d4b9de6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,8 @@ | |
|
|
||
| #include <QJsonArray> | ||
|
|
||
| AdapterClient::AdapterClient(AdapterProcess* pProcess, QObject* parent) : QObject(parent), _pProcess(pProcess) | ||
| AdapterClient::AdapterClient(AdapterProcess* pProcess, QObject* parent, int handshakeTimeoutMs) | ||
| : QObject(parent), _pProcess(pProcess), _handshakeTimeoutMs(handshakeTimeoutMs) | ||
| { | ||
| Q_ASSERT(pProcess); | ||
| _pProcess->setParent(this); | ||
|
|
@@ -16,6 +17,7 @@ AdapterClient::AdapterClient(AdapterProcess* pProcess, QObject* parent) : QObjec | |
| connect(_pProcess, &AdapterProcess::errorReceived, this, &AdapterClient::onErrorReceived); | ||
| connect(_pProcess, &AdapterProcess::processError, this, &AdapterClient::onProcessError); | ||
| connect(_pProcess, &AdapterProcess::processFinished, this, &AdapterClient::onProcessFinished); | ||
| connect(_pProcess, &AdapterProcess::notificationReceived, this, &AdapterClient::onNotificationReceived); | ||
| } | ||
|
|
||
| AdapterClient::~AdapterClient() = default; | ||
|
|
@@ -35,7 +37,7 @@ void AdapterClient::prepareAdapter(const QString& adapterPath) | |
|
|
||
| qCInfo(scopeComm) << "AdapterClient: process started, sending initialize"; | ||
| _state = State::INITIALIZING; | ||
| _handshakeTimer.start(cHandshakeTimeoutMs); | ||
| _handshakeTimer.start(_handshakeTimeoutMs); | ||
| _pProcess->sendRequest("adapter.initialize", QJsonObject()); | ||
| } | ||
|
|
||
|
|
@@ -50,7 +52,7 @@ void AdapterClient::provideConfig(QJsonObject config, QStringList registerExpres | |
| _pendingExpressions = registerExpressions; | ||
| _pendingConfig = config; | ||
| _state = State::CONFIGURING; | ||
| _handshakeTimer.start(cHandshakeTimeoutMs); | ||
| _handshakeTimer.start(_handshakeTimeoutMs); | ||
| QJsonObject params; | ||
| params["config"] = _pendingConfig; | ||
| _pProcess->sendRequest("adapter.configure", params); | ||
|
|
@@ -91,12 +93,13 @@ void AdapterClient::stopSession() | |
| { | ||
| _state = State::STOPPING; | ||
| _pProcess->sendRequest("adapter.shutdown", QJsonObject()); | ||
| _handshakeTimer.start(_handshakeTimeoutMs); | ||
| } | ||
| else | ||
| { | ||
| _state = State::STOPPING; | ||
| _pProcess->stop(); | ||
| _state = State::IDLE; | ||
| emit sessionStopped(); | ||
| /* sessionStopped is emitted from onProcessFinished once the process exits */ | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -111,8 +114,10 @@ void AdapterClient::onResponseReceived(int id, const QString& method, const QJso | |
| { | ||
| qCWarning(scopeComm) << "AdapterClient: unexpected non-object result for" << method; | ||
| _handshakeTimer.stop(); | ||
| /* Set IDLE before stop() so onProcessFinished's IDLE guard suppresses any | ||
| duplicate sessionError emission when the process exits asynchronously. */ | ||
| _state = State::IDLE; | ||
| QTimer::singleShot(0, this, [this]() { _pProcess->stop(); }); | ||
| _pProcess->stop(); | ||
| emit sessionError(QString("Unexpected non-object result for %1").arg(method)); | ||
|
Comment on lines
119
to
121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not switch back to
Also applies to: 130-132, 147-175 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
@@ -125,8 +130,10 @@ void AdapterClient::onErrorReceived(int id, const QString& method, const QJsonOb | |
| qCWarning(scopeComm) << "AdapterClient: error for" << method << ":" << errorMsg; | ||
|
|
||
| State previousState = _state; | ||
| /* Set IDLE before stop() so onProcessFinished's IDLE guard suppresses any | ||
| duplicate sessionError emission when the process exits asynchronously. */ | ||
| _state = State::IDLE; | ||
| QTimer::singleShot(0, this, [this]() { _pProcess->stop(); }); | ||
| _pProcess->stop(); | ||
|
|
||
| if (previousState != State::STOPPING) | ||
| { | ||
|
|
@@ -137,14 +144,22 @@ void AdapterClient::onErrorReceived(int id, const QString& method, const QJsonOb | |
| void AdapterClient::onProcessError(const QString& message) | ||
| { | ||
| _handshakeTimer.stop(); | ||
| _state = State::IDLE; | ||
| emit sessionError(message); | ||
| if (_state != State::STOPPING) | ||
| { | ||
| _state = State::IDLE; | ||
| emit sessionError(message); | ||
| } | ||
| } | ||
|
|
||
| void AdapterClient::onProcessFinished() | ||
| { | ||
| _handshakeTimer.stop(); | ||
| if (_state != State::IDLE) | ||
| if (_state == State::STOPPING) | ||
| { | ||
| _state = State::IDLE; | ||
| emit sessionStopped(); | ||
| } | ||
| else if (_state != State::IDLE) | ||
| { | ||
| _state = State::IDLE; | ||
| emit sessionError("Adapter process exited unexpectedly"); | ||
|
|
@@ -154,9 +169,35 @@ void AdapterClient::onProcessFinished() | |
| void AdapterClient::onHandshakeTimeout() | ||
| { | ||
| qCWarning(scopeComm) << "AdapterClient: handshake timed out in state" << static_cast<int>(_state); | ||
| bool wasStopping = (_state == State::STOPPING); | ||
| _state = State::IDLE; | ||
| QTimer::singleShot(0, this, [this]() { _pProcess->stop(); }); | ||
| emit sessionError("Adapter handshake timed out"); | ||
| _pProcess->stop(); | ||
| if (wasStopping) | ||
| { | ||
| emit sessionStopped(); | ||
| } | ||
| else | ||
| { | ||
| emit sessionError("Adapter handshake timed out"); | ||
| } | ||
| } | ||
|
Comment on lines
169
to
+183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the shutdown timer fires (line 173), Additionally, when Consider keeping Proposed fix void AdapterClient::onHandshakeTimeout()
{
qCWarning(scopeComm) << "AdapterClient: handshake timed out in state" << static_cast<int>(_state);
- bool wasStopping = (_state == State::STOPPING);
- _state = State::IDLE;
+ if (_state == State::STOPPING)
+ {
+ // Let onProcessFinished emit sessionStopped after the process exits
+ _pProcess->stop();
+ return;
+ }
+ _state = State::STOPPING;
_pProcess->stop();
- if (wasStopping)
- {
- emit sessionStopped();
- }
- else
- {
- emit sessionError("Adapter handshake timed out");
- }
+ // sessionError will not be emitted since we're now STOPPING;
+ // onProcessFinished will emit sessionStopped
}Actually, this changes semantics—errors during handshake should still emit 🤖 Prompt for AI Agents |
||
|
|
||
| void AdapterClient::onNotificationReceived(QString method, QJsonValue params) | ||
| { | ||
| if (method != QStringLiteral("adapter.diagnostic")) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (!params.isObject()) | ||
| { | ||
| qCWarning(scopeComm) << "AdapterClient: adapter.diagnostic params is not an object"; | ||
| return; | ||
| } | ||
|
|
||
| QJsonObject obj = params.toObject(); | ||
| emit diagnosticReceived(obj.value(QStringLiteral("level")).toString(), | ||
| obj.value(QStringLiteral("message")).toString()); | ||
jgeudens marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| void AdapterClient::handleLifecycleResponse(const QString& method, const QJsonObject& result) | ||
|
|
@@ -215,8 +256,7 @@ void AdapterClient::handleLifecycleResponse(const QString& method, const QJsonOb | |
| { | ||
| qCInfo(scopeComm) << "AdapterClient: shutdown acknowledged"; | ||
| _pProcess->stop(); | ||
| _state = State::IDLE; | ||
| emit sessionStopped(); | ||
| /* sessionStopped is emitted from onProcessFinished once the process exits */ | ||
| } | ||
| else | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,7 @@ class AdapterClient : public QObject | |
| Q_OBJECT | ||
|
|
||
| public: | ||
| explicit AdapterClient(AdapterProcess* pProcess, QObject* parent = nullptr); | ||
| explicit AdapterClient(AdapterProcess* pProcess, QObject* parent = nullptr, int handshakeTimeoutMs = 10000); | ||
| ~AdapterClient(); | ||
|
|
||
| /*! | ||
|
|
@@ -71,7 +71,10 @@ class AdapterClient : public QObject | |
| void requestStatus(); | ||
|
|
||
| /*! | ||
| * \brief Send adapter.shutdown and terminate the adapter process. | ||
| * \brief Send adapter.shutdown and signal the adapter process to stop. | ||
| * | ||
| * The sessionStopped() signal is emitted asynchronously once the process | ||
| * has fully exited. | ||
| */ | ||
| void stopSession(); | ||
|
|
||
|
|
@@ -114,6 +117,13 @@ class AdapterClient : public QObject | |
| */ | ||
| void sessionStopped(); | ||
|
|
||
| /*! | ||
| * \brief Emitted when an adapter.diagnostic notification is received from the adapter. | ||
| * \param level Severity level string: "debug", "info", or "warning". | ||
| * \param message The diagnostic message from the adapter. | ||
|
Comment on lines
+120
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Signal docs are stale for diagnostic severity levels. The comment lists only 🤖 Prompt for AI Agents |
||
| */ | ||
| void diagnosticReceived(QString level, QString message); | ||
|
|
||
| protected: | ||
| enum class State | ||
| { | ||
|
|
@@ -135,6 +145,7 @@ private slots: | |
| void onProcessError(const QString& message); | ||
| void onProcessFinished(); | ||
| void onHandshakeTimeout(); | ||
| void onNotificationReceived(QString method, QJsonValue params); | ||
|
|
||
| private: | ||
| void handleLifecycleResponse(const QString& method, const QJsonObject& result); | ||
|
|
@@ -143,6 +154,7 @@ private slots: | |
|
|
||
| AdapterProcess* _pProcess; | ||
| QTimer _handshakeTimer; | ||
| int _handshakeTimeoutMs; | ||
| QJsonObject _pendingConfig; | ||
| QStringList _pendingExpressions; | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defaults now diverge from the published contract.
persistentandint32LittleEndianwere changed totrue, butadapters/json-rpc-spec.mdstill documents both defaults asfalse. This creates a behavioural/API mismatch for clients consumingadapter.describe.Proposed fix (if the default change was unintentional)
Also applies to: 26-26
🤖 Prompt for AI Agents