feat: extend Telegram dock commands and config hashing (#929)
Thanks @grp06. Co-authored-by: George Pickett <gpickett00@gmail.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Browser: add tests for snapshot labels/efficient query params and labeled image responses.
|
- Browser: add tests for snapshot labels/efficient query params and labeled image responses.
|
||||||
|
- Telegram: register dock native commands with underscores to avoid `BOT_COMMAND_INVALID` (#929, fixes #901) — thanks @grp06.
|
||||||
- Google: downgrade unsigned thinking blocks before send to avoid missing signature errors.
|
- Google: downgrade unsigned thinking blocks before send to avoid missing signature errors.
|
||||||
- Agents: cap tool call IDs for OpenAI/OpenRouter to avoid request rejections. (#875) — thanks @j1philli.
|
- Agents: cap tool call IDs for OpenAI/OpenRouter to avoid request rejections. (#875) — thanks @j1philli.
|
||||||
- Doctor: avoid re-adding WhatsApp config when only legacy ack reactions are set. (#927, fixes #900) — thanks @grp06.
|
- Doctor: avoid re-adding WhatsApp config when only legacy ack reactions are set. (#927, fixes #900) — thanks @grp06.
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ Text + native (when enabled):
|
|||||||
- `/cost on|off` (toggle per-response usage line)
|
- `/cost on|off` (toggle per-response usage line)
|
||||||
- `/stop`
|
- `/stop`
|
||||||
- `/restart`
|
- `/restart`
|
||||||
- `/dock-telegram` (switch replies to Telegram)
|
- `/dock-telegram` (alias: `/dock_telegram`) (switch replies to Telegram)
|
||||||
- `/dock-discord` (switch replies to Discord)
|
- `/dock-discord` (alias: `/dock_discord`) (switch replies to Discord)
|
||||||
- `/dock-slack` (switch replies to Slack)
|
- `/dock-slack` (alias: `/dock_slack`) (switch replies to Slack)
|
||||||
- `/activation mention|always` (groups only)
|
- `/activation mention|always` (groups only)
|
||||||
- `/send on|off|inherit` (owner-only)
|
- `/send on|off|inherit` (owner-only)
|
||||||
- `/reset` or `/new`
|
- `/reset` or `/new`
|
||||||
|
|||||||
@@ -27,6 +27,18 @@ function defineChatCommand(command: DefineChatCommandInput): ChatCommandDefiniti
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChannelDock = ReturnType<typeof listChannelDocks>[number];
|
||||||
|
|
||||||
|
function defineDockCommand(dock: ChannelDock): ChatCommandDefinition {
|
||||||
|
return defineChatCommand({
|
||||||
|
key: `dock:${dock.id}`,
|
||||||
|
nativeName: `dock_${dock.id}`,
|
||||||
|
description: `Switch to ${dock.id} for replies.`,
|
||||||
|
textAliases: [`/dock-${dock.id}`, `/dock_${dock.id}`],
|
||||||
|
acceptsArgs: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function registerAlias(commands: ChatCommandDefinition[], key: string, ...aliases: string[]): void {
|
function registerAlias(commands: ChatCommandDefinition[], key: string, ...aliases: string[]): void {
|
||||||
const command = commands.find((entry) => entry.key === key);
|
const command = commands.find((entry) => entry.key === key);
|
||||||
if (!command) {
|
if (!command) {
|
||||||
@@ -238,15 +250,7 @@ export const CHAT_COMMANDS: ChatCommandDefinition[] = (() => {
|
|||||||
}),
|
}),
|
||||||
...listChannelDocks()
|
...listChannelDocks()
|
||||||
.filter((dock) => dock.capabilities.nativeCommands)
|
.filter((dock) => dock.capabilities.nativeCommands)
|
||||||
.map((dock) =>
|
.map((dock) => defineDockCommand(dock)),
|
||||||
defineChatCommand({
|
|
||||||
key: `dock:${dock.id}`,
|
|
||||||
nativeName: `dock-${dock.id}`,
|
|
||||||
description: `Switch to ${dock.id} for replies.`,
|
|
||||||
textAlias: `/dock-${dock.id}`,
|
|
||||||
acceptsArgs: false,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
registerAlias(commands, "status", "/usage");
|
registerAlias(commands, "status", "/usage");
|
||||||
|
|||||||
@@ -113,4 +113,8 @@ describe("commands registry", () => {
|
|||||||
"/help@otherbot",
|
"/help@otherbot",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes dock command aliases", () => {
|
||||||
|
expect(normalizeCommandBody("/dock_telegram")).toBe("/dock-telegram");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export {
|
|||||||
loadConfig,
|
loadConfig,
|
||||||
parseConfigJson5,
|
parseConfigJson5,
|
||||||
readConfigFileSnapshot,
|
readConfigFileSnapshot,
|
||||||
|
resolveConfigSnapshotHash,
|
||||||
writeConfigFile,
|
writeConfigFile,
|
||||||
} from "./io.js";
|
} from "./io.js";
|
||||||
export { migrateLegacyConfig } from "./legacy-migrate.js";
|
export { migrateLegacyConfig } from "./legacy-migrate.js";
|
||||||
|
|||||||
@@ -58,6 +58,18 @@ function hashConfigRaw(raw: string | null): string {
|
|||||||
.digest("hex");
|
.digest("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveConfigSnapshotHash(snapshot: {
|
||||||
|
hash?: string;
|
||||||
|
raw?: string | null;
|
||||||
|
}): string | null {
|
||||||
|
if (typeof snapshot.hash === "string") {
|
||||||
|
const trimmed = snapshot.hash.trim();
|
||||||
|
if (trimmed) return trimmed;
|
||||||
|
}
|
||||||
|
if (typeof snapshot.raw !== "string") return null;
|
||||||
|
return hashConfigRaw(snapshot.raw);
|
||||||
|
}
|
||||||
|
|
||||||
export type ConfigIoDeps = {
|
export type ConfigIoDeps = {
|
||||||
fs?: typeof fs;
|
fs?: typeof fs;
|
||||||
json5?: typeof JSON5;
|
json5?: typeof JSON5;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
loadConfig,
|
loadConfig,
|
||||||
parseConfigJson5,
|
parseConfigJson5,
|
||||||
readConfigFileSnapshot,
|
readConfigFileSnapshot,
|
||||||
|
resolveConfigSnapshotHash,
|
||||||
validateConfigObject,
|
validateConfigObject,
|
||||||
writeConfigFile,
|
writeConfigFile,
|
||||||
} from "../config/config.js";
|
} from "../config/config.js";
|
||||||
@@ -33,7 +34,8 @@ function requireConfigBaseHash(
|
|||||||
snapshot: Awaited<ReturnType<typeof readConfigFileSnapshot>>,
|
snapshot: Awaited<ReturnType<typeof readConfigFileSnapshot>>,
|
||||||
): { ok: true } | { ok: false; error: { code: string; message: string } } {
|
): { ok: true } | { ok: false; error: { code: string; message: string } } {
|
||||||
if (!snapshot.exists) return { ok: true };
|
if (!snapshot.exists) return { ok: true };
|
||||||
if (typeof snapshot.raw !== "string" || !snapshot.hash) {
|
const snapshotHash = resolveConfigSnapshotHash(snapshot);
|
||||||
|
if (!snapshotHash) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
error: {
|
error: {
|
||||||
@@ -52,7 +54,7 @@ function requireConfigBaseHash(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (baseHash !== snapshot.hash) {
|
if (baseHash !== snapshotHash) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
error: {
|
error: {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
loadConfig,
|
loadConfig,
|
||||||
parseConfigJson5,
|
parseConfigJson5,
|
||||||
readConfigFileSnapshot,
|
readConfigFileSnapshot,
|
||||||
|
resolveConfigSnapshotHash,
|
||||||
validateConfigObject,
|
validateConfigObject,
|
||||||
writeConfigFile,
|
writeConfigFile,
|
||||||
} from "../../config/config.js";
|
} from "../../config/config.js";
|
||||||
@@ -42,7 +43,8 @@ function requireConfigBaseHash(
|
|||||||
respond: RespondFn,
|
respond: RespondFn,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!snapshot.exists) return true;
|
if (!snapshot.exists) return true;
|
||||||
if (typeof snapshot.raw !== "string" || !snapshot.hash) {
|
const snapshotHash = resolveConfigSnapshotHash(snapshot);
|
||||||
|
if (!snapshotHash) {
|
||||||
respond(
|
respond(
|
||||||
false,
|
false,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -65,7 +67,7 @@ function requireConfigBaseHash(
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (baseHash !== snapshot.hash) {
|
if (baseHash !== snapshotHash) {
|
||||||
respond(
|
respond(
|
||||||
false,
|
false,
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { resolveConfigSnapshotHash } from "../config/config.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
connectOk,
|
connectOk,
|
||||||
installGatewayTestHooks,
|
installGatewayTestHooks,
|
||||||
@@ -43,12 +45,15 @@ describe("gateway config.patch", () => {
|
|||||||
params: {},
|
params: {},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const getRes = await onceMessage<{ ok: boolean; payload?: { hash?: string } }>(
|
const getRes = await onceMessage<{ ok: boolean; payload?: { hash?: string; raw?: string } }>(
|
||||||
ws,
|
ws,
|
||||||
(o) => o.type === "res" && o.id === getId,
|
(o) => o.type === "res" && o.id === getId,
|
||||||
);
|
);
|
||||||
expect(getRes.ok).toBe(true);
|
expect(getRes.ok).toBe(true);
|
||||||
const baseHash = getRes.payload?.hash;
|
const baseHash = resolveConfigSnapshotHash({
|
||||||
|
hash: getRes.payload?.hash,
|
||||||
|
raw: getRes.payload?.raw,
|
||||||
|
});
|
||||||
expect(typeof baseHash).toBe("string");
|
expect(typeof baseHash).toBe("string");
|
||||||
|
|
||||||
const patchId = "req-patch";
|
const patchId = "req-patch";
|
||||||
|
|||||||
Reference in New Issue
Block a user