@0xmaxma/claude-gateway 1.7.10 → 1.8.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/README.md +167 -41
- package/dist/agent/builtin-commands.js +2 -2
- package/dist/agent/builtin-commands.js.map +1 -1
- package/dist/agent/knowledge/config.d.ts.map +1 -1
- package/dist/agent/knowledge/config.js +2 -9
- package/dist/agent/knowledge/config.js.map +1 -1
- package/dist/agent/model-catalog.d.ts +62 -0
- package/dist/agent/model-catalog.d.ts.map +1 -0
- package/dist/agent/model-catalog.js +264 -0
- package/dist/agent/model-catalog.js.map +1 -0
- package/dist/agent/runner.d.ts +76 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +189 -36
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/cron-router.d.ts +12 -8
- package/dist/api/cron-router.d.ts.map +1 -1
- package/dist/api/cron-router.js +82 -16
- package/dist/api/cron-router.js.map +1 -1
- package/dist/api/gateway-router.d.ts +4 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +14 -0
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/manifest-sources.d.ts +15 -0
- package/dist/api/manifest-sources.d.ts.map +1 -0
- package/dist/api/manifest-sources.js +27 -0
- package/dist/api/manifest-sources.js.map +1 -0
- package/dist/api/meta-router.d.ts +13 -0
- package/dist/api/meta-router.d.ts.map +1 -0
- package/dist/api/meta-router.js +26 -0
- package/dist/api/meta-router.js.map +1 -0
- package/dist/api/packages.d.ts.map +1 -1
- package/dist/api/packages.js +18 -198
- package/dist/api/packages.js.map +1 -1
- package/dist/api/route-registry.d.ts +81 -0
- package/dist/api/route-registry.d.ts.map +1 -0
- package/dist/api/route-registry.js +44 -0
- package/dist/api/route-registry.js.map +1 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +21 -3
- package/dist/api/router.js.map +1 -1
- package/dist/apps/installer.d.ts +73 -5
- package/dist/apps/installer.d.ts.map +1 -1
- package/dist/apps/installer.js +237 -22
- package/dist/apps/installer.js.map +1 -1
- package/dist/cli/args.d.ts +25 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +55 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/colors.d.ts +37 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +63 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/command-names.d.ts +98 -0
- package/dist/cli/command-names.d.ts.map +1 -0
- package/dist/cli/command-names.js +109 -0
- package/dist/cli/command-names.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +28 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +321 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/channels.d.ts +17 -0
- package/dist/cli/commands/channels.d.ts.map +1 -0
- package/dist/cli/commands/channels.js +101 -0
- package/dist/cli/commands/channels.js.map +1 -0
- package/dist/cli/commands/debug-bundle.d.ts +6 -0
- package/dist/cli/commands/debug-bundle.d.ts.map +1 -0
- package/dist/cli/commands/debug-bundle.js +220 -0
- package/dist/cli/commands/debug-bundle.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +104 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/gateway.d.ts +12 -0
- package/dist/cli/commands/gateway.d.ts.map +1 -0
- package/dist/cli/commands/gateway.js +145 -0
- package/dist/cli/commands/gateway.js.map +1 -0
- package/dist/cli/commands/service.d.ts +33 -0
- package/dist/cli/commands/service.d.ts.map +1 -0
- package/dist/cli/commands/service.js +513 -0
- package/dist/cli/commands/service.js.map +1 -0
- package/dist/cli/commands/update.d.ts +16 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +149 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands.generated.d.ts +4 -0
- package/dist/cli/commands.generated.d.ts.map +1 -0
- package/dist/cli/commands.generated.js +220 -0
- package/dist/cli/commands.generated.js.map +1 -0
- package/dist/cli/health.d.ts +31 -0
- package/dist/cli/health.d.ts.map +1 -0
- package/dist/cli/health.js +49 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/http-client.d.ts +140 -0
- package/dist/cli/http-client.d.ts.map +1 -0
- package/dist/cli/http-client.js +332 -0
- package/dist/cli/http-client.js.map +1 -0
- package/dist/cli/index.d.ts +27 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +372 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/manager.d.ts +64 -0
- package/dist/cli/manager.d.ts.map +1 -0
- package/dist/cli/manager.js +181 -0
- package/dist/cli/manager.js.map +1 -0
- package/dist/cli/output.d.ts +49 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +101 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/prompt.d.ts +29 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +154 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/redact.d.ts +3 -0
- package/dist/cli/redact.d.ts.map +1 -0
- package/dist/cli/redact.js +33 -0
- package/dist/cli/redact.js.map +1 -0
- package/dist/cli/types.d.ts +22 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/config/bootstrap.d.ts +33 -0
- package/dist/config/bootstrap.d.ts.map +1 -0
- package/dist/config/bootstrap.js +117 -0
- package/dist/config/bootstrap.js.map +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +5 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +2 -35
- package/dist/config/watcher.js.map +1 -1
- package/dist/discord/receiver.d.ts +6 -1
- package/dist/discord/receiver.d.ts.map +1 -1
- package/dist/discord/receiver.js +9 -1
- package/dist/discord/receiver.js.map +1 -1
- package/dist/entry.d.ts +3 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +83 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.js +160 -46
- package/dist/index.js.map +1 -1
- package/dist/load-dotenv.d.ts +2 -0
- package/dist/load-dotenv.d.ts.map +1 -0
- package/dist/load-dotenv.js +68 -0
- package/dist/load-dotenv.js.map +1 -0
- package/dist/packages/registry.d.ts +50 -0
- package/dist/packages/registry.d.ts.map +1 -0
- package/dist/packages/registry.js +209 -0
- package/dist/packages/registry.js.map +1 -0
- package/dist/shutdown-signals.d.ts +47 -0
- package/dist/shutdown-signals.d.ts.map +1 -0
- package/dist/shutdown-signals.js +62 -0
- package/dist/shutdown-signals.js.map +1 -0
- package/dist/telegram/receiver.d.ts +6 -1
- package/dist/telegram/receiver.d.ts.map +1 -1
- package/dist/telegram/receiver.js +9 -1
- package/dist/telegram/receiver.js.map +1 -1
- package/dist/utils/orphan-receivers.d.ts +90 -0
- package/dist/utils/orphan-receivers.d.ts.map +1 -0
- package/dist/utils/orphan-receivers.js +218 -0
- package/dist/utils/orphan-receivers.js.map +1 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +60 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/stop-child.d.ts +21 -0
- package/dist/utils/stop-child.d.ts.map +1 -0
- package/dist/utils/stop-child.js +64 -0
- package/dist/utils/stop-child.js.map +1 -0
- package/lib/pairing.ts +1 -1
- package/mcp/tools/agent/handlers.ts +1 -1
- package/mcp/tools/discord/receiver-server.ts +4 -0
- package/mcp/tools/telegram/receiver-server.ts +6 -4
- package/package.json +6 -4
package/dist/agent/runner.js
CHANGED
|
@@ -51,6 +51,7 @@ const slack_client_1 = require("../api/slack-client");
|
|
|
51
51
|
const markdown_1 = require("../telegram/markdown");
|
|
52
52
|
const skills_1 = require("../skills");
|
|
53
53
|
const builtin_commands_1 = require("./builtin-commands");
|
|
54
|
+
const model_catalog_1 = require("./model-catalog");
|
|
54
55
|
const safe_mode_1 = require("./safe-mode");
|
|
55
56
|
const recovery_executor_1 = require("./recovery-executor");
|
|
56
57
|
const recovery_policy_1 = require("./recovery-policy");
|
|
@@ -434,7 +435,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
434
435
|
// Check if this is a built-in channel command
|
|
435
436
|
const trimmedContent = content.trim();
|
|
436
437
|
if ((0, builtin_commands_1.isBuiltinCommand)(trimmedContent, channelSource)) {
|
|
437
|
-
this.handleSessionCommand(chatId, trimmedContent)
|
|
438
|
+
this.handleSessionCommand(chatId, trimmedContent, meta)
|
|
438
439
|
.then(() => this.writeTypingDone(chatId))
|
|
439
440
|
.catch((err) => {
|
|
440
441
|
this.logger.error('Session command failed', { error: err.message });
|
|
@@ -685,8 +686,17 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
685
686
|
return;
|
|
686
687
|
}
|
|
687
688
|
if (command === 'get_models') {
|
|
688
|
-
|
|
689
|
-
|
|
689
|
+
// Live catalog first, static list as the fallback (issue #409). The
|
|
690
|
+
// picker is a UI action, so a slow or unreachable catalog must not hang
|
|
691
|
+
// it — fetchModelCatalog is bounded and never rejects.
|
|
692
|
+
void this.availableModels()
|
|
693
|
+
.then((availableModels) => {
|
|
694
|
+
respond({ models: availableModels.map(m => ({ id: m.id, label: m.label })) });
|
|
695
|
+
})
|
|
696
|
+
// Without this the request would hang and the picker with it: the
|
|
697
|
+
// receiver is blocked on this response. Every other async branch in
|
|
698
|
+
// this handler answers on rejection too.
|
|
699
|
+
.catch((err) => respond({ success: false, error: String(err) }, 500));
|
|
690
700
|
return;
|
|
691
701
|
}
|
|
692
702
|
// `/cli` webview terminal viewer — create a pairing on behalf of an
|
|
@@ -735,15 +745,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
735
745
|
respond({ success: false, error: err.message });
|
|
736
746
|
return;
|
|
737
747
|
}
|
|
738
|
-
|
|
739
|
-
const restartPromises = [];
|
|
740
|
-
for (const [key, session] of this.sessions) {
|
|
741
|
-
if (session.source !== 'api') {
|
|
742
|
-
restarted = true;
|
|
743
|
-
restartPromises.push(this.restartProcess(key));
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
await Promise.all(restartPromises);
|
|
748
|
+
const restarted = await this.restartChannelSessions();
|
|
747
749
|
respond({ success: true, model: newModel, restarted });
|
|
748
750
|
return;
|
|
749
751
|
}
|
|
@@ -788,9 +790,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
788
790
|
return respond({ success: true, text: 'No active session found.' });
|
|
789
791
|
}
|
|
790
792
|
const effectiveModel = this.agentConfig.claude.model;
|
|
791
|
-
const
|
|
792
|
-
const modelConfig = availableModels.find(m => m.id === effectiveModel);
|
|
793
|
-
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
793
|
+
const contextWindow = await this.contextWindowFor(effectiveModel);
|
|
794
794
|
const contextTokens = meta.lastInputTokens ?? 0;
|
|
795
795
|
const usedPct = Math.round((contextTokens / contextWindow) * 100);
|
|
796
796
|
let msgs;
|
|
@@ -1879,7 +1879,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1879
1879
|
/**
|
|
1880
1880
|
* Dispatch a session management command to the appropriate handler.
|
|
1881
1881
|
*/
|
|
1882
|
-
async handleSessionCommand(chatId, content) {
|
|
1882
|
+
async handleSessionCommand(chatId, content, meta = {}) {
|
|
1883
1883
|
const agentId = this.agentConfig.id;
|
|
1884
1884
|
if (content.startsWith('/sessions')) {
|
|
1885
1885
|
await this.handleCommandSessions(agentId, chatId);
|
|
@@ -1901,10 +1901,78 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1901
1901
|
const name = content.replace('/rename', '').trim();
|
|
1902
1902
|
await this.handleCommandRename(agentId, chatId, name);
|
|
1903
1903
|
}
|
|
1904
|
+
else if (content.startsWith('/models')) {
|
|
1905
|
+
await this.handleCommandModels(chatId);
|
|
1906
|
+
}
|
|
1907
|
+
else if (content.startsWith('/model')) {
|
|
1908
|
+
await this.handleCommandModel(chatId, content.replace('/model', '').trim(), meta);
|
|
1909
|
+
}
|
|
1904
1910
|
else if (content.startsWith('/stop')) {
|
|
1905
1911
|
await this.handleCommandStop(chatId);
|
|
1906
1912
|
}
|
|
1907
1913
|
}
|
|
1914
|
+
/**
|
|
1915
|
+
* /models — the model picker for channels without an inline keyboard.
|
|
1916
|
+
*
|
|
1917
|
+
* Telegram never reaches this: its receiver answers `/models` itself with a
|
|
1918
|
+
* button keyboard and does not forward the message. Discord and LINE have no
|
|
1919
|
+
* picker of their own, so they get the list as text plus the command that
|
|
1920
|
+
* selects from it (issue #409).
|
|
1921
|
+
*
|
|
1922
|
+
* A channel listed in BUILTIN_COMMANDS but missing a branch here is worse
|
|
1923
|
+
* than one not listed at all — the gate swallows the message and the user
|
|
1924
|
+
* gets silence instead of the agent's reply. `/model` on Discord was in
|
|
1925
|
+
* exactly that state before this.
|
|
1926
|
+
*/
|
|
1927
|
+
async handleCommandModels(chatId) {
|
|
1928
|
+
const models = await this.availableModels();
|
|
1929
|
+
const current = this.agentConfig.claude.model;
|
|
1930
|
+
const lines = [`Current model: ${current}`, ''];
|
|
1931
|
+
for (const m of models) {
|
|
1932
|
+
const marker = m.id === current ? '✅ ' : '• ';
|
|
1933
|
+
lines.push(`${marker}${m.label} — \`${m.id}\``);
|
|
1934
|
+
}
|
|
1935
|
+
lines.push('', 'Switch with /model <id or alias>');
|
|
1936
|
+
this.writeAutoForward(chatId, lines.join('\n'));
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* /model — show the current model, or switch to one from the catalog.
|
|
1940
|
+
*
|
|
1941
|
+
* An unrecognised argument is refused rather than persisted: unlike the API
|
|
1942
|
+
* (where the caller is authenticated and the provider validates the id), a
|
|
1943
|
+
* typo in a chat message would otherwise be written into config.json and
|
|
1944
|
+
* break the agent's next spawn. Anything the catalog knows — including a
|
|
1945
|
+
* model that exists only upstream and not in the static list — is accepted.
|
|
1946
|
+
*/
|
|
1947
|
+
async handleCommandModel(chatId, arg, meta = {}) {
|
|
1948
|
+
const current = this.agentConfig.claude.model;
|
|
1949
|
+
if (!arg) {
|
|
1950
|
+
this.writeAutoForward(chatId, `Current model: ${current}\n\nUse /models to see the list.`);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
if (!this.isDirectChat(meta)) {
|
|
1954
|
+
this.writeAutoForward(chatId, 'Switching models is only available in a direct message with the bot — '
|
|
1955
|
+
+ 'it changes the model for every chat of this agent and restarts them. '
|
|
1956
|
+
+ 'Use /models here to see the list.');
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
const models = await this.selectableModels();
|
|
1960
|
+
const wanted = arg.toLowerCase();
|
|
1961
|
+
const match = models.find((m) => m.id.toLowerCase() === wanted || m.alias.toLowerCase() === wanted);
|
|
1962
|
+
if (!match) {
|
|
1963
|
+
this.writeAutoForward(chatId, `Unknown model "${arg}". Use /models to see the list.`);
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
if (match.id === current) {
|
|
1967
|
+
this.writeAutoForward(chatId, `Already using ${match.label} (${match.id}).`);
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
await this.setModel(match.id);
|
|
1971
|
+
// Same restart the Telegram picker's set_model does — the running session
|
|
1972
|
+
// was spawned with the old model and would otherwise keep using it.
|
|
1973
|
+
const restarted = await this.restartChannelSessions();
|
|
1974
|
+
this.writeAutoForward(chatId, `Model set to ${match.label} (${match.id}).${restarted ? ' Restarting the session…' : ''}`);
|
|
1975
|
+
}
|
|
1908
1976
|
/**
|
|
1909
1977
|
* /sessions — list all sessions for this chat.
|
|
1910
1978
|
*/
|
|
@@ -1933,9 +2001,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1933
2001
|
return;
|
|
1934
2002
|
}
|
|
1935
2003
|
const effectiveModel = this.agentConfig.claude.model;
|
|
1936
|
-
const
|
|
1937
|
-
const modelConfig = availableModels.find(m => m.id === effectiveModel);
|
|
1938
|
-
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
2004
|
+
const contextWindow = await this.contextWindowFor(effectiveModel);
|
|
1939
2005
|
const contextTokens = meta.lastInputTokens ?? 0;
|
|
1940
2006
|
const usedPct = Math.round((contextTokens / contextWindow) * 100);
|
|
1941
2007
|
let msgs;
|
|
@@ -2053,9 +2119,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2053
2119
|
const meta = index.sessions.find(s => s.id === sessionId);
|
|
2054
2120
|
const name = meta?.name ?? 'Session';
|
|
2055
2121
|
const compactModel = this.agentConfig.claude.model;
|
|
2056
|
-
const
|
|
2057
|
-
const modelConfig = availableModels.find(m => m.id === compactModel);
|
|
2058
|
-
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
2122
|
+
const contextWindow = await this.contextWindowFor(compactModel);
|
|
2059
2123
|
this.writeAutoForward(chatId, `⏳ Compacting session "${name}"...`);
|
|
2060
2124
|
try {
|
|
2061
2125
|
const compactor = new compactor_1.SessionCompactor(this.sessionStore);
|
|
@@ -2571,7 +2635,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2571
2635
|
stopTelegramReceiver() {
|
|
2572
2636
|
if (!this.receiver)
|
|
2573
2637
|
return;
|
|
2574
|
-
|
|
2638
|
+
// Hot-remove is a config-change response, not a shutdown: detach immediately
|
|
2639
|
+
// and let teardown finish in the background rather than blocking the caller
|
|
2640
|
+
// for the SIGKILL grace period. stop() never rejects.
|
|
2641
|
+
void this.receiver.stop();
|
|
2575
2642
|
this.receiver = null;
|
|
2576
2643
|
this.logger.info('TelegramReceiver stopped', { agentId: this.agentConfig.id });
|
|
2577
2644
|
}
|
|
@@ -2587,7 +2654,10 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2587
2654
|
stopDiscordReceiver() {
|
|
2588
2655
|
if (!this.discordReceiver)
|
|
2589
2656
|
return;
|
|
2590
|
-
|
|
2657
|
+
// Hot-remove is a config-change response, not a shutdown: detach immediately
|
|
2658
|
+
// and let teardown finish in the background rather than blocking the caller
|
|
2659
|
+
// for the SIGKILL grace period. stop() never rejects.
|
|
2660
|
+
void this.discordReceiver.stop();
|
|
2591
2661
|
this.discordReceiver = null;
|
|
2592
2662
|
this.logger.info('DiscordReceiver stopped', { agentId: this.agentConfig.id });
|
|
2593
2663
|
}
|
|
@@ -2609,10 +2679,17 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2609
2679
|
clearTimeout(buf.timer);
|
|
2610
2680
|
}
|
|
2611
2681
|
this.channelCoalesce.clear();
|
|
2612
|
-
|
|
2613
|
-
|
|
2682
|
+
// Receiver teardown now resolves only once the child has exited, so it must
|
|
2683
|
+
// be awaited or shutdown() can exit the gateway out from under a receiver
|
|
2684
|
+
// that is still shutting down (issue #405). Run it concurrently with session
|
|
2685
|
+
// teardown rather than serially — both are bounded, neither depends on the
|
|
2686
|
+
// other, and shutdown latency is user-visible.
|
|
2687
|
+
const receiversStopped = [this.receiver?.stop(), this.discordReceiver?.stop()];
|
|
2614
2688
|
this.stopLineReply();
|
|
2615
|
-
await Promise.all([
|
|
2689
|
+
await Promise.all([
|
|
2690
|
+
...receiversStopped,
|
|
2691
|
+
...[...this.sessions.values()].map((s) => s.stop()),
|
|
2692
|
+
]);
|
|
2616
2693
|
this.sessions.clear();
|
|
2617
2694
|
}
|
|
2618
2695
|
/**
|
|
@@ -3412,9 +3489,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3412
3489
|
responseText = `Session: (unnamed)\nMessages: ${messageCount}\nContext used: 0%\nModel: ${effectiveModel}`;
|
|
3413
3490
|
}
|
|
3414
3491
|
else {
|
|
3415
|
-
const
|
|
3416
|
-
const modelConfig = availableModels.find((m) => m.id === effectiveModel);
|
|
3417
|
-
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
3492
|
+
const contextWindow = await this.contextWindowFor(effectiveModel);
|
|
3418
3493
|
const contextUsedPct = Math.round(((meta.lastInputTokens ?? 0) / contextWindow) * 100);
|
|
3419
3494
|
result = { sessionId, sessionName: meta.name, messageCount, archivedCount: meta.archivedCount ?? 0, contextUsedPct, model: effectiveModel };
|
|
3420
3495
|
responseText = [
|
|
@@ -3471,9 +3546,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3471
3546
|
const ch = 'api';
|
|
3472
3547
|
const activeSession = this.sessions.get(sessionId);
|
|
3473
3548
|
const compactEffectiveModel = opts?.model ?? activeSession?.modelOverride ?? this.agentConfig.claude.model;
|
|
3474
|
-
const
|
|
3475
|
-
const modelConfig = availableModels.find((m) => m.id === compactEffectiveModel);
|
|
3476
|
-
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
3549
|
+
const contextWindow = await this.contextWindowFor(compactEffectiveModel);
|
|
3477
3550
|
const compactor = new compactor_1.SessionCompactor(this.sessionStore);
|
|
3478
3551
|
const compactResult = await compactor.compact(agentId, storeChatId, sessionId, compactEffectiveModel, contextWindow, ch);
|
|
3479
3552
|
await this.sessionStore.updateSessionMeta(agentId, storeChatId, sessionId, {
|
|
@@ -3507,6 +3580,88 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3507
3580
|
persist('assistant', responseText, forcePersist);
|
|
3508
3581
|
return { result, responseText };
|
|
3509
3582
|
}
|
|
3583
|
+
/**
|
|
3584
|
+
* Whether an inbound channel message came from a 1:1 chat with the bot.
|
|
3585
|
+
*
|
|
3586
|
+
* Gates the one channel command that reaches past its own chat: `/model <x>`
|
|
3587
|
+
* rewrites `config.json` for the whole agent and restarts every session in
|
|
3588
|
+
* every chat. The channel access gates do not cover that. Discord's, in a
|
|
3589
|
+
* guild, checks the guild, the channel, and a mention — never the user
|
|
3590
|
+
* (`allowFrom` in mcp/tools/discord/access.ts is DM-only), so without this
|
|
3591
|
+
* any member of an allowlisted channel could switch the model out from under
|
|
3592
|
+
* everyone else and kill their in-flight turns. LINE forwards group and room
|
|
3593
|
+
* messages the same way. Telegram's receiver answers `/model` itself behind
|
|
3594
|
+
* a private-chat + allowlist check and does not forward it here.
|
|
3595
|
+
*
|
|
3596
|
+
* Fails closed: a channel that does not report a chat type cannot switch.
|
|
3597
|
+
* Listing models (`/models`, bare `/model`) is unaffected — it reads nothing
|
|
3598
|
+
* and changes nothing.
|
|
3599
|
+
*/
|
|
3600
|
+
isDirectChat(meta) {
|
|
3601
|
+
const kind = meta['chat_type'] ?? meta['line_chat_type'] ?? meta['slack_chat_type'];
|
|
3602
|
+
return kind === 'direct' || kind === 'user';
|
|
3603
|
+
}
|
|
3604
|
+
/**
|
|
3605
|
+
* Restart every non-api session so a model change actually takes effect.
|
|
3606
|
+
*
|
|
3607
|
+
* `setModel` only rewrites config — a session process was spawned with the
|
|
3608
|
+
* old model on its command line and keeps using it until it is restarted.
|
|
3609
|
+
* Reporting "model set" without this is a false success: the next turn still
|
|
3610
|
+
* runs on the previous model. Returns whether anything was restarted.
|
|
3611
|
+
*/
|
|
3612
|
+
async restartChannelSessions() {
|
|
3613
|
+
const restartPromises = [];
|
|
3614
|
+
for (const [key, session] of this.sessions) {
|
|
3615
|
+
if (session.source !== 'api')
|
|
3616
|
+
restartPromises.push(this.restartProcess(key));
|
|
3617
|
+
}
|
|
3618
|
+
await Promise.all(restartPromises);
|
|
3619
|
+
return restartPromises.length > 0;
|
|
3620
|
+
}
|
|
3621
|
+
/**
|
|
3622
|
+
* The model list this gateway offers: the live catalog when one is reachable,
|
|
3623
|
+
* the configured/static list otherwise (issue #409). Cached upstream, so the
|
|
3624
|
+
* six callers that only want one model's context window pay for at most one
|
|
3625
|
+
* fetch a minute between them.
|
|
3626
|
+
*/
|
|
3627
|
+
async availableModels() {
|
|
3628
|
+
const staticModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
3629
|
+
return (await (0, model_catalog_1.fetchModelCatalog)(staticModels)) ?? staticModels;
|
|
3630
|
+
}
|
|
3631
|
+
/**
|
|
3632
|
+
* Context window for a model id. Resolving this through the live catalog is
|
|
3633
|
+
* what keeps `/session`'s "context used" percentage and `/compact`'s window
|
|
3634
|
+
* honest for a model that exists only upstream — before, any such model fell
|
|
3635
|
+
* through to the 200k default no matter how large its real window.
|
|
3636
|
+
*/
|
|
3637
|
+
async contextWindowFor(modelId) {
|
|
3638
|
+
const models = await this.availableModels();
|
|
3639
|
+
// Configured list second, not just as the catalog's fallback: the catalog
|
|
3640
|
+
// *replaces* the list rather than merging with it, so a configured model
|
|
3641
|
+
// the upstream catalog does not list (the local `[1m]` ids are exactly
|
|
3642
|
+
// that shape) would otherwise drop to the 200k default — reporting
|
|
3643
|
+
// /session context use against a fifth of the real window and handing
|
|
3644
|
+
// SessionCompactor a window small enough to compact far too early.
|
|
3645
|
+
return models.find((m) => m.id === modelId)?.contextWindow
|
|
3646
|
+
?? (this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS).find((m) => m.id === modelId)?.contextWindow
|
|
3647
|
+
?? model_catalog_1.DEFAULT_CONTEXT_WINDOW;
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Models `/model <arg>` may switch to: what the catalog offers, plus the
|
|
3651
|
+
* configured list.
|
|
3652
|
+
*
|
|
3653
|
+
* The union is only for *resolution*, never for the `/models` listing — the
|
|
3654
|
+
* listing has to reflect what upstream actually offers, or a model removed
|
|
3655
|
+
* upstream would linger in the picker forever, which is the bug this all
|
|
3656
|
+
* started from. Without the union a user who switches to a live-only model
|
|
3657
|
+
* is trapped: their previous configured model is no longer nameable.
|
|
3658
|
+
*/
|
|
3659
|
+
async selectableModels() {
|
|
3660
|
+
const live = await this.availableModels();
|
|
3661
|
+
const configured = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
3662
|
+
const ids = new Set(live.map((m) => m.id));
|
|
3663
|
+
return [...live, ...configured.filter((m) => !ids.has(m.id))];
|
|
3664
|
+
}
|
|
3510
3665
|
async setModel(newModel) {
|
|
3511
3666
|
// Allow any model string through — BYOK/third-party models (openrouter/* etc.)
|
|
3512
3667
|
// are validated by the upstream provider, not the local config list.
|
|
@@ -3555,9 +3710,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
3555
3710
|
if (!meta)
|
|
3556
3711
|
return null;
|
|
3557
3712
|
const effectiveModel = this.agentConfig.claude.model;
|
|
3558
|
-
const
|
|
3559
|
-
const modelConfig = availableModels.find((m) => m.id === effectiveModel);
|
|
3560
|
-
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
3713
|
+
const contextWindow = await this.contextWindowFor(effectiveModel);
|
|
3561
3714
|
const contextUsedPct = Math.round(((meta.lastInputTokens ?? 0) / contextWindow) * 100);
|
|
3562
3715
|
return {
|
|
3563
3716
|
sessionId: meta.id,
|