@inetafrica/open-claudia 2.2.15 → 2.2.16

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,8 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.2.16
4
+ - New `/compactwindow` slash command (alias `/autocompact`) lets each user override the auto-compact token threshold from chat instead of editing `AUTO_COMPACT_TOKENS` in `.env` and restarting. Quick-pick buttons for 200k / 300k / 380k / 500k / Off / Default, or free-form `/compactwindow 250k` / `/compactwindow 0.5m` / `/compactwindow off` / `/compactwindow default`. Stored per-user as `settings.compactWindow` (persists across restarts via `state.json`); `null` falls back to the env default, `0` disables auto-compact entirely (manual `/compact` still works). `/status` now reports the effective window, and `/usage`'s "context is large" tip reflects the user's override.
5
+
3
6
  ## v2.2.15
4
7
  - Fix compaction loop: `compactActiveSession` no longer holds `isCompacting=true` for the full duration of its two-step flow. The flag now clears after step 1 (the summarizer) finishes, so step 2 (the seed-the-fresh-session call) runs as a regular long-running task. Previously, the seed step could pick up where the prior conversation left off and do real work — dev servers, package installs — for hours, all while the bot reported "Compacting context, will pick this up next…" to every incoming message. After a `/restart` the in-memory flag reset but `lastSessionId` still pointed to the same huge session, triggering an auto-compact on the next message and looping the same trap. New behaviour: the summarizer-only phase shows the compaction message; once the summary is written the bot returns to its normal "Queued." reply for any messages that arrive while the seed continuation runs.
5
8
  - Add `COMPACT_SUMMARY_TIMEOUT` (10 minutes) and thread it through `runClaudeCapture` via `opts.timeoutMs`. The summarizer is a single-shot summarisation call — if it hasn't returned in 10 minutes it's hung, not slow. Previously it could sit on the 6-hour `MAX_PROCESS_TIMEOUT` and lock the bot for a quarter of a day. The seed continuation keeps the full 6-hour budget since it can legitimately be a long-running agent task.
