@inetafrica/open-claudia 2.14.1 → 2.14.3
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/CHANGELOG.md +9 -0
- package/core/channel-wizard.js +34 -5
- package/core/kazee-probe.js +56 -28
- package/package.json +1 -1
- package/setup.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.14.3
|
|
4
|
+
- **`/channel add kazee` owner-confirm no longer depends on the inline button round-tripping.** After the wizard captures a candidate owner message it asks *"Is this you — the owner?"* with Yes/No/Skip buttons on the operator's channel — but if that button callback never reaches the bot (e.g. a channel that drops `message:action` server-side), the operator was stuck: the confirm step (`core/channel-wizard.js` `handleText`) only understood "skip" or a pasted 24-hex id, so typing "yes" did nothing. It now accepts **typed "yes"/"no"** (plus y/n/yep/yeah/nope) as a first-class answer — "yes" finalizes the captured candidate as owner, "no" clears it and keeps listening — and any other text nudges with the three options. The confirm prompt now reads *"Tap a button below, or just reply "yes" / "no"."*
|
|
5
|
+
- **Agent-space / OpenClaw:** no new deps, no new env, no schema change; Docker image builds identically. Purely a robustness fix to the Kazee onboarding wizard's owner-confirm step — inert for deployments that never run `/channel add kazee`.
|
|
6
|
+
|
|
7
|
+
## v2.14.2
|
|
8
|
+
- **Token validation now hits the right service — the previous probe could never succeed.** v2.14.0/.1 validated a Kazee bot token by `GET {host}/api/me`, but chat-central has **no `/me` route** (confirmed against `src/config/routes.ts`) — every attempt 404'd. chat-central doesn't resolve identity itself; it delegates on every request to `@libraries/inet-central-auth`, which POSTs the opaque `kzb_…` token to **Central's `POST /api/principal/verify`**. `core/kazee-probe.js` now calls that endpoint: one request both **validates the token and returns the bot's own user id** (`principal.id`, with `principal.kind === "Bot"` enforced) plus its display name. Verified live end-to-end (real token → `ok` + botUserId + "Maya Bot"; bad token → clean 401).
|
|
9
|
+
- **Central is hard-coded too (env-overridable).** New `CENTRAL_DEFAULT_URL = "https://central.inet.africa"` mirrors chat-central's own default; override with `CENTRAL_BASE_URL` for non-prod. The wizard now says "Verifying the bot token…" and greets the bot by name ("Connected as Maya Bot ✅"); `setup.js` prints "Verified as <name>."
|
|
10
|
+
- **Agent-space / OpenClaw:** no new deps, no new required env, no schema change; Docker image builds identically. Correctness fix to the onboarding probe — the wizard flow (token-first, listen-to-capture owner) is unchanged.
|
|
11
|
+
|
|
3
12
|
## v2.14.1
|
|
4
13
|
- **The Kazee host URL is no longer asked — it's fixed infrastructure, like Telegram's `api.telegram.org`.** v2.14.0 fixed the probe but still made Step 1 ask the operator to type the host; the Kazee host never varies, so prompting for it is pure friction (and a chance to get it wrong). `core/config.js` now defines `KAZEE_DEFAULT_URL = "https://chat.inet.africa"` and `loadChannels` falls back to it whenever `KAZEE_URL` is unset. `/channel add kazee` is now **two steps, token-first**: Step 1 asks only for the bot token, Step 2 is the listen-to-capture owner flow. `setup.js` likewise drops its URL prompt (default inlined, since `config.js` exits before an `.env` exists). An explicit `KAZEE_URL` in `.env` still overrides, so nothing breaks for a deployment that set one.
|
|
5
14
|
- **Agent-space / OpenClaw:** no new deps, no new required env, no schema change; Docker image builds identically. Pure UX fix to the onboarding wizard.
|
package/core/channel-wizard.js
CHANGED
|
@@ -93,7 +93,7 @@ async function handleText(envelope) {
|
|
|
93
93
|
}
|
|
94
94
|
session.data.token = text;
|
|
95
95
|
session.step = "validating";
|
|
96
|
-
await send("
|
|
96
|
+
await send("Verifying the bot token…");
|
|
97
97
|
const check = await validateKazee({ url: session.data.url, token: session.data.token });
|
|
98
98
|
if (!check.ok) {
|
|
99
99
|
sessions.delete(envelope.canonicalUserId);
|
|
@@ -102,6 +102,7 @@ async function handleText(envelope) {
|
|
|
102
102
|
}
|
|
103
103
|
session.data.url = check.url || session.data.url.replace(/\/+$/, "");
|
|
104
104
|
session.data.botUserId = check.botUserId || "";
|
|
105
|
+
session.data.botName = check.botName || "";
|
|
105
106
|
|
|
106
107
|
// Bring the adapter live now so it can receive the owner's message.
|
|
107
108
|
const live = await bringLive(session);
|
|
@@ -111,15 +112,19 @@ async function handleText(envelope) {
|
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
113
114
|
session.step = "awaiting-owner-message";
|
|
115
|
+
const connected = session.data.botName
|
|
116
|
+
? `Connected as ${session.data.botName} ✅`
|
|
117
|
+
: "Connected to Kazee ✅";
|
|
114
118
|
await send(
|
|
115
|
-
|
|
119
|
+
`${connected}\n\nStep 2 of 2: open Kazee and send me any message — I'll grab your account and confirm it here.\n\n(Or send "skip" to leave the owner unset.)`,
|
|
116
120
|
{ keyboard: cancelKeyboard },
|
|
117
121
|
);
|
|
118
122
|
return;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
// Text fallback for step 3 — the operator can
|
|
122
|
-
// "skip" on their own channel instead of
|
|
125
|
+
// Text fallback for step 3 — the operator can type "yes"/"no" to answer the
|
|
126
|
+
// owner-confirm prompt, or a user id / "skip", on their own channel instead of
|
|
127
|
+
// relying on the inline buttons (which may not round-trip on every channel).
|
|
123
128
|
if (session.step === "awaiting-owner-message" || session.step === "confirming-owner") {
|
|
124
129
|
if (text.toLowerCase() === "skip") {
|
|
125
130
|
await finalizeOwner(envelope.canonicalUserId, session, "");
|
|
@@ -129,6 +134,30 @@ async function handleText(envelope) {
|
|
|
129
134
|
await finalizeOwner(envelope.canonicalUserId, session, text);
|
|
130
135
|
return;
|
|
131
136
|
}
|
|
137
|
+
if (session.step === "confirming-owner") {
|
|
138
|
+
const t = text.toLowerCase();
|
|
139
|
+
if (t === "yes" || t === "y" || t === "yep" || t === "yeah") {
|
|
140
|
+
const cand = session.data.candidate;
|
|
141
|
+
if (cand?.userId) {
|
|
142
|
+
await finalizeOwner(envelope.canonicalUserId, session, cand.userId);
|
|
143
|
+
} else {
|
|
144
|
+
session.step = "awaiting-owner-message";
|
|
145
|
+
await send("Lost that candidate — send another message from Kazee.", { keyboard: cancelKeyboard });
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (t === "no" || t === "n" || t === "nope") {
|
|
150
|
+
session.data.candidate = null;
|
|
151
|
+
session.step = "awaiting-owner-message";
|
|
152
|
+
await send("OK — still listening. Send a message from the owner's Kazee account.", { keyboard: cancelKeyboard });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
await send(
|
|
156
|
+
`Reply "yes" to confirm ${session.data.candidate?.name || "that account"} as the owner, "no" to keep listening, or "skip".`,
|
|
157
|
+
{ keyboard: cancelKeyboard },
|
|
158
|
+
);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
132
161
|
if (session.step === "awaiting-owner-message") {
|
|
133
162
|
await send(
|
|
134
163
|
"Still listening. Send a message from your Kazee account, paste a user id, or send \"skip\".",
|
|
@@ -158,7 +187,7 @@ async function tryCaptureOwner(envelope) {
|
|
|
158
187
|
const who = name ? `${name} (${userId})` : userId;
|
|
159
188
|
await sayToOrigin(
|
|
160
189
|
session,
|
|
161
|
-
`Got a Kazee message from ${who}.\n\nIs this you — the owner
|
|
190
|
+
`Got a Kazee message from ${who}.\n\nIs this you — the owner? Tap a button below, or just reply "yes" / "no".`,
|
|
162
191
|
{ keyboard: { inline_keyboard: [
|
|
163
192
|
[{ text: "Yes, that's me", callback_data: "chw:owner-yes" }],
|
|
164
193
|
[{ text: "No, keep listening", callback_data: "chw:owner-no" }],
|
package/core/kazee-probe.js
CHANGED
|
@@ -1,57 +1,85 @@
|
|
|
1
|
-
// Standalone Kazee
|
|
2
|
-
// can use it before .env exists (channel-wizard
|
|
3
|
-
//
|
|
1
|
+
// Standalone Kazee bot-token probe. Lives outside the adapter so setup.js
|
|
2
|
+
// can use it before .env exists (channel-wizard imports it too).
|
|
3
|
+
//
|
|
4
|
+
// A Kazee bot token is an inet-central OPAQUE token (kzb_…). chat-central
|
|
5
|
+
// itself has NO /me endpoint — it delegates auth on every request to
|
|
6
|
+
// inet-central-auth, which POSTs the token to Central's
|
|
7
|
+
// `/api/principal/verify`. That single call both validates the token and
|
|
8
|
+
// returns the principal, whose `id` IS the bot's Kazee user id and whose
|
|
9
|
+
// `kind` must be "Bot". So we validate against Central, not chat-central.
|
|
10
|
+
// (Verified live 2026-07-09: principal { id, kind:"Bot", firstName, … }.)
|
|
4
11
|
|
|
5
12
|
const https = require("https");
|
|
6
13
|
const http = require("http");
|
|
7
14
|
const { URL } = require("url");
|
|
8
15
|
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
16
|
+
// Central is fixed infrastructure — like the Kazee host it never varies, so
|
|
17
|
+
// it's hard-coded (env override for non-prod). Mirrors chat-central's own
|
|
18
|
+
// default (src/config/inet-central-auth.ts).
|
|
19
|
+
const CENTRAL_DEFAULT_URL = "https://central.inet.africa";
|
|
20
|
+
|
|
21
|
+
// The adapter mounts REST under {root}/api and talks socket.io at {root}.
|
|
22
|
+
// Callers may hand us either the bare root or a URL already ending in /api —
|
|
23
|
+
// normalise both to the root so we store one canonical KAZEE_URL.
|
|
13
24
|
function normalizeKazeeRoot(url) {
|
|
14
25
|
return String(url || "").trim().replace(/\/+$/, "").replace(/\/api$/i, "");
|
|
15
26
|
}
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
// Validate a Kazee bot token and resolve the bot's own user id.
|
|
29
|
+
// url Kazee host (only normalised + echoed back for storage)
|
|
30
|
+
// token the kzb_… bot token to verify
|
|
31
|
+
// centralUrl override Central base (defaults to env or CENTRAL_DEFAULT_URL)
|
|
32
|
+
// Resolves { ok:true, url, botUserId, botName } or { ok:false, error }.
|
|
33
|
+
function validateKazee({ url, token, centralUrl }) {
|
|
18
34
|
return new Promise((resolve) => {
|
|
19
35
|
const root = normalizeKazeeRoot(url);
|
|
36
|
+
const central = String(centralUrl || process.env.CENTRAL_BASE_URL || CENTRAL_DEFAULT_URL)
|
|
37
|
+
.trim().replace(/\/+$/, "");
|
|
20
38
|
let u;
|
|
21
|
-
try { u = new URL(
|
|
22
|
-
catch (e) { return resolve({ ok: false, error: `Bad URL: ${e.message}` }); }
|
|
39
|
+
try { u = new URL(central + "/api/principal/verify"); }
|
|
40
|
+
catch (e) { return resolve({ ok: false, error: `Bad Central URL: ${e.message}` }); }
|
|
41
|
+
|
|
42
|
+
const payload = JSON.stringify({ token: String(token || "") });
|
|
23
43
|
const lib = u.protocol === "https:" ? https : http;
|
|
24
44
|
const req = lib.request({
|
|
25
|
-
method: "
|
|
45
|
+
method: "POST",
|
|
26
46
|
hostname: u.hostname,
|
|
27
47
|
port: u.port || (u.protocol === "https:" ? 443 : 80),
|
|
28
48
|
path: u.pathname + (u.search || ""),
|
|
29
|
-
headers: {
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": "application/json",
|
|
51
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
52
|
+
Accept: "application/json",
|
|
53
|
+
},
|
|
30
54
|
}, (res) => {
|
|
31
55
|
let chunks = "";
|
|
32
56
|
res.on("data", (c) => { chunks += c; });
|
|
33
57
|
res.on("end", () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
resolve({ ok: false, error:
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
resolve({ ok: false, error: `
|
|
58
|
+
let body = null;
|
|
59
|
+
try { body = chunks ? JSON.parse(chunks) : null; } catch (e) { body = null; }
|
|
60
|
+
|
|
61
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
62
|
+
const detail = body && body.error ? `: ${body.error}` : "";
|
|
63
|
+
return resolve({ ok: false, error: `Central rejected the token (HTTP ${res.statusCode})${detail}. Check the bot token.` });
|
|
64
|
+
}
|
|
65
|
+
if (!body || !body.success || !body.valid || !body.principal) {
|
|
66
|
+
const detail = body && body.error ? `: ${body.error}` : "";
|
|
67
|
+
return resolve({ ok: false, error: `Token not valid${detail}. Paste a current Kazee bot token.` });
|
|
68
|
+
}
|
|
69
|
+
const p = body.principal;
|
|
70
|
+
if (p.kind !== "Bot") {
|
|
71
|
+
return resolve({ ok: false, error: `That token is a ${p.kind || "non-bot"} principal, not a bot. Paste a bot token (kzb_…).` });
|
|
48
72
|
}
|
|
73
|
+
const botUserId = String(p.id || p._id || p.userId || "");
|
|
74
|
+
const botName = p.name || [p.firstName, p.lastName].filter(Boolean).join(" ") || "";
|
|
75
|
+
resolve({ ok: true, url: root, botUserId, botName });
|
|
49
76
|
});
|
|
50
77
|
});
|
|
51
78
|
req.on("error", (e) => resolve({ ok: false, error: e.message }));
|
|
52
|
-
req.setTimeout(15000, () => req.destroy(new Error("
|
|
79
|
+
req.setTimeout(15000, () => req.destroy(new Error("Central verify timed out after 15s")));
|
|
80
|
+
req.write(payload);
|
|
53
81
|
req.end();
|
|
54
82
|
});
|
|
55
83
|
}
|
|
56
84
|
|
|
57
|
-
module.exports = { validateKazee, normalizeKazeeRoot };
|
|
85
|
+
module.exports = { validateKazee, normalizeKazeeRoot, CENTRAL_DEFAULT_URL };
|
package/package.json
CHANGED
package/setup.js
CHANGED
|
@@ -681,7 +681,7 @@ async function main() {
|
|
|
681
681
|
while (true) {
|
|
682
682
|
const token = (await ask(" Kazee bot token: ")).trim();
|
|
683
683
|
if (!token) { console.log(" Empty token."); continue; }
|
|
684
|
-
console.log("
|
|
684
|
+
console.log(" Verifying the bot token…");
|
|
685
685
|
const check = await validateKazee({ url, token });
|
|
686
686
|
if (!check.ok) {
|
|
687
687
|
console.log(` Validation failed: ${check.error}`);
|
|
@@ -689,6 +689,7 @@ async function main() {
|
|
|
689
689
|
if (retry.toLowerCase() === "n") break;
|
|
690
690
|
continue;
|
|
691
691
|
}
|
|
692
|
+
if (check.botName) console.log(` Verified as ${check.botName}.`);
|
|
692
693
|
const ownerUserId = (await ask(" Kazee owner user id (blank to skip): ")).trim();
|
|
693
694
|
state.data.kazee = { url: check.url || url.replace(/\/+$/, ""), token, ownerUserId, botUserId: check.botUserId || "" };
|
|
694
695
|
console.log(" Kazee channel configured.");
|