@inetafrica/open-claudia 3.0.24 → 3.0.25
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 +7 -0
- package/channels/spaces/adapter.js +38 -5
- package/package.json +1 -1
- package/test-provider-language.js +9 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v3.0.25 — The bot types back on a Spaces task
|
|
4
|
+
|
|
5
|
+
- **"Open Claudia is typing…" now shows on a Spaces task thread while the bot composes.** Core already runs a typing heartbeat around every agent turn (calling `adapter.typing()` every ~4s and `adapter.typingStop()` at turn-end); on Spaces those were no-op stubs. They now re-emit the exact `comment:typing` event a human client sends over the adapter's existing authenticated socket, so a teammate watching a task sees the bot compose like any other member — the same three-dot indicator the web and mobile apps already render.
|
|
6
|
+
- **Part 4 of the cross-repo typing indicator.** The backend (`comment:typing` re-broadcast to the `space:<id>` room, identity stamped from the authenticated socket, no persistence), web, and mobile surfaces shipped in prior deploys — human↔human typing was already live end-to-end. This closes the loop so the bot participates too.
|
|
7
|
+
- **Minimal by construction.** The adapter caches each task's `spaceId` (from the notification payload and the thread-context fetch) so the heartbeat — which only knows the `task:<id>` channel — can name the room. The emit sends just `targetId + spaceId + isTyping`; the server derives who is typing. Fully best-effort: no socket, no cached space, or a transport blip is a silent no-op, so a typing dot can never affect or delay the actual reply.
|
|
8
|
+
- **Agent-space / OpenClaw:** no new deps, no new env, no schema change; Docker image builds identically (FROM the pre-baked `:base`). Existing pods pick this up on their next approved upgrade.
|
|
9
|
+
|
|
3
10
|
## v3.0.24 — Spaces threads read like a group chat; memory notes go quiet unless you're watching
|
|
4
11
|
|
|
5
12
|
- **The "🧠 Jotted this down" memory notes are now gated behind `/recall`.** After each turn the pack reviewer still writes to long-term memory exactly as before — but the chat announcement of what it filed away only surfaces when `/recall` is on (`state.settings.showRecall`), the same toggle that already gates the recall/tool banners. Off by default, so ordinary turns stay clean; flip `/recall on` to watch memory (and recall) work. That announcement was the one learning-surface bypassing the gate; it no longer does.
|
|
@@ -59,6 +59,7 @@ class SpacesAdapter {
|
|
|
59
59
|
this._socket = null;
|
|
60
60
|
this._seen = new Set(); // notification-id dedup across socket + poll
|
|
61
61
|
this._taskChains = new Map(); // taskId -> tail promise, for per-task serialization
|
|
62
|
+
this._taskSpace = new Map(); // taskId -> spaceId, so typing() can name the room
|
|
62
63
|
this._pollTimer = null;
|
|
63
64
|
this._initTimer = null;
|
|
64
65
|
}
|
|
@@ -230,6 +231,10 @@ class SpacesAdapter {
|
|
|
230
231
|
const n = this._normalize(payload);
|
|
231
232
|
if (!n.taskId) return;
|
|
232
233
|
|
|
234
|
+
// Cache the task's space so typing() can address the space:<id> room later —
|
|
235
|
+
// the heartbeat only gets a channelId ("task:<id>"), not the spaceId.
|
|
236
|
+
if (n.spaceId) this._taskSpace.set(String(n.taskId), String(n.spaceId));
|
|
237
|
+
|
|
233
238
|
// Receiving any Spaces notification proves membership → mark connected. This
|
|
234
239
|
// is the socket half of the hybrid "connected ⇒ conversational" signal; no
|
|
235
240
|
// env flag needed to turn auto-replies on.
|
|
@@ -317,6 +322,15 @@ class SpacesAdapter {
|
|
|
317
322
|
try { task = await this.client.getTask(taskId); }
|
|
318
323
|
catch (e) { task = null; }
|
|
319
324
|
|
|
325
|
+
// Cache the task's space for typing() — buildThreadContext runs at the very
|
|
326
|
+
// start of an engaged turn, before the typing heartbeat first fires.
|
|
327
|
+
if (task && typeof task === "object") {
|
|
328
|
+
const sid = task.spaceId && typeof task.spaceId === "object"
|
|
329
|
+
? (task.spaceId.id || task.spaceId._id || null)
|
|
330
|
+
: (task.spaceId || null);
|
|
331
|
+
if (sid) this._taskSpace.set(String(taskId), String(sid));
|
|
332
|
+
}
|
|
333
|
+
|
|
320
334
|
const lines = ["[Kazee Spaces thread context]"];
|
|
321
335
|
lines.push(`Task: ${(task && task.title) || envelope.spaces?.title || "(untitled task)"}`);
|
|
322
336
|
|
|
@@ -473,13 +487,32 @@ class SpacesAdapter {
|
|
|
473
487
|
}
|
|
474
488
|
}
|
|
475
489
|
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
// call them unconditionally.
|
|
490
|
+
// Comment edit/delete are out of scope for v1. These keep the adapter contract
|
|
491
|
+
// total so core code can call them unconditionally.
|
|
479
492
|
async edit() { /* not supported */ }
|
|
480
493
|
async delete() { /* not supported */ }
|
|
481
|
-
|
|
482
|
-
|
|
494
|
+
|
|
495
|
+
// Ephemeral "Open Claudia is typing…" on the task thread. Core's typing
|
|
496
|
+
// heartbeat (runner.js) calls typing() every ~4s during a run and typingStop()
|
|
497
|
+
// at turn-end; we re-emit the same comment:typing event a human client sends,
|
|
498
|
+
// so teammates watching the thread see the bot compose like anyone else. The
|
|
499
|
+
// backend stamps identity from the authenticated socket and re-broadcasts to
|
|
500
|
+
// space:<id> (excluding us), so we only send targetId + spaceId + isTyping.
|
|
501
|
+
// Best-effort: no socket, no cached space, or a transport blip is silently a
|
|
502
|
+
// no-op — a typing dot must never affect the actual reply.
|
|
503
|
+
async typing(channelId) { this._emitTyping(channelId, true); }
|
|
504
|
+
async typingStop(channelId) { this._emitTyping(channelId, false); }
|
|
505
|
+
|
|
506
|
+
_emitTyping(channelId, isTyping) {
|
|
507
|
+
try {
|
|
508
|
+
if (!this._socket?.connected) return;
|
|
509
|
+
const taskId = String(channelId || "").replace(/^task:/, "");
|
|
510
|
+
if (!taskId) return;
|
|
511
|
+
const spaceId = this._taskSpace.get(taskId);
|
|
512
|
+
if (!spaceId) return;
|
|
513
|
+
this._socket.emit("comment:typing", { targetType: "task", targetId: taskId, spaceId, isTyping: !!isTyping });
|
|
514
|
+
} catch (e) { /* typing is best-effort */ }
|
|
515
|
+
}
|
|
483
516
|
async sendVoice() { return false; }
|
|
484
517
|
async sendPhoto() { return false; }
|
|
485
518
|
async sendFile() { return false; }
|
package/package.json
CHANGED
|
@@ -32,16 +32,15 @@ assert.doesNotMatch(setup, /Your Claude Code bot is connected|Description=Claude
|
|
|
32
32
|
|
|
33
33
|
const pkg = JSON.parse(read("package.json"));
|
|
34
34
|
assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
35
|
-
// 3.0.
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
assert.strictEqual(pkg.version, "3.0.24", "release version must remain unchanged without explicit approval");
|
|
35
|
+
// 3.0.25 approved by Sumeet 2026-07-15 ("Ship it"): the bot now emits its own
|
|
36
|
+
// comment:typing on a Spaces task thread. Core's typing heartbeat drives the
|
|
37
|
+
// SpacesAdapter typing()/typingStop() (previously no-op stubs), which re-emit the
|
|
38
|
+
// same ephemeral event a human client sends, so teammates watching a task see
|
|
39
|
+
// "Open Claudia is typing…" while it composes a reply. Part 4 of the cross-repo
|
|
40
|
+
// typing-indicator work (backend/web/mobile shipped in prior deploys); the
|
|
41
|
+
// backend stamps identity from the authenticated socket and re-broadcasts to the
|
|
42
|
+
// space room, so the adapter only sends targetId + spaceId + isTyping.
|
|
43
|
+
assert.strictEqual(pkg.version, "3.0.25", "release version must remain unchanged without explicit approval");
|
|
45
44
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
46
45
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
47
46
|
}
|