@inetafrica/open-claudia 2.2.3 → 2.2.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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.2.5
4
+ - Fix `/codex` resume crash: `buildCodexArgs` no longer appends `--add-dir <transcripts-dir>`, which the Codex CLI does not accept and which caused every `codex exec resume` invocation to exit 2 with `error: unexpected argument '--add-dir' found`. Transcript pointer is still injected into the prompt via `promptWithTranscriptPointer`.
5
+ - Backend-aware empty-output failure message: when a non-Claude backend exits with no assistant output, the reply now labels it correctly (`Codex` / `Cursor`) and points at the right diagnostic commands (`/codex_auth_status`, `/codex_login`, `/codex_setup_token`, or `agent login`) instead of always saying "Claude exited" and recommending Claude-only commands.
6
+
7
+ ## v2.2.4
8
+ - Bearer auth on `/api/*`: when `BOT_CONTROL_TOKEN` is set in the env, requests with `Authorization: Bearer <token>` are accepted in addition to the existing cookie session. Behaviour is unchanged when the env var is not set, so local installs are not affected.
9
+ - `/upgrade` detects an AgentSpace-managed pod (both `AGENTSPACE_POD_TOKEN` and `AGENTSPACE_API_URL` set) and delegates to `POST /pods/self/upgrade` on the control plane instead of running `npm install -g` locally. The control plane rolls the deployment with a fresh image pull. Local installs fall through to the existing npm upgrade path.
10
+ - Password change in the web UI fires a fire-and-forget `POST /pods/self/password-changed` callback so the control plane can flag the pod as having a user-set password and refuse to leak the stale initial value.
11
+
3
12
  ## v2.2.3
4
13
  - Queue drain now batches: when multiple messages are received while a task is running, they're delivered as one combined follow-up turn instead of N isolated turns. The model sees them together (numbered, with HH:MM:SS queue timestamps), in context of what it just finished, so it can plan across them. A single queued message still delivers as before — no behavior change for the common case.
5
14
 
package/core/handlers.js CHANGED
@@ -301,10 +301,55 @@ register({
301
301
  },
302
302
  });
303
303
 
