v0.9.7: a turn that cannot hang, a session list that reads nothing twice, and PDFs
- consent: on_permission / on_ask_user wait at most two / five minutes, and in
the background (a phone off screen, a window that is gone) the answer is
refused at once with a reason -- a dialog nobody can see blocked the worker
for good, which is what "the app hangs while the agent works" looked like.
- finishTurn() no longer joins the thread it is called from; the event loop
reaps the Turn instead.
- AndroidTools: the hop to the GUI thread pings first and gives up after 3 s
instead of waiting forever on a busy GUI thread.
- the session list stopped reading every transcript: cst_agent_store_info()
counts newlines in 64 KiB blocks (first_user only within the first 256 KiB),
and AgentCore::sessions() reuses a row while (mtime, size) is unchanged.
- session_search gets a budget (max_bytes 32 MB, max_seconds 5 s), skips lines
over 64 KiB, and says when the budget stopped the walk.
- the header shows the phase ("shell: make -j8"), so a long turn no longer
looks like a stuck one.
- md_to_pdf: the front end's own Qt tool (owner app:qt, every platform):
markdown or a workspace .md/.html becomes an A4 PDF with 15 mm margins,
paginated by hand, written into the workspace and delivered as a file card.
No PrintSupport, so the phone gets it too.
- --uitest runs md_to_pdf through the registry with Chinese text and checks the
bytes are a PDF; app/CMakeLists.txt no longer drops src/web_md.c from the
standalone engine build, which the window needs to render markdown.
Version 0.9.7.
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QJniObject>
|
||||
#include <QMetaObject>
|
||||
#include <QSemaphore>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
@@ -36,6 +37,7 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "core/AgentCore.h"
|
||||
@@ -59,16 +61,34 @@ void setOut(cst_tool_result_t *r, const QByteArray &text, int rc) {
|
||||
r->exit_code = rc;
|
||||
}
|
||||
|
||||
/* How long the worker is willing to wait for the GUI thread to pick its work
|
||||
* up. The hop itself is always waited for to the end (the closure writes into
|
||||
* the caller's stack), so what this bounds is the case that makes a turn look
|
||||
* stuck: a GUI thread that is not getting to the event loop at all. */
|
||||
constexpr int kGuiStallMs = 3000;
|
||||
|
||||
/* Anything that touches a Qt GUI object (the clipboard) or an Activity has to
|
||||
* run on the GUI thread; the turn's worker thread waits for it. */
|
||||
* run on the GUI thread; the turn's worker thread waits for it.
|
||||
*
|
||||
* The wait is bounded: a blocking queued call to a GUI thread that is busy
|
||||
* (the session list reading transcripts used to do exactly that) would park
|
||||
* the worker for as long as it lasts. A cheap ping first tells whether the
|
||||
* GUI thread is there at all; only then is the real work handed over and
|
||||
* waited for, which keeps the closure valid for its whole life. */
|
||||
template <typename Fn>
|
||||
void onGuiThread(Fn &&fn) {
|
||||
bool onGuiThread(Fn &&fn) {
|
||||
if (!qApp || QThread::currentThread() == qApp->thread()) {
|
||||
fn();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
auto pinged = std::make_shared<QSemaphore>(0);
|
||||
QMetaObject::invokeMethod(qApp, [pinged] { pinged->release(); },
|
||||
Qt::QueuedConnection);
|
||||
if (!pinged->tryAcquire(1, kGuiStallMs))
|
||||
return false; /* a GUI thread that is not answering: do not wait on it */
|
||||
QMetaObject::invokeMethod(qApp, std::forward<Fn>(fn),
|
||||
Qt::BlockingQueuedConnection);
|
||||
return true;
|
||||
}
|
||||
|
||||
jint sdkInt() {
|
||||
|
||||
Reference in New Issue
Block a user