Skip to content

Commit 66a0e96

Browse files
chore: release pass fixes (crash guards, clang-tidy, README sync)
- Model picker menu lives on the heap so a combo destroyed while it is open no longer double-destroys a stack QMenu. - PM2 banner buttons run a copy of their action: updateBanner() replaced the stored std::function while it was executing. - clang-tidy: no-automatic-move, enum-size, implicit-conversion-in-loop, and move the pending entry out before erasing it. - README: PM2 Processes dialog and Prewalk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent e144e8b commit 66a0e96

6 files changed

Lines changed: 36 additions & 25 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ You shouldn't have to leave your editor to talk to an AI, run git, or open a she
1010

1111
NotepadAI speaks the Agent Client Protocol (ACP) over stdio. Claude Code and Codex ship as factory agents and can be edited or deleted in Settings. You can also add any other ACP-compatible agent, including Gemini, Auggie, or your own command. Agents read and write files, run terminal commands, and see your workspace context, so they work on the same code you do. Start a session from the AI menu or by right-clicking a folder in the file tree.
1212

13-
In a chat, a finished turn folds into a single "Worked for 2m14s" row; click it to bring back the tool calls and intermediate messages. Tool calls show how long they took. Claude Code background tasks and the wake-ups or cron jobs it schedules for itself appear under the transcript, with a Stop link for tasks that can be stopped. When the agent offers `/advisor`, an Advisor checkbox in the composer turns it on and off. When an agent needs a decision, its questions appear as a form in the chat with the suggested answer preselected, and a note box at the end takes anything else you want to add.
13+
In a chat, a finished turn folds into a single "Worked for 2m14s" row; click it to bring back the tool calls and intermediate messages. Tool calls show how long they took. Claude Code background tasks and the wake-ups or cron jobs it schedules for itself appear under the transcript, with a Stop link for tasks that can be stopped. When the agent offers `/advisor`, an Advisor checkbox in the composer turns it on and off. For Claude Code, Prewalk (at the top of the model picker) switches to a cheaper model once the agent starts editing files and back when the turn ends. When an agent needs a decision, its questions appear as a form in the chat with the suggested answer preselected, and a note box at the end takes anything else you want to add.
1414

1515
There is also a Goal Agent: pick an ACP judge (Claude Code, Codex, Opencode) or a Custom API endpoint that speaks Anthropic's Messages API. You set success criteria and it drives the working agent until those criteria hold, or attach a goal to an existing conversation. Criteria presets can be saved globally, or into the current project's `.agents/.goals` folder so they travel with the repo; files already in that folder show up in the load list. A judge that needs a human action, or that hits the iteration cap with the criterion still unmet, stops and hands the criterion back instead of marking the goal done. With Prefix /goal checked, the first criterion is sent immediately when the agent supports `/goal`. If a reply is still running and the agent does not support `/goal`, that goal waits in the send queue so the current reply is not stopped. Later criteria wait until the previous is achieved. Send with Goal defaults to the agent's own `/goal` command instead: each criterion goes out on its own, with no separate judge. A `/goal` typed in the composer is sent while a turn is still running only when the agent supports that command; otherwise it waits in the queue. A goal is not judged, and auto compact does not run, while the agent still has background tasks running or a wake-up turn pending. When a goal is achieved, the session can compact automatically. Prompts sent while an agent is still working are queued and delivered when it finishes. AI chats group by project, with a session strip to switch conversations in the same dock. Open chats come back after a restart and resume their agent session on the first prompt. A local Claude Code chat can be moved to another agent from the agent button in the dock's title bar, keeping its transcript. The composer can ask the agent to work in a new git worktree. When you commit, AI can write the commit message from your staged diff. That generator (and other LLM calls) can talk to an OpenAI-compatible endpoint or Anthropic's Messages API. Leave the commit box empty (when not amending) and Commit lets you pick an ACP agent to do the commit. Protocol details and how to wire up a custom ACP agent are in [doc/AcpAgents.md](doc/AcpAgents.md).
1616

@@ -22,6 +22,8 @@ Inline blame, gutter diff markers, commit history, staging and unstaging (includ
2222

2323
A PTY terminal built on libvterm and libptyqt, with mouse reporting and a scrollback buffer. New terminals open as a bottom tab under the editor. When a workspace opens with no terminal, that area shows a placeholder with a New terminal button and the workspace's configured tasks; close it to hide it for that workspace, or turn it off under Preferences → Terminal. The tab strip follows the raised workspace: only terminals whose working directory belongs to that folder (or a nested workspace, longest match) are shown. If any open terminal sits outside every workspace, the filter turns off and every tab stays visible. Closing a workspace closes its terminals. Programs launched from a local terminal share a memory ceiling (16 GB by default, changeable under Preferences → Terminal) so a runaway process is stopped instead of exhausting the machine. A local terminal's title bar shows that process tree's CPU and RAM; hover the CPU figure for each logical core's load. Remote terminals omit those figures. It reads Justfile, Makefile, package.json, and deno.json, finds which tasks you can run, and draws clickable run icons in the editor margin. Open a terminal at the active workspace or at the current file's folder.
2424

25+
Terminal → PM2 Processes lists every process of your local pm2 with live status, uptime, CPU and memory. You can start, stop, restart, or delete them, follow their logs in a terminal tab, and run a new command under pm2 with its own directory, shell, and environment variables. pm2 is fetched through npx if it is not installed.
26+
2527

2628
### Editor
2729

‎src/pm2/Pm2Client.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ QString Pm2Client::helperScriptPath(QString *error)
6969

7070
const QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
7171
+ QStringLiteral("/pm2-helper");
72-
const QString path = dir + QStringLiteral("/pm2-helper.cjs");
72+
QString path = dir + QStringLiteral("/pm2-helper.cjs");
7373
QFile current(path);
7474
if (current.open(QIODevice::ReadOnly) && current.size() == script.size() && current.readAll() == script)
7575
return path;

‎src/pm2/Pm2ProcessModel.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ class Pm2ProcessModel : public QAbstractTableModel
2525
Q_OBJECT
2626

2727
public:
28-
enum class Column : int { Name, Status, Uptime, Restarts, Cpu, Memory, Pid, Cwd, Count };
29-
enum Role : int {
28+
enum class Column : uint8_t { Name, Status, Uptime, Restarts, Cpu, Memory, Pid, Cwd, Count };
29+
enum Role : uint16_t {
3030
SortRole = Qt::UserRole + 1, // numeric value for numeric columns
3131
StatusRole, // Pm2Status as int
3232
};

‎src/pm2/Pm2ProcessesDialog.cpp‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -269,13 +269,15 @@ Pm2ProcessesDialog::Pm2ProcessesDialog(TerminalManager *terminals, std::function
269269
connect(m_restartAllButton, &QToolButton::clicked, this, &Pm2ProcessesDialog::restartAll);
270270
connect(m_stopAllButton, &QToolButton::clicked, this, &Pm2ProcessesDialog::stopAll);
271271
connect(m_resurrectButton, &QToolButton::clicked, this, &Pm2ProcessesDialog::resurrect);
272+
// Run a copy: every action ends in updateBanner(), which replaces the
273+
// stored one while it is still running.
272274
connect(m_bannerPrimary, &QPushButton::clicked, this, [this]() {
273-
if (m_bannerPrimaryAction)
274-
m_bannerPrimaryAction();
275+
if (const std::function<void()> action = m_bannerPrimaryAction)
276+
action();
275277
});
276278
connect(m_bannerSecondary, &QPushButton::clicked, this, [this]() {
277-
if (m_bannerSecondaryAction)
278-
m_bannerSecondaryAction();
279+
if (const std::function<void()> action = m_bannerSecondaryAction)
280+
action();
279281
});
280282
connect(m_table, &QTableView::doubleClicked, this, &Pm2ProcessesDialog::showLogs);
281283
connect(m_table, &QTableView::customContextMenuRequested, this, &Pm2ProcessesDialog::showContextMenu);
@@ -402,10 +404,11 @@ void Pm2ProcessesDialog::track(int requestId, Done done, bool saveAfter, const Q
402404

403405
void Pm2ProcessesDialog::onCommandFinished(int requestId, bool ok, const QString &error)
404406
{
405-
const auto it = m_pending.constFind(requestId);
406-
if (it == m_pending.cend())
407+
const auto it = m_pending.find(requestId);
408+
if (it == m_pending.end())
407409
return;
408-
const Pending finished = *it;
410+
// Moved out: erasing the entry ends the iterator's life.
411+
const Pending finished = std::move(it.value());
409412
m_pending.erase(it);
410413
if (finished.done)
411414
finished.done(ok, error);

‎src/pm2/Pm2Protocol.cpp‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ QStringList stringList(const QJsonArray &a)
5353
{
5454
QStringList out;
5555
out.reserve(a.size());
56-
for (const QJsonValue &v : a)
56+
for (const auto &v : a)
5757
out.append(v.toString());
5858
return out;
5959
}
@@ -108,7 +108,7 @@ Pm2HelperMessage parseLine(QByteArrayView text)
108108
m.kind = Pm2HelperMessage::Kind::List;
109109
const QJsonArray procs = o.value(QLatin1String("procs")).toArray();
110110
m.processes.reserve(procs.size());
111-
for (const QJsonValue &v : procs) {
111+
for (const auto &v : procs) {
112112
Pm2ProcessInfo p = processFromJson(v.toObject());
113113
if (p.pmId >= 0)
114114
m.processes.append(std::move(p));

‎src/widgets/AcpSessionView.cpp‎

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -348,33 +348,39 @@ class MenuComboBox : public QComboBox
348348
{
349349
if (count() == 0)
350350
return;
351-
QMenu menu(this);
352-
menu.setToolTipsVisible(true);
351+
// On the heap: if the combo dies while the menu is open, the menu dies
352+
// with it (a stack menu would then be destroyed twice).
353+
auto *menu = new QMenu(this);
354+
menu->setToolTipsVisible(true);
353355
if (addLeadingEntries)
354-
addLeadingEntries(&menu);
355-
if (!menu.isEmpty())
356-
menu.addSeparator();
357-
auto *items = new QActionGroup(&menu);
356+
addLeadingEntries(menu);
357+
if (!menu->isEmpty())
358+
menu->addSeparator();
359+
auto *items = new QActionGroup(menu);
358360
for (int i = 0; i < count(); ++i) {
359-
QAction *a = menu.addAction(itemText(i));
361+
QAction *a = menu->addAction(itemText(i));
360362
a->setCheckable(true);
361363
a->setChecked(i == currentIndex());
362364
a->setData(itemData(i));
363365
items->addAction(a);
364366
}
365-
menu.setMinimumWidth(width());
367+
menu->setMinimumWidth(width());
366368
// Below the combo, or above it when the screen ends first.
367369
QPoint at = mapToGlobal(QPoint(0, height()));
368370
const QScreen *screen = this->screen();
369-
const int h = menu.sizeHint().height();
371+
const int h = menu->sizeHint().height();
370372
if (screen && at.y() + h > screen->availableGeometry().bottom())
371373
at = mapToGlobal(QPoint(0, 0)) - QPoint(0, h);
372374
const QPointer<MenuComboBox> self(this);
373-
QAction *chosen = menu.exec(at);
375+
QAction *chosen = menu->exec(at);
376+
if (!self)
377+
return;
374378
// The items may have been rebuilt while the menu was open: go by data.
375-
if (!self || !chosen || chosen->actionGroup() != items)
379+
const QVariant data = chosen && chosen->actionGroup() == items ? chosen->data() : QVariant();
380+
delete menu;
381+
if (!data.isValid())
376382
return;
377-
const int index = findData(chosen->data());
383+
const int index = findData(data);
378384
if (index >= 0)
379385
setCurrentIndex(index);
380386
}

0 commit comments

Comments
 (0)