304
+ async function requestAgentSpaceUpgrade() {
305
+ const apiUrl = process.env.AGENTSPACE_API_URL;
306
+ const token = process.env.AGENTSPACE_POD_TOKEN;
307
+ if (!apiUrl || !token) return null;
308
+ const u = new URL("/pods/self/upgrade", apiUrl);
309
+ const lib = u.protocol === "https:" ? require("https") : require("http");
310
+ return new Promise((resolve) => {
311
+ const req = lib.request({
312
+ method: "POST",
313
+ hostname: u.hostname,
314
+ port: u.port || (u.protocol === "https:" ? 443 : 80),
315
+ path: u.pathname + u.search,
316
+ headers: {
317
+ "Authorization": `Bearer ${token}`,
318
+ "Content-Type": "application/json",
319
+ "Content-Length": "0",
320
+ },
321
+ }, (res) => {
322
+ let data = "";
323
+ res.on("data", (c) => { data += c; });
324
+ res.on("end", () => resolve({ status: res.statusCode, body: data }));
325
+ });
326
+ req.on("error", (e) => resolve({ status: 0, body: String(e.message || e) }));
327
+ req.setTimeout(15000, () => { req.destroy(new Error("timeout")); });
328
+ req.end();
329
+ });
330
+ }
331
+
304
332
  register({
305
333
  name: "upgrade", description: "Upgrade and restart", ownerOnly: true,
306
334
  handler: async (env) => {
307
335
  if (!ownerEnv(env)) return;
336
+ // Container-managed upgrade: if AgentSpace injected pod-self credentials,
337
+ // delegate to the control plane so the deployment can be rolled with a
338
+ // fresh image pull. The local npm path can't do that.
339
+ if (process.env.AGENTSPACE_POD_TOKEN && process.env.AGENTSPACE_API_URL) {
340
+ try {
341
+ const result = await requestAgentSpaceUpgrade();
342
+ if (result && (result.status === 202 || result.status === 200)) {
343
+ await send("Upgrade requested — AgentSpace will pull latest image and restart, see you in a minute.");
344
+ return;
345
+ }
346
+ await send(`Upgrade request failed (status ${result?.status || "?"}). ${(result?.body || "").slice(0, 300)}`);
347
+ return;
348
+ } catch (e) {
349
+ await send(`Upgrade request error: ${e.message || e}`);
350
+ return;
351
+ }
352
+ }
308
353
  try { process.chdir(process.env.HOME || require("os").homedir()); } catch (e) {}
309
354
  let latest = null;
310
355
  try {
package/core/runner.js CHANGED
@@ -145,8 +145,6 @@ function buildCodexArgs(prompt, opts = {}) {
145
145
  else if (resumeId) args.push("exec", "resume", resumeId);
146
146
  else args.push("exec");
147
147
  args.push("--json", "--skip-git-repo-check");
148
- const transcriptInfo = transcriptProjectInfo(state);
149
- if (transcriptInfo) args.push("--add-dir", transcriptInfo.transcriptsDir);
150
148
  if (settings.permissionMode === "plan") args.push("--sandbox", "read-only");
151
149
  else args.push("--dangerously-bypass-approvals-and-sandbox");
152
150
  if (settings.model) args.push("--model", settings.model);
@@ -177,21 +175,35 @@ function preflightClaudeAuthMessage() {
177
175
  }
178
176
 
179
177
  function claudeEmptyFailureMessage(code, stderrText = "") {
178
+ const backend = currentState().settings.backend || "claude";
180
179
  const stderr = redactSensitive(String(stderrText || "").trim());
181
- if (isClaudeUsageLimitText(stderr)) return claudeUsageLimitMessage(stderr.slice(-1200));
182
- if (isClaudeAuthErrorText(stderr)) return claudeAuthRecoveryMessage(stderr.slice(-1200));
183
- const authStatus = runClaudeAuthStatusDiagnostic();
184
- if (isClaudeAuthErrorText(authStatus)) return claudeAuthRecoveryMessage(authStatus.slice(-1200));
185
- if (isClaudeUsageLimitText(authStatus)) return claudeUsageLimitMessage(authStatus.slice(-1200));
180
+ if (backend === "claude") {
181
+ if (isClaudeUsageLimitText(stderr)) return claudeUsageLimitMessage(stderr.slice(-1200));
182
+ if (isClaudeAuthErrorText(stderr)) return claudeAuthRecoveryMessage(stderr.slice(-1200));
183
+ const authStatus = runClaudeAuthStatusDiagnostic();
184
+ if (isClaudeAuthErrorText(authStatus)) return claudeAuthRecoveryMessage(authStatus.slice(-1200));
185
+ if (isClaudeUsageLimitText(authStatus)) return claudeUsageLimitMessage(authStatus.slice(-1200));
186
+ return [
187
+ `Claude exited with code ${code} but produced no assistant output.`,
188
+ stderr ? `\nStderr:\n${stderr.slice(-1200)}` : "\nStderr: (empty)",
189
+ authStatus ? `\nAuth status:\n${redactSensitive(authStatus).slice(-1200)}` : "",
190
+ "",
191
+ "Useful next steps:",
192
+ "• /auth_status — verify Claude auth",
193
+ "• /model sonnet — switch away from Opus if usage-limited",
194
+ "• /setup_token — create a launchd-safe OAuth token if Keychain is the issue",
195
+ ].filter(Boolean).join("\n");
196
+ }
197
+ const label = backend === "codex" ? "Codex" : "Cursor";
198
+ const nextSteps = backend === "codex"
199
+ ? ["• /codex_auth_status — verify Codex auth", "• /codex_login — device-code login", "• /codex_setup_token — paste an OpenAI API key"]
200
+ : ["• /backend — confirm Cursor is selected", "• Run `agent login` on the host machine", "• /claude — fall back to Claude if Cursor stays broken"];
186
201
  return [
187
- `Claude exited with code ${code} but produced no assistant output.`,
202
+ `${label} exited with code ${code} but produced no assistant output.`,
188
203
  stderr ? `\nStderr:\n${stderr.slice(-1200)}` : "\nStderr: (empty)",
189
- authStatus ? `\nAuth status:\n${redactSensitive(authStatus).slice(-1200)}` : "",
190
204
  "",
191
205
  "Useful next steps:",
192
- "• /auth_status — verify Claude auth",
193
- "• /model sonnet — switch away from Opus if usage-limited",
194
- "• /setup_token — create a launchd-safe OAuth token if Keychain is the issue",
206
+ ...nextSteps,
195
207
  ].filter(Boolean).join("\n");
196
208
  }
197
209
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "description": "Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram or Kazee Chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
package/web.js CHANGED
@@ -48,9 +48,48 @@ function validatePasswordComplexity(pw) {
48
48
  function setPassword(newPassword) {
49
49
  fs.writeFileSync(WEB_PASSWORD_FILE, newPassword);
50
50
  fs.writeFileSync(PASSWORD_CHANGED_FILE, new Date().toISOString());
51
+ notifyAgentSpacePasswordChanged();
52
+ }
53
+
54
+ function notifyAgentSpacePasswordChanged() {
55
+ const apiUrl = process.env.AGENTSPACE_API_URL;
56
+ const token = process.env.AGENTSPACE_POD_TOKEN;
57
+ if (!apiUrl || !token) return;
58
+ let u;
59
+ try { u = new URL("/pods/self/password-changed", apiUrl); } catch (e) { return; }
60
+ const lib = u.protocol === "https:" ? require("https") : require("http");
61
+ const req = lib.request({
62
+ method: "POST",
63
+ hostname: u.hostname,
64
+ port: u.port || (u.protocol === "https:" ? 443 : 80),
65
+ path: u.pathname + u.search,
66
+ headers: {
67
+ "Authorization": `Bearer ${token}`,
68
+ "Content-Type": "application/json",
69
+ "Content-Length": "0",
70
+ },
71
+ });
72
+ req.on("error", () => {});
73
+ req.setTimeout(5000, () => { try { req.destroy(); } catch (e) {} });
74
+ req.end();
75
+ }
76
+
77
+ function checkBearerAuth(req) {
78
+ const expected = process.env.BOT_CONTROL_TOKEN;
79
+ if (!expected) return false;
80
+ const header = req.headers.authorization || "";
81
+ if (!header.startsWith("Bearer ")) return false;
82
+ const presented = header.slice(7).trim();
83
+ if (presented.length !== expected.length) return false;
84
+ try {
85
+ return crypto.timingSafeEqual(Buffer.from(presented), Buffer.from(expected));
86
+ } catch (e) {
87
+ return false;
88
+ }
51
89
  }
52
90
 
53
91
  function checkAuth(req) {
92
+ if (checkBearerAuth(req)) return true;
54
93
  const cookie = (req.headers.cookie || "").split(";").find((c) => c.trim().startsWith("oc_session="));
55
94
  if (!cookie) return false;
56
95
  const token = cookie.split("=")[1]?.trim();