@songsid/agend 2.1.4-beta.5 → 2.1.4-beta.7
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/codex.js +9 -3
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/opencode.d.ts +27 -11
- package/dist/backend/opencode.js +48 -37
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +4 -0
- package/dist/backend/types.js +7 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/types.d.ts +1 -1
- package/dist/classic-channel-manager.d.ts +34 -3
- package/dist/classic-channel-manager.js +190 -31
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/config-validator.js +32 -7
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.js +35 -3
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-context.d.ts +5 -0
- package/dist/fleet-manager.d.ts +4 -0
- package/dist/fleet-manager.js +83 -12
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.js +22 -0
- package/dist/locale.js.map +1 -1
- package/dist/topic-commands.d.ts +2 -0
- package/dist/topic-commands.js +13 -4
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/fleet-context.d.ts
CHANGED
|
@@ -100,4 +100,9 @@ export interface FleetContext {
|
|
|
100
100
|
applyEffort?(instanceName: string, level: string): Promise<string>;
|
|
101
101
|
/** TG inline-keyboard effort menu; same contract as promptModelMenu. */
|
|
102
102
|
promptEffortMenu?(instanceName: string, userId: string, channelId: string, adapter: import("./channel/types.js").ChannelAdapter, chatId: string, threadId?: string): Promise<string | null>;
|
|
103
|
+
/**
|
|
104
|
+
* Post a nonce-armed confirmation before the destructive `/clear` command.
|
|
105
|
+
* Returns null when the buttons were posted, otherwise a user-facing error.
|
|
106
|
+
*/
|
|
107
|
+
promptClearConfirmation?(instanceName: string, channelId: string, adapter: import("./channel/types.js").ChannelAdapter, chatId: string, threadId?: string): Promise<string | null>;
|
|
103
108
|
}
|
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -563,6 +563,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
563
563
|
private consumeNonceCallback;
|
|
564
564
|
/** Collapse a consumed prompt's buttons into a final status line. */
|
|
565
565
|
private retireNonceButtons;
|
|
566
|
+
/** Post the destructive `/clear` confirmation in the invoking channel. */
|
|
567
|
+
promptClearConfirmation(instanceName: string, channelId: string, adapter: ChannelAdapter, chatId: string, threadId?: string): Promise<string | null>;
|
|
568
|
+
/** Consume `/clear` Confirm/Cancel exactly once; only Confirm reaches IPC. */
|
|
569
|
+
private handleClearConfirmation;
|
|
566
570
|
/**
|
|
567
571
|
* Drop every pending prompt that refers to an instance being stopped or
|
|
568
572
|
* restarted — a restart offer for an instance the operator just restarted
|
package/dist/fleet-manager.js
CHANGED
|
@@ -30,7 +30,7 @@ import { processAttachments } from "./channel/attachment-handler.js";
|
|
|
30
30
|
import { routeToolCall } from "./channel/tool-router.js";
|
|
31
31
|
import { Scheduler } from "./scheduler/index.js";
|
|
32
32
|
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
33
|
-
import { TopicCommands, saveCommandForBackend, parseSaveFilename, parsePauseWakeCommand, SAVE_FILENAME_RE, SAVE_UNSUPPORTED_MSG, resolveInstanceContext, forgetInstanceContext } from "./topic-commands.js";
|
|
33
|
+
import { TopicCommands, saveCommandForBackend, parseSaveFilename, parsePauseWakeCommand, SAVE_FILENAME_RE, SAVE_UNSUPPORTED_MSG, CLEAR_UNSUPPORTED_MSG, resolveInstanceContext, forgetInstanceContext } from "./topic-commands.js";
|
|
34
34
|
import { DailySummary } from "./daily-summary.js";
|
|
35
35
|
import { WebhookEmitter } from "./webhook-emitter.js";
|
|
36
36
|
import { TmuxControlClient } from "./tmux-control.js";
|
|
@@ -175,7 +175,9 @@ const EFFORT_SELECT_CALLBACK_PREFIX = "effort-select:";
|
|
|
175
175
|
const INTERACTIVE_ASSIST_CALLBACK_PREFIX = "interactive-assist:";
|
|
176
176
|
const EXIT_RESTART_CALLBACK_PREFIX = "exit-restart:";
|
|
177
177
|
const HANG_CALLBACK_PREFIX = "hang:";
|
|
178
|
-
|
|
178
|
+
const CLEAR_CONFIRM_CALLBACK_PREFIX = "clear-confirm:";
|
|
179
|
+
const CLEAR_CONFIRM_TIMEOUT_MS = 15_000;
|
|
180
|
+
/** Default lifetime for long-lived nonce prompts (clear overrides this to 15s). */
|
|
179
181
|
const NONCE_BUTTON_TIMEOUT_MS = 15 * 60_000;
|
|
180
182
|
const CLI_ENV_TTL_MS = 24 * 60 * 60 * 1000; // /model reads cached CLI env within 24h
|
|
181
183
|
export class FleetManager {
|
|
@@ -600,7 +602,14 @@ export class FleetManager {
|
|
|
600
602
|
await data.respond(t("classic.no_agent"));
|
|
601
603
|
return;
|
|
602
604
|
}
|
|
603
|
-
|
|
605
|
+
const adapter = this.adapters.get(adapterId) ?? this.adapter;
|
|
606
|
+
if (!adapter) {
|
|
607
|
+
await data.respond(t("clear.prompt_unavailable"));
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const chatId = String(this.getChannelConfig(adapterId)?.group_id ?? data.channelId);
|
|
611
|
+
const fallback = await this.promptClearConfirmation(name, data.channelId, adapter, chatId, data.channelId);
|
|
612
|
+
await data.respond(fallback ?? t("clear.confirm_posted"));
|
|
604
613
|
}
|
|
605
614
|
/** Get the adapter bound to an instance, falling back to primary adapter */
|
|
606
615
|
getAdapterForInstance(name) {
|
|
@@ -1482,9 +1491,8 @@ export class FleetManager {
|
|
|
1482
1491
|
// routing does NOT go through the routing engine (single-key, can't hold two
|
|
1483
1492
|
// bots in one channel) — it resolves per-bot via getInstanceByChannel.
|
|
1484
1493
|
this.classicChannels = new ClassicChannelManager(this.dataDir, this.logger);
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
this.classicChannels.setPrimaryAdapterId(primaryCh.id ?? primaryCh.type);
|
|
1494
|
+
const classicAdapters = fleet.channels?.length ? fleet.channels : (fleet.channel ? [fleet.channel] : []);
|
|
1495
|
+
this.classicChannels.configureAdapters(classicAdapters);
|
|
1488
1496
|
// Restore the persisted bot binding so replies/cancel go through the right
|
|
1489
1497
|
// bot after a restart (before this, inbound would re-bind lazily).
|
|
1490
1498
|
for (const ch of this.classicChannels.getAll()) {
|
|
@@ -2120,6 +2128,8 @@ export class FleetManager {
|
|
|
2120
2128
|
await this.handleInboundReaction(r);
|
|
2121
2129
|
}, this.logger, "adapter.reaction"));
|
|
2122
2130
|
this.adapter.on("callback_query", safeHandler(async (data) => {
|
|
2131
|
+
if (await this.handleClearConfirmation(data, adapterId, this.adapter ?? undefined))
|
|
2132
|
+
return;
|
|
2123
2133
|
if (await this.handleExitRestartPrompt(data, adapterId, this.adapter ?? undefined))
|
|
2124
2134
|
return;
|
|
2125
2135
|
if (await this.handleInteractivePromptAssist(data, adapterId, this.adapter ?? undefined))
|
|
@@ -2410,6 +2420,8 @@ export class FleetManager {
|
|
|
2410
2420
|
await this.handleInboundReaction(r);
|
|
2411
2421
|
}, this.logger, `adapter[${adapterId}].reaction`));
|
|
2412
2422
|
adapter.on("callback_query", safeHandler(async (data) => {
|
|
2423
|
+
if (await this.handleClearConfirmation(data, adapterId, adapter))
|
|
2424
|
+
return;
|
|
2413
2425
|
if (await this.handleExitRestartPrompt(data, adapterId, adapter))
|
|
2414
2426
|
return;
|
|
2415
2427
|
if (await this.handleInteractivePromptAssist(data, adapterId, adapter))
|
|
@@ -3190,7 +3202,11 @@ export class FleetManager {
|
|
|
3190
3202
|
await msgAdapter?.sendText(chatId, t("classic.no_agent_start"));
|
|
3191
3203
|
return;
|
|
3192
3204
|
}
|
|
3193
|
-
|
|
3205
|
+
if (!msgAdapter)
|
|
3206
|
+
return;
|
|
3207
|
+
const fallback = await this.promptClearConfirmation(clearName, chatId, msgAdapter, chatId);
|
|
3208
|
+
if (fallback)
|
|
3209
|
+
await msgAdapter.sendText(chatId, fallback);
|
|
3194
3210
|
return;
|
|
3195
3211
|
}
|
|
3196
3212
|
// Handle /cancel command
|
|
@@ -4490,12 +4506,12 @@ export class FleetManager {
|
|
|
4490
4506
|
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send notification (no topic)"));
|
|
4491
4507
|
}
|
|
4492
4508
|
}
|
|
4493
|
-
// ── Nonce-armed button prompts (hang /
|
|
4509
|
+
// ── Nonce-armed button prompts (hang / assist / exit / clear) ──
|
|
4494
4510
|
//
|
|
4495
4511
|
// One shared lifecycle for every "notification with decision buttons":
|
|
4496
|
-
// post with a 128-bit nonce, arm a
|
|
4512
|
+
// post with a 128-bit nonce, arm a bounded expiry, bind the click to the
|
|
4497
4513
|
// exact adapter+chat+thread+message that created it, require fleet admin,
|
|
4498
|
-
// consume exactly once. The
|
|
4514
|
+
// consume exactly once. The features differ only in what they post
|
|
4499
4515
|
// and what a consumed click does.
|
|
4500
4516
|
/**
|
|
4501
4517
|
* Post decision buttons whose callback ids are `<prefix><nonce>:<action>`.
|
|
@@ -4577,12 +4593,16 @@ export class FleetManager {
|
|
|
4577
4593
|
}
|
|
4578
4594
|
// Bind the capability to the exact message/world that created it. Telegram
|
|
4579
4595
|
// keyboards are visible to everyone, so the click also requires fleet admin.
|
|
4596
|
+
const isAuthorized = data.userId
|
|
4597
|
+
? pending.authChannelId
|
|
4598
|
+
? this.isModelAdmin(data.userId, pending.authChannelId, callbackAdapterId)
|
|
4599
|
+
: this.isFleetAdmin(data.userId, callbackAdapterId)
|
|
4600
|
+
: false;
|
|
4580
4601
|
if (pending.adapterId !== callbackAdapterId
|
|
4581
4602
|
|| data.chatId !== pending.chatId
|
|
4582
4603
|
|| (pending.threadId != null && data.threadId !== pending.threadId)
|
|
4583
4604
|
|| (pending.messageId != null && data.messageId !== pending.messageId)
|
|
4584
|
-
|| !
|
|
4585
|
-
|| !this.isFleetAdmin(data.userId, callbackAdapterId)) {
|
|
4605
|
+
|| !isAuthorized) {
|
|
4586
4606
|
// Deliberately does NOT consume the nonce: the real admin can still click.
|
|
4587
4607
|
this.logger.warn({ instanceName: pending.instanceName, prefix, userId: data.userId }, "Rejected unauthorized or mismatched button callback");
|
|
4588
4608
|
return "consumed";
|
|
@@ -4609,6 +4629,57 @@ export class FleetManager {
|
|
|
4609
4629
|
this.logger.warn({ err, instanceName: pending.instanceName }, "Failed to retire prompt buttons");
|
|
4610
4630
|
}
|
|
4611
4631
|
}
|
|
4632
|
+
/** Post the destructive `/clear` confirmation in the invoking channel. */
|
|
4633
|
+
async promptClearConfirmation(instanceName, channelId, adapter, chatId, threadId) {
|
|
4634
|
+
if (!this.topicCommands.supportsClear(instanceName))
|
|
4635
|
+
return CLEAR_UNSUPPORTED_MSG;
|
|
4636
|
+
const adapterId = adapter.id;
|
|
4637
|
+
if (!adapterId)
|
|
4638
|
+
return t("clear.prompt_unavailable");
|
|
4639
|
+
const nonce = await this.postNonceButtonPrompt({
|
|
4640
|
+
prefix: CLEAR_CONFIRM_CALLBACK_PREFIX,
|
|
4641
|
+
alertType: "clear_confirm",
|
|
4642
|
+
instanceName,
|
|
4643
|
+
adapter,
|
|
4644
|
+
adapterId,
|
|
4645
|
+
chatId,
|
|
4646
|
+
threadId,
|
|
4647
|
+
message: t("clear.confirm_message", instanceName),
|
|
4648
|
+
choices: [
|
|
4649
|
+
{ action: "confirm", label: t("clear.confirm") },
|
|
4650
|
+
{ action: "cancel", label: t("clear.cancel") },
|
|
4651
|
+
],
|
|
4652
|
+
expiredText: t("clear.expired", instanceName),
|
|
4653
|
+
extra: { authChannelId: channelId },
|
|
4654
|
+
timeoutMs: CLEAR_CONFIRM_TIMEOUT_MS,
|
|
4655
|
+
});
|
|
4656
|
+
return nonce ? null : t("clear.prompt_unavailable");
|
|
4657
|
+
}
|
|
4658
|
+
/** Consume `/clear` Confirm/Cancel exactly once; only Confirm reaches IPC. */
|
|
4659
|
+
async handleClearConfirmation(data, callbackAdapterId, receivingAdapter) {
|
|
4660
|
+
const claimed = this.consumeNonceCallback(CLEAR_CONFIRM_CALLBACK_PREFIX, /^clear-confirm:([0-9a-f]+):(confirm|cancel)$/, data, callbackAdapterId, receivingAdapter);
|
|
4661
|
+
if (claimed === null)
|
|
4662
|
+
return false;
|
|
4663
|
+
if (claimed === "consumed")
|
|
4664
|
+
return true;
|
|
4665
|
+
const { entry: pending, action } = claimed;
|
|
4666
|
+
this.eventLog?.insert(pending.instanceName, "clear_action", { action, userId: data.userId });
|
|
4667
|
+
if (action === "cancel") {
|
|
4668
|
+
await this.retireNonceButtons(pending, pending.messageId ?? data.messageId, t("clear.cancelled", pending.instanceName));
|
|
4669
|
+
return true;
|
|
4670
|
+
}
|
|
4671
|
+
await this.retireNonceButtons(pending, pending.messageId ?? data.messageId, t("clear.clearing", pending.instanceName));
|
|
4672
|
+
try {
|
|
4673
|
+
const result = await this.topicCommands.sendClear(pending.instanceName);
|
|
4674
|
+
await pending.adapter.editMessage(pending.chatId, pending.messageId ?? data.messageId, result, pending.threadId);
|
|
4675
|
+
}
|
|
4676
|
+
catch (err) {
|
|
4677
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4678
|
+
this.logger.error({ err, instanceName: pending.instanceName }, "Clear command failed");
|
|
4679
|
+
await pending.adapter.editMessage(pending.chatId, pending.messageId ?? data.messageId, t("clear.failed", pending.instanceName, message), pending.threadId).catch(editErr => this.logger.warn({ err: editErr, instanceName: pending.instanceName }, "Failed to show clear command error"));
|
|
4680
|
+
}
|
|
4681
|
+
return true;
|
|
4682
|
+
}
|
|
4612
4683
|
/**
|
|
4613
4684
|
* Drop every pending prompt that refers to an instance being stopped or
|
|
4614
4685
|
* restarted — a restart offer for an instance the operator just restarted
|