package/core/actions.js CHANGED
@@ -257,6 +257,19 @@ async function handleAction(envelope) {
257
257
  if (d.startsWith("m:")) { state.settings.model = d.slice(2) === "default" ? null : d.slice(2); await send(`Model: ${state.settings.model || "default"}`); return; }
258
258
  if (d.startsWith("e:")) { const e = d.slice(2); state.settings.effort = e === "default" ? null : e; await send(`Effort: ${state.settings.effort || "default"}`); return; }
259
259
  if (d.startsWith("b:")) { const b = d.slice(2); state.settings.budget = b === "none" ? null : parseFloat(b); await send(`Budget: ${state.settings.budget ? "$" + state.settings.budget : "none"}`); return; }
260
+ if (d.startsWith("cw:")) {
261
+ const v = d.slice(3);
262
+ if (v === "default") state.settings.compactWindow = null;
263
+ else if (v === "off") state.settings.compactWindow = 0;
264
+ else {
265
+ const n = parseInt(v, 10);
266
+ if (Number.isFinite(n) && n > 0) state.settings.compactWindow = n;
267
+ }
268
+ saveState();
269
+ const { formatCompactWindow } = require("./handlers");
270
+ await send(`Auto-compact window: ${formatCompactWindow(state)}`);
271
+ return;
272
+ }
260
273
  if (d.startsWith("be:")) {
261
274
  const be = d.slice(3);
262
275
  if (be === "cursor" && !resolvedCursorPath) { await send("Cursor Agent CLI not found."); return; }
package/core/handlers.js CHANGED
@@ -24,7 +24,7 @@ const { runDoctorChecks, formatDoctorReport } = require("./doctor");
24
24
  const jobs = require("./jobs");
25
25
  const scheduler = require("./scheduler");
26
26
  const {
27
- runClaude, compactActiveSession, getActiveSessionId,
27
+ runClaude, compactActiveSession, getActiveSessionId, effectiveCompactThreshold,
28
28
  } = require("./runner");
29
29
  const {
30
30
  getClaudeOAuthToken, claudeSubprocessEnv, saveClaudeOAuthToken,
@@ -119,7 +119,7 @@ register({
119
119
  if (!authorized(env)) return;
120
120
  send([
121
121
  "Session: /session /sessions /projects /continue /status /stop /end",
122
- "Settings: /model /effort /budget /plan /compact /worktree /mode",
122
+ "Settings: /model /effort /budget /plan /compact /compactwindow /worktree /mode",
123
123
  "Identity: /whoami /link",
124
124
  "Team: /people /intros /auth (owner)",
125
125
  "Automation: /cron /vault /soul",
@@ -671,6 +671,58 @@ register({
671
671
  },
672
672
  });
673
673
 
674
+ function parseCompactWindow(raw) {
675
+ const s = String(raw || "").trim().toLowerCase();
676
+ if (!s) return { value: undefined };
677
+ if (s === "default" || s === "auto" || s === "reset") return { value: null };
678
+ if (s === "off" || s === "disable" || s === "disabled") return { value: 0 };
679
+ const m = s.match(/^(\d+(?:\.\d+)?)\s*([km]?)$/);
680
+ if (!m) return { error: `Couldn't parse "${raw}". Use a number like 250000, 250k, off, or default.` };
681
+ let n = parseFloat(m[1]);
682
+ if (m[2] === "k") n *= 1000;
683
+ else if (m[2] === "m") n *= 1000000;
684
+ n = Math.round(n);
685
+ if (n === 0) return { value: 0 };
686
+ if (n < 10000) return { error: "Compaction window must be at least 10k tokens (or use 'off' to disable)." };
687
+ return { value: n };
688
+ }
689
+
690
+ function formatCompactWindow(state) {
691
+ const fmt = (n) => n >= 1000 ? (n / 1000).toFixed(0) + "k" : String(n);
692
+ const cw = state.settings?.compactWindow;
693
+ if (cw === 0) return "off";
694
+ if (cw == null) return `default (${fmt(AUTO_COMPACT_TOKENS)})`;
695
+ return fmt(cw);
696
+ }
697
+
698
+ register({
699
+ name: "compactwindow", aliases: ["autocompact"],
700
+ description: "Set auto-compact token threshold",
701
+ args: "[<tokens> | off | default]",
702
+ handler: async (env, { tail }) => {
703
+ if (!authorized(env)) return;
704
+ const state = currentState();
705
+ if (tail) {
706
+ const { value, error } = parseCompactWindow(tail);
707
+ if (error) return send(error);
708
+ state.settings.compactWindow = value;
709
+ saveState();
710
+ return send(`Auto-compact window: ${formatCompactWindow(state)}`);
711
+ }
712
+ send(
713
+ `Auto-compact window: ${formatCompactWindow(state)}\n\n` +
714
+ "When the last turn's input context exceeds this, the next reply triggers a compaction. " +
715
+ "Type /compactwindow <n> for a custom value (e.g. 250k).",
716
+ { keyboard: { inline_keyboard: [
717
+ [{ text: "200k", callback_data: "cw:200000" }, { text: "300k", callback_data: "cw:300000" }, { text: "380k", callback_data: "cw:380000" }, { text: "500k", callback_data: "cw:500000" }],
718
+ [{ text: "Off", callback_data: "cw:off" }, { text: "Default", callback_data: "cw:default" }],
719
+ ] } },
720
+ );
721
+ },
722
+ });
723
+
724
+ module.exports.formatCompactWindow = formatCompactWindow;
725
+
674
726
  register({
675
727
  name: "worktree", description: "Toggle isolated git branch",
676
728
  handler: async (env) => {
@@ -787,6 +839,7 @@ register({
787
839
  `Project: ${state.currentSession.name}`,
788
840
  `Backend: ${backendLabel}`,
789
841
  `Model: ${settings.model || "default"} | Effort: ${settings.effort || "default"}`,
842
+ `Auto-compact: ${formatCompactWindow(state)}`,
790
843
  `Vault: ${vault.isUnlocked() ? "unlocked" : "locked"} | Crons: ${activeCrons.size}`,
791
844
  state.runningProcess ? "Working..." : "Ready.",
792
845
  ].join("\n"));
@@ -825,7 +878,14 @@ register({
825
878
  `Cost: $${u.costUsd.toFixed(4)}`,
826
879
  `Last turn context: ${fmt(u.lastInputTokens)}`,
827
880
  ];
828
- if (u.lastInputTokens > 200000) lines.push(`\nTip: context is large. The bot auto-compacts after the next reply at ${fmt(AUTO_COMPACT_TOKENS)} tokens; /compact does it now.`);
881
+ if (u.lastInputTokens > 200000) {
882
+ const threshold = effectiveCompactThreshold(currentState());
883
+ if (threshold === 0) {
884
+ lines.push(`\nTip: context is large. Auto-compact is off (/compactwindow); /compact does it now.`);
885
+ } else {
886
+ lines.push(`\nTip: context is large. The bot auto-compacts after the next reply at ${fmt(threshold)} tokens (/compactwindow); /compact does it now.`);
887
+ }
888
+ }
829
889
  send(lines.join("\n"), { parseMode: "Markdown" });
830
890
  },
831
891
  });
package/core/runner.js CHANGED
@@ -87,10 +87,18 @@ function getActiveSessionKey(state = currentState()) {
87
87
  return "lastSessionId";
88
88
  }
89
89
 
90
+ function effectiveCompactThreshold(state = currentState()) {
91
+ const override = state.settings?.compactWindow;
92
+ if (override === 0) return 0;
93
+ if (Number.isFinite(override) && override > 0) return override;
94
+ return Number.isFinite(AUTO_COMPACT_TOKENS) ? AUTO_COMPACT_TOKENS : 380000;
95
+ }
96
+
90
97
  function shouldAutoCompact(state = currentState(), opts = {}) {
91
98
  if (opts.skipAutoCompact || opts.fresh || opts.continueSession || state.isCompacting) return false;
92
99
  if (!state[getActiveSessionKey(state)]) return false;
93
- const threshold = Number.isFinite(AUTO_COMPACT_TOKENS) ? AUTO_COMPACT_TOKENS : 380000;
100
+ const threshold = effectiveCompactThreshold(state);
101
+ if (threshold === 0) return false;
94
102
  if ((state.sessionUsage?.lastInputTokens || 0) < threshold) return false;
95
103
  const minInterval = Number.isFinite(MIN_COMPACT_INTERVAL_MS) ? MIN_COMPACT_INTERVAL_MS : 1800000;
96
104
  if (state.lastCompactedAt && (Date.now() - state.lastCompactedAt) < minInterval) return false;
@@ -757,6 +765,7 @@ module.exports = {
757
765
  getActiveBinary,
758
766
  getActiveSessionKey,
759
767
  shouldAutoCompact,
768
+ effectiveCompactThreshold,
760
769
  formatProgress,
761
770
  preflightClaudeAuthMessage,
762
771
  claudeEmptyFailureMessage,
package/core/state.js CHANGED
@@ -43,7 +43,7 @@ const savedState = (() => {
43
43
  const userStates = new Map();
44
44
 
45
45
  function freshSettings() {
46
- return { model: null, effort: null, budget: null, permissionMode: null, worktree: false, backend: "claude" };
46
+ return { model: null, effort: null, budget: null, permissionMode: null, worktree: false, backend: "claude", compactWindow: null };
47
47
  }
48
48
 
49
49
  function freshUsage() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.2.15",
3
+ "version": "2.2.16",
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": {