clawchef 0.1.10 → 0.1.12
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 +5 -1
- package/dist/openclaw/command-provider.js +41 -4
- package/dist/orchestrator.js +13 -0
- package/dist/recipe.js +14 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.js +3 -2
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/openclaw/command-provider.ts +48 -4
- package/src/orchestrator.ts +18 -1
- package/src/recipe.ts +20 -1
- package/src/schema.ts +3 -2
- package/src/types.ts +1 -0
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Recipe-driven OpenClaw environment orchestrator.
|
|
|
14
14
|
- When installed OpenClaw version mismatches recipe version, prompts: ignore / abort / force reinstall (silent mode auto-picks force reinstall).
|
|
15
15
|
- Supports scoped execution via `--scope full|files|workspace`.
|
|
16
16
|
- `full` scope runs factory reset first (with confirmation prompt unless `-s/--silent` is used).
|
|
17
|
+
- Factory reset includes removing local `~/.openclaw` directory.
|
|
17
18
|
- If `openclaw` is missing, auto-installs the recipe version and skips factory reset.
|
|
18
19
|
- Starts OpenClaw gateway after each recipe execution based on `--gateway-mode`.
|
|
19
20
|
- Creates workspaces and agents (default workspace path: `~/.openclaw/workspace-<workspace-name>`).
|
|
@@ -392,6 +393,7 @@ channels:
|
|
|
392
393
|
token: "${telegram_bot_token}"
|
|
393
394
|
account: "default"
|
|
394
395
|
agent: "main"
|
|
396
|
+
group_policy: "allowlist"
|
|
395
397
|
|
|
396
398
|
- channel: "telegram"
|
|
397
399
|
token: "${alerts_bot_token}"
|
|
@@ -412,11 +414,13 @@ channels:
|
|
|
412
414
|
Supported common fields:
|
|
413
415
|
|
|
414
416
|
- required: `channel`
|
|
415
|
-
- optional: `account`, `agent`, `name`, `token`, `token_file`, `use_env`, `bot_token`, `access_token`, `app_token`, `webhook_url`, `webhook_path`, `signal_number`, `password`, `login`, `login_mode`, `login_account`
|
|
417
|
+
- optional: `account`, `agent`, `group_policy`, `name`, `token`, `token_file`, `use_env`, `bot_token`, `access_token`, `app_token`, `webhook_url`, `webhook_path`, `signal_number`, `password`, `login`, `login_mode`, `login_account`
|
|
416
418
|
- advanced passthrough: `extra_flags` (`snake_case` keys become `--kebab-case` CLI flags)
|
|
417
419
|
|
|
418
420
|
`channels[].agent` currently supports `channel: "telegram"` only.
|
|
419
421
|
If `agent` is set and `account` is omitted, clawchef defaults `account` to the same value as `agent`.
|
|
422
|
+
`channels[].group_policy` currently supports `channel: "telegram"` only and is applied after `channels add` via `openclaw config set` so it is not overwritten by add-flow writes.
|
|
423
|
+
If `channel: "telegram"` has `token: ""` or `bot_token: ""`, clawchef auto-disables that telegram account (`enabled=false`) and skips channel add/bind.
|
|
420
424
|
|
|
421
425
|
## Workspace path behavior
|
|
422
426
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tmpdir } from "node:os";
|
|
1
|
+
import { homedir, tmpdir } from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
@@ -129,6 +129,28 @@ function parseVersionOutput(output) {
|
|
|
129
129
|
const match = output.match(/\b(\d+\.\d+\.\d+)\b/);
|
|
130
130
|
return match?.[1] ?? output.trim();
|
|
131
131
|
}
|
|
132
|
+
function telegramGroupPolicyPath(account) {
|
|
133
|
+
const trimmed = account?.trim();
|
|
134
|
+
if (!trimmed) {
|
|
135
|
+
return "channels.telegram.groupPolicy";
|
|
136
|
+
}
|
|
137
|
+
return `channels.telegram.accounts[${trimmed}].groupPolicy`;
|
|
138
|
+
}
|
|
139
|
+
function telegramEnabledPath(account) {
|
|
140
|
+
const trimmed = account?.trim();
|
|
141
|
+
if (!trimmed) {
|
|
142
|
+
return "channels.telegram.enabled";
|
|
143
|
+
}
|
|
144
|
+
return `channels.telegram.accounts[${trimmed}].enabled`;
|
|
145
|
+
}
|
|
146
|
+
function shouldAutoDisableTelegramChannel(channel) {
|
|
147
|
+
if (channel.channel !== "telegram") {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
|
|
151
|
+
const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
|
|
152
|
+
return emptyToken || emptyBotToken;
|
|
153
|
+
}
|
|
132
154
|
async function chooseVersionMismatchAction(currentVersion, expectedVersion, silent) {
|
|
133
155
|
if (silent) {
|
|
134
156
|
return "force";
|
|
@@ -341,10 +363,13 @@ export class CommandOpenClawProvider {
|
|
|
341
363
|
async factoryReset(config, dryRun) {
|
|
342
364
|
const bin = config.bin ?? "openclaw";
|
|
343
365
|
const resetCmd = commandFor(config, "factory_reset", { bin, version: config.version });
|
|
344
|
-
if (
|
|
345
|
-
|
|
366
|
+
if (resetCmd.trim()) {
|
|
367
|
+
await runShell(resetCmd, dryRun);
|
|
368
|
+
}
|
|
369
|
+
const openclawHome = path.join(homedir(), ".openclaw");
|
|
370
|
+
if (!dryRun) {
|
|
371
|
+
await rm(openclawHome, { recursive: true, force: true });
|
|
346
372
|
}
|
|
347
|
-
await runShell(resetCmd, dryRun);
|
|
348
373
|
}
|
|
349
374
|
async installPlugin(config, pluginSpec, dryRun) {
|
|
350
375
|
const trimmed = pluginSpec.trim();
|
|
@@ -393,6 +418,12 @@ export class CommandOpenClawProvider {
|
|
|
393
418
|
}
|
|
394
419
|
async configureChannel(config, channel, dryRun) {
|
|
395
420
|
const bin = config.bin ?? "openclaw";
|
|
421
|
+
if (shouldAutoDisableTelegramChannel(channel)) {
|
|
422
|
+
const enabledPath = telegramEnabledPath(channel.account);
|
|
423
|
+
const disableCmd = `${bin} config set ${shellQuote(enabledPath)} false --strict-json`;
|
|
424
|
+
await runShell(disableCmd, dryRun);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
396
427
|
const enablePluginTemplate = config.commands?.enable_plugin;
|
|
397
428
|
if (enablePluginTemplate?.trim()) {
|
|
398
429
|
const enablePluginCmd = fillTemplate(enablePluginTemplate, {
|
|
@@ -443,6 +474,12 @@ export class CommandOpenClawProvider {
|
|
|
443
474
|
}
|
|
444
475
|
const cmd = `${bin} channels add ${flags.join(" ")}`;
|
|
445
476
|
await runShell(cmd, dryRun);
|
|
477
|
+
if (channel.channel === "telegram" && channel.group_policy) {
|
|
478
|
+
const configPath = telegramGroupPolicyPath(channel.account);
|
|
479
|
+
const policyValue = JSON.stringify(channel.group_policy);
|
|
480
|
+
const setPolicyCmd = `${bin} config set ${shellQuote(configPath)} ${shellQuote(policyValue)} --strict-json`;
|
|
481
|
+
await runShell(setPolicyCmd, dryRun);
|
|
482
|
+
}
|
|
446
483
|
}
|
|
447
484
|
async bindChannelAgent(config, channel, agent, dryRun) {
|
|
448
485
|
const account = channel.account?.trim();
|
package/dist/orchestrator.js
CHANGED
|
@@ -76,6 +76,14 @@ function isHttpUrl(value) {
|
|
|
76
76
|
return false;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
function shouldAutoDisableTelegramChannel(channel) {
|
|
80
|
+
if (channel.channel !== "telegram") {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
|
|
84
|
+
const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
|
|
85
|
+
return emptyToken || emptyBotToken;
|
|
86
|
+
}
|
|
79
87
|
function isNotFoundError(err) {
|
|
80
88
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
81
89
|
}
|
|
@@ -314,7 +322,12 @@ export async function runRecipe(recipe, recipeOrigin, options, logger) {
|
|
|
314
322
|
const effectiveChannel = channel.agent?.trim() && !channel.account?.trim()
|
|
315
323
|
? { ...channel, account: channel.agent.trim() }
|
|
316
324
|
: channel;
|
|
325
|
+
const autoDisabledTelegram = shouldAutoDisableTelegramChannel(effectiveChannel);
|
|
317
326
|
await provider.configureChannel(recipe.openclaw, effectiveChannel, options.dryRun);
|
|
327
|
+
if (autoDisabledTelegram) {
|
|
328
|
+
logger.info(`Telegram channel disabled due to empty bot token: ${effectiveChannel.channel}${effectiveChannel.account ? `/${effectiveChannel.account}` : ""}`);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
318
331
|
logger.info(`Channel configured: ${effectiveChannel.channel}${effectiveChannel.account ? `/${effectiveChannel.account}` : ""}`);
|
|
319
332
|
if (effectiveChannel.agent?.trim()) {
|
|
320
333
|
await provider.bindChannelAgent(recipe.openclaw, effectiveChannel, effectiveChannel.agent, options.dryRun);
|
package/dist/recipe.js
CHANGED
|
@@ -44,6 +44,14 @@ const ALLOWED_CHANNELS = new Set([
|
|
|
44
44
|
]);
|
|
45
45
|
const CHANNEL_SECRET_FIELDS = ["token", "bot_token", "access_token", "app_token", "password"];
|
|
46
46
|
const TEMPLATE_TOKEN_RE = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
|
|
47
|
+
function hasExplicitEmptyTelegramToken(channel) {
|
|
48
|
+
if (channel.channel !== "telegram") {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
|
|
52
|
+
const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
|
|
53
|
+
return emptyToken || emptyBotToken;
|
|
54
|
+
}
|
|
47
55
|
function assertNoInlineSecrets(recipe) {
|
|
48
56
|
const bootstrap = recipe.openclaw.bootstrap;
|
|
49
57
|
if (bootstrap) {
|
|
@@ -193,6 +201,9 @@ function semanticValidate(recipe) {
|
|
|
193
201
|
(channel.login || channel.login_mode !== undefined || channel.login_account !== undefined)) {
|
|
194
202
|
throw new ClawChefError("channels[] entry for telegram does not support login/login_mode/login_account. Configure token (or use_env/token_file), then start gateway.");
|
|
195
203
|
}
|
|
204
|
+
if (channel.group_policy !== undefined && channel.channel !== "telegram") {
|
|
205
|
+
throw new ClawChefError(`channels[] entry for ${channel.channel} does not support group_policy. Use channel: telegram.`);
|
|
206
|
+
}
|
|
196
207
|
if (channel.agent?.trim()) {
|
|
197
208
|
if (channel.channel !== "telegram") {
|
|
198
209
|
throw new ClawChefError(`channels[] entry for ${channel.channel} does not support agent binding. Use channel: telegram with agent.`);
|
|
@@ -219,6 +230,9 @@ function semanticValidate(recipe) {
|
|
|
219
230
|
}
|
|
220
231
|
return /(token|password|secret|api[_-]?key|webhook)/i.test(key) && String(value).trim().length > 0;
|
|
221
232
|
});
|
|
233
|
+
if (hasExplicitEmptyTelegramToken(channel)) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
222
236
|
if (!hasAuth) {
|
|
223
237
|
throw new ClawChefError(`channels[] entry for ${channel.channel} requires at least one auth input (for example token/bot_token/access_token/token_file/use_env)`);
|
|
224
238
|
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -356,6 +356,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
356
356
|
channel: z.ZodString;
|
|
357
357
|
account: z.ZodOptional<z.ZodString>;
|
|
358
358
|
agent: z.ZodOptional<z.ZodString>;
|
|
359
|
+
group_policy: z.ZodOptional<z.ZodEnum<["open", "allowlist", "disabled"]>>;
|
|
359
360
|
login: z.ZodOptional<z.ZodBoolean>;
|
|
360
361
|
login_mode: z.ZodOptional<z.ZodEnum<["interactive"]>>;
|
|
361
362
|
login_account: z.ZodOptional<z.ZodString>;
|
|
@@ -376,6 +377,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
376
377
|
token?: string | undefined;
|
|
377
378
|
account?: string | undefined;
|
|
378
379
|
agent?: string | undefined;
|
|
380
|
+
group_policy?: "open" | "allowlist" | "disabled" | undefined;
|
|
379
381
|
login?: boolean | undefined;
|
|
380
382
|
login_mode?: "interactive" | undefined;
|
|
381
383
|
login_account?: string | undefined;
|
|
@@ -395,6 +397,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
395
397
|
token?: string | undefined;
|
|
396
398
|
account?: string | undefined;
|
|
397
399
|
agent?: string | undefined;
|
|
400
|
+
group_policy?: "open" | "allowlist" | "disabled" | undefined;
|
|
398
401
|
login?: boolean | undefined;
|
|
399
402
|
login_mode?: "interactive" | undefined;
|
|
400
403
|
login_account?: string | undefined;
|
|
@@ -572,6 +575,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
572
575
|
token?: string | undefined;
|
|
573
576
|
account?: string | undefined;
|
|
574
577
|
agent?: string | undefined;
|
|
578
|
+
group_policy?: "open" | "allowlist" | "disabled" | undefined;
|
|
575
579
|
login?: boolean | undefined;
|
|
576
580
|
login_mode?: "interactive" | undefined;
|
|
577
581
|
login_account?: string | undefined;
|
|
@@ -687,6 +691,7 @@ export declare const recipeSchema: z.ZodObject<{
|
|
|
687
691
|
token?: string | undefined;
|
|
688
692
|
account?: string | undefined;
|
|
689
693
|
agent?: string | undefined;
|
|
694
|
+
group_policy?: "open" | "allowlist" | "disabled" | undefined;
|
|
690
695
|
login?: boolean | undefined;
|
|
691
696
|
login_mode?: "interactive" | undefined;
|
|
692
697
|
login_account?: string | undefined;
|
package/dist/schema.js
CHANGED
|
@@ -102,14 +102,15 @@ const channelSchema = z
|
|
|
102
102
|
channel: z.string().min(1),
|
|
103
103
|
account: z.string().min(1).optional(),
|
|
104
104
|
agent: z.string().min(1).optional(),
|
|
105
|
+
group_policy: z.enum(["open", "allowlist", "disabled"]).optional(),
|
|
105
106
|
login: z.boolean().optional(),
|
|
106
107
|
login_mode: z.enum(["interactive"]).optional(),
|
|
107
108
|
login_account: z.string().min(1).optional(),
|
|
108
109
|
name: z.string().min(1).optional(),
|
|
109
|
-
token: z.string().
|
|
110
|
+
token: z.string().optional(),
|
|
110
111
|
token_file: z.string().min(1).optional(),
|
|
111
112
|
use_env: z.boolean().optional(),
|
|
112
|
-
bot_token: z.string().
|
|
113
|
+
bot_token: z.string().optional(),
|
|
113
114
|
access_token: z.string().min(1).optional(),
|
|
114
115
|
app_token: z.string().min(1).optional(),
|
|
115
116
|
webhook_url: z.string().min(1).optional(),
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tmpdir } from "node:os";
|
|
1
|
+
import { homedir, tmpdir } from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
@@ -178,6 +178,31 @@ function parseVersionOutput(output: string): string {
|
|
|
178
178
|
return match?.[1] ?? output.trim();
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
function telegramGroupPolicyPath(account: string | undefined): string {
|
|
182
|
+
const trimmed = account?.trim();
|
|
183
|
+
if (!trimmed) {
|
|
184
|
+
return "channels.telegram.groupPolicy";
|
|
185
|
+
}
|
|
186
|
+
return `channels.telegram.accounts[${trimmed}].groupPolicy`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function telegramEnabledPath(account: string | undefined): string {
|
|
190
|
+
const trimmed = account?.trim();
|
|
191
|
+
if (!trimmed) {
|
|
192
|
+
return "channels.telegram.enabled";
|
|
193
|
+
}
|
|
194
|
+
return `channels.telegram.accounts[${trimmed}].enabled`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function shouldAutoDisableTelegramChannel(channel: ChannelDef): boolean {
|
|
198
|
+
if (channel.channel !== "telegram") {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
|
|
202
|
+
const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
|
|
203
|
+
return emptyToken || emptyBotToken;
|
|
204
|
+
}
|
|
205
|
+
|
|
181
206
|
type VersionMismatchChoice = "ignore" | "abort" | "force";
|
|
182
207
|
|
|
183
208
|
async function chooseVersionMismatchAction(
|
|
@@ -440,10 +465,14 @@ export class CommandOpenClawProvider implements OpenClawProvider {
|
|
|
440
465
|
async factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void> {
|
|
441
466
|
const bin = config.bin ?? "openclaw";
|
|
442
467
|
const resetCmd = commandFor(config, "factory_reset", { bin, version: config.version });
|
|
443
|
-
if (
|
|
444
|
-
|
|
468
|
+
if (resetCmd.trim()) {
|
|
469
|
+
await runShell(resetCmd, dryRun);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const openclawHome = path.join(homedir(), ".openclaw");
|
|
473
|
+
if (!dryRun) {
|
|
474
|
+
await rm(openclawHome, { recursive: true, force: true });
|
|
445
475
|
}
|
|
446
|
-
await runShell(resetCmd, dryRun);
|
|
447
476
|
}
|
|
448
477
|
|
|
449
478
|
async installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void> {
|
|
@@ -498,6 +527,14 @@ export class CommandOpenClawProvider implements OpenClawProvider {
|
|
|
498
527
|
|
|
499
528
|
async configureChannel(config: OpenClawSection, channel: ChannelDef, dryRun: boolean): Promise<void> {
|
|
500
529
|
const bin = config.bin ?? "openclaw";
|
|
530
|
+
|
|
531
|
+
if (shouldAutoDisableTelegramChannel(channel)) {
|
|
532
|
+
const enabledPath = telegramEnabledPath(channel.account);
|
|
533
|
+
const disableCmd = `${bin} config set ${shellQuote(enabledPath)} false --strict-json`;
|
|
534
|
+
await runShell(disableCmd, dryRun);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
501
538
|
const enablePluginTemplate = config.commands?.enable_plugin;
|
|
502
539
|
if (enablePluginTemplate?.trim()) {
|
|
503
540
|
const enablePluginCmd = fillTemplate(enablePluginTemplate, {
|
|
@@ -554,6 +591,13 @@ export class CommandOpenClawProvider implements OpenClawProvider {
|
|
|
554
591
|
|
|
555
592
|
const cmd = `${bin} channels add ${flags.join(" ")}`;
|
|
556
593
|
await runShell(cmd, dryRun);
|
|
594
|
+
|
|
595
|
+
if (channel.channel === "telegram" && channel.group_policy) {
|
|
596
|
+
const configPath = telegramGroupPolicyPath(channel.account);
|
|
597
|
+
const policyValue = JSON.stringify(channel.group_policy);
|
|
598
|
+
const setPolicyCmd = `${bin} config set ${shellQuote(configPath)} ${shellQuote(policyValue)} --strict-json`;
|
|
599
|
+
await runShell(setPolicyCmd, dryRun);
|
|
600
|
+
}
|
|
557
601
|
}
|
|
558
602
|
|
|
559
603
|
async bindChannelAgent(config: OpenClawSection, channel: ChannelDef, agent: string, dryRun: boolean): Promise<void> {
|
package/src/orchestrator.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { validateReply } from "./assertions.js";
|
|
|
8
8
|
import { ClawChefError } from "./errors.js";
|
|
9
9
|
import { Logger } from "./logger.js";
|
|
10
10
|
import { createProvider } from "./openclaw/factory.js";
|
|
11
|
-
import type { Recipe, RunOptions } from "./types.js";
|
|
11
|
+
import type { ChannelDef, Recipe, RunOptions } from "./types.js";
|
|
12
12
|
import type { RecipeOrigin } from "./recipe.js";
|
|
13
13
|
|
|
14
14
|
async function exists(filePath: string): Promise<boolean> {
|
|
@@ -84,6 +84,15 @@ function isHttpUrl(value: string): boolean {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function shouldAutoDisableTelegramChannel(channel: ChannelDef): boolean {
|
|
88
|
+
if (channel.channel !== "telegram") {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
|
|
92
|
+
const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
|
|
93
|
+
return emptyToken || emptyBotToken;
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
function isNotFoundError(err: unknown): boolean {
|
|
88
97
|
return typeof err === "object" && err !== null && "code" in err && (err as { code?: string }).code === "ENOENT";
|
|
89
98
|
}
|
|
@@ -362,8 +371,16 @@ export async function runRecipe(
|
|
|
362
371
|
const effectiveChannel = channel.agent?.trim() && !channel.account?.trim()
|
|
363
372
|
? { ...channel, account: channel.agent.trim() }
|
|
364
373
|
: channel;
|
|
374
|
+
const autoDisabledTelegram = shouldAutoDisableTelegramChannel(effectiveChannel);
|
|
365
375
|
|
|
366
376
|
await provider.configureChannel(recipe.openclaw, effectiveChannel, options.dryRun);
|
|
377
|
+
if (autoDisabledTelegram) {
|
|
378
|
+
logger.info(
|
|
379
|
+
`Telegram channel disabled due to empty bot token: ${effectiveChannel.channel}${effectiveChannel.account ? `/${effectiveChannel.account}` : ""}`,
|
|
380
|
+
);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
367
384
|
logger.info(`Channel configured: ${effectiveChannel.channel}${effectiveChannel.account ? `/${effectiveChannel.account}` : ""}`);
|
|
368
385
|
if (effectiveChannel.agent?.trim()) {
|
|
369
386
|
await provider.bindChannelAgent(recipe.openclaw, effectiveChannel, effectiveChannel.agent, options.dryRun);
|
package/src/recipe.ts
CHANGED
|
@@ -7,7 +7,7 @@ import YAML from "js-yaml";
|
|
|
7
7
|
import { recipeSchema } from "./schema.js";
|
|
8
8
|
import { ClawChefError } from "./errors.js";
|
|
9
9
|
import { deepResolveTemplates } from "./template.js";
|
|
10
|
-
import type { Recipe, RunOptions } from "./types.js";
|
|
10
|
+
import type { ChannelDef, Recipe, RunOptions } from "./types.js";
|
|
11
11
|
|
|
12
12
|
export type RecipeOrigin =
|
|
13
13
|
| {
|
|
@@ -73,6 +73,15 @@ const CHANNEL_SECRET_FIELDS = ["token", "bot_token", "access_token", "app_token"
|
|
|
73
73
|
|
|
74
74
|
const TEMPLATE_TOKEN_RE = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
|
|
75
75
|
|
|
76
|
+
function hasExplicitEmptyTelegramToken(channel: ChannelDef): boolean {
|
|
77
|
+
if (channel.channel !== "telegram") {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const emptyToken = channel.token !== undefined && channel.token.trim().length === 0;
|
|
81
|
+
const emptyBotToken = channel.bot_token !== undefined && channel.bot_token.trim().length === 0;
|
|
82
|
+
return emptyToken || emptyBotToken;
|
|
83
|
+
}
|
|
84
|
+
|
|
76
85
|
function assertNoInlineSecrets(recipe: Recipe): void {
|
|
77
86
|
const bootstrap = recipe.openclaw.bootstrap;
|
|
78
87
|
if (bootstrap) {
|
|
@@ -252,6 +261,12 @@ function semanticValidate(recipe: Recipe): void {
|
|
|
252
261
|
);
|
|
253
262
|
}
|
|
254
263
|
|
|
264
|
+
if (channel.group_policy !== undefined && channel.channel !== "telegram") {
|
|
265
|
+
throw new ClawChefError(
|
|
266
|
+
`channels[] entry for ${channel.channel} does not support group_policy. Use channel: telegram.`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
255
270
|
if (channel.agent?.trim()) {
|
|
256
271
|
if (channel.channel !== "telegram") {
|
|
257
272
|
throw new ClawChefError(
|
|
@@ -285,6 +300,10 @@ function semanticValidate(recipe: Recipe): void {
|
|
|
285
300
|
return /(token|password|secret|api[_-]?key|webhook)/i.test(key) && String(value).trim().length > 0;
|
|
286
301
|
});
|
|
287
302
|
|
|
303
|
+
if (hasExplicitEmptyTelegramToken(channel)) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
|
|
288
307
|
if (!hasAuth) {
|
|
289
308
|
throw new ClawChefError(
|
|
290
309
|
`channels[] entry for ${channel.channel} requires at least one auth input (for example token/bot_token/access_token/token_file/use_env)`,
|
package/src/schema.ts
CHANGED
|
@@ -112,14 +112,15 @@ const channelSchema = z
|
|
|
112
112
|
channel: z.string().min(1),
|
|
113
113
|
account: z.string().min(1).optional(),
|
|
114
114
|
agent: z.string().min(1).optional(),
|
|
115
|
+
group_policy: z.enum(["open", "allowlist", "disabled"]).optional(),
|
|
115
116
|
login: z.boolean().optional(),
|
|
116
117
|
login_mode: z.enum(["interactive"]).optional(),
|
|
117
118
|
login_account: z.string().min(1).optional(),
|
|
118
119
|
name: z.string().min(1).optional(),
|
|
119
|
-
token: z.string().
|
|
120
|
+
token: z.string().optional(),
|
|
120
121
|
token_file: z.string().min(1).optional(),
|
|
121
122
|
use_env: z.boolean().optional(),
|
|
122
|
-
bot_token: z.string().
|
|
123
|
+
bot_token: z.string().optional(),
|
|
123
124
|
access_token: z.string().min(1).optional(),
|
|
124
125
|
app_token: z.string().min(1).optional(),
|
|
125
126
|
webhook_url: z.string().min(1).optional(),
|