feat(telegram): enable the channel on apply + guarantee Telegram-ready boards
The config template seeds channels.telegram.default disabled with an empty token, so setting only the token leaves the listener off. The wizard's apply now flips `enabled = true` alongside bot_token (two config writes, one reload). Provisioning: re-assert Telegram-readiness idempotently in provision-node-app.sh — a board's telegram listener only starts if the [channels.telegram.default] block exists AND agents.default subscribes to `telegram.default`. The template ships both; the provisioner now patches them back in if a carried config drifted, so every board is Telegram-ready out of the box. (Verified the patcher against the template + a drifted config: no-op when ready, additive when not, idempotent.) Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a4d06ebddb
commit
66723ebd35
+11
-8
@@ -155,18 +155,21 @@ export async function promptAndWait(node: NodeRef, message: string, agent = 'def
|
|||||||
*/
|
*/
|
||||||
export async function configureTelegram(node: NodeRef, token: string): Promise<void> {
|
export async function configureTelegram(node: NodeRef, token: string): Promise<void> {
|
||||||
const auth = { authorization: `Bearer ${node.token}` }
|
const auth = { authorization: `Bearer ${node.token}` }
|
||||||
const put = await fetch(`${node.url}/api/config/prop`, {
|
const setProp = async (path: string, value: unknown) => {
|
||||||
|
const res = await fetch(`${node.url}/api/config/prop`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { ...auth, 'content-type': 'application/json' },
|
headers: { ...auth, 'content-type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({ path, value, comment: 'set via APESS onboarding' }),
|
||||||
path: 'channels.telegram.default.bot_token',
|
|
||||||
value: token,
|
|
||||||
comment: 'set via APESS onboarding',
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
if (!put.ok) throw new Error(`config write failed (${put.status})`)
|
if (!res.ok) throw new Error(`config write ${path} failed (${res.status})`)
|
||||||
|
}
|
||||||
|
// The template seeds telegram.default disabled with an empty token — set the
|
||||||
|
// token AND flip enabled, or the channel never starts listening.
|
||||||
|
await setProp('channels.telegram.default.bot_token', token)
|
||||||
|
await setProp('channels.telegram.default.enabled', true)
|
||||||
// Best-effort: instant reload on boards that allow remote admin; the board's
|
// Best-effort: instant reload on boards that allow remote admin; the board's
|
||||||
// own watcher applies it otherwise. Never throw on a refused remote reload.
|
// own in-container watcher applies it otherwise. Never throw on a refused
|
||||||
|
// remote reload.
|
||||||
try {
|
try {
|
||||||
await fetch(`${node.url}/admin/reload`, { method: 'POST', headers: auth })
|
await fetch(`${node.url}/admin/reload`, { method: 'POST', headers: auth })
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -37,6 +37,36 @@ S "cp -f /home/arduino/.zeroclaw/.secret_key $DEST/.zeroclaw/.secret_key"
|
|||||||
# web dashboard assets must live inside the app dir (/app in-container), not a host
|
# web dashboard assets must live inside the app dir (/app in-container), not a host
|
||||||
# path — carry them in and repoint web_dist_dir so :8080/ serves the dashboard.
|
# path — carry them in and repoint web_dist_dir so :8080/ serves the dashboard.
|
||||||
S "cp -rf /home/arduino/web-dist $DEST/web-dist 2>/dev/null; sed -i 's#^web_dist_dir = .*#web_dist_dir = \"/app/web-dist\"#' $DEST/.zeroclaw/config.toml"
|
S "cp -rf /home/arduino/web-dist $DEST/web-dist 2>/dev/null; sed -i 's#^web_dist_dir = .*#web_dist_dir = \"/app/web-dist\"#' $DEST/.zeroclaw/config.toml"
|
||||||
|
# Telegram-ready guarantee — the wizard (dashboard "add a bot token") sets
|
||||||
|
# channels.telegram.default.bot_token + enabled, but that only starts a listener
|
||||||
|
# if the block exists AND agents.default subscribes to `telegram.default`. The
|
||||||
|
# config template ships both; re-assert them idempotently in case the carried
|
||||||
|
# config drifted, so every board is Telegram-ready out of the box.
|
||||||
|
S "python3 - $DEST/.zeroclaw/config.toml" <<'PY'
|
||||||
|
import re, sys
|
||||||
|
p = sys.argv[1]
|
||||||
|
with open(p) as f: t = f.read()
|
||||||
|
orig = t
|
||||||
|
# 1) agents.default must subscribe to telegram.default
|
||||||
|
m = re.search(r'(?ms)^\[agents\.default\][^\[]*', t)
|
||||||
|
if m:
|
||||||
|
sec = m.group(0)
|
||||||
|
if 'telegram.default' not in sec:
|
||||||
|
if re.search(r'(?m)^channels\s*=', sec):
|
||||||
|
sec2 = re.sub(r'(?m)^(channels\s*=\s*\[)([^\]]*)\]',
|
||||||
|
lambda x: f'{x.group(1)}{(x.group(2).strip()+", " ) if x.group(2).strip() else ""}"telegram.default"]', sec, count=1)
|
||||||
|
else:
|
||||||
|
sec2 = sec.rstrip() + '\nchannels = ["telegram.default"]\n'
|
||||||
|
t = t[:m.start()] + sec2 + t[m.end():]
|
||||||
|
# 2) the telegram.default channel block must exist (seeded disabled + empty)
|
||||||
|
if '[channels.telegram.default]' not in t:
|
||||||
|
t = t.rstrip() + '\n\n[channels.telegram.default]\nenabled = false\nbot_token = ""\napi_base_url = "https://api.telegram.org"\nmention_only = false\n'
|
||||||
|
if t != orig:
|
||||||
|
with open(p, 'w') as f: f.write(t)
|
||||||
|
print(' patched config → Telegram-ready')
|
||||||
|
else:
|
||||||
|
print(' config already Telegram-ready')
|
||||||
|
PY
|
||||||
# hardware skills — the [skill_bundles.unoq] bundle loads from shared/skills so the
|
# hardware skills — the [skill_bundles.unoq] bundle loads from shared/skills so the
|
||||||
# agent knows it's on an Uno Q and how to drive its devices. (copy skills/ directly,
|
# agent knows it's on an Uno Q and how to drive its devices. (copy skills/ directly,
|
||||||
# not the parent, to avoid cp nesting when the dir already exists.)
|
# not the parent, to avoid cp nesting when the dir already exists.)
|
||||||
|
|||||||
Reference in New Issue
Block a user