@inetafrica/open-claudia 2.14.4 → 2.14.5
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 +5 -0
- package/core/handlers.js +20 -9
- package/core/identity.js +20 -0
- package/core/people.js +25 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.14.5
|
|
4
|
+
- **`/channel remove <id>` is now a complete teardown — a re-add starts from a genuinely clean slate.** Removal used to clear only the `.env` channel config (`CHANNELS` + the `KAZEE_*` keys), but the wizard's v2.14.4 owner-capture also links the owner's Kazee handle onto their people record (`people.linkHandle`) and mirrors an identity mapping into `identities.json` (`setIdentityMapping`). Those were left behind, so a subsequent `/channel add kazee` stacked on stale handle + identity state. `/channel remove` now also drops every handle for that transport across all people records (new `people.unlinkAdapterHandles`) and removes its identity mappings — channel→canonical links and preferred-channel pointers (new `identity.removeTransportIdentities`) — and clears the previously-orphaned `KAZEE_BOT_USER_ID`.
|
|
5
|
+
- **The cleanup goes through the in-memory stores, so disk and the running process stay consistent.** `identities.json` is loaded once at boot into a shared in-memory object (`identity.js`), and `saveIdentities()` re-serializes that whole object on every write. Because `router.js` calls `autoLinkOwnerChannel` → `setIdentityMapping` → `saveIdentities` on each owner message, any entry lingering in memory is rewritten to disk continuously — so an out-of-band file edit can't stick, and a partial teardown leaves ghosts that reappear. Removing through the in-memory maps (then persisting) means the teardown holds. The `/channel remove` reply now reports what it cleared (e.g. `Removed channel: kazee — cleared 1 handle + identity links`).
|
|
6
|
+
- **Agent-space / OpenClaw:** no new deps, no new env, no schema change; Docker image builds identically. Scoped entirely to the owner-driven `/channel remove` path — inert for deployments that never remove a channel. Full `npm test` green.
|
|
7
|
+
|
|
3
8
|
## v2.14.4
|
|
4
9
|
- **The owner no longer gets asked to double-approve their own Kazee reply — split-brain owner resolution is fixed.** On Kazee a person is identified by `userId` while the room is `channelId` (the chat-document); the two differ. The auth layer already recognised the owner correctly (via `access.matchesTransportOwner` → `currentUserId()`), but the independent relationship guardrail re-derived ownership from `channelId` — which never equals the configured `KAZEE_OWNER_USER_ID` — and so classed the owner as *external*, escalating the owner's own approval a second time. `relationship.speakerFor` now takes an optional third `userId` and keys identity/owner classification off `speakerId = userId || channelId` (the person), while still replying/escalating to `channelId` (the room). The raw per-user id is threaded to every subprocess egress guard via a new `OC_CHANNEL_USER_ID` env var + loopback query/body param (`bin/cli.js`, `bin/entity.js`, `bin/tool.js`, `bin/loopback-client.js`, `core/io.js`, `core/loopback.js`, `core/system-prompt.js`). **On Telegram `userId === channelId` in DMs, so this is a strict no-op** for existing Telegram deployments.
|
|
5
10
|
- **`/channel add kazee` now links the owner's Kazee handle at capture time.** Owner recognition previously lived only in the env/adapter (`KAZEE_OWNER_USER_ID`); the owner's people record had no Kazee handle, so the guardrail (which resolves through canonical identity) re-derived the operator as external. `finalizeOwner` now calls `people.linkHandle(owner.id, { adapter: "kazee", channelId: ownerUserId, … })` so the wizard's captured owner is recognised the same way the rest of the bot resolves identity — belt-and-suspenders with the `speakerFor` fix above.
|
package/core/handlers.js
CHANGED
|
@@ -1674,21 +1674,32 @@ register({
|
|
|
1674
1674
|
if (id === "telegram") return send("Refusing to remove the Telegram adapter from a /channel command — that's your own connection.");
|
|
1675
1675
|
const result = await registry.removeAdapter(id);
|
|
1676
1676
|
if (!result.ok) return send(result.error);
|
|
1677
|
+
const transport = id.split(":")[0];
|
|
1677
1678
|
// Strip from .env so the change survives restart.
|
|
1678
1679
|
const channels = (config.CHANNELS || "telegram").split(",").map((s) => s.trim()).filter(Boolean);
|
|
1679
|
-
const kept = channels.filter((c) => c !== id && !c.startsWith(id + ":") && c !==
|
|
1680
|
+
const kept = channels.filter((c) => c !== id && !c.startsWith(id + ":") && c !== transport);
|
|
1680
1681
|
const next = kept.join(",") || "telegram";
|
|
1681
1682
|
saveEnvKey("CHANNELS", next);
|
|
1682
1683
|
config.CHANNELS = next;
|
|
1683
|
-
if (
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
config.KAZEE_BOT_TOKEN = "";
|
|
1689
|
-
config.KAZEE_OWNER_USER_ID = "";
|
|
1684
|
+
if (transport === "kazee") {
|
|
1685
|
+
for (const k of ["KAZEE_URL", "KAZEE_BOT_TOKEN", "KAZEE_OWNER_USER_ID", "KAZEE_BOT_USER_ID"]) {
|
|
1686
|
+
saveEnvKey(k, "");
|
|
1687
|
+
config[k] = "";
|
|
1688
|
+
}
|
|
1690
1689
|
}
|
|
1691
|
-
|
|
1690
|
+
// Complete the teardown: drop this transport's handles from every person
|
|
1691
|
+
// record and its identity mappings. Both mutate through the in-memory
|
|
1692
|
+
// stores, so disk and the running process stay consistent — otherwise a
|
|
1693
|
+
// stale handle/link survives in memory and gets rewritten on the next
|
|
1694
|
+
// add, which is exactly what made a re-add feel un-clean.
|
|
1695
|
+
let unlinked = 0;
|
|
1696
|
+
try { unlinked = require("./people").unlinkAdapterHandles(transport).length; } catch (e) {}
|
|
1697
|
+
let idsCleared = false;
|
|
1698
|
+
try { idsCleared = require("./identity").removeTransportIdentities(transport); } catch (e) {}
|
|
1699
|
+
const extra = (unlinked || idsCleared)
|
|
1700
|
+
? ` — cleared ${unlinked} handle${unlinked === 1 ? "" : "s"} + identity links`
|
|
1701
|
+
: "";
|
|
1702
|
+
return send(`Removed channel: ${id}${extra}`);
|
|
1692
1703
|
}
|
|
1693
1704
|
|
|
1694
1705
|
send("Usage: /channel | /channel add kazee | /channel remove <id>");
|
package/core/identity.js
CHANGED
|
@@ -137,6 +137,25 @@ function setIdentityMapping(transport, channelId, canonicalUserId) {
|
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// Remove every identity mapping for a transport — used when a channel is torn
|
|
141
|
+
// down (/channel remove) so no stale channel→canonical link or preferred-channel
|
|
142
|
+
// pointer lingers to be rewritten to disk on the next identity write. Mutates
|
|
143
|
+
// the in-memory map the whole process shares, THEN persists, so memory and disk
|
|
144
|
+
// stay in sync (a disk-only edit would be resurrected by the cached object).
|
|
145
|
+
function removeTransportIdentities(transport) {
|
|
146
|
+
const t = String(transport || "").toLowerCase();
|
|
147
|
+
if (!t) return false;
|
|
148
|
+
let changed = false;
|
|
149
|
+
for (const key of Object.keys(identities.channels)) {
|
|
150
|
+
if (key.slice(0, t.length + 1) === t + ":") { delete identities.channels[key]; changed = true; }
|
|
151
|
+
}
|
|
152
|
+
for (const [userId, pref] of Object.entries(identities.preferred)) {
|
|
153
|
+
if (pref && String(pref.transport || "").toLowerCase() === t) { delete identities.preferred[userId]; changed = true; }
|
|
154
|
+
}
|
|
155
|
+
if (changed) saveIdentities();
|
|
156
|
+
return changed;
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
module.exports = {
|
|
141
160
|
identities,
|
|
142
161
|
normalizeCanonicalUserId,
|
|
@@ -149,5 +168,6 @@ module.exports = {
|
|
|
149
168
|
canonicalForTelegram,
|
|
150
169
|
canonicalForStoredUserKey,
|
|
151
170
|
setIdentityMapping,
|
|
171
|
+
removeTransportIdentities,
|
|
152
172
|
saveIdentities,
|
|
153
173
|
};
|
package/core/people.js
CHANGED
|
@@ -159,6 +159,30 @@ function unlinkHandle(personId, { adapter, channelId }) {
|
|
|
159
159
|
return p;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// Remove every handle for a given adapter across ALL people records — used when
|
|
163
|
+
// a channel is torn down (/channel remove) so no stranded handle survives to be
|
|
164
|
+
// re-linked or re-mirrored on a later add. primaryChannel falls back to the next
|
|
165
|
+
// remaining handle. Returns the ids of the people that were changed.
|
|
166
|
+
function unlinkAdapterHandles(adapter) {
|
|
167
|
+
const a = String(adapter || "").toLowerCase();
|
|
168
|
+
if (!a) return [];
|
|
169
|
+
const all = load();
|
|
170
|
+
const affected = [];
|
|
171
|
+
for (const p of all) {
|
|
172
|
+
const before = (p.handles || []).length;
|
|
173
|
+
if (!before) continue;
|
|
174
|
+
p.handles = p.handles.filter((h) => h.adapter !== a);
|
|
175
|
+
if (p.handles.length === before) continue;
|
|
176
|
+
if (p.primaryChannel && p.primaryChannel.adapter === a) {
|
|
177
|
+
p.primaryChannel = p.handles[0] ? { adapter: p.handles[0].adapter, channelId: p.handles[0].channelId } : null;
|
|
178
|
+
}
|
|
179
|
+
p.updatedAt = nowIso();
|
|
180
|
+
affected.push(p.id);
|
|
181
|
+
}
|
|
182
|
+
if (affected.length) save(all);
|
|
183
|
+
return affected;
|
|
184
|
+
}
|
|
185
|
+
|
|
162
186
|
function setPrimary(personId, { adapter, channelId }) {
|
|
163
187
|
const all = load();
|
|
164
188
|
const p = all.find((x) => x.id === personId);
|
|
@@ -264,7 +288,7 @@ module.exports = {
|
|
|
264
288
|
load, save, list, roster,
|
|
265
289
|
add, update, remove,
|
|
266
290
|
findById, findByName, findByCanonicalUserId, findByHandle,
|
|
267
|
-
linkHandle, unlinkHandle, setPrimary,
|
|
291
|
+
linkHandle, unlinkHandle, unlinkAdapterHandles, setPrimary,
|
|
268
292
|
note, removeNote,
|
|
269
293
|
setEntitySlug, resolveEntitySlug,
|
|
270
294
|
owners, hasOwnerRecord, seedOwnerFromLegacy,
|
package/package.json
CHANGED