@songsid/agend 2.1.4-beta.21 → 2.1.4-beta.22
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/fleet-context.d.ts +2 -0
- package/dist/fleet-manager.d.ts +6 -3
- package/dist/fleet-manager.js +66 -19
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.js +4 -0
- package/dist/locale.js.map +1 -1
- package/dist/scheduler/db.d.ts +7 -0
- package/dist/scheduler/db.js +24 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/tips.d.ts +5 -1
- package/dist/tips.js +13 -9
- package/dist/tips.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-context.d.ts
CHANGED
|
@@ -58,6 +58,8 @@ export interface FleetContext {
|
|
|
58
58
|
getDeliveryEpoch?(name: string): number;
|
|
59
59
|
/** Subscription provider IDs used by running or paused fleet/Classic instances. */
|
|
60
60
|
getActiveUsageProviderIds?(): ReadonlySet<string>;
|
|
61
|
+
/** Effective backend IDs used by running or paused fleet/Classic instances. */
|
|
62
|
+
getActiveBackendIds?(): ReadonlySet<string>;
|
|
61
63
|
toggleFleetCollab(instanceName: string): boolean;
|
|
62
64
|
/** Apply a model to an instance (runtime paste or persist + restart). Returns a status string. */
|
|
63
65
|
applyModel(instanceName: string, model: string): Promise<string>;
|
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -289,6 +289,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
289
289
|
* Classic channels use their effective backend merge chain, not the raw row.
|
|
290
290
|
*/
|
|
291
291
|
getActiveUsageProviderIds(): ReadonlySet<string>;
|
|
292
|
+
/** Effective backends with a running or persisted-paused fleet/Classic instance. */
|
|
293
|
+
getActiveBackendIds(): ReadonlySet<string>;
|
|
292
294
|
isClassicInstance(name: string): boolean;
|
|
293
295
|
private cacheInstanceExecutionState;
|
|
294
296
|
private cancelIdleButtonRetirement;
|
|
@@ -600,13 +602,14 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
600
602
|
*/
|
|
601
603
|
private postNonceButtonPrompt;
|
|
602
604
|
/**
|
|
603
|
-
*
|
|
605
|
+
* Validate a nonce-armed callback and optionally claim it exactly once.
|
|
604
606
|
*
|
|
605
607
|
* Returns:
|
|
606
608
|
* - null — the callback is not for this prefix; try the next handler
|
|
607
609
|
* - "consumed" — for this prefix but stale/denied/malformed; stop dispatch
|
|
608
|
-
* - the entry+action — the click is authorized
|
|
609
|
-
*
|
|
610
|
+
* - the entry+action — the click is authorized; consuming actions are
|
|
611
|
+
* claimed before any await, while explicitly non-consuming actions keep
|
|
612
|
+
* the prompt available for a later decision
|
|
610
613
|
*
|
|
611
614
|
* A stale click (expired nonce, or a pre-upgrade button whose payload no
|
|
612
615
|
* longer parses) collapses the clicked message so the dead button stops
|
package/dist/fleet-manager.js
CHANGED
|
@@ -56,7 +56,7 @@ import { GENERAL_PAUSE_ERROR, isGeneralInstance } from "./general-instance.js";
|
|
|
56
56
|
import { loadOrCreateWebToken, WEB_TOKEN_INVALID_MESSAGE } from "./web-auth.js";
|
|
57
57
|
import { RestartProgress } from "./restart-progress.js";
|
|
58
58
|
import { collectRedundantInstanceDefaultPaths } from "./fleet-yaml-slim.js";
|
|
59
|
-
import { canUnlockAdvancedTips, DailyTipScheduler, selectTip } from "./tips.js";
|
|
59
|
+
import { canUnlockAdvancedTips, DailyTipScheduler, selectTip, visibleTipLevels, } from "./tips.js";
|
|
60
60
|
import { getTmuxSession } from "./config.js";
|
|
61
61
|
export function resolveReplyThreadId(argsThreadId, instanceConfig) {
|
|
62
62
|
if (typeof argsThreadId === "string" && argsThreadId.length > 0) {
|
|
@@ -835,13 +835,22 @@ export class FleetManager {
|
|
|
835
835
|
*/
|
|
836
836
|
getActiveUsageProviderIds() {
|
|
837
837
|
const providers = new Set();
|
|
838
|
+
for (const backend of this.getActiveBackendIds()) {
|
|
839
|
+
const provider = usageProviderIdForBackend(backend);
|
|
840
|
+
if (provider)
|
|
841
|
+
providers.add(provider);
|
|
842
|
+
}
|
|
843
|
+
return providers;
|
|
844
|
+
}
|
|
845
|
+
/** Effective backends with a running or persisted-paused fleet/Classic instance. */
|
|
846
|
+
getActiveBackendIds() {
|
|
847
|
+
const backends = new Set();
|
|
838
848
|
const add = (name, backend) => {
|
|
839
849
|
const status = this.getInstanceStatus(name);
|
|
840
850
|
if (status !== "running" && status !== "paused")
|
|
841
851
|
return;
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
providers.add(provider);
|
|
852
|
+
if (backend)
|
|
853
|
+
backends.add(backend);
|
|
845
854
|
};
|
|
846
855
|
for (const [name, config] of Object.entries(this.fleetConfig?.instances ?? {})) {
|
|
847
856
|
// loadFleetConfig() has already merged the fleet default into each row.
|
|
@@ -850,7 +859,7 @@ export class FleetManager {
|
|
|
850
859
|
for (const channel of this.classicChannels?.getAll() ?? []) {
|
|
851
860
|
add(channel.instanceName, this.classicChannels?.getBackendByInstance(channel.instanceName, this.fleetConfig?.defaults?.backend));
|
|
852
861
|
}
|
|
853
|
-
return
|
|
862
|
+
return backends;
|
|
854
863
|
}
|
|
855
864
|
isClassicInstance(name) {
|
|
856
865
|
return this.classicChannels?.getAll().some(channel => channel.instanceName === name) ?? false;
|
|
@@ -5039,19 +5048,20 @@ export class FleetManager {
|
|
|
5039
5048
|
}
|
|
5040
5049
|
}
|
|
5041
5050
|
/**
|
|
5042
|
-
*
|
|
5051
|
+
* Validate a nonce-armed callback and optionally claim it exactly once.
|
|
5043
5052
|
*
|
|
5044
5053
|
* Returns:
|
|
5045
5054
|
* - null — the callback is not for this prefix; try the next handler
|
|
5046
5055
|
* - "consumed" — for this prefix but stale/denied/malformed; stop dispatch
|
|
5047
|
-
* - the entry+action — the click is authorized
|
|
5048
|
-
*
|
|
5056
|
+
* - the entry+action — the click is authorized; consuming actions are
|
|
5057
|
+
* claimed before any await, while explicitly non-consuming actions keep
|
|
5058
|
+
* the prompt available for a later decision
|
|
5049
5059
|
*
|
|
5050
5060
|
* A stale click (expired nonce, or a pre-upgrade button whose payload no
|
|
5051
5061
|
* longer parses) collapses the clicked message so the dead button stops
|
|
5052
5062
|
* inviting clicks — the same courtesy the cancel button extends.
|
|
5053
5063
|
*/
|
|
5054
|
-
consumeNonceCallback(prefix, actionRe, data, callbackAdapterId, receivingAdapter) {
|
|
5064
|
+
consumeNonceCallback(prefix, actionRe, data, callbackAdapterId, receivingAdapter, consume = true) {
|
|
5055
5065
|
if (!data.callbackData.startsWith(prefix))
|
|
5056
5066
|
return null;
|
|
5057
5067
|
const match = data.callbackData.match(actionRe);
|
|
@@ -5085,11 +5095,13 @@ export class FleetManager {
|
|
|
5085
5095
|
this.logger.warn({ instanceName: pending.instanceName, prefix, userId: data.userId }, "Rejected unauthorized or mismatched button callback");
|
|
5086
5096
|
return "consumed";
|
|
5087
5097
|
}
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5098
|
+
if (consume) {
|
|
5099
|
+
// Claim before any await: double clicks and duplicate callback delivery
|
|
5100
|
+
// can never act twice for state-changing actions.
|
|
5101
|
+
this.pendingNonceButtons.delete(match[1]);
|
|
5102
|
+
if (pending.timer)
|
|
5103
|
+
clearTimeout(pending.timer);
|
|
5104
|
+
}
|
|
5093
5105
|
return { entry: pending, action: match[2] };
|
|
5094
5106
|
}
|
|
5095
5107
|
/** Collapse a consumed prompt's buttons into a final status line. */
|
|
@@ -5128,7 +5140,9 @@ export class FleetManager {
|
|
|
5128
5140
|
}
|
|
5129
5141
|
pickAvailableTip() {
|
|
5130
5142
|
const state = this.readTipState();
|
|
5131
|
-
return state
|
|
5143
|
+
return state
|
|
5144
|
+
? selectTip(state.dismissed, Math.random, state.advancedUnlocked, this.getActiveBackendIds())
|
|
5145
|
+
: null;
|
|
5132
5146
|
}
|
|
5133
5147
|
tipText(tip) {
|
|
5134
5148
|
return getLocale() === "zh-TW" ? tip.text_zh : tip.text_en;
|
|
@@ -5158,12 +5172,14 @@ export class FleetManager {
|
|
|
5158
5172
|
const state = this.readTipState();
|
|
5159
5173
|
if (!state)
|
|
5160
5174
|
return "unavailable";
|
|
5161
|
-
if (!state.advancedUnlocked
|
|
5175
|
+
if (!state.advancedUnlocked
|
|
5176
|
+
&& visibleTipLevels(true).has("advanced")
|
|
5177
|
+
&& canUnlockAdvancedTips(state.dismissed)) {
|
|
5162
5178
|
return await this.promptAdvancedTipUnlock(generalName, adapter, chatId, threadId)
|
|
5163
5179
|
? "posted"
|
|
5164
5180
|
: "unavailable";
|
|
5165
5181
|
}
|
|
5166
|
-
const tip = selectTip(state.dismissed, Math.random, state.advancedUnlocked);
|
|
5182
|
+
const tip = selectTip(state.dismissed, Math.random, state.advancedUnlocked, this.getActiveBackendIds());
|
|
5167
5183
|
if (!tip)
|
|
5168
5184
|
return this.scheduler ? "empty" : "unavailable";
|
|
5169
5185
|
const nonce = await this.postNonceButtonPrompt({
|
|
@@ -5175,7 +5191,10 @@ export class FleetManager {
|
|
|
5175
5191
|
chatId,
|
|
5176
5192
|
threadId,
|
|
5177
5193
|
message: this.formatTip(tip),
|
|
5178
|
-
choices: [
|
|
5194
|
+
choices: [
|
|
5195
|
+
{ action: "dismiss", label: t("tips.dismiss") },
|
|
5196
|
+
{ action: "confused", label: t("tips.confused") },
|
|
5197
|
+
],
|
|
5179
5198
|
// Expiry removes only the stale button; the useful tip remains readable.
|
|
5180
5199
|
expiredText: this.formatTip(tip),
|
|
5181
5200
|
extra: { allowAnyUser: true, tipId: tip.id },
|
|
@@ -5217,12 +5236,40 @@ export class FleetManager {
|
|
|
5217
5236
|
}
|
|
5218
5237
|
}
|
|
5219
5238
|
async handleTipDismiss(data, callbackAdapterId, receivingAdapter) {
|
|
5220
|
-
const
|
|
5239
|
+
const isConfused = data.callbackData.endsWith(":confused");
|
|
5240
|
+
const claimed = this.consumeNonceCallback(TIP_DISMISS_CALLBACK_PREFIX, /^tip-dismiss:([0-9a-f]+):(dismiss|confused)$/, data, callbackAdapterId, receivingAdapter, !isConfused);
|
|
5221
5241
|
if (claimed === null)
|
|
5222
5242
|
return false;
|
|
5223
5243
|
if (claimed === "consumed")
|
|
5224
5244
|
return true;
|
|
5225
5245
|
const { entry: pending } = claimed;
|
|
5246
|
+
if (claimed.action === "confused") {
|
|
5247
|
+
if (!pending.tipId || !data.userId || !this.scheduler) {
|
|
5248
|
+
this.logger.error({ tipId: pending.tipId, userId: data.userId }, "Tip feedback has incomplete persistence context");
|
|
5249
|
+
await pending.adapter.sendText(pending.chatId, t("tips.unavailable"), { threadId: pending.threadId });
|
|
5250
|
+
return true;
|
|
5251
|
+
}
|
|
5252
|
+
try {
|
|
5253
|
+
this.scheduler.db.recordTipFeedback(data.userId, pending.tipId, "confused");
|
|
5254
|
+
}
|
|
5255
|
+
catch (err) {
|
|
5256
|
+
this.logger.warn({ err, tipId: pending.tipId, userId: data.userId }, "Failed to persist tip feedback");
|
|
5257
|
+
await pending.adapter.sendText(pending.chatId, t("tips.unavailable"), { threadId: pending.threadId }).catch(() => { });
|
|
5258
|
+
return true;
|
|
5259
|
+
}
|
|
5260
|
+
this.logger.info({
|
|
5261
|
+
tipId: pending.tipId,
|
|
5262
|
+
userId: data.userId,
|
|
5263
|
+
feedbackType: "confused",
|
|
5264
|
+
}, "Tip feedback recorded");
|
|
5265
|
+
this.eventLog?.insert(pending.instanceName, "tip_feedback", {
|
|
5266
|
+
tipId: pending.tipId,
|
|
5267
|
+
userId: data.userId,
|
|
5268
|
+
feedbackType: "confused",
|
|
5269
|
+
});
|
|
5270
|
+
await pending.adapter.sendText(pending.chatId, t("tips.feedback_recorded"), { threadId: pending.threadId }).catch(err => this.logger.warn({ err, tipId: pending.tipId }, "Failed to acknowledge tip feedback"));
|
|
5271
|
+
return true;
|
|
5272
|
+
}
|
|
5226
5273
|
if (!pending.tipId || !data.userId || !this.scheduler) {
|
|
5227
5274
|
this.logger.error({ tipId: pending.tipId, userId: data.userId }, "Tip dismissal has incomplete persistence context");
|
|
5228
5275
|
await this.retireNonceButtons(pending, pending.messageId ?? data.messageId, t("tips.unavailable"));
|