charness 3.0.0 โ 3.2.0
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/.env.example +8 -7
- package/DEVELOPMENT.md +12 -6
- package/MIGRATION_1_TO_2.md +1 -1
- package/MIGRATION_RENAME_TO_CHARNESS.md +133 -0
- package/README.md +221 -555
- package/SETUP.md +539 -0
- package/dist/adapters/claudeCliAdapter.d.ts +16 -1
- package/dist/adapters/claudeCliAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeCliAdapter.js +37 -2
- package/dist/adapters/claudeCliAdapter.js.map +1 -1
- package/dist/adapters/claudeConfigDir.d.ts +40 -0
- package/dist/adapters/claudeConfigDir.d.ts.map +1 -0
- package/dist/adapters/claudeConfigDir.js +3 -0
- package/dist/adapters/claudeConfigDir.js.map +1 -0
- package/dist/adapters/claudeJsonStreamAdapter.d.ts +32 -0
- package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeJsonStreamAdapter.js +90 -8
- package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -1
- package/dist/adapters/claudePlatformFlags.d.ts +4 -4
- package/dist/adapters/claudePlatformFlags.js +4 -4
- package/dist/adapters/createAdapter.d.ts +8 -0
- package/dist/adapters/createAdapter.d.ts.map +1 -1
- package/dist/adapters/createAdapter.js +37 -0
- package/dist/adapters/createAdapter.js.map +1 -1
- package/dist/adapters/openCodeAdapter.d.ts +23 -0
- package/dist/adapters/openCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/openCodeAdapter.js +85 -2
- package/dist/adapters/openCodeAdapter.js.map +1 -1
- package/dist/agentLogin/agentLogin.d.ts +15 -2
- package/dist/agentLogin/agentLogin.d.ts.map +1 -1
- package/dist/agentLogin/agentLogin.js +53 -16
- package/dist/agentLogin/agentLogin.js.map +1 -1
- package/dist/apiErrorRetry.d.ts +8 -3
- package/dist/apiErrorRetry.d.ts.map +1 -1
- package/dist/apiErrorRetry.js +36 -5
- package/dist/apiErrorRetry.js.map +1 -1
- package/dist/apiRetryKick.d.ts +9 -0
- package/dist/apiRetryKick.d.ts.map +1 -1
- package/dist/apiRetryKick.js +16 -0
- package/dist/apiRetryKick.js.map +1 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +642 -26
- package/dist/bot.js.map +1 -1
- package/dist/cli/bot.d.ts +4 -2
- package/dist/cli/bot.d.ts.map +1 -1
- package/dist/cli/bot.js +11 -3
- package/dist/cli/bot.js.map +1 -1
- package/dist/cli/botEntry.js +3 -1
- package/dist/cli/botEntry.js.map +1 -1
- package/dist/cli/claudeProfiles.d.ts +42 -0
- package/dist/cli/claudeProfiles.d.ts.map +1 -0
- package/dist/cli/claudeProfiles.js +106 -0
- package/dist/cli/claudeProfiles.js.map +1 -0
- package/dist/cli/envLoader.d.ts +6 -5
- package/dist/cli/envLoader.d.ts.map +1 -1
- package/dist/cli/envLoader.js +8 -27
- package/dist/cli/envLoader.js.map +1 -1
- package/dist/cli/hot.d.ts +2 -2
- package/dist/cli/hot.d.ts.map +1 -1
- package/dist/cli/hot.js +26 -16
- package/dist/cli/hot.js.map +1 -1
- package/dist/cli.js +19 -9
- package/dist/cli.js.map +1 -1
- package/dist/connectors/jira/adf.d.ts +23 -0
- package/dist/connectors/jira/adf.d.ts.map +1 -1
- package/dist/connectors/jira/adf.js +44 -0
- package/dist/connectors/jira/adf.js.map +1 -1
- package/dist/connectors/jira/answerSink.d.ts +27 -3
- package/dist/connectors/jira/answerSink.d.ts.map +1 -1
- package/dist/connectors/jira/answerSink.js +145 -18
- package/dist/connectors/jira/answerSink.js.map +1 -1
- package/dist/connectors/jira/attachmentTool.d.ts +7 -0
- package/dist/connectors/jira/attachmentTool.d.ts.map +1 -1
- package/dist/connectors/jira/attachmentTool.js +16 -0
- package/dist/connectors/jira/attachmentTool.js.map +1 -1
- package/dist/connectors/jira/client.d.ts +36 -0
- package/dist/connectors/jira/client.d.ts.map +1 -1
- package/dist/connectors/jira/client.js +46 -5
- package/dist/connectors/jira/client.js.map +1 -1
- package/dist/connectors/jira/config.d.ts +13 -17
- package/dist/connectors/jira/config.d.ts.map +1 -1
- package/dist/connectors/jira/config.js +15 -33
- package/dist/connectors/jira/config.js.map +1 -1
- package/dist/connectors/jira/connector.d.ts +10 -1
- package/dist/connectors/jira/connector.d.ts.map +1 -1
- package/dist/connectors/jira/connector.js +90 -16
- package/dist/connectors/jira/connector.js.map +1 -1
- package/dist/connectors/jira/contextLedger.d.ts +7 -0
- package/dist/connectors/jira/contextLedger.d.ts.map +1 -1
- package/dist/connectors/jira/contextLedger.js +18 -0
- package/dist/connectors/jira/contextLedger.js.map +1 -1
- package/dist/connectors/jira/contextPreamble.d.ts.map +1 -1
- package/dist/connectors/jira/contextPreamble.js +6 -3
- package/dist/connectors/jira/contextPreamble.js.map +1 -1
- package/dist/connectors/jira/inbound.d.ts +72 -10
- package/dist/connectors/jira/inbound.d.ts.map +1 -1
- package/dist/connectors/jira/inbound.js +204 -36
- package/dist/connectors/jira/inbound.js.map +1 -1
- package/dist/connectors/jira/issueBlocks.d.ts +31 -3
- package/dist/connectors/jira/issueBlocks.d.ts.map +1 -1
- package/dist/connectors/jira/issueBlocks.js +108 -29
- package/dist/connectors/jira/issueBlocks.js.map +1 -1
- package/dist/connectors/jira/issueContext.d.ts +11 -5
- package/dist/connectors/jira/issueContext.d.ts.map +1 -1
- package/dist/connectors/jira/issueContext.js +23 -6
- package/dist/connectors/jira/issueContext.js.map +1 -1
- package/dist/connectors/jira/issueDelta.d.ts +3 -0
- package/dist/connectors/jira/issueDelta.d.ts.map +1 -1
- package/dist/connectors/jira/issueDelta.js +15 -6
- package/dist/connectors/jira/issueDelta.js.map +1 -1
- package/dist/connectors/jira/mediaPlaceholders.d.ts +2 -0
- package/dist/connectors/jira/mediaPlaceholders.d.ts.map +1 -1
- package/dist/connectors/jira/mediaPlaceholders.js +16 -3
- package/dist/connectors/jira/mediaPlaceholders.js.map +1 -1
- package/dist/connectors/jira/prompt.d.ts +4 -0
- package/dist/connectors/jira/prompt.d.ts.map +1 -1
- package/dist/connectors/jira/prompt.js +20 -1
- package/dist/connectors/jira/prompt.js.map +1 -1
- package/dist/connectors/jira/sessionKeyCodec.d.ts +2 -0
- package/dist/connectors/jira/sessionKeyCodec.d.ts.map +1 -1
- package/dist/connectors/jira/sessionKeyCodec.js +5 -0
- package/dist/connectors/jira/sessionKeyCodec.js.map +1 -1
- package/dist/connectors/jira/trigger.d.ts +16 -3
- package/dist/connectors/jira/trigger.d.ts.map +1 -1
- package/dist/connectors/jira/trigger.js +17 -2
- package/dist/connectors/jira/trigger.js.map +1 -1
- package/dist/connectors/jira/triggerLog.d.ts +12 -3
- package/dist/connectors/jira/triggerLog.d.ts.map +1 -1
- package/dist/connectors/jira/triggerLog.js +18 -2
- package/dist/connectors/jira/triggerLog.js.map +1 -1
- package/dist/connectors/jira/trustedGroup.d.ts +54 -0
- package/dist/connectors/jira/trustedGroup.d.ts.map +1 -0
- package/dist/connectors/jira/trustedGroup.js +98 -0
- package/dist/connectors/jira/trustedGroup.js.map +1 -0
- package/dist/connectors/telegram/commands/autoContinueLimits.d.ts +3 -0
- package/dist/connectors/telegram/commands/autoContinueLimits.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/autoContinueLimits.js +15 -45
- package/dist/connectors/telegram/commands/autoContinueLimits.js.map +1 -1
- package/dist/connectors/telegram/commands/claudeAccounts.d.ts +58 -0
- package/dist/connectors/telegram/commands/claudeAccounts.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/claudeAccounts.js +591 -0
- package/dist/connectors/telegram/commands/claudeAccounts.js.map +1 -0
- package/dist/connectors/telegram/commands/fallbackChain.d.ts +49 -0
- package/dist/connectors/telegram/commands/fallbackChain.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/fallbackChain.js +624 -0
- package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -0
- package/dist/connectors/telegram/commands/modelProviders.d.ts +23 -1
- package/dist/connectors/telegram/commands/modelProviders.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/modelProviders.js +131 -6
- package/dist/connectors/telegram/commands/modelProviders.js.map +1 -1
- package/dist/i18n/de.d.ts.map +1 -1
- package/dist/i18n/de.js +137 -9
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +137 -9
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +137 -9
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/fr.js +137 -9
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/hi.d.ts.map +1 -1
- package/dist/i18n/hi.js +137 -9
- package/dist/i18n/hi.js.map +1 -1
- package/dist/i18n/ja.d.ts.map +1 -1
- package/dist/i18n/ja.js +137 -9
- package/dist/i18n/ja.js.map +1 -1
- package/dist/i18n/ka.d.ts.map +1 -1
- package/dist/i18n/ka.js +137 -9
- package/dist/i18n/ka.js.map +1 -1
- package/dist/i18n/pt.d.ts.map +1 -1
- package/dist/i18n/pt.js +137 -9
- package/dist/i18n/pt.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +137 -9
- package/dist/i18n/ru.js.map +1 -1
- package/dist/i18n/uk.d.ts.map +1 -1
- package/dist/i18n/uk.js +137 -9
- package/dist/i18n/uk.js.map +1 -1
- package/dist/i18n/uz.d.ts.map +1 -1
- package/dist/i18n/uz.js +137 -9
- package/dist/i18n/uz.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +137 -9
- package/dist/i18n/zh.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/installManager.d.ts +16 -2
- package/dist/installManager.d.ts.map +1 -1
- package/dist/installManager.js +42 -12
- package/dist/installManager.js.map +1 -1
- package/dist/instanceLock.d.ts.map +1 -1
- package/dist/instanceLock.js +10 -8
- package/dist/instanceLock.js.map +1 -1
- package/dist/legacyMigration.d.ts +69 -0
- package/dist/legacyMigration.d.ts.map +1 -0
- package/dist/legacyMigration.js +338 -0
- package/dist/legacyMigration.js.map +1 -0
- package/dist/legacyNames.d.ts +107 -0
- package/dist/legacyNames.d.ts.map +1 -0
- package/dist/legacyNames.js +230 -0
- package/dist/legacyNames.js.map +1 -0
- package/dist/platform/answerSink.d.ts +5 -0
- package/dist/platform/answerSink.d.ts.map +1 -1
- package/dist/platform/answerSink.js.map +1 -1
- package/dist/requests/answerRequest.d.ts +2 -0
- package/dist/requests/answerRequest.d.ts.map +1 -1
- package/dist/requests/answerRequest.js +3 -1
- package/dist/requests/answerRequest.js.map +1 -1
- package/dist/requests/answerStyle.d.ts +11 -0
- package/dist/requests/answerStyle.d.ts.map +1 -0
- package/dist/requests/answerStyle.js +14 -0
- package/dist/requests/answerStyle.js.map +1 -0
- package/dist/requests/answerTail.d.ts.map +1 -1
- package/dist/requests/answerTail.js +2 -0
- package/dist/requests/answerTail.js.map +1 -1
- package/dist/requests/requestHeader.d.ts.map +1 -1
- package/dist/requests/requestHeader.js +3 -0
- package/dist/requests/requestHeader.js.map +1 -1
- package/dist/requests/requestLedger.js +1 -1
- package/dist/requests/requestLedger.js.map +1 -1
- package/dist/requests/types.d.ts +3 -1
- package/dist/requests/types.d.ts.map +1 -1
- package/dist/scheduler/mcpSurface.d.ts +17 -1
- package/dist/scheduler/mcpSurface.d.ts.map +1 -1
- package/dist/scheduler/mcpSurface.js +86 -13
- package/dist/scheduler/mcpSurface.js.map +1 -1
- package/dist/state.d.ts +175 -2
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +303 -2
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/autoContinueOnLimit.d.ts +7 -0
- package/dist/utils/autoContinueOnLimit.d.ts.map +1 -1
- package/dist/utils/autoContinueOnLimit.js +10 -0
- package/dist/utils/autoContinueOnLimit.js.map +1 -1
- package/dist/utils/claudeAccountPickerPlan.d.ts +85 -0
- package/dist/utils/claudeAccountPickerPlan.d.ts.map +1 -0
- package/dist/utils/claudeAccountPickerPlan.js +123 -0
- package/dist/utils/claudeAccountPickerPlan.js.map +1 -0
- package/dist/utils/claudeLimitSwitch.d.ts +40 -0
- package/dist/utils/claudeLimitSwitch.d.ts.map +1 -0
- package/dist/utils/claudeLimitSwitch.js +54 -0
- package/dist/utils/claudeLimitSwitch.js.map +1 -0
- package/dist/utils/claudeProfileUsageLines.d.ts +15 -0
- package/dist/utils/claudeProfileUsageLines.d.ts.map +1 -0
- package/dist/utils/claudeProfileUsageLines.js +42 -0
- package/dist/utils/claudeProfileUsageLines.js.map +1 -0
- package/dist/utils/claudeProfiles.d.ts +124 -0
- package/dist/utils/claudeProfiles.d.ts.map +1 -0
- package/dist/utils/claudeProfiles.js +304 -0
- package/dist/utils/claudeProfiles.js.map +1 -0
- package/dist/utils/claudeStreamJson.d.ts +13 -1
- package/dist/utils/claudeStreamJson.d.ts.map +1 -1
- package/dist/utils/claudeStreamJson.js +13 -0
- package/dist/utils/claudeStreamJson.js.map +1 -1
- package/dist/utils/claudeUsage.d.ts +209 -0
- package/dist/utils/claudeUsage.d.ts.map +1 -0
- package/dist/utils/claudeUsage.js +413 -0
- package/dist/utils/claudeUsage.js.map +1 -0
- package/dist/utils/claudeUsageRefresher.d.ts +44 -0
- package/dist/utils/claudeUsageRefresher.d.ts.map +1 -0
- package/dist/utils/claudeUsageRefresher.js +95 -0
- package/dist/utils/claudeUsageRefresher.js.map +1 -0
- package/dist/utils/fallbackBridge.d.ts +32 -0
- package/dist/utils/fallbackBridge.d.ts.map +1 -0
- package/dist/utils/fallbackBridge.js +62 -0
- package/dist/utils/fallbackBridge.js.map +1 -0
- package/dist/utils/fallbackChain.d.ts +299 -0
- package/dist/utils/fallbackChain.d.ts.map +1 -0
- package/dist/utils/fallbackChain.js +542 -0
- package/dist/utils/fallbackChain.js.map +1 -0
- package/dist/utils/jsonStreamHost.d.ts +14 -3
- package/dist/utils/jsonStreamHost.d.ts.map +1 -1
- package/dist/utils/jsonStreamHost.js +26 -5
- package/dist/utils/jsonStreamHost.js.map +1 -1
- package/dist/utils/modelRecentPlan.d.ts +56 -0
- package/dist/utils/modelRecentPlan.d.ts.map +1 -0
- package/dist/utils/modelRecentPlan.js +107 -0
- package/dist/utils/modelRecentPlan.js.map +1 -0
- package/dist/utils/openCodeCompactPlugin.d.ts +11 -3
- package/dist/utils/openCodeCompactPlugin.d.ts.map +1 -1
- package/dist/utils/openCodeCompactPlugin.js +37 -5
- package/dist/utils/openCodeCompactPlugin.js.map +1 -1
- package/dist/utils/threadStatusReport.d.ts +25 -1
- package/dist/utils/threadStatusReport.d.ts.map +1 -1
- package/dist/utils/threadStatusReport.js +24 -1
- package/dist/utils/threadStatusReport.js.map +1 -1
- package/examples/.env.example +7 -7
- package/examples/docker-compose.yml +15 -14
- package/package.json +3 -1
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.limitsUsageRefreshBudgetMs = void 0;
|
|
4
|
+
exports.createFallbackChain = createFallbackChain;
|
|
5
|
+
/**
|
|
6
|
+
* `/limits` โ everything about a usage limit on one screen: the instance-wide priority chain of backend +
|
|
7
|
+
* account / provider + model a topic continues on (reorder, include / exclude, remove, add through a two-step
|
|
8
|
+
* sub-picker, rebuild a draft, clear โ from EVERY topic, one instance-wide list), the two settings that decide what a
|
|
9
|
+
* limit does (auto switch to a fallback model, wait for the reset and continue โ instance defaults in General,
|
|
10
|
+
* on / off / same-as-General overrides in a topic, chosen in a per-setting sub-menu) โ and the ยซ๐ Switch to โฆยป
|
|
11
|
+
* buttons a usage-limit notice carries. The message text is deliberately short: the Claude accounts' usage block
|
|
12
|
+
* on top (the `/accounts` renderer), then the topic's position and where its settings come from โ the list and
|
|
13
|
+
* the settings' values are on the buttons. `/fallback` and `/auto_continue_limits` are hidden aliases of this
|
|
14
|
+
* screen. See README.md in this folder and `utils/fallbackChain.ts`.
|
|
15
|
+
*/
|
|
16
|
+
const telegraf_1 = require("telegraf");
|
|
17
|
+
const i18n_1 = require("../../../i18n");
|
|
18
|
+
const paginateList_1 = require("../../../utils/paginateList");
|
|
19
|
+
const claudeUsage_1 = require("../../../utils/claudeUsage");
|
|
20
|
+
const claudeProfileUsageLines_1 = require("../../../utils/claudeProfileUsageLines");
|
|
21
|
+
const claudeUsageRefresher_1 = require("../../../utils/claudeUsageRefresher");
|
|
22
|
+
const autoContinueOnLimit_1 = require("../../../utils/autoContinueOnLimit");
|
|
23
|
+
const fallbackChain_1 = require("../../../utils/fallbackChain");
|
|
24
|
+
const callbackMessage_1 = require("./callbackMessage");
|
|
25
|
+
/** Models per page in the ยซadd an OpenCode modelยป step (the `/model` picker's density). */
|
|
26
|
+
const addModelPageSize = 8;
|
|
27
|
+
/**
|
|
28
|
+
* How long opening the screen waits for the usage refresh (one poll is ~0.5 s). A slower one โ a hung endpoint
|
|
29
|
+
* runs into the fetch's own 15 s timeout โ keeps going in the background and the next render shows its result.
|
|
30
|
+
*/
|
|
31
|
+
exports.limitsUsageRefreshBudgetMs = 2_000;
|
|
32
|
+
const settingIds = ['switch', 'wait'];
|
|
33
|
+
const settingCallbacks = {
|
|
34
|
+
switch: { open: fallbackChain_1.fallbackAutoSwitchOpenCallback, on: fallbackChain_1.fallbackAutoSwitchOnCallback, off: fallbackChain_1.fallbackAutoSwitchOffCallback, same: fallbackChain_1.fallbackAutoSwitchSameCallback },
|
|
35
|
+
wait: { open: fallbackChain_1.fallbackWaitOpenCallback, on: fallbackChain_1.fallbackWaitOnCallback, off: fallbackChain_1.fallbackWaitOffCallback, same: fallbackChain_1.fallbackWaitSameCallback },
|
|
36
|
+
};
|
|
37
|
+
/** A setting's texts: its main-screen button (the name with the value, `{state}`) and the title of its sub-menu. */
|
|
38
|
+
const settingTextKeys = {
|
|
39
|
+
switch: { button: 'limits.switchButton', menuTitle: 'limits.switchMenuTitle' },
|
|
40
|
+
wait: { button: 'limits.waitButton', menuTitle: 'limits.waitMenuTitle' },
|
|
41
|
+
};
|
|
42
|
+
/** @description Build the `/limits` feature over its ports: the command (+ the hidden aliases), its buttons, the limit-notice switch button. */
|
|
43
|
+
function createFallbackChain(ports) {
|
|
44
|
+
const { bot, command, getState, replyToThread, editThreadMessage, authoriseContext, withThreadLocale, checkIsGeneral, getInstanceTimezone, refreshClaudeUsage, detectFallbackOptions, getTopicPosition, takeSwitchOffer, applyAutoContinue, getArmedLimitRetryFireAt, } = ports;
|
|
45
|
+
const addSnapshots = new Map();
|
|
46
|
+
function getLabelWords() {
|
|
47
|
+
return { claude: (0, i18n_1.t)('fallback.backendClaude'), openCode: (0, i18n_1.t)('fallback.backendOpenCode'), topicModel: (0, i18n_1.t)('fallback.topicModel') };
|
|
48
|
+
}
|
|
49
|
+
/** The entry for a row or a button. */
|
|
50
|
+
function formatEntry(entry) {
|
|
51
|
+
return (0, fallbackChain_1.formatFallbackEntryLabel)(entry, getLabelWords());
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A rung on its label button: `3. Claude ยท work โ 14:00` / `1. openai / gpt-6.1 ๐ซ` โ the FULL name (Telegram
|
|
55
|
+
* sizes and truncates the button as it sees fit). The rung's state rides as a suffix (โ limited until, ๐ not
|
|
56
|
+
* signed in, โ not offered any more, ๐ซ left out of switching, โ where the topic runs now). The one place the
|
|
57
|
+
* label is built: a two-row layout (label on its own row) only changes how `buildPicker` assembles the row.
|
|
58
|
+
*/
|
|
59
|
+
function formatRowButtonLabel(index, entry, detection, current, nowMs) {
|
|
60
|
+
const base = entry.backend === 'claude'
|
|
61
|
+
? formatEntry(entry)
|
|
62
|
+
: `${entry.providerId} / ${entry.modelId}`;
|
|
63
|
+
const availability = (0, fallbackChain_1.getFallbackEntryAvailability)(entry, detection, nowMs);
|
|
64
|
+
let suffix = '';
|
|
65
|
+
if (availability.status === 'limited')
|
|
66
|
+
suffix += ` โ ${(0, claudeUsage_1.formatClaudeUsageResetAt)(availability.until, getInstanceTimezone(), nowMs)}`;
|
|
67
|
+
else if (availability.status === 'signedOut')
|
|
68
|
+
suffix += ' ๐';
|
|
69
|
+
else if (availability.status === 'missing')
|
|
70
|
+
suffix += ' โ';
|
|
71
|
+
if ((0, fallbackChain_1.checkIsFallbackEntryDisabled)(entry))
|
|
72
|
+
suffix += ' ๐ซ';
|
|
73
|
+
if (current && (0, fallbackChain_1.checkIsSameFallbackAccount)(entry, current))
|
|
74
|
+
suffix += ' โ';
|
|
75
|
+
return `${index + 1}. ${base}${suffix}`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A rung's detail view, in the message the list sits in: its name and state, then โฌ๏ธ Up / โฌ๏ธ Down (hidden at
|
|
79
|
+
* the ends), Use / Don't use, Remove and Back. Every edit button bakes the chain revision and the index, so a
|
|
80
|
+
* stale tap re-renders the list instead of acting.
|
|
81
|
+
*/
|
|
82
|
+
async function renderRungDetail(key, messageId, index, isGeneral) {
|
|
83
|
+
if (messageId === null)
|
|
84
|
+
return;
|
|
85
|
+
const chain = getState().getFallbackChain();
|
|
86
|
+
const entry = chain[index];
|
|
87
|
+
if (!entry) {
|
|
88
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const detection = await detectFallbackOptions();
|
|
92
|
+
const current = isGeneral ? null : getTopicPosition(key);
|
|
93
|
+
const nowMs = Date.now();
|
|
94
|
+
const availability = (0, fallbackChain_1.getFallbackEntryAvailability)(entry, detection, nowMs);
|
|
95
|
+
const lines = [(0, i18n_1.t)('limits.rungTitle', { n: index + 1, total: chain.length, label: formatEntry(entry) })];
|
|
96
|
+
if (availability.status === 'limited')
|
|
97
|
+
lines.push((0, i18n_1.t)('limits.rungLimited', { until: (0, claudeUsage_1.formatClaudeUsageResetAt)(availability.until, getInstanceTimezone(), nowMs) }));
|
|
98
|
+
else if (availability.status === 'signedOut')
|
|
99
|
+
lines.push((0, i18n_1.t)('limits.rungSignedOut'));
|
|
100
|
+
else if (availability.status === 'missing')
|
|
101
|
+
lines.push((0, i18n_1.t)('limits.rungMissing'));
|
|
102
|
+
else
|
|
103
|
+
lines.push((0, i18n_1.t)('limits.rungReady'));
|
|
104
|
+
if ((0, fallbackChain_1.checkIsFallbackEntryDisabled)(entry))
|
|
105
|
+
lines.push((0, i18n_1.t)('limits.rungDisabled'));
|
|
106
|
+
if (current && (0, fallbackChain_1.checkIsSameFallbackAccount)(entry, current))
|
|
107
|
+
lines.push((0, i18n_1.t)('limits.rungHere'));
|
|
108
|
+
const revision = (0, fallbackChain_1.getFallbackChainRevision)(chain);
|
|
109
|
+
const move = [];
|
|
110
|
+
if (index > 0)
|
|
111
|
+
move.push(telegraf_1.Markup.button.callback((0, i18n_1.t)('limits.btnUp'), (0, fallbackChain_1.buildFallbackMoveUpCallback)(index, revision)));
|
|
112
|
+
if (index < chain.length - 1)
|
|
113
|
+
move.push(telegraf_1.Markup.button.callback((0, i18n_1.t)('limits.btnDown'), (0, fallbackChain_1.buildFallbackMoveDownCallback)(index, revision)));
|
|
114
|
+
const rows = [
|
|
115
|
+
...(move.length > 0 ? [move] : []),
|
|
116
|
+
[
|
|
117
|
+
telegraf_1.Markup.button.callback((0, i18n_1.t)((0, fallbackChain_1.checkIsFallbackEntryDisabled)(entry) ? 'limits.btnUse' : 'limits.btnDontUse'), (0, fallbackChain_1.buildFallbackToggleCallback)(index, revision)),
|
|
118
|
+
telegraf_1.Markup.button.callback((0, i18n_1.t)('account.removeButton'), (0, fallbackChain_1.buildFallbackRemoveCallback)(index, revision)),
|
|
119
|
+
],
|
|
120
|
+
buildBackRow(),
|
|
121
|
+
];
|
|
122
|
+
await editThreadMessage(key, messageId, lines.join('\n'), telegraf_1.Markup.inlineKeyboard(rows));
|
|
123
|
+
}
|
|
124
|
+
/** A setting's value for the topic (General: the instance default, where there is no override). */
|
|
125
|
+
function readSetting(id, key, isGeneral) {
|
|
126
|
+
if (id === 'switch') {
|
|
127
|
+
return {
|
|
128
|
+
effective: isGeneral ? getState().getClaudeAutoSwitchOnLimitGlobalDefault() : getState().checkIsClaudeAutoSwitchOnLimitEnabled(key),
|
|
129
|
+
override: isGeneral ? undefined : getState().getClaudeAutoSwitchOnLimitOverride(key),
|
|
130
|
+
generalValue: getState().getClaudeAutoSwitchOnLimitGlobalDefault(),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
effective: isGeneral ? getState().getAutoContinueOnLimitGlobalDefault() : getState().checkIsAutoContinueOnLimitEnabled(key),
|
|
135
|
+
override: isGeneral ? undefined : getState().getAutoContinueOnLimitOverride(key),
|
|
136
|
+
generalValue: getState().getAutoContinueOnLimitGlobalDefault(),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function formatState(enabled) {
|
|
140
|
+
return enabled ? (0, i18n_1.t)('autoContinueLimits.on') : (0, i18n_1.t)('autoContinueLimits.off');
|
|
141
|
+
}
|
|
142
|
+
/** Where a topic's setting value comes from: General, or the topic's own override. */
|
|
143
|
+
function formatSettingSource(setting) {
|
|
144
|
+
return setting.override === undefined ? (0, i18n_1.t)('limits.sourceGeneral') : (0, i18n_1.t)('limits.sourceTopic');
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* One setting's button on the main screen: one full-width button with the setting's name and its effective value
|
|
148
|
+
* (`๐ Auto switch: ON`). Where a topic's value comes from is in the message text above and in the sub-menu
|
|
149
|
+
* (the โ), not on the button โ the worded source made it too long for a phone and cut it again. General: a tap
|
|
150
|
+
* flips the value. A topic: a tap opens the setting's sub-menu ({@link renderSettingMenu}) in the same message โ
|
|
151
|
+
* three choices on one row would split the width evenly and cut every label.
|
|
152
|
+
*/
|
|
153
|
+
function buildSettingRow(id, setting, isGeneral) {
|
|
154
|
+
const callbacks = settingCallbacks[id];
|
|
155
|
+
const tapData = isGeneral ? (setting.effective ? callbacks.off : callbacks.on) : callbacks.open;
|
|
156
|
+
return [telegraf_1.Markup.button.callback((0, i18n_1.t)(settingTextKeys[id].button, { state: formatState(setting.effective) }), tapData)];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A topic's setting sub-menu, in the message the screen sits in: the setting's name, then ON / OFF / As in
|
|
160
|
+
* General (with the General value it would give) as full-width buttons โ the current choice marked `โ`, which
|
|
161
|
+
* is also where the value comes from โ and Back. The options are the setting's own callbacks, which apply the
|
|
162
|
+
* choice and return to the main screen.
|
|
163
|
+
*/
|
|
164
|
+
async function renderSettingMenu(key, messageId, id) {
|
|
165
|
+
if (messageId === null)
|
|
166
|
+
return;
|
|
167
|
+
const setting = readSetting(id, key, false);
|
|
168
|
+
const callbacks = settingCallbacks[id];
|
|
169
|
+
const mark = (isCurrent) => (isCurrent ? ' โ' : '');
|
|
170
|
+
const rows = [
|
|
171
|
+
[telegraf_1.Markup.button.callback(`${formatState(true)}${mark(setting.override === true)}`, callbacks.on)],
|
|
172
|
+
[telegraf_1.Markup.button.callback(`${formatState(false)}${mark(setting.override === false)}`, callbacks.off)],
|
|
173
|
+
[telegraf_1.Markup.button.callback(`${(0, i18n_1.t)('limits.settingSameButton', { state: formatState(setting.generalValue) })}${mark(setting.override === undefined)}`, callbacks.same)],
|
|
174
|
+
buildBackRow(),
|
|
175
|
+
];
|
|
176
|
+
await editThreadMessage(key, messageId, (0, i18n_1.t)(settingTextKeys[id].menuTitle), telegraf_1.Markup.inlineKeyboard(rows));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The usage block at the top of the screen: a heading that says the percentages are USED, then each Claude
|
|
180
|
+
* profile's windows as `/accounts` shows them under its row (`formatClaudeProfileUsageLines`), one row per
|
|
181
|
+
* profile โ the name, then its windows (a limit mark on the next row). A signed-out profile is named as such;
|
|
182
|
+
* one never polled has nothing to show and is left out (no rows โ no heading either). OpenCode's limit marks
|
|
183
|
+
* are not repeated here: they are on the rung buttons (`โ until`).
|
|
184
|
+
*/
|
|
185
|
+
function formatUsageBlock(detection, nowMs) {
|
|
186
|
+
const timezone = getInstanceTimezone();
|
|
187
|
+
const rows = [];
|
|
188
|
+
for (const profile of detection.claudeProfiles) {
|
|
189
|
+
if (profile.loggedIn === false) {
|
|
190
|
+
rows.push(`โข ${profile.name} โ ${(0, i18n_1.t)('account.row.loggedOut')}`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// The first line hangs under a row in `/accounts` (the locale indents it); here it sits after the name.
|
|
194
|
+
const [first, ...rest] = (0, claudeProfileUsageLines_1.formatClaudeProfileUsageLines)(profile, timezone, nowMs);
|
|
195
|
+
if (first === undefined)
|
|
196
|
+
continue;
|
|
197
|
+
rows.push([`โข ${profile.name} โ ${first.trimStart()}`, ...rest].join('\n'));
|
|
198
|
+
}
|
|
199
|
+
return rows.length === 0 ? '' : [(0, i18n_1.t)('limits.usageHeading'), ...rows].join('\n');
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The `/limits` screen. Text: the Claude accounts' usage, then what the topic runs on and where its two
|
|
203
|
+
* settings come from (General: just that these are defaults) and the list's heading โ the list itself, its
|
|
204
|
+
* marks and the two settings' values are on the buttons. A topic also gets ยซskip onceยป while a limit wait is
|
|
205
|
+
* armed for it.
|
|
206
|
+
*/
|
|
207
|
+
async function buildPicker(key, isGeneral) {
|
|
208
|
+
const chain = getState().getFallbackChain();
|
|
209
|
+
const detection = await detectFallbackOptions();
|
|
210
|
+
const current = isGeneral ? null : getTopicPosition(key);
|
|
211
|
+
const nowMs = Date.now();
|
|
212
|
+
const switchSetting = readSetting('switch', key, isGeneral);
|
|
213
|
+
const waitSetting = readSetting('wait', key, isGeneral);
|
|
214
|
+
const parts = [];
|
|
215
|
+
const usageBlock = formatUsageBlock(detection, nowMs);
|
|
216
|
+
if (usageBlock !== '')
|
|
217
|
+
parts.push(usageBlock);
|
|
218
|
+
if (isGeneral) {
|
|
219
|
+
parts.push((0, i18n_1.t)('limits.settingsGeneral'));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
parts.push([
|
|
223
|
+
(0, i18n_1.t)('limits.topicRuns', { current: current ? formatEntry(current) : (0, i18n_1.t)('fallback.noAgent') }),
|
|
224
|
+
(0, i18n_1.t)('limits.sourceLine', { switch: formatSettingSource(switchSetting), wait: formatSettingSource(waitSetting) }),
|
|
225
|
+
].join('\n'));
|
|
226
|
+
}
|
|
227
|
+
parts.push(chain.length === 0 ? (0, i18n_1.t)('limits.listNoteEmpty') : (0, i18n_1.t)('limits.listNote'));
|
|
228
|
+
const rows = [];
|
|
229
|
+
const revision = (0, fallbackChain_1.getFallbackChainRevision)(chain);
|
|
230
|
+
// One full-width button per rung; a tap opens the rung's detail view in the same message.
|
|
231
|
+
chain.forEach((entry, index) => {
|
|
232
|
+
rows.push([telegraf_1.Markup.button.callback(formatRowButtonLabel(index, entry, detection, current, nowMs), (0, fallbackChain_1.buildFallbackOpenCallback)(index, revision))]);
|
|
233
|
+
});
|
|
234
|
+
const actions = [
|
|
235
|
+
telegraf_1.Markup.button.callback((0, i18n_1.t)('fallback.addButton'), fallbackChain_1.fallbackAddCallback),
|
|
236
|
+
telegraf_1.Markup.button.callback((0, i18n_1.t)('limits.rebuildButton'), fallbackChain_1.fallbackProposeCallback),
|
|
237
|
+
];
|
|
238
|
+
if (chain.length > 0)
|
|
239
|
+
actions.push(telegraf_1.Markup.button.callback((0, i18n_1.t)('fallback.clearButton'), fallbackChain_1.fallbackClearCallback));
|
|
240
|
+
rows.push(actions);
|
|
241
|
+
rows.push(buildSettingRow('switch', switchSetting, isGeneral));
|
|
242
|
+
rows.push(buildSettingRow('wait', waitSetting, isGeneral));
|
|
243
|
+
const armedFireAt = isGeneral ? null : getArmedLimitRetryFireAt(key);
|
|
244
|
+
if (armedFireAt !== null)
|
|
245
|
+
rows.push([telegraf_1.Markup.button.callback((0, i18n_1.t)('autoContinueLimits.skipButton'), (0, autoContinueOnLimit_1.buildSkipArmedRetryCallbackData)(armedFireAt))]);
|
|
246
|
+
return { text: parts.join('\n\n'), keyboard: telegraf_1.Markup.inlineKeyboard(rows) };
|
|
247
|
+
}
|
|
248
|
+
/** Re-render the picker IN PLACE (it is meant for repeated use, like `/account`) off the cached usage; best-effort. */
|
|
249
|
+
async function rerenderPicker(key, messageId, isGeneral) {
|
|
250
|
+
if (messageId === null)
|
|
251
|
+
return;
|
|
252
|
+
addSnapshots.delete(messageId);
|
|
253
|
+
const picker = await buildPicker(key, isGeneral);
|
|
254
|
+
await editThreadMessage(key, messageId, picker.text, picker.keyboard);
|
|
255
|
+
}
|
|
256
|
+
function buildBackRow() {
|
|
257
|
+
return [telegraf_1.Markup.button.callback((0, i18n_1.t)('fallback.backButton'), fallbackChain_1.fallbackBackCallback)];
|
|
258
|
+
}
|
|
259
|
+
/** Step 1 of ยซaddยป: the signed-in Claude profiles and the OpenCode providers the server is authenticated for. */
|
|
260
|
+
async function renderAddAccountStep(key, messageId) {
|
|
261
|
+
const detection = await detectFallbackOptions();
|
|
262
|
+
const words = getLabelWords();
|
|
263
|
+
const rows = [];
|
|
264
|
+
for (const profile of detection.claudeProfiles) {
|
|
265
|
+
if (profile.loggedIn === false)
|
|
266
|
+
continue;
|
|
267
|
+
rows.push([telegraf_1.Markup.button.callback(`${words.claude} ยท ${profile.name}`, (0, fallbackChain_1.buildFallbackAddClaudeCallback)(profile.name))]);
|
|
268
|
+
}
|
|
269
|
+
const providers = (0, fallbackChain_1.listOpenCodeProviders)(detection.openCodeModels);
|
|
270
|
+
providers.forEach((providerId, index) => {
|
|
271
|
+
rows.push([telegraf_1.Markup.button.callback(`${words.openCode} ยท ${providerId}`, (0, fallbackChain_1.buildFallbackAddProviderCallback)(index))]);
|
|
272
|
+
});
|
|
273
|
+
if (rows.length === 0) {
|
|
274
|
+
await replyToThread(key, (0, i18n_1.t)('fallback.nothingDetected'));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
addSnapshots.set(messageId, { providers, providerId: null, models: [] });
|
|
278
|
+
rows.push(buildBackRow());
|
|
279
|
+
await editThreadMessage(key, messageId, (0, i18n_1.t)('fallback.pickAccount'), telegraf_1.Markup.inlineKeyboard(rows));
|
|
280
|
+
}
|
|
281
|
+
/** Step 2 for a Claude profile: keep the topic's model, or pin one of the CLI aliases. */
|
|
282
|
+
async function renderAddClaudeModelStep(key, messageId, profile) {
|
|
283
|
+
const rows = [[telegraf_1.Markup.button.callback((0, i18n_1.t)('fallback.topicModelButton'), (0, fallbackChain_1.buildFallbackAddClaudeModelCallback)(profile, null))]];
|
|
284
|
+
rows.push(fallbackChain_1.fallbackClaudeModelAliases.map((model) => telegraf_1.Markup.button.callback(model, (0, fallbackChain_1.buildFallbackAddClaudeModelCallback)(profile, model))));
|
|
285
|
+
rows.push(buildBackRow());
|
|
286
|
+
await editThreadMessage(key, messageId, (0, i18n_1.t)('fallback.pickModel', { account: `${getLabelWords().claude} ยท ${profile}` }), telegraf_1.Markup.inlineKeyboard(rows));
|
|
287
|
+
}
|
|
288
|
+
/** Step 2 for an OpenCode provider: one page of its models (absolute indexes into the snapshot). */
|
|
289
|
+
async function renderAddModelPage(key, messageId, snapshot, page) {
|
|
290
|
+
const { slice, currentPage, totalPages } = (0, paginateList_1.paginateList)(snapshot.models, page, addModelPageSize);
|
|
291
|
+
const offset = currentPage * addModelPageSize;
|
|
292
|
+
const rows = slice.map((modelId, index) => [telegraf_1.Markup.button.callback(modelId, (0, fallbackChain_1.buildFallbackAddModelCallback)(offset + index))]);
|
|
293
|
+
if (totalPages > 1) {
|
|
294
|
+
const nav = [];
|
|
295
|
+
if (currentPage > 0)
|
|
296
|
+
nav.push(telegraf_1.Markup.button.callback('โ๏ธ', (0, fallbackChain_1.buildFallbackAddModelPageCallback)(currentPage - 1)));
|
|
297
|
+
nav.push(telegraf_1.Markup.button.callback(`${currentPage + 1}/${totalPages}`, (0, fallbackChain_1.buildFallbackAddModelPageCallback)(currentPage)));
|
|
298
|
+
if (currentPage < totalPages - 1)
|
|
299
|
+
nav.push(telegraf_1.Markup.button.callback('โถ๏ธ', (0, fallbackChain_1.buildFallbackAddModelPageCallback)(currentPage + 1)));
|
|
300
|
+
rows.push(nav);
|
|
301
|
+
}
|
|
302
|
+
rows.push(buildBackRow());
|
|
303
|
+
await editThreadMessage(key, messageId, (0, i18n_1.t)('fallback.pickModel', { account: `${getLabelWords().openCode} ยท ${snapshot.providerId}` }), telegraf_1.Markup.inlineKeyboard(rows));
|
|
304
|
+
}
|
|
305
|
+
/** Append an entry to the chain and RETURN the confirmation (the one write path behind both add steps). */
|
|
306
|
+
async function applyAdd(entry) {
|
|
307
|
+
const result = (0, fallbackChain_1.addFallbackEntry)(getState().getFallbackChain(), entry);
|
|
308
|
+
if (!result.ok) {
|
|
309
|
+
return result.reason === 'full' ? (0, i18n_1.t)('fallback.full', { max: fallbackChain_1.fallbackChainMaxLength }) : (0, i18n_1.t)('fallback.alreadyInChain', { label: formatEntry(entry) });
|
|
310
|
+
}
|
|
311
|
+
await getState().setFallbackChain(result.chain);
|
|
312
|
+
return (0, i18n_1.t)('fallback.added', { label: formatEntry(entry) });
|
|
313
|
+
}
|
|
314
|
+
/** Replace the chain with the draft and RETURN the confirmation. */
|
|
315
|
+
async function applyPropose() {
|
|
316
|
+
const chain = (0, fallbackChain_1.proposeFallbackChain)(await detectFallbackOptions(), getState().getFallbackChain());
|
|
317
|
+
if (chain.length === 0)
|
|
318
|
+
return (0, i18n_1.t)('fallback.proposeNothing');
|
|
319
|
+
await getState().setFallbackChain(chain);
|
|
320
|
+
return (0, i18n_1.t)('fallback.proposed', { count: chain.length });
|
|
321
|
+
}
|
|
322
|
+
async function applyClear() {
|
|
323
|
+
await getState().setFallbackChain([]);
|
|
324
|
+
return (0, i18n_1.t)('fallback.cleared');
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* @description Set whether the bot moves a limited topic to the next rung by itself (OFF by default: moving
|
|
328
|
+
* work between accounts and providers is an operator decision) and RETURN the confirmation. General sets the
|
|
329
|
+
* instance default, a topic its override (`null` = follow General again).
|
|
330
|
+
*/
|
|
331
|
+
async function applyAutoSwitch(key, isGeneral, enabled) {
|
|
332
|
+
const what = (0, i18n_1.t)('limits.whatSwitch');
|
|
333
|
+
if (isGeneral) {
|
|
334
|
+
await getState().setClaudeAutoSwitchOnLimitGlobalDefault(enabled ?? false);
|
|
335
|
+
return (0, i18n_1.t)('limits.setGlobal', { what, state: formatState(enabled ?? false) });
|
|
336
|
+
}
|
|
337
|
+
await getState().setClaudeAutoSwitchOnLimitOverride(key, enabled);
|
|
338
|
+
return enabled === null
|
|
339
|
+
? (0, i18n_1.t)('limits.setSame', { what, state: formatState(getState().getClaudeAutoSwitchOnLimitGlobalDefault()) })
|
|
340
|
+
: (0, i18n_1.t)('limits.setThisTopic', { what, state: formatState(enabled) });
|
|
341
|
+
}
|
|
342
|
+
/** The ยซwait for the resetยป twin of {@link applyAutoSwitch}; the write (and the armed-wait cleanup) is `autoContinueLimits.ts`'s. */
|
|
343
|
+
async function applyWait(key, isGeneral, enabled) {
|
|
344
|
+
const what = (0, i18n_1.t)('limits.whatWait');
|
|
345
|
+
await applyAutoContinue(key, isGeneral, isGeneral ? (enabled ?? true) : enabled);
|
|
346
|
+
if (isGeneral)
|
|
347
|
+
return (0, i18n_1.t)('limits.setGlobal', { what, state: formatState(enabled ?? true) });
|
|
348
|
+
return enabled === null
|
|
349
|
+
? (0, i18n_1.t)('limits.setSame', { what, state: formatState(getState().getAutoContinueOnLimitGlobalDefault()) })
|
|
350
|
+
: (0, i18n_1.t)('limits.setThisTopic', { what, state: formatState(enabled) });
|
|
351
|
+
}
|
|
352
|
+
// โโ the command โโ
|
|
353
|
+
function registerFallbackChainCommands() {
|
|
354
|
+
// `/limits` โ bare: the screen (the chain is edited from every topic). `propose` / `rebuild`
|
|
355
|
+
// drafts the chain, `clear` empties it, `switch|autoswitch on|off|same` and `wait on|off|same` flip the two
|
|
356
|
+
// settings. `/fallback` is the same command under its old name (hidden alias). Not gated on a session:
|
|
357
|
+
// the chain must be settable before any limit hits.
|
|
358
|
+
command(['limits', 'fallback'], async (_ctx, key, parsed) => {
|
|
359
|
+
const [verb = '', value = ''] = parsed.args;
|
|
360
|
+
await runLimitsCommand(key, verb, value);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Refresh the usage snapshots (a snapshot younger than a minute is kept โ the `/accounts` freshness), waiting at
|
|
365
|
+
* most {@link limitsUsageRefreshBudgetMs}: the screen is never held by a slow usage endpoint.
|
|
366
|
+
*/
|
|
367
|
+
async function refreshUsageWithinBudget() {
|
|
368
|
+
let timer;
|
|
369
|
+
const budget = new Promise((resolve) => { timer = setTimeout(resolve, exports.limitsUsageRefreshBudgetMs); });
|
|
370
|
+
try {
|
|
371
|
+
await Promise.race([refreshClaudeUsage({ maxAgeMs: claudeUsageRefresher_1.claudeUsageOnDemandMaxAgeMs }), budget]);
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
clearTimeout(timer);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/** Open the screen as a fresh message (the `/auto_continue_limits` alias lands here); the usage is refreshed first. */
|
|
378
|
+
async function openLimitsView(key) {
|
|
379
|
+
await refreshUsageWithinBudget();
|
|
380
|
+
const picker = await buildPicker(key, checkIsGeneral(key));
|
|
381
|
+
await replyToThread(key, picker.text, picker.keyboard);
|
|
382
|
+
}
|
|
383
|
+
async function runLimitsCommand(key, verb, value) {
|
|
384
|
+
const isGeneral = checkIsGeneral(key);
|
|
385
|
+
const word = value.toLowerCase();
|
|
386
|
+
const parseState = () => {
|
|
387
|
+
if (word === 'on')
|
|
388
|
+
return true;
|
|
389
|
+
if (word === 'off')
|
|
390
|
+
return false;
|
|
391
|
+
if (word === 'same' && !isGeneral)
|
|
392
|
+
return null;
|
|
393
|
+
return undefined;
|
|
394
|
+
};
|
|
395
|
+
switch (verb.toLowerCase()) {
|
|
396
|
+
case '':
|
|
397
|
+
await openLimitsView(key);
|
|
398
|
+
return;
|
|
399
|
+
case 'propose':
|
|
400
|
+
case 'rebuild': {
|
|
401
|
+
await replyToThread(key, await applyPropose());
|
|
402
|
+
await openLimitsView(key);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
case 'clear': {
|
|
406
|
+
await replyToThread(key, await applyClear());
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
case 'switch':
|
|
410
|
+
case 'autoswitch': {
|
|
411
|
+
const enabled = parseState();
|
|
412
|
+
if (enabled === undefined) {
|
|
413
|
+
await replyToThread(key, (0, i18n_1.t)('limits.usage'));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
await replyToThread(key, await applyAutoSwitch(key, isGeneral, enabled));
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
case 'wait': {
|
|
420
|
+
const enabled = parseState();
|
|
421
|
+
if (enabled === undefined) {
|
|
422
|
+
await replyToThread(key, (0, i18n_1.t)('limits.usage'));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
await replyToThread(key, await applyWait(key, isGeneral, enabled));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
default:
|
|
429
|
+
await replyToThread(key, (0, i18n_1.t)('limits.usage'));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/** The shared tap prologue: authorise, resolve the topic, run under its locale. */
|
|
433
|
+
async function handleTap(ctx, run) {
|
|
434
|
+
const key = await authoriseContext(ctx);
|
|
435
|
+
if (!key) {
|
|
436
|
+
await ctx.answerCbQuery((0, i18n_1.t)('cb.access_denied'));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
await withThreadLocale(key, () => run(key, checkIsGeneral(key), (0, callbackMessage_1.getCallbackMessageId)(ctx)));
|
|
440
|
+
}
|
|
441
|
+
/** `answerCbQuery` is capped at 200 characters: a long confirmation goes to the topic instead. */
|
|
442
|
+
async function answerTap(ctx, key, text) {
|
|
443
|
+
if (text.length <= 180) {
|
|
444
|
+
await ctx.answerCbQuery(text);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
await ctx.answerCbQuery();
|
|
448
|
+
await replyToThread(key, text);
|
|
449
|
+
}
|
|
450
|
+
/** An edit tap: a stale revision only re-renders. */
|
|
451
|
+
function registerEdit(re, edit, confirm,
|
|
452
|
+
/** Where the rung sits after the edit (the detail view follows it); `null` = back to the list. */
|
|
453
|
+
nextIndex) {
|
|
454
|
+
bot.action(re, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
455
|
+
const chain = getState().getFallbackChain();
|
|
456
|
+
const parsed = (0, fallbackChain_1.parseFallbackEditCallback)(ctx.match[0], re, (0, fallbackChain_1.getFallbackChainRevision)(chain));
|
|
457
|
+
if (parsed === null) {
|
|
458
|
+
await ctx.answerCbQuery();
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (!parsed.isCurrent) {
|
|
462
|
+
await ctx.answerCbQuery((0, i18n_1.t)('fallback.stale'));
|
|
463
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const result = edit(chain, parsed.index);
|
|
467
|
+
if (!result.ok) {
|
|
468
|
+
await ctx.answerCbQuery((0, i18n_1.t)('fallback.stale'));
|
|
469
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
await getState().setFallbackChain(result.chain);
|
|
473
|
+
await answerTap(ctx, key, confirm(chain, parsed.index));
|
|
474
|
+
const next = nextIndex(parsed.index);
|
|
475
|
+
if (next === null)
|
|
476
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
477
|
+
else
|
|
478
|
+
await renderRungDetail(key, messageId, next, isGeneral);
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
function registerFallbackChainCallbacks() {
|
|
482
|
+
registerEdit(fallbackChain_1.fallbackMoveUpCallbackRe, (chain, index) => (0, fallbackChain_1.moveFallbackEntry)(chain, index, -1), () => (0, i18n_1.t)('fallback.moved'), (index) => Math.max(0, index - 1));
|
|
483
|
+
registerEdit(fallbackChain_1.fallbackMoveDownCallbackRe, (chain, index) => (0, fallbackChain_1.moveFallbackEntry)(chain, index, 1), () => (0, i18n_1.t)('fallback.moved'), (index) => index + 1);
|
|
484
|
+
registerEdit(fallbackChain_1.fallbackToggleCallbackRe, fallbackChain_1.toggleFallbackEntryDisabled, (chain, index) => (0, i18n_1.t)((0, fallbackChain_1.checkIsFallbackEntryDisabled)(chain[index]) ? 'limits.entryEnabled' : 'limits.entryDisabled', { label: formatEntry(chain[index]) }), (index) => index);
|
|
485
|
+
registerEdit(fallbackChain_1.fallbackRemoveCallbackRe, fallbackChain_1.removeFallbackEntry, (chain, index) => (0, i18n_1.t)('fallback.removed', { label: formatEntry(chain[index]) }), () => null);
|
|
486
|
+
// A rung's button on the list: open its detail view (a stale revision only re-renders the list).
|
|
487
|
+
bot.action(fallbackChain_1.fallbackOpenCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
488
|
+
const chain = getState().getFallbackChain();
|
|
489
|
+
const parsed = (0, fallbackChain_1.parseFallbackEditCallback)(ctx.match[0], fallbackChain_1.fallbackOpenCallbackRe, (0, fallbackChain_1.getFallbackChainRevision)(chain));
|
|
490
|
+
if (parsed === null) {
|
|
491
|
+
await ctx.answerCbQuery();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (!parsed.isCurrent || parsed.index >= chain.length) {
|
|
495
|
+
await ctx.answerCbQuery((0, i18n_1.t)('fallback.stale'));
|
|
496
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
await ctx.answerCbQuery();
|
|
500
|
+
await renderRungDetail(key, messageId, parsed.index, isGeneral);
|
|
501
|
+
}));
|
|
502
|
+
bot.action(fallbackChain_1.fallbackAddCallback, (ctx) => handleTap(ctx, async (key, _isGeneral, messageId) => {
|
|
503
|
+
await ctx.answerCbQuery();
|
|
504
|
+
if (messageId !== null)
|
|
505
|
+
await renderAddAccountStep(key, messageId);
|
|
506
|
+
}));
|
|
507
|
+
bot.action(fallbackChain_1.fallbackAddClaudeCallbackRe, (ctx) => handleTap(ctx, async (key, _isGeneral, messageId) => {
|
|
508
|
+
await ctx.answerCbQuery();
|
|
509
|
+
if (messageId !== null)
|
|
510
|
+
await renderAddClaudeModelStep(key, messageId, ctx.match[1]);
|
|
511
|
+
}));
|
|
512
|
+
bot.action(fallbackChain_1.fallbackAddClaudeModelCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
513
|
+
const model = ctx.match[2] === '-' ? null : ctx.match[2];
|
|
514
|
+
await answerTap(ctx, key, await applyAdd({ backend: 'claude', profile: ctx.match[1], model }));
|
|
515
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
516
|
+
}));
|
|
517
|
+
bot.action(fallbackChain_1.fallbackAddProviderCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
518
|
+
const snapshot = messageId === null ? undefined : addSnapshots.get(messageId);
|
|
519
|
+
const providerId = snapshot?.providers[Number(ctx.match[1])];
|
|
520
|
+
if (messageId === null || !snapshot || providerId === undefined) {
|
|
521
|
+
await ctx.answerCbQuery((0, i18n_1.t)('fallback.stale'));
|
|
522
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
await ctx.answerCbQuery();
|
|
526
|
+
const detection = await detectFallbackOptions();
|
|
527
|
+
const models = (0, fallbackChain_1.listOpenCodeModelsOfProvider)(detection.openCodeModels, providerId);
|
|
528
|
+
const next = { ...snapshot, providerId, models };
|
|
529
|
+
addSnapshots.set(messageId, next);
|
|
530
|
+
await renderAddModelPage(key, messageId, next, 0);
|
|
531
|
+
}));
|
|
532
|
+
bot.action(fallbackChain_1.fallbackAddModelPageCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
533
|
+
const snapshot = messageId === null ? undefined : addSnapshots.get(messageId);
|
|
534
|
+
if (messageId === null || !snapshot || snapshot.providerId === null) {
|
|
535
|
+
await ctx.answerCbQuery((0, i18n_1.t)('fallback.stale'));
|
|
536
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
await ctx.answerCbQuery();
|
|
540
|
+
await renderAddModelPage(key, messageId, snapshot, Number(ctx.match[1]));
|
|
541
|
+
}));
|
|
542
|
+
bot.action(fallbackChain_1.fallbackAddModelCallbackRe, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
543
|
+
const snapshot = messageId === null ? undefined : addSnapshots.get(messageId);
|
|
544
|
+
const modelId = snapshot?.models[Number(ctx.match[1])];
|
|
545
|
+
if (!snapshot || snapshot.providerId === null || modelId === undefined) {
|
|
546
|
+
await ctx.answerCbQuery((0, i18n_1.t)('fallback.stale'));
|
|
547
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
await answerTap(ctx, key, await applyAdd({ backend: 'opencode', providerId: snapshot.providerId, modelId }));
|
|
551
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
552
|
+
}));
|
|
553
|
+
bot.action(fallbackChain_1.fallbackProposeCallback, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
554
|
+
if (getState().getFallbackChain().length === 0) {
|
|
555
|
+
await answerTap(ctx, key, await applyPropose());
|
|
556
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
await ctx.answerCbQuery();
|
|
560
|
+
if (messageId !== null) {
|
|
561
|
+
await editThreadMessage(key, messageId, (0, i18n_1.t)('limits.rebuildConfirm'), telegraf_1.Markup.inlineKeyboard([
|
|
562
|
+
[telegraf_1.Markup.button.callback((0, i18n_1.t)('fallback.proposeConfirmButton'), fallbackChain_1.fallbackProposeConfirmCallback)],
|
|
563
|
+
buildBackRow(),
|
|
564
|
+
]));
|
|
565
|
+
}
|
|
566
|
+
}));
|
|
567
|
+
bot.action(fallbackChain_1.fallbackProposeConfirmCallback, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
568
|
+
await answerTap(ctx, key, await applyPropose());
|
|
569
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
570
|
+
}));
|
|
571
|
+
bot.action(fallbackChain_1.fallbackClearCallback, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
572
|
+
await answerTap(ctx, key, await applyClear());
|
|
573
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
574
|
+
}));
|
|
575
|
+
bot.action(fallbackChain_1.fallbackBackCallback, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
576
|
+
await ctx.answerCbQuery();
|
|
577
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
578
|
+
}));
|
|
579
|
+
// A topic's setting button: open its sub-menu. General has no sub-menu (its button flips the value), so a tap
|
|
580
|
+
// that still arrives there only re-renders the screen.
|
|
581
|
+
for (const id of settingIds) {
|
|
582
|
+
bot.action(settingCallbacks[id].open, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
583
|
+
await ctx.answerCbQuery();
|
|
584
|
+
if (isGeneral)
|
|
585
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
586
|
+
else
|
|
587
|
+
await renderSettingMenu(key, messageId, id);
|
|
588
|
+
}));
|
|
589
|
+
}
|
|
590
|
+
const settingTaps = [
|
|
591
|
+
[fallbackChain_1.fallbackAutoSwitchOnCallback, applyAutoSwitch, true], [fallbackChain_1.fallbackAutoSwitchOffCallback, applyAutoSwitch, false], [fallbackChain_1.fallbackAutoSwitchSameCallback, applyAutoSwitch, null],
|
|
592
|
+
[fallbackChain_1.fallbackWaitOnCallback, applyWait, true], [fallbackChain_1.fallbackWaitOffCallback, applyWait, false], [fallbackChain_1.fallbackWaitSameCallback, applyWait, null],
|
|
593
|
+
];
|
|
594
|
+
for (const [callback, apply, enabled] of settingTaps) {
|
|
595
|
+
bot.action(callback, (ctx) => handleTap(ctx, async (key, isGeneral, messageId) => {
|
|
596
|
+
if (enabled === null && isGeneral) {
|
|
597
|
+
await ctx.answerCbQuery();
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
await answerTap(ctx, key, await apply(key, isGeneral, enabled));
|
|
601
|
+
await rerenderPicker(key, messageId, isGeneral);
|
|
602
|
+
}));
|
|
603
|
+
}
|
|
604
|
+
// ยซ๐ Switch to <entry>ยป on a usage-limit notice: a one-shot button โ the notice is edited into the
|
|
605
|
+
// confirmation (keyboard dropped); a stale tap (the episode is over) only says so.
|
|
606
|
+
bot.action(fallbackChain_1.fallbackSwitchCallbackRe, (ctx) => handleTap(ctx, async (key, _isGeneral, messageId) => {
|
|
607
|
+
const parsed = (0, fallbackChain_1.parseFallbackSwitchCallbackData)(ctx.match[0]);
|
|
608
|
+
if (parsed === null) {
|
|
609
|
+
await ctx.answerCbQuery();
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const confirmation = await takeSwitchOffer(key, parsed.episodeId, parsed.index);
|
|
613
|
+
if (confirmation === null) {
|
|
614
|
+
await ctx.answerCbQuery((0, i18n_1.t)('fallback.switchExpired'));
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
await ctx.answerCbQuery();
|
|
618
|
+
if (messageId !== null)
|
|
619
|
+
await editThreadMessage(key, messageId, confirmation);
|
|
620
|
+
}));
|
|
621
|
+
}
|
|
622
|
+
return { registerFallbackChainCommands, registerFallbackChainCallbacks, openLimitsView, applyAutoSwitch, formatFallbackEntry: formatEntry };
|
|
623
|
+
}
|
|
624
|
+
//# sourceMappingURL=fallbackChain.js.map
|