Compare commits
3 Commits
main
...
fix/capabi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ab4c3a3c4 | ||
|
|
be6536a635 | ||
|
|
c2e10710f4 |
@ -44,6 +44,7 @@
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Sub-agents: normalize announce delivery origin + queue bucketing by accountId to keep multi-account routing stable. (#1061, #1058) — thanks @adam91holt.
|
- Sub-agents: normalize announce delivery origin + queue bucketing by accountId to keep multi-account routing stable. (#1061, #1058) — thanks @adam91holt.
|
||||||
|
- Config: handle object-format Telegram capabilities in channel capability resolution. (#1071) — thanks @danielz1z.
|
||||||
- Sessions: include deliveryContext in sessions.list and reuse normalized delivery routing for announce/restart fallbacks. (#1058)
|
- Sessions: include deliveryContext in sessions.list and reuse normalized delivery routing for announce/restart fallbacks. (#1058)
|
||||||
- Sessions: propagate deliveryContext into last-route updates to keep account/channel routing stable. (#1058)
|
- Sessions: propagate deliveryContext into last-route updates to keep account/channel routing stable. (#1058)
|
||||||
- Gateway: honor explicit delivery targets without implicit accountId fallback; preserve lastAccountId for implicit routing.
|
- Gateway: honor explicit delivery targets without implicit accountId fallback; preserve lastAccountId for implicit routing.
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const DSR_PATTERN = /\x1b\[\??6n/g;
|
const ESC = String.fromCharCode(0x1b);
|
||||||
|
const DSR_PATTERN = new RegExp(`${ESC}\\[\\??6n`, "g");
|
||||||
|
|
||||||
export function stripDsrRequests(input: string): { cleaned: string; requests: number } {
|
export function stripDsrRequests(input: string): { cleaned: string; requests: number } {
|
||||||
let requests = 0;
|
let requests = 0;
|
||||||
|
|||||||
@ -3,6 +3,36 @@ import { normalizeMainKey } from "../../routing/session-key.js";
|
|||||||
|
|
||||||
export type SessionKind = "main" | "group" | "cron" | "hook" | "node" | "other";
|
export type SessionKind = "main" | "group" | "cron" | "hook" | "node" | "other";
|
||||||
|
|
||||||
|
export type SessionListDeliveryContext = {
|
||||||
|
channel?: string;
|
||||||
|
to?: string;
|
||||||
|
accountId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SessionListRow = {
|
||||||
|
key: string;
|
||||||
|
kind: SessionKind;
|
||||||
|
channel: string;
|
||||||
|
label?: string;
|
||||||
|
displayName?: string;
|
||||||
|
deliveryContext?: SessionListDeliveryContext;
|
||||||
|
updatedAt?: number | null;
|
||||||
|
sessionId?: string;
|
||||||
|
model?: string;
|
||||||
|
contextTokens?: number | null;
|
||||||
|
totalTokens?: number | null;
|
||||||
|
thinkingLevel?: string;
|
||||||
|
verboseLevel?: string;
|
||||||
|
systemSent?: boolean;
|
||||||
|
abortedLastRun?: boolean;
|
||||||
|
sendPolicy?: string;
|
||||||
|
lastChannel?: string;
|
||||||
|
lastTo?: string;
|
||||||
|
lastAccountId?: string;
|
||||||
|
transcriptPath?: string;
|
||||||
|
messages?: unknown[];
|
||||||
|
};
|
||||||
|
|
||||||
function normalizeKey(value?: string) {
|
function normalizeKey(value?: string) {
|
||||||
const trimmed = value?.trim();
|
const trimmed = value?.trim();
|
||||||
return trimmed ? trimmed : undefined;
|
return trimmed ? trimmed : undefined;
|
||||||
|
|||||||
@ -17,34 +17,10 @@ import {
|
|||||||
resolveDisplaySessionKey,
|
resolveDisplaySessionKey,
|
||||||
resolveInternalSessionKey,
|
resolveInternalSessionKey,
|
||||||
resolveMainSessionAlias,
|
resolveMainSessionAlias,
|
||||||
type SessionKind,
|
type SessionListRow,
|
||||||
stripToolMessages,
|
stripToolMessages,
|
||||||
} from "./sessions-helpers.js";
|
} from "./sessions-helpers.js";
|
||||||
|
|
||||||
type SessionListRow = {
|
|
||||||
key: string;
|
|
||||||
kind: SessionKind;
|
|
||||||
channel: string;
|
|
||||||
label?: string;
|
|
||||||
displayName?: string;
|
|
||||||
deliveryContext?: { channel?: string; to?: string; accountId?: string };
|
|
||||||
updatedAt?: number | null;
|
|
||||||
sessionId?: string;
|
|
||||||
model?: string;
|
|
||||||
contextTokens?: number | null;
|
|
||||||
totalTokens?: number | null;
|
|
||||||
thinkingLevel?: string;
|
|
||||||
verboseLevel?: string;
|
|
||||||
systemSent?: boolean;
|
|
||||||
abortedLastRun?: boolean;
|
|
||||||
sendPolicy?: string;
|
|
||||||
lastChannel?: string;
|
|
||||||
lastTo?: string;
|
|
||||||
lastAccountId?: string;
|
|
||||||
transcriptPath?: string;
|
|
||||||
messages?: unknown[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const SessionsListToolSchema = Type.Object({
|
const SessionsListToolSchema = Type.Object({
|
||||||
kinds: Type.Optional(Type.Array(Type.String())),
|
kinds: Type.Optional(Type.Array(Type.String())),
|
||||||
limit: Type.Optional(Type.Number({ minimum: 1 })),
|
limit: Type.Optional(Type.Number({ minimum: 1 })),
|
||||||
|
|||||||
@ -679,15 +679,15 @@ export function registerHooksCli(program: Command): void {
|
|||||||
for (const hookId of targets) {
|
for (const hookId of targets) {
|
||||||
const record = installs[hookId];
|
const record = installs[hookId];
|
||||||
if (!record) {
|
if (!record) {
|
||||||
defaultRuntime.log(chalk.yellow(`No install record for \"${hookId}\".`));
|
defaultRuntime.log(chalk.yellow(`No install record for "${hookId}".`));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (record.source !== "npm") {
|
if (record.source !== "npm") {
|
||||||
defaultRuntime.log(chalk.yellow(`Skipping \"${hookId}\" (source: ${record.source}).`));
|
defaultRuntime.log(chalk.yellow(`Skipping "${hookId}" (source: ${record.source}).`));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!record.spec) {
|
if (!record.spec) {
|
||||||
defaultRuntime.log(chalk.yellow(`Skipping \"${hookId}\" (missing npm spec).`));
|
defaultRuntime.log(chalk.yellow(`Skipping "${hookId}" (missing npm spec).`));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -105,6 +105,49 @@ describe("resolveChannelCapabilities", () => {
|
|||||||
}),
|
}),
|
||||||
).toEqual(["polls"]);
|
).toEqual(["polls"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("handles object-format capabilities gracefully (e.g., { inlineButtons: 'dm' })", () => {
|
||||||
|
const cfg = {
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
// Object format - used for granular control like inlineButtons scope.
|
||||||
|
// Channel-specific handlers (resolveTelegramInlineButtonsScope) process these.
|
||||||
|
capabilities: { inlineButtons: "dm" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Should return undefined (not crash), allowing channel-specific handlers to process it.
|
||||||
|
expect(
|
||||||
|
resolveChannelCapabilities({
|
||||||
|
cfg: cfg as ClawdbotConfig,
|
||||||
|
channel: "telegram",
|
||||||
|
}),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to channel capabilities when account capabilities use object format", () => {
|
||||||
|
const cfg = {
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
capabilities: ["inlineButtons"],
|
||||||
|
accounts: {
|
||||||
|
default: {
|
||||||
|
capabilities: { inlineButtons: "dm" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Partial<ClawdbotConfig>;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveChannelCapabilities({
|
||||||
|
cfg: cfg as ClawdbotConfig,
|
||||||
|
channel: "telegram",
|
||||||
|
accountId: "default",
|
||||||
|
}),
|
||||||
|
).toEqual(["inlineButtons"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const createRegistry = (channels: PluginRegistry["channels"]): PluginRegistry => ({
|
const createRegistry = (channels: PluginRegistry["channels"]): PluginRegistry => ({
|
||||||
|
|||||||
@ -4,6 +4,9 @@ import type { ClawdbotConfig } from "./config.js";
|
|||||||
|
|
||||||
function normalizeCapabilities(capabilities: string[] | undefined): string[] | undefined {
|
function normalizeCapabilities(capabilities: string[] | undefined): string[] | undefined {
|
||||||
if (!capabilities) return undefined;
|
if (!capabilities) return undefined;
|
||||||
|
// Handle object-format capabilities (e.g., { inlineButtons: "dm" }) gracefully.
|
||||||
|
// Channel-specific handlers (like resolveTelegramInlineButtonsScope) process these separately.
|
||||||
|
if (!Array.isArray(capabilities)) return undefined;
|
||||||
const normalized = capabilities.map((entry) => entry.trim()).filter(Boolean);
|
const normalized = capabilities.map((entry) => entry.trim()).filter(Boolean);
|
||||||
return normalized.length > 0 ? normalized : undefined;
|
return normalized.length > 0 ? normalized : undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user