@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 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
- // Spaces has no per-thread bot typing indicator, and comment edit/delete are
477
- // out of scope for v1. These keep the adapter contract total so core code can
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
- async typing() { /* no-op */ }
482
- async typingStop() { /* no-op */ }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.0.24",
3
+ "version": "3.0.25",
4
4
  "description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -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.24 approved by Sumeet 2026-07-14: turn-context add-on (an engaged Spaces
36
- // turn gets task metadata + every comment since the bot's last reply, via the
37
- // channel-agnostic buildThreadContext() adapter seam the router prepends) PLUS
38
- // /recall now gates the "🧠 Jotted this down" memory-write announcements memory
39
- // still writes every turn, the chat note only surfaces when /recall is on
40
- // (state.settings.showRecall), matching how the recall/tool banners already gate.
41
- // 3.0.23 shipped the turn-context work but its CI build failed on THIS gate
42
- // (package.json bumped without updating the assertion) nothing published under
43
- // 3.0.23; 3.0.24 is the corrected re-ship (same pattern as 3.0.21 → 3.0.22).
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
  }