@songsid/agend 2.1.2-beta.29 → 2.1.2-beta.30
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.
- package/dist/backend/claude-code.d.ts +20 -0
- package/dist/backend/claude-code.js +21 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/daemon.d.ts +21 -1
- package/dist/daemon.js +36 -5
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +18 -0
- package/dist/fleet-manager.js +54 -21
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.js +2 -0
- package/dist/locale.js.map +1 -1
- package/dist/usage/statusline-usage.js +17 -5
- package/dist/usage/statusline-usage.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -111,6 +111,8 @@ const STATE_REPORT_STALE_MS = 30 * 60_000;
|
|
|
111
111
|
* wreckage, stuck or not.
|
|
112
112
|
*/
|
|
113
113
|
const CANCEL_BTN_MAX_LIFETIME_MS = 24 * 60 * 60_000;
|
|
114
|
+
/** A click on a button the fleet no longer tracks may fire at most this often. */
|
|
115
|
+
const STALE_CANCEL_CLICK_COOLDOWN_MS = 10_000;
|
|
114
116
|
/** Orphaned-button ledger, swept at startup. Lives in the fleet data dir. */
|
|
115
117
|
const CANCEL_BTN_LEDGER_FILE = "cancel-buttons.json";
|
|
116
118
|
/**
|
|
@@ -237,6 +239,8 @@ export class FleetManager {
|
|
|
237
239
|
// Model failover state
|
|
238
240
|
failoverActive = new Map(); // instance → current failover model
|
|
239
241
|
// IPC reconnect: tracks instances being intentionally stopped (skip reconnect)
|
|
242
|
+
/** instance → when a click with no live button entry last fired a cancel. */
|
|
243
|
+
staleCancelClickAt = new Map();
|
|
240
244
|
ipcStoppingInstances = new Set();
|
|
241
245
|
/** Set the moment a graceful stop begins — see isPlannedRestart(). */
|
|
242
246
|
shuttingDown = false;
|
|
@@ -1734,12 +1738,7 @@ export class FleetManager {
|
|
|
1734
1738
|
return;
|
|
1735
1739
|
}
|
|
1736
1740
|
if (data.callbackData.startsWith("cancel:")) {
|
|
1737
|
-
|
|
1738
|
-
// Idempotent: a button click only acts while the button is live. A
|
|
1739
|
-
// second click (entry already cleared) is a no-op — don't re-send the
|
|
1740
|
-
// interrupt key. (The /cancel command path calls cancelInstance directly.)
|
|
1741
|
-
if (this.hasCancelButton(instanceName))
|
|
1742
|
-
this.cancelInstance(instanceName);
|
|
1741
|
+
this.handleCancelClick(data.callbackData.slice("cancel:".length), this.adapter, data);
|
|
1743
1742
|
return;
|
|
1744
1743
|
}
|
|
1745
1744
|
}, this.logger, "adapter.callback_query"));
|
|
@@ -2046,10 +2045,7 @@ export class FleetManager {
|
|
|
2046
2045
|
return;
|
|
2047
2046
|
}
|
|
2048
2047
|
if (data.callbackData.startsWith("cancel:")) {
|
|
2049
|
-
|
|
2050
|
-
// Idempotent: only the first click (while the button is live) acts.
|
|
2051
|
-
if (this.hasCancelButton(instanceName))
|
|
2052
|
-
this.cancelInstance(instanceName);
|
|
2048
|
+
this.handleCancelClick(data.callbackData.slice("cancel:".length), adapter, data);
|
|
2053
2049
|
return;
|
|
2054
2050
|
}
|
|
2055
2051
|
}, this.logger, `adapter[${adapterId}].callback_query`));
|
|
@@ -4141,6 +4137,40 @@ export class FleetManager {
|
|
|
4141
4137
|
await data.respond(t("save.sent", cmd, target.name));
|
|
4142
4138
|
}
|
|
4143
4139
|
/** Whether the instance currently has at least one live cancel button. */
|
|
4140
|
+
/**
|
|
4141
|
+
* A click on a cancel button, whether or not the fleet still tracks it.
|
|
4142
|
+
*
|
|
4143
|
+
* The old rule was "act only while an entry is live", which made a click on a
|
|
4144
|
+
* button the fleet had forgotten a silent no-op — no cancel, no message, not
|
|
4145
|
+
* even a log line. That is indistinguishable from a broken button, and it is
|
|
4146
|
+
* what the "按鈕點了沒反應" reports were: the entry is briefly absent while a
|
|
4147
|
+
* button is being replaced, and a delete that fails leaves the message on
|
|
4148
|
+
* screen with no entry at all.
|
|
4149
|
+
*
|
|
4150
|
+
* So: honour the click if the instance is actually running, and say so plainly
|
|
4151
|
+
* if it is not. The stale-click path is rate-limited because the original
|
|
4152
|
+
* concern was real — a second click must not fire a second interrupt key at an
|
|
4153
|
+
* instance that has already started a new turn.
|
|
4154
|
+
*/
|
|
4155
|
+
handleCancelClick(instanceName, adapter, data) {
|
|
4156
|
+
if (this.hasCancelButton(instanceName)) {
|
|
4157
|
+
this.cancelInstance(instanceName);
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
4160
|
+
const lastAt = this.staleCancelClickAt.get(instanceName) ?? 0;
|
|
4161
|
+
if (Date.now() - lastAt < STALE_CANCEL_CLICK_COOLDOWN_MS)
|
|
4162
|
+
return;
|
|
4163
|
+
this.staleCancelClickAt.set(instanceName, Date.now());
|
|
4164
|
+
// cancelInstance returns false when there is no daemon — i.e. nothing to
|
|
4165
|
+
// cancel, which is the one case where the button really is dead.
|
|
4166
|
+
if (this.cancelInstance(instanceName)) {
|
|
4167
|
+
this.logger.info({ instanceName }, "Cancel click honoured with no live button entry");
|
|
4168
|
+
return;
|
|
4169
|
+
}
|
|
4170
|
+
this.logger.info({ instanceName }, "Cancel click on an expired button — instance not running");
|
|
4171
|
+
adapter?.editMessage(data.chatId, data.messageId, t("cancel.button_stale", instanceName), data.threadId)
|
|
4172
|
+
.catch(() => { });
|
|
4173
|
+
}
|
|
4144
4174
|
hasCancelButton(instanceName) {
|
|
4145
4175
|
for (const e of this.cancelButtons.values()) {
|
|
4146
4176
|
if (e.instanceName === instanceName)
|
|
@@ -4149,10 +4179,12 @@ export class FleetManager {
|
|
|
4149
4179
|
return false;
|
|
4150
4180
|
}
|
|
4151
4181
|
async sendCancelButton(instanceName, correlationId) {
|
|
4152
|
-
//
|
|
4153
|
-
//
|
|
4154
|
-
//
|
|
4155
|
-
|
|
4182
|
+
// Post first, retire after (see the tail of this method). Retiring up front
|
|
4183
|
+
// meant that from the delete until the new message came back — a chat API
|
|
4184
|
+
// round trip, and every reply goes through here — the instance had NO live
|
|
4185
|
+
// entry, while the old button was still on screen. A click in that window
|
|
4186
|
+
// hit `hasCancelButton() === false` and was silently dropped: the reported
|
|
4187
|
+
// "按鈕失效". If notifyAlert then failed, the button was simply gone.
|
|
4156
4188
|
const adapter = this.getAdapterForInstance(instanceName) ?? this.adapter;
|
|
4157
4189
|
if (!adapter)
|
|
4158
4190
|
return;
|
|
@@ -4191,13 +4223,6 @@ export class FleetManager {
|
|
|
4191
4223
|
message: "👀 處理中…",
|
|
4192
4224
|
choices: [{ id: `cancel:${instanceName}`, label: t("cancel.button") }],
|
|
4193
4225
|
}, threadId ? { threadId } : undefined);
|
|
4194
|
-
// A concurrent sendCancelButton for the same instance may have posted its
|
|
4195
|
-
// own button while we awaited notifyAlert. Retire any other buttons for
|
|
4196
|
-
// this instance (not the one we just posted) so only the newest shows.
|
|
4197
|
-
for (const other of this.cancelButtons.values()) {
|
|
4198
|
-
if (other.instanceName === instanceName)
|
|
4199
|
-
this.retireButton(other);
|
|
4200
|
-
}
|
|
4201
4226
|
const entry = {
|
|
4202
4227
|
instanceName,
|
|
4203
4228
|
adapterId,
|
|
@@ -4241,6 +4266,14 @@ export class FleetManager {
|
|
|
4241
4266
|
});
|
|
4242
4267
|
}, CANCEL_BTN_IDLE_CHECK_INTERVAL_MS);
|
|
4243
4268
|
this.cancelButtons.set(sent.messageId, entry);
|
|
4269
|
+
// Only now: at most one button per instance, but never zero. Covers both
|
|
4270
|
+
// the previous turn's button and any button a concurrent
|
|
4271
|
+
// sendCancelButton posted while we were awaiting notifyAlert.
|
|
4272
|
+
for (const other of [...this.cancelButtons.values()]) {
|
|
4273
|
+
if (other.instanceName === instanceName && other.messageId !== sent.messageId) {
|
|
4274
|
+
this.retireButton(other);
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4244
4277
|
this.persistCancelButtons();
|
|
4245
4278
|
this.logger.info({ instanceName, messageId: sent.messageId }, "Cancel button sent");
|
|
4246
4279
|
}
|