@inetafrica/open-claudia 2.13.1 → 2.14.0
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 +6 -0
- package/core/channel-wizard.js +168 -43
- package/core/handlers.js +1 -1
- package/core/kazee-probe.js +15 -6
- package/core/router.js +6 -0
- package/package.json +1 -1
- package/setup.js +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.14.0
|
|
4
|
+
- **`/channel add kazee` now finishes cleanly — the validation step was probing the wrong path.** The live adapter mounts every REST route under `{root}/api`, but the setup probe hit `{root}/me`. So the correct host (`https://chat.example.com`) always 404'd at validation, while the only URL that *passed* (`…/api`) then made the adapter build `…/api/api` and break. There was no single URL a user could type to succeed. `kazee-probe` now normalises either form to the root and probes the real `{root}/api/me` (new exported `normalizeKazeeRoot`); the wizard and `setup.js` both store the normalised root. Step 1 now asks for the host and states "I add the /api path myself."
|
|
5
|
+
- **The bot's own user id is no longer thrown away.** Validation already fetches it from `/me`, but the wizard never persisted `KAZEE_BOT_USER_ID` or passed it to the adapter — so a wizard-added channel booted without self-echo filtering or slash-command registration (and `loadChannels` warned on every restart). The wizard and `setup.js` now save it and hand it to the adapter.
|
|
6
|
+
- **Owner is captured by listening, not by pasting a Mongo id (Step 3 redesign).** After URL+token validate, the wizard brings Kazee **live immediately** (owner still blank) and asks the operator to send any message from their Kazee account. A new cross-channel hook — `router.tryCaptureOwner` → `channel-wizard.tryCaptureOwner` — intercepts that inbound Kazee message (it lands on a different channel than the operator's, so the operator-keyed `isAwaiting()` gate can't see it), then asks back on the operator's channel *"Is this you — the owner?"* with **Yes / No, keep listening / Skip** buttons. Yes grabs the sender's Kazee user id; owner recognition then flows through the existing env path (`identity.isConfiguredOwnerChannel` reads `KAZEE_OWNER_USER_ID`). Pasting an id or "skip" still works as a fallback; Cancel now tears the half-added adapter back down and rolls the `.env` edits back.
|
|
7
|
+
- **Agent-space / OpenClaw:** no new deps, no new env, no schema change; Docker image builds identically. Purely a UX + correctness fix to the Kazee onboarding wizard — inert for deployments that never run `/channel add kazee`.
|
|
8
|
+
|
|
3
9
|
## v2.13.1
|
|
4
10
|
- **Guardrail no longer mistakes the owner for an external speaker on `/link`'d or env-unified channels (Phase 3 fix).** v2.13.0's relationship classifier resolved ownership through `people.findByHandle` alone — the handles physically listed on the owner's person record — while the queue/state/run-lock resolve it through `identity.canonicalForChannel`, which also honours explicit `/link`s (`identities.json`) and the configured-owner env path. A channel the owner had unified by `/link` or env-config (e.g. a second Kazee handle) was therefore *owner* to the run-lock (shared queue → "Queued.") but *external* to the guardrail (default-deny → the stranger reply), so the bot could greet its own owner as a limited-access outsider. The classifier now resolves ownership through **canonical identity — the same source of truth the rest of the bot uses**: a channel is the owner's when it resolves to the owner canonical **or** its person record is `isOwner`. New `identity.channelIsOwner()`; `relationship.speakerFor` consults it before default-deny. Every guard consumer (external-mode system-prompt block, `io`/`loopback` egress gates, runner turn-guard, CLI guards) funnels through this one function, so the fix lands everywhere at once.
|
|
5
11
|
- **R1 preserved — a note still can never confer owner.** `channelIsOwner` is driven only by the canonical map, which is written **exclusively by owner/operator actions** (`/link`, `.env`), never by a note; and the owner stays anchored to a real owner record via `ownerCanonical → people.isOwner`. The default-deny posture for genuinely external or unmapped channels is unchanged.
|
package/core/channel-wizard.js
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
// enters the wizard their next plain-text message becomes the wizard's
|
|
6
6
|
// input. A Cancel button (payload "chw:cancel") bails out at any step.
|
|
7
7
|
//
|
|
8
|
+
// Owner capture is cross-channel: once URL+token validate, we bring the
|
|
9
|
+
// Kazee adapter live and listen for an inbound message ON KAZEE, then ask
|
|
10
|
+
// the operator (back on the channel they ran /channel from) to confirm
|
|
11
|
+
// "is this you?". router.tryCaptureOwner() feeds that inbound message in —
|
|
12
|
+
// it arrives on a different channel than the operator's, so isAwaiting()
|
|
13
|
+
// (keyed to the operator) never sees it via handleText().
|
|
14
|
+
//
|
|
8
15
|
// validateKazee() is exported separately so setup.js can reuse the same
|
|
9
16
|
// /me probe without going through the chat-driven flow.
|
|
10
17
|
|
|
@@ -31,22 +38,45 @@ function endSession(canonicalUserId) {
|
|
|
31
38
|
|
|
32
39
|
const cancelKeyboard = { inline_keyboard: [[{ text: "Cancel", callback_data: "chw:cancel" }]] };
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
// Prompts issued while listening for (and confirming) the owner's Kazee
|
|
42
|
+
// message must land on the channel the operator ran /channel from — not the
|
|
43
|
+
// Kazee chat a trigger arrived on. Reach it through the stored origin
|
|
44
|
+
// adapter, bypassing the ambient io.send() scope.
|
|
45
|
+
async function sayToOrigin(session, text, opts) {
|
|
46
|
+
try {
|
|
47
|
+
if (session?.origin?.adapter && session.origin.channelId) {
|
|
48
|
+
return await session.origin.adapter.send(session.origin.channelId, text, opts);
|
|
49
|
+
}
|
|
50
|
+
} catch (e) { console.error("channel-wizard sayToOrigin:", e.message); }
|
|
51
|
+
return send(text, opts); // fallback: ambient scope
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function start(canonicalUserId, kind, envelope) {
|
|
35
55
|
if (kind !== "kazee") {
|
|
36
56
|
await send(`Channel type "${kind}" not supported yet.`);
|
|
37
57
|
return;
|
|
38
58
|
}
|
|
39
|
-
|
|
59
|
+
const origin = envelope?.adapter && envelope?.channelId
|
|
60
|
+
? { adapter: envelope.adapter, channelId: envelope.channelId }
|
|
61
|
+
: null;
|
|
62
|
+
sessions.set(canonicalUserId, { kind: "kazee", step: "awaiting-url", data: {}, origin });
|
|
40
63
|
await send(
|
|
41
|
-
"Add a Kazee channel.\n\nStep 1 of 3: Kazee
|
|
64
|
+
"Add a Kazee channel.\n\nStep 1 of 3: Kazee host URL (e.g. https://chat.example.com). I add the /api path myself.",
|
|
42
65
|
{ keyboard: cancelKeyboard },
|
|
43
66
|
);
|
|
44
67
|
}
|
|
45
68
|
|
|
46
69
|
async function cancel(canonicalUserId, { silent } = {}) {
|
|
47
|
-
|
|
70
|
+
const session = sessions.get(canonicalUserId);
|
|
71
|
+
if (!session) return;
|
|
48
72
|
sessions.delete(canonicalUserId);
|
|
49
|
-
|
|
73
|
+
// If we already brought the adapter live for owner-capture, tear it back
|
|
74
|
+
// down and roll the .env edits back so a cancel leaves no half-added channel.
|
|
75
|
+
if (session.data?.live) {
|
|
76
|
+
try { await registry.removeAdapter("kazee"); } catch (e) {}
|
|
77
|
+
revertEnv(session.data.previous);
|
|
78
|
+
}
|
|
79
|
+
if (!silent) await sayToOrigin(session, "Channel setup cancelled.");
|
|
50
80
|
}
|
|
51
81
|
|
|
52
82
|
async function handleText(envelope) {
|
|
@@ -59,7 +89,7 @@ async function handleText(envelope) {
|
|
|
59
89
|
await send("That doesn't look like a URL. Try again, or hit Cancel.", { keyboard: cancelKeyboard });
|
|
60
90
|
return;
|
|
61
91
|
}
|
|
62
|
-
session.data.url = text
|
|
92
|
+
session.data.url = text;
|
|
63
93
|
session.step = "awaiting-token";
|
|
64
94
|
await send(
|
|
65
95
|
"Step 2 of 3: Kazee bot token. I'll delete your message after reading.",
|
|
@@ -83,83 +113,177 @@ async function handleText(envelope) {
|
|
|
83
113
|
await send(`Validation failed: ${check.error}\n\nRun /channel add kazee to try again.`);
|
|
84
114
|
return;
|
|
85
115
|
}
|
|
116
|
+
session.data.url = check.url || session.data.url.replace(/\/+$/, "");
|
|
86
117
|
session.data.botUserId = check.botUserId || "";
|
|
87
|
-
|
|
118
|
+
|
|
119
|
+
// Bring the adapter live now so it can receive the owner's message.
|
|
120
|
+
const live = await bringLive(session);
|
|
121
|
+
if (!live.ok) {
|
|
122
|
+
sessions.delete(envelope.canonicalUserId);
|
|
123
|
+
await send(`Could not start the Kazee adapter: ${live.error}\n\nReverted .env. Run /channel add kazee to try again.`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
session.step = "awaiting-owner-message";
|
|
88
127
|
await send(
|
|
89
|
-
|
|
128
|
+
"Connected to Kazee ✅\n\nStep 3 of 3: 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.)",
|
|
90
129
|
{ keyboard: cancelKeyboard },
|
|
91
130
|
);
|
|
92
131
|
return;
|
|
93
132
|
}
|
|
94
133
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
134
|
+
// Text fallback for step 3 — the operator can still type a user id or
|
|
135
|
+
// "skip" on their own channel instead of sending a Kazee message.
|
|
136
|
+
if (session.step === "awaiting-owner-message" || session.step === "confirming-owner") {
|
|
137
|
+
if (text.toLowerCase() === "skip") {
|
|
138
|
+
await finalizeOwner(envelope.canonicalUserId, session, "");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (/^[a-f0-9]{24}$/i.test(text)) {
|
|
142
|
+
await finalizeOwner(envelope.canonicalUserId, session, text);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (session.step === "awaiting-owner-message") {
|
|
146
|
+
await send(
|
|
147
|
+
"Still listening. Send a message from your Kazee account, paste a user id, or send \"skip\".",
|
|
148
|
+
{ keyboard: cancelKeyboard },
|
|
149
|
+
);
|
|
150
|
+
}
|
|
100
151
|
return;
|
|
101
152
|
}
|
|
102
153
|
}
|
|
103
154
|
|
|
155
|
+
// Fired by router.tryCaptureOwner() for inbound messages arriving ON the
|
|
156
|
+
// freshly-added Kazee adapter while a session is listening. Returns true
|
|
157
|
+
// when it consumed the message (so the router stops normal processing).
|
|
158
|
+
async function tryCaptureOwner(envelope) {
|
|
159
|
+
const adapterId = envelope.adapter?.id;
|
|
160
|
+
if (!adapterId) return false;
|
|
161
|
+
for (const [, session] of sessions) {
|
|
162
|
+
if (session.step !== "awaiting-owner-message") continue;
|
|
163
|
+
if (session.data.adapterId !== adapterId) continue;
|
|
164
|
+
const userId = String(envelope.userId || envelope.from?.id || "");
|
|
165
|
+
if (!userId) return true; // consume malformed input, keep listening
|
|
166
|
+
const name = envelope.from?.name || envelope.from?.username || "";
|
|
167
|
+
session.data.candidate = { userId, name };
|
|
168
|
+
session.step = "confirming-owner";
|
|
169
|
+
// Light ack on the Kazee side so the sender knows it landed.
|
|
170
|
+
try { await envelope.adapter.send(envelope.channelId, "Got your message 👋 — finishing setup on my end."); } catch (e) {}
|
|
171
|
+
const who = name ? `${name} (${userId})` : userId;
|
|
172
|
+
await sayToOrigin(
|
|
173
|
+
session,
|
|
174
|
+
`Got a Kazee message from ${who}.\n\nIs this you — the owner?`,
|
|
175
|
+
{ keyboard: { inline_keyboard: [
|
|
176
|
+
[{ text: "Yes, that's me", callback_data: "chw:owner-yes" }],
|
|
177
|
+
[{ text: "No, keep listening", callback_data: "chw:owner-no" }],
|
|
178
|
+
[{ text: "Skip (no owner)", callback_data: "chw:owner-skip" }],
|
|
179
|
+
] } },
|
|
180
|
+
);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
104
186
|
async function handleAction(envelope) {
|
|
105
187
|
const payload = envelope.action?.payload || envelope.action?.buttonId || "";
|
|
106
|
-
if (payload
|
|
107
|
-
|
|
188
|
+
if (!String(payload).startsWith("chw:")) return false;
|
|
189
|
+
const session = sessions.get(envelope.canonicalUserId);
|
|
190
|
+
|
|
191
|
+
if (payload === "chw:cancel") {
|
|
192
|
+
await cancel(envelope.canonicalUserId);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
if (!session) return true; // stale button — swallow so it doesn't fall through
|
|
196
|
+
|
|
197
|
+
if (payload === "chw:owner-yes") {
|
|
198
|
+
const cand = session.data.candidate;
|
|
199
|
+
if (!cand?.userId) {
|
|
200
|
+
session.step = "awaiting-owner-message";
|
|
201
|
+
await sayToOrigin(session, "Lost that candidate — send another message from Kazee.", { keyboard: cancelKeyboard });
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
await finalizeOwner(envelope.canonicalUserId, session, cand.userId);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (payload === "chw:owner-no") {
|
|
208
|
+
session.data.candidate = null;
|
|
209
|
+
session.step = "awaiting-owner-message";
|
|
210
|
+
await sayToOrigin(session, "OK — still listening. Send a message from the owner's Kazee account.", { keyboard: cancelKeyboard });
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (payload === "chw:owner-skip") {
|
|
214
|
+
await finalizeOwner(envelope.canonicalUserId, session, "");
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
108
217
|
return true;
|
|
109
218
|
}
|
|
110
219
|
|
|
111
|
-
|
|
112
|
-
const { url, token, ownerUserId } = session.data;
|
|
220
|
+
// ── env plumbing ────────────────────────────────────────────────────
|
|
113
221
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const previous = {
|
|
222
|
+
function snapshotEnv() {
|
|
223
|
+
return {
|
|
117
224
|
KAZEE_URL: config.KAZEE_URL || "",
|
|
118
225
|
KAZEE_BOT_TOKEN: config.KAZEE_BOT_TOKEN || "",
|
|
226
|
+
KAZEE_BOT_USER_ID: config.KAZEE_BOT_USER_ID || "",
|
|
119
227
|
KAZEE_OWNER_USER_ID: config.KAZEE_OWNER_USER_ID || "",
|
|
120
228
|
CHANNELS: config.CHANNELS || "telegram",
|
|
121
229
|
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function revertEnv(previous) {
|
|
233
|
+
if (!previous) return;
|
|
234
|
+
for (const [k, v] of Object.entries(previous)) {
|
|
235
|
+
saveEnvKey(k, v);
|
|
236
|
+
config[k] = v;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Persist URL/token/botUserId, add "kazee" to CHANNELS, and boot the adapter
|
|
241
|
+
// (owner still blank). Snapshots the prior env for rollback on failure/cancel.
|
|
242
|
+
async function bringLive(session) {
|
|
243
|
+
const { url, token, botUserId } = session.data;
|
|
244
|
+
const previous = snapshotEnv();
|
|
245
|
+
session.data.previous = previous;
|
|
122
246
|
|
|
123
247
|
saveEnvKey("KAZEE_URL", url);
|
|
124
248
|
saveEnvKey("KAZEE_BOT_TOKEN", token);
|
|
125
|
-
saveEnvKey("
|
|
126
|
-
|
|
249
|
+
saveEnvKey("KAZEE_BOT_USER_ID", botUserId);
|
|
127
250
|
const channels = previous.CHANNELS.split(",").map((s) => s.trim()).filter(Boolean);
|
|
128
|
-
if (!channels.some((c) => c === "kazee" || c.startsWith("kazee:")))
|
|
129
|
-
channels.push("kazee");
|
|
130
|
-
}
|
|
251
|
+
if (!channels.some((c) => c === "kazee" || c.startsWith("kazee:"))) channels.push("kazee");
|
|
131
252
|
const newChannels = channels.join(",");
|
|
132
253
|
saveEnvKey("CHANNELS", newChannels);
|
|
133
254
|
|
|
134
|
-
// Refresh in-process view so subsequent loadChannels() sees the new
|
|
135
|
-
// values without a restart.
|
|
136
255
|
config.KAZEE_URL = url;
|
|
137
256
|
config.KAZEE_BOT_TOKEN = token;
|
|
138
|
-
config.
|
|
257
|
+
config.KAZEE_BOT_USER_ID = botUserId;
|
|
139
258
|
config.CHANNELS = newChannels;
|
|
140
259
|
|
|
141
260
|
const result = await registry.addAdapter(
|
|
142
|
-
{ id: "kazee", type: "kazee", opts: { url, token, ownerUserId } },
|
|
261
|
+
{ id: "kazee", type: "kazee", opts: { url, token, ownerUserId: "", botUserId } },
|
|
143
262
|
{ publicCommands: publicCommands() },
|
|
144
263
|
);
|
|
145
|
-
|
|
146
264
|
if (!result.ok) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
saveEnvKey("KAZEE_BOT_TOKEN", previous.KAZEE_BOT_TOKEN);
|
|
150
|
-
saveEnvKey("KAZEE_OWNER_USER_ID", previous.KAZEE_OWNER_USER_ID);
|
|
151
|
-
saveEnvKey("CHANNELS", previous.CHANNELS);
|
|
152
|
-
config.KAZEE_URL = previous.KAZEE_URL;
|
|
153
|
-
config.KAZEE_BOT_TOKEN = previous.KAZEE_BOT_TOKEN;
|
|
154
|
-
config.KAZEE_OWNER_USER_ID = previous.KAZEE_OWNER_USER_ID;
|
|
155
|
-
config.CHANNELS = previous.CHANNELS;
|
|
156
|
-
sessions.delete(canonicalUserId);
|
|
157
|
-
await send(`Could not start the Kazee adapter: ${result.error}\n\nReverted .env. Run /channel add kazee to try again.`);
|
|
158
|
-
return;
|
|
265
|
+
revertEnv(previous);
|
|
266
|
+
return result;
|
|
159
267
|
}
|
|
268
|
+
session.data.adapterId = "kazee";
|
|
269
|
+
session.data.live = true;
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
160
272
|
|
|
273
|
+
// Owner decided (id or blank). Persist it, flip the live adapter's owner,
|
|
274
|
+
// and close the session.
|
|
275
|
+
async function finalizeOwner(canonicalUserId, session, ownerUserId) {
|
|
276
|
+
saveEnvKey("KAZEE_OWNER_USER_ID", ownerUserId);
|
|
277
|
+
config.KAZEE_OWNER_USER_ID = ownerUserId;
|
|
278
|
+
const adapter = registry.findAdapter("kazee");
|
|
279
|
+
if (adapter) adapter.ownerUserId = ownerUserId;
|
|
161
280
|
sessions.delete(canonicalUserId);
|
|
162
|
-
await
|
|
281
|
+
await sayToOrigin(
|
|
282
|
+
session,
|
|
283
|
+
ownerUserId
|
|
284
|
+
? "Kazee channel added and live. You're set as the owner — message me from Kazee and I'll know it's you. 🎉"
|
|
285
|
+
: "Kazee channel added and live. No owner set — run /channel add kazee again if you want to set one.",
|
|
286
|
+
);
|
|
163
287
|
}
|
|
164
288
|
|
|
165
289
|
module.exports = {
|
|
@@ -170,5 +294,6 @@ module.exports = {
|
|
|
170
294
|
cancel,
|
|
171
295
|
handleText,
|
|
172
296
|
handleAction,
|
|
297
|
+
tryCaptureOwner,
|
|
173
298
|
validateKazee,
|
|
174
299
|
};
|
package/core/handlers.js
CHANGED
|
@@ -1664,7 +1664,7 @@ register({
|
|
|
1664
1664
|
if (addMatch) {
|
|
1665
1665
|
const kind = addMatch[1].toLowerCase();
|
|
1666
1666
|
if (kind !== "kazee") return send(`Channel type "${kind}" not supported.`);
|
|
1667
|
-
await wizard.start(env.canonicalUserId, "kazee");
|
|
1667
|
+
await wizard.start(env.canonicalUserId, "kazee", env);
|
|
1668
1668
|
return;
|
|
1669
1669
|
}
|
|
1670
1670
|
|
package/core/kazee-probe.js
CHANGED
|
@@ -6,10 +6,19 @@ const https = require("https");
|
|
|
6
6
|
const http = require("http");
|
|
7
7
|
const { URL } = require("url");
|
|
8
8
|
|
|
9
|
+
// The adapter mounts every REST route under {root}/api and talks socket.io
|
|
10
|
+
// at {root}. Callers may hand us either the bare root host or a URL that
|
|
11
|
+
// already ends in /api — normalise both to the root so we always probe the
|
|
12
|
+
// real {root}/api/me and the caller can store one canonical KAZEE_URL.
|
|
13
|
+
function normalizeKazeeRoot(url) {
|
|
14
|
+
return String(url || "").trim().replace(/\/+$/, "").replace(/\/api$/i, "");
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
function validateKazee({ url, token }) {
|
|
10
18
|
return new Promise((resolve) => {
|
|
19
|
+
const root = normalizeKazeeRoot(url);
|
|
11
20
|
let u;
|
|
12
|
-
try { u = new URL(
|
|
21
|
+
try { u = new URL(root + "/api/me"); }
|
|
13
22
|
catch (e) { return resolve({ ok: false, error: `Bad URL: ${e.message}` }); }
|
|
14
23
|
const lib = u.protocol === "https:" ? https : http;
|
|
15
24
|
const req = lib.request({
|
|
@@ -26,16 +35,16 @@ function validateKazee({ url, token }) {
|
|
|
26
35
|
try {
|
|
27
36
|
const body = chunks ? JSON.parse(chunks) : {};
|
|
28
37
|
const botUserId = body?.user?._id || body?.user?.id || body?._id || "";
|
|
29
|
-
resolve({ ok: true, botUserId });
|
|
38
|
+
resolve({ ok: true, botUserId, url: root });
|
|
30
39
|
} catch (e) {
|
|
31
|
-
resolve({ ok: true, botUserId: "" });
|
|
40
|
+
resolve({ ok: true, botUserId: "", url: root });
|
|
32
41
|
}
|
|
33
42
|
} else if (res.statusCode === 401 || res.statusCode === 403) {
|
|
34
43
|
resolve({ ok: false, error: "Token rejected (401/403). Check that it's a valid Kazee bot token." });
|
|
35
44
|
} else if (res.statusCode === 404) {
|
|
36
|
-
resolve({ ok: false, error: "GET /me returned 404. Check
|
|
45
|
+
resolve({ ok: false, error: "GET /api/me returned 404. Check the URL points at your Kazee host (e.g. https://chat.example.com)." });
|
|
37
46
|
} else {
|
|
38
|
-
resolve({ ok: false, error: `GET /me → HTTP ${res.statusCode}` });
|
|
47
|
+
resolve({ ok: false, error: `GET /api/me → HTTP ${res.statusCode}` });
|
|
39
48
|
}
|
|
40
49
|
});
|
|
41
50
|
});
|
|
@@ -45,4 +54,4 @@ function validateKazee({ url, token }) {
|
|
|
45
54
|
});
|
|
46
55
|
}
|
|
47
56
|
|
|
48
|
-
module.exports = { validateKazee };
|
|
57
|
+
module.exports = { validateKazee, normalizeKazeeRoot };
|
package/core/router.js
CHANGED
|
@@ -63,6 +63,12 @@ async function onMessage(envelope) {
|
|
|
63
63
|
// owner's canonical bucket on first contact, before any state is read.
|
|
64
64
|
try { autoLinkOwnerChannel(envelope.adapter?.type, envelope.channelId); } catch (e) {}
|
|
65
65
|
|
|
66
|
+
// A channel wizard listening for the owner's message on a freshly-added
|
|
67
|
+
// adapter consumes that inbound message here: it arrives on a different
|
|
68
|
+
// channel than the operator's, so the isAwaiting() gate below (keyed to
|
|
69
|
+
// the operator) never sees it.
|
|
70
|
+
if (await channelWizard.tryCaptureOwner(envelope)) return;
|
|
71
|
+
|
|
66
72
|
// While a channel wizard is open, capture text input before slash
|
|
67
73
|
// dispatch — except for /cancel, which always bails out.
|
|
68
74
|
if (channelWizard.isAwaiting(envelope.canonicalUserId)) {
|
package/package.json
CHANGED
package/setup.js
CHANGED
|
@@ -674,11 +674,11 @@ async function main() {
|
|
|
674
674
|
const addKazee = await ask(" Add a Kazee channel now? (y/n) [n]: ");
|
|
675
675
|
if (addKazee.toLowerCase() === "y") {
|
|
676
676
|
while (true) {
|
|
677
|
-
const url = (await ask(" Kazee
|
|
677
|
+
const url = (await ask(" Kazee host URL (e.g. https://chat.example.com — I add /api myself): ")).trim();
|
|
678
678
|
if (!/^https?:\/\//i.test(url)) { console.log(" That doesn't look like a URL."); continue; }
|
|
679
679
|
const token = (await ask(" Kazee bot token: ")).trim();
|
|
680
680
|
if (!token) { console.log(" Empty token."); continue; }
|
|
681
|
-
console.log(" Validating against Kazee /me…");
|
|
681
|
+
console.log(" Validating against Kazee /api/me…");
|
|
682
682
|
const check = await validateKazee({ url, token });
|
|
683
683
|
if (!check.ok) {
|
|
684
684
|
console.log(` Validation failed: ${check.error}`);
|
|
@@ -687,7 +687,7 @@ async function main() {
|
|
|
687
687
|
continue;
|
|
688
688
|
}
|
|
689
689
|
const ownerUserId = (await ask(" Kazee owner user id (blank to skip): ")).trim();
|
|
690
|
-
state.data.kazee = { url: url.replace(
|
|
690
|
+
state.data.kazee = { url: check.url || url.replace(/\/+$/, ""), token, ownerUserId, botUserId: check.botUserId || "" };
|
|
691
691
|
console.log(" Kazee channel configured.");
|
|
692
692
|
break;
|
|
693
693
|
}
|
|
@@ -756,6 +756,7 @@ async function main() {
|
|
|
756
756
|
if (d.kazee) {
|
|
757
757
|
envLines.push(`KAZEE_URL=${d.kazee.url}`);
|
|
758
758
|
envLines.push(`KAZEE_BOT_TOKEN=${d.kazee.token}`);
|
|
759
|
+
envLines.push(`KAZEE_BOT_USER_ID=${d.kazee.botUserId || ""}`);
|
|
759
760
|
envLines.push(`KAZEE_OWNER_USER_ID=${d.kazee.ownerUserId || ""}`);
|
|
760
761
|
}
|
|
761
762
|
const env = envLines.join("\n");
|