@inetafrica/open-claudia 3.0.25 → 3.0.26

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.26 — Spaces replies actually post (and stop saying "Queued.")
4
+
5
+ - **An engaged Spaces reply no longer fails with 400 "Parent comment not found."** Core threads every agent reply under the triggering message id — a Telegram-ism. On Spaces that id is a *notification* id, not a comment id, so the backend rejected the whole post and the bot silently failed to answer any mention. `send()` now only threads under an explicit `comment:`-prefixed id and otherwise posts a top-level task comment; if a stale/deleted parent still 400s, it retries once at top level so the reply always lands.
6
+ - **The bot threads its reply under the exact comment it's answering.** When a notification names a specific comment (a reply's `replyId`, or a mention/comment that targets a comment), the reply is threaded under it instead of floating at the task root. Task-level mentions and assignments stay top-level, as before.
7
+ - **No more "Queued." noise on a task thread.** The transient queue/compaction acknowledgement is a chat-UX affordance for Telegram/Kazee. On a Spaces task it posted a throwaway "Queued." comment when a second turn stacked (and, pre-fix, that post itself 400'd). Spaces now stays silent while queued and simply posts the real reply when its turn runs. Per-identity run serialization is unchanged and intended — one being, one hand.
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.25 — The bot types back on a Spaces task
4
11
 
5
12
  - **"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.
@@ -202,9 +202,22 @@ class SpacesAdapter {
202
202
  actorName = actorName || `${actorId.firstName || ""} ${actorId.lastName || ""}`.trim() || actorId.email;
203
203
  actorId = actorId.id || actorId._id;
204
204
  }
205
+ // The specific comment to thread a reply under, when the event is about one:
206
+ // a reply → the reply comment (replyId); a comment/mention that targets a
207
+ // comment → that comment id. Absent for task-level mentions and assignments,
208
+ // where the reply is a top-level task comment.
209
+ const commentId = String(
210
+ data.replyId || inner.replyId
211
+ || data.commentId || inner.commentId
212
+ || (data.targetType === "comment" ? data.targetId : "")
213
+ || (inner.targetType === "comment" ? inner.targetId : "")
214
+ || (payload.targetType === "comment" ? payload.targetId : "")
215
+ || "",
216
+ );
205
217
  return {
206
218
  type,
207
219
  taskId,
220
+ commentId,
208
221
  actorId: actorId ? String(actorId) : "",
209
222
  actorName: actorName || "",
210
223
  spaceId: payload.spaceId || inner.spaceId || null,
@@ -261,7 +274,10 @@ class SpacesAdapter {
261
274
  userId: n.actorId || n.taskId,
262
275
  type: "text",
263
276
  text: n.content || `[${n.type} on task ${n.title || n.taskId}]`,
264
- messageId: id,
277
+ // Thread the bot's reply under the exact comment that triggered this engage
278
+ // (a reply, or a mention targeting a comment). Falls back to the
279
+ // notification id, which send() posts as a top-level task comment.
280
+ messageId: n.commentId ? `comment:${n.commentId}` : id,
265
281
  from: { id: n.actorId, name: n.actorName },
266
282
  spaces: { taskId: n.taskId, spaceId: n.spaceId, notificationType: n.type, deepLink: n.deepLink, title: n.title, mode: this._taskLoopMode() },
267
283
  raw: payload,
@@ -473,15 +489,24 @@ class SpacesAdapter {
473
489
  async send(channelId, text, opts = {}) {
474
490
  const taskId = String(channelId || "").replace(/^task:/, "");
475
491
  if (!taskId) { console.error("Spaces send: no taskId in channelId", channelId); return null; }
492
+ // Thread only under an EXPLICIT comment id (a "comment:"-prefixed replyTo,
493
+ // set on an engaged reply/comment-mention). Core's default replyTo is the
494
+ // triggering NOTIFICATION id, which is not a comment — threading under it
495
+ // 400s ("Parent comment not found"), so treat anything else as a top-level
496
+ // task comment.
497
+ const replyTo = String(opts.replyTo || "");
498
+ const parentId = replyTo.startsWith("comment:") ? replyTo.slice("comment:".length) : null;
499
+ const post = (parent) => this.client.createComment({ taskId, text, mentions: opts.mentions || [], parentId: parent });
500
+ const idOf = (c) => (c && (c._id || c.id) ? String(c._id || c.id) : true);
476
501
  try {
477
- const comment = await this.client.createComment({
478
- taskId,
479
- text,
480
- mentions: opts.mentions || [],
481
- parentId: opts.replyTo ? String(opts.replyTo).replace(/^comment:/, "") : null,
482
- });
483
- return comment && (comment._id || comment.id) ? String(comment._id || comment.id) : true;
502
+ return idOf(await post(parentId));
484
503
  } catch (e) {
504
+ // A stale/deleted parent 400s; retry once at top level so the reply always
505
+ // lands on the task rather than being dropped.
506
+ if (parentId && /parent comment not found/i.test(e.message || "")) {
507
+ try { return idOf(await post(null)); }
508
+ catch (e2) { console.error("Spaces send error (top-level retry):", e2.message); return null; }
509
+ }
485
510
  console.error("Spaces send error:", e.message);
486
511
  return null;
487
512
  }
package/core/runner.js CHANGED
@@ -1982,7 +1982,13 @@ function runAgent(prompt, cwd, replyToMsgId, opts = {}) {
1982
1982
  }
1983
1983
  const deferred = deferredRunResult();
1984
1984
  state.messageQueue.push({ runContext, store, deferred, queuedAt: Date.now() });
1985
- send(state.isCompacting ? "Compacting context, will pick this up next…" : "Queued.", { replyTo: replyToMsgId }).catch(() => {});
1985
+ // Transient queue/compaction acks are a chat-UX affordance (Telegram/Kazee).
1986
+ // On a Spaces task thread they'd post a throwaway "Queued." comment on the
1987
+ // task — noise. Spaces stays quiet and just posts the real reply when its
1988
+ // turn runs.
1989
+ if (currentAdapter()?.type !== "spaces") {
1990
+ send(state.isCompacting ? "Compacting context, will pick this up next…" : "Queued.", { replyTo: replyToMsgId }).catch(() => {});
1991
+ }
1986
1992
  return deferred.promise;
1987
1993
  }
1988
1994
  return executeAdmittedRun(runContext, state, store, false, true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.0.25",
3
+ "version": "3.0.26",
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,15 +32,16 @@ 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.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");
35
+ // 3.0.26 approved by Sumeet 2026-07-15: fix Spaces reply posting. (1) An engaged
36
+ // reply no longer 400s send() only threads under an explicit "comment:"-prefixed
37
+ // id (the triggering reply/comment-mention) and otherwise posts a top-level task
38
+ // comment, with a top-level retry if a stale parent 400s. (2) The bot threads its
39
+ // reply under the exact triggering comment when the notification names one
40
+ // (replyId / comment-target). (3) The transient "Queued." ack is suppressed on
41
+ // Spaces a stacked turn stays silent and just posts the real reply when it runs,
42
+ // instead of dropping a throwaway "Queued." comment on the task. Builds on 3.0.25's
43
+ // comment:typing emit.
44
+ assert.strictEqual(pkg.version, "3.0.26", "release version must remain unchanged without explicit approval");
44
45
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
45
46
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
46
47
  }