@inetafrica/open-claudia 3.5.12 → 3.5.13

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/.env.example CHANGED
@@ -63,6 +63,8 @@ OC_THREAD_TITLES=on
63
63
  THREAD_TITLE_PROVIDER=
64
64
  THREAD_TITLE_MODEL_CLAUDE=
65
65
  THREAD_TITLE_MODEL_CODEX=
66
+ # Token usage alerts to the owner: on | off (default off). Never posted in group chats.
67
+ USAGE_ALERTS=off
66
68
  USAGE_ALERT_CONTEXT_TOKENS=120000
67
69
  USAGE_ALERT_RATE_MULTIPLIER=1.75
68
70
  USAGE_ALERT_BASELINE_TURNS=20
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.5.13
4
+
5
+ Group chats stop seeing notes meant only for the owner, and token usage alerts are off by default. Canary first, on the sales bot.
6
+
7
+ - **Fix: no more junk "📌 standing rule" posts.** After every owner turn the rule detector scanned the whole prompt, including the group or thread messages the bot was shown, so it saved the bot's own lines as the owner's rules. Its own "📌 Got it — I'll keep this loaded as a standing rule from now on" note then set off the next one, one more after every owner message in a staff group (sales bot, 4 Oct; same cause as the 1 Oct thread report). Rules now come only from the owner's own words (the typed message, voice transcript or caption), and only in a direct chat with the bot: group chats never capture rules or see 📌 notes.
8
+ - **Token usage alerts are off by default** (Sumeet, 4 Oct: "turn off this token usage thing by default on all the bots"). `USAGE_ALERTS=on` brings them back, with the same `USAGE_ALERT_*` thresholds, and even then they never go to a group chat.
9
+ - **Tests:** `test-group-status-notes.js` (new).
10
+
3
11
  ## 3.5.12
4
12
 
5
13
  Two fixes: "No response requested." no longer reaches the chat, and voice notes wait out a busy speech server. Canary first, on the sales bot. (3.5.11 was tagged but never published: its release run failed on the new test, which assumed an agent CLI is installed; CI has none.)
package/README.md CHANGED
@@ -448,8 +448,9 @@ All stored in `~/.open-claudia/`:
448
448
  | `AGENT_ENV_PASSTHROUGH` | No | Comma-separated project environment keys to pass to provider children; reserved provider/control/keyring keys are always filtered and re-added only by the selected provider |
449
449
  | `CLAUDE_MODEL` / `CODEX_MODEL` | No | Optional default model for the matching provider |
450
450
  | `AUTO_COMPACT_TOKENS` | No | Auto-compact threshold in tokens (also settable via `/compactwindow`) |
451
- | `USAGE_ALERT_CONTEXT_TOKENS` | No | Alert when one completed turn's context tokens exceed this ceiling (default `120000`, `off` disables) |
452
- | `USAGE_ALERT_RATE_MULTIPLIER` | No | Alert when the latest context-token rate exceeds the recent baseline by this multiple (default `1.75`, `off` disables) |
451
+ | `USAGE_ALERTS` | No | Token usage alerts to the owner: `on` or `off` (default `off`). Never posted in group chats. The thresholds below apply only when on |
452
+ | `USAGE_ALERT_CONTEXT_TOKENS` | No | With alerts on: alert when one completed turn's context tokens exceed this ceiling (default `120000`, `off` disables) |
453
+ | `USAGE_ALERT_RATE_MULTIPLIER` | No | With alerts on: alert when the latest context-token rate exceeds the recent baseline by this multiple (default `1.75`, `off` disables) |
453
454
  | `USAGE_ALERT_BASELINE_TURNS` / `USAGE_ALERT_MIN_BASELINE_TURNS` / `USAGE_ALERT_COOLDOWN_MS` | No | Tune token-rate baseline size, minimum sample size, and alert cooldown |
454
455
  | `MEMORY_RECALL_MAX_CHARS` | No | Hard cap for auto-injected pack/entity memory per turn (default `9000`, `off` disables auto recall injection) |
455
456
  | `RECALL_ENGINE` | No | Default recall engine when a chat hasn't set one via `/engine` (`classic` or `discoverer`, default `discoverer`) |
package/core/runner.js CHANGED
@@ -234,10 +234,13 @@ function logTurnUsage(state, usage, costUsd, announce, opts = {}) {
234
234
  if (result.alert) {
235
235
  state.usageAlertLastAt = Date.now();
236
236
  // Owner-only: version/model/context internals must never surface to an
237
- // external speaker, regardless of the usage-alert policy.
237
+ // external speaker, regardless of the usage-alert policy. Never in a group
238
+ // chat either: everyone else in the room sees only noise.
238
239
  let usageOwnerOnly = true;
239
240
  try { usageOwnerOnly = !require("./relationship").isCurrentSpeakerGuarded(); } catch (e) {}
240
- if (announce && usageOwnerOnly) {
241
+ let inGroup = false;
242
+ try { inGroup = require("./context").currentIsGroup(); } catch (e) {}
243
+ if (announce && usageOwnerOnly && !inGroup) {
241
244
  const baseline = result.trend?.baselineAvgContextTokens || 0;
242
245
  const rate = result.trend?.latestRate || 0;
243
246
  const contextLabel = usageScope === "session_total" && !hasPeak ? "Session context total" : "Context this turn";
@@ -2231,16 +2234,27 @@ function nameThreadAfterTurn(result, runContext, store) {
2231
2234
  // detector spots durable-rule statements ("from now on / never / stop") in the
2232
2235
  // owner's message and captures them into lessons.md (or asks first). Owner-only
2233
2236
  // and best-effort: it never blocks or breaks a turn.
2237
+ //
2238
+ // Only the owner's own words count: store.inboundText (the typed message,
2239
+ // voice transcript or caption, set by admitGuardedTurn), never the prompt,
2240
+ // which also carries injected group/thread messages, quoted replies and the
2241
+ // bot's own earlier lines. Reading the prompt saved the bot's own "📌 ... from
2242
+ // now on" notes as the owner's rules, one more after every owner turn in a
2243
+ // group (sales bot, 4 Oct 2026). Group chats never capture: a rule note there
2244
+ // is noise for everyone else, so rules are set in a direct chat with the bot.
2234
2245
  async function considerGuardrail(result, runContext, store) {
2235
2246
  try {
2236
2247
  if (!result.ok || runContext.purpose !== "foreground") return;
2248
+ if (store?.isGroup) return;
2249
+ const ownWords = String(store?.inboundText || "").trim();
2250
+ if (!ownWords) return;
2237
2251
  const guardrail = require("./guardrail");
2238
2252
  if (!guardrail.enabled()) return;
2239
2253
  const relationship = require("./relationship");
2240
2254
  const speaker = relationship.speakerFor(store?.adapter?.type, store?.channelId, store?.userId);
2241
2255
  if (relationship.guardActive(speaker)) return; // external speakers never capture
2242
2256
  const announce = (text, opts) => chatContext.run(store, () => (statusChatterMuted() ? true : send(text, opts)));
2243
- await guardrail.consider({ text: runContext.userPrompt, speaker, announce });
2257
+ await guardrail.consider({ text: ownWords, speaker, announce });
2244
2258
  } catch (e) { /* best-effort — a guardrail miss must never break a turn */ }
2245
2259
  }
2246
2260
 
@@ -2718,4 +2732,6 @@ module.exports = {
2718
2732
  archiveCompactionBrief,
2719
2733
  collectRepoStateFacts,
2720
2734
  collectRecentTurns,
2735
+ considerGuardrail,
2736
+ logTurnUsage,
2721
2737
  };
package/core/usage-log.js CHANGED
@@ -56,11 +56,20 @@ function floatEnv(env, key, fallback) {
56
56
  return Number.isFinite(n) ? n : fallback;
57
57
  }
58
58
 
59
+ // Token usage alerts are an operator's tuning aid, so they are off unless
60
+ // USAGE_ALERTS=on (Sumeet, 4 Oct 2026: "turn off this token usage thing by
61
+ // default on all the bots", after one landed in a staff group). The
62
+ // thresholds below only apply once they are switched on.
63
+ function usageAlertsOn(env = {}) {
64
+ const raw = env.USAGE_ALERTS ?? process.env.USAGE_ALERTS;
65
+ return /^(on|true|yes|1)$/i.test(String(raw ?? "").trim());
66
+ }
67
+
59
68
  function usageAlertPolicy(env = {}) {
60
69
  const absoluteContextTokens = intEnv(env, "USAGE_ALERT_CONTEXT_TOKENS", 120000);
61
70
  const rateMultiplier = floatEnv(env, "USAGE_ALERT_RATE_MULTIPLIER", 1.75);
62
71
  return {
63
- enabled: absoluteContextTokens > 0 || rateMultiplier > 0,
72
+ enabled: usageAlertsOn(env) && (absoluteContextTokens > 0 || rateMultiplier > 0),
64
73
  absoluteContextTokens,
65
74
  rateMultiplier,
66
75
  baselineTurns: intEnv(env, "USAGE_ALERT_BASELINE_TURNS", 20),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.5.12",
3
+ "version": "3.5.13",
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": {
@@ -10,7 +10,7 @@
10
10
  "test:kazee-call": "node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
11
11
  "setup": "node setup.js",
12
12
  "start": "node bot.js",
13
- "pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-turn-observer.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-codex-device-login.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-upgrade-path.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-kazee-context-media.js && node test-kazee-typing-join.js && node test-kazee-inbound-dedup.js && node test-typing-phase-push.js && node test-socket-reconnect.js && node test-speech-loopback.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
13
+ "pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-turn-observer.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-codex-device-login.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-upgrade-path.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-kazee-context-media.js && node test-kazee-typing-join.js && node test-kazee-inbound-dedup.js && node test-typing-phase-push.js && node test-socket-reconnect.js && node test-speech-loopback.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-group-status-notes.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
14
14
  "test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && node test-codex-rollout.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-lanes.js && node test-thread-titles.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-stop.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stream-preview-purpose.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-attest.js && node test-sandbox-probe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-bot-endpoint.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cli-owner-gates.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-watch-check-env.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-grants.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-room-provider-settings.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-store-durability.js && node test-kazee-cards.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-placeholder-reply.js && node test-media-speech.js && node test-passive-replies-router.js && node test-loopback-cards.js"
15
15
  },
16
16
  "files": [
@@ -85,6 +85,7 @@
85
85
  "test-agency-ledger.js",
86
86
  "test-agency-deliberate.js",
87
87
  "test-guardrail.js",
88
+ "test-group-status-notes.js",
88
89
  "test-delivery-contract.js",
89
90
  "test-run-lock.js",
90
91
  "test-thread-lanes.js",
@@ -0,0 +1,140 @@
1
+ // Notes meant only for the owner stay out of group chats, and a standing rule
2
+ // comes only from the owner's own words. Sales bot, 4 Oct 2026: after each of
3
+ // Sumeet's turns in a staff group it posted "📌 Got it — I'll keep this loaded
4
+ // as a standing rule from now on" and saved its OWN previous 📌 line (shown to
5
+ // it as group context) as his rule, one more per turn; a "Token usage alert"
6
+ // landed in the same group. Token alerts are now off unless USAGE_ALERTS=on.
7
+ // No network, redirected config dir.
8
+
9
+ const assert = require("assert");
10
+ const fs = require("fs");
11
+ const os = require("os");
12
+ const path = require("path");
13
+
14
+ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "oc-group-notes-"));
15
+ process.env.OPEN_CLAUDIA_TEST = "1";
16
+ process.env.OPEN_CLAUDIA_CONFIG_DIR = TMP;
17
+ process.env.WORKSPACE = TMP;
18
+ process.env.GUARDRAIL_CAPTURE = "on";
19
+ delete process.env.USAGE_ALERTS;
20
+
21
+ const lessons = require("./core/lessons");
22
+ const guardrail = require("./core/guardrail");
23
+ const relationship = require("./core/relationship");
24
+ const { runInChat } = require("./core/context");
25
+ const { usageAlertPolicy, evaluateUsageAlert } = require("./core/usage-log");
26
+ const { considerGuardrail, logTurnUsage } = require("./core/runner");
27
+
28
+ // The speaker is the owner throughout: who may capture at all is
29
+ // test-guardrail.js's job, this file is about which words and which rooms.
30
+ relationship.speakerFor = () => ({ isOwner: true, channelId: "kazee:room" });
31
+ relationship.guardActive = () => false;
32
+ relationship.isCurrentSpeakerGuarded = () => false;
33
+
34
+ function resetLessons() {
35
+ try { fs.rmSync(lessons.LESSONS_FILE, { force: true }); } catch (e) {}
36
+ try { fs.rmSync(lessons.LESSONS_META_FILE, { force: true }); } catch (e) {}
37
+ try { fs.rmSync(guardrail.PENDING_FILE, { force: true }); } catch (e) {}
38
+ }
39
+
40
+ // A chat store as the router builds it: inboundText is what the person said
41
+ // (typed text, voice transcript or caption), isGroup marks a group room.
42
+ function chat({ isGroup, inboundText }) {
43
+ const sent = [];
44
+ const adapter = {
45
+ type: "kazee",
46
+ send: async (channelId, text) => { sent.push(text); return { ok: true, messageId: `m${sent.length}` }; },
47
+ };
48
+ return { sent, store: { adapter, channelId: "kazee:room", userId: "owner", isGroup, inboundText } };
49
+ }
50
+
51
+ const OK = { ok: true };
52
+ const turn = (userPrompt) => ({ purpose: "foreground", userPrompt });
53
+ const RULE = "From now on, always send the deep link after posting a Spaces comment.";
54
+ const PIN = "📌 Got it — I'll keep this loaded as a standing rule from now on";
55
+
56
+ (async () => {
57
+ // 1. The live case: a question in a group whose context starts with the
58
+ // bot's own last 📌 note. Nothing is saved and nothing is posted.
59
+ resetLessons();
60
+ let c = chat({ isGroup: true, inboundText: "How many leads did we get last month?" });
61
+ await considerGuardrail(OK, turn([
62
+ "[Group context] Messages since your last reply:",
63
+ ` [1d ago] You: ${PIN}`,
64
+ " [2m ago] Sumeet Rajpal: How many leads did we get last month?",
65
+ "",
66
+ "How many leads did we get last month?",
67
+ ].join("\n")), c.store);
68
+ assert.deepStrictEqual(lessons.readLessons(), [], "the bot's own 📌 line in the context is not the owner's rule");
69
+ assert.deepStrictEqual(c.sent, [], "no 📌 note in the group");
70
+
71
+ // 2. A direct thread with the bot quoting its earlier 📌 note: the owner's
72
+ // own words hold no rule, so nothing is saved.
73
+ resetLessons();
74
+ c = chat({ isGroup: false, inboundText: "And deals?" });
75
+ await considerGuardrail(OK, turn(`[Kazee thread context]\n [36s ago] You: ${PIN}\n\nAnd deals?`), c.store);
76
+ assert.deepStrictEqual(lessons.readLessons(), [], "a quoted bot line never becomes a rule");
77
+ assert.deepStrictEqual(c.sent, []);
78
+
79
+ // 3. The owner states a rule in a direct chat: saved, and the 📌 note goes
80
+ // to that chat with its Undo.
81
+ resetLessons();
82
+ c = chat({ isGroup: false, inboundText: RULE });
83
+ await considerGuardrail(OK, turn(`[Kazee thread context]\n [5m ago] You: Done.\n\n${RULE}`), c.store);
84
+ const saved = lessons.readLessons();
85
+ assert.strictEqual(saved.length, 1, "the owner's own rule is saved");
86
+ assert.ok(/deep link/i.test(saved[0].text), saved[0].text);
87
+ assert.strictEqual(c.sent.length, 1);
88
+ assert.ok(c.sent[0].startsWith("📌"), c.sent[0]);
89
+
90
+ // 4. The same words in a group: rules are set in a direct chat, so the
91
+ // group gets no note and nothing is saved.
92
+ resetLessons();
93
+ c = chat({ isGroup: true, inboundText: RULE });
94
+ await considerGuardrail(OK, turn(RULE), c.store);
95
+ assert.deepStrictEqual(lessons.readLessons(), [], "groups never capture");
96
+ assert.deepStrictEqual(c.sent, []);
97
+
98
+ // 5. A turn with none of the owner's words (wake-up, cron): the prompt alone
99
+ // never captures, even when it reads like a rule.
100
+ resetLessons();
101
+ c = chat({ isGroup: false, inboundText: "" });
102
+ await considerGuardrail(OK, turn(`[Scheduled wakeup "x" fired] ${RULE}`), c.store);
103
+ assert.deepStrictEqual(lessons.readLessons(), [], "no own words, no capture");
104
+
105
+ // Token usage alerts: off by default; thresholds alone do not switch them
106
+ // on; USAGE_ALERTS=on does, unless both thresholds are off.
107
+ const big = { contextTokens: 127758 };
108
+ assert.strictEqual(usageAlertPolicy({}).enabled, false, "alerts are off by default");
109
+ assert.strictEqual(evaluateUsageAlert([], big, usageAlertPolicy({})).alert, false);
110
+ assert.strictEqual(usageAlertPolicy({ USAGE_ALERT_CONTEXT_TOKENS: "120000" }).enabled, false);
111
+ assert.strictEqual(usageAlertPolicy({ USAGE_ALERTS: "on" }).enabled, true);
112
+ assert.strictEqual(evaluateUsageAlert([], big, usageAlertPolicy({ USAGE_ALERTS: "on" })).alert, true);
113
+ assert.strictEqual(usageAlertPolicy({
114
+ USAGE_ALERTS: "on", USAGE_ALERT_CONTEXT_TOKENS: "off", USAGE_ALERT_RATE_MULTIPLIER: "off",
115
+ }).enabled, false);
116
+
117
+ // The turn's usage log: a 127.8k-context turn announces nothing by default,
118
+ // announces once in a direct chat with USAGE_ALERTS=on, and never in a group.
119
+ const usage = { input_tokens: 1000, output_tokens: 200, cache_read_input_tokens: 126558 };
120
+ async function alertsFor({ isGroup }) {
121
+ const alerts = [];
122
+ const announce = async (text) => { alerts.push(text); };
123
+ const state = { userId: "owner", sessionUsage: {} };
124
+ const { store } = chat({ isGroup, inboundText: "And deals?" });
125
+ await runInChat({ ...store }, async () => logTurnUsage(state, usage, 0, announce, {
126
+ backend: "claude", model: "claude-test", sessionId: "s1", liveContextTokens: 127758,
127
+ }));
128
+ return alerts;
129
+ }
130
+ assert.deepStrictEqual(await alertsFor({ isGroup: false }), [], "off by default in a direct chat too");
131
+ process.env.USAGE_ALERTS = "on";
132
+ const direct = await alertsFor({ isGroup: false });
133
+ assert.strictEqual(direct.length, 1, "USAGE_ALERTS=on announces in a direct chat");
134
+ assert.ok(direct[0].startsWith("Token usage alert:"), direct[0]);
135
+ assert.deepStrictEqual(await alertsFor({ isGroup: true }), [], "never in a group");
136
+ delete process.env.USAGE_ALERTS;
137
+
138
+ console.log("group status notes: ok");
139
+ process.exit(0);
140
+ })().catch((error) => { console.error(error); process.exit(1); });
@@ -546,7 +546,14 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
546
546
  // the speech queue and say why they fail. 3.5.11 was tagged but never
547
547
  // published (its release run failed on a test), so the same release ships as
548
548
  // 3.5.12. Canary on the sales bot only.
549
- assert.strictEqual(pkg.version, "3.5.12", "release version must remain unchanged without explicit approval");
549
+ // 3.5.13 approved by Sumeet 2026-10-04 12:27 EAT ("Yes, fix and ship" on the card
550
+ // "Switch token alerts off on every bot, fix the junk 📌 posts, and ship 3.5.13
551
+ // to the sales bot?"), after asking at 11:44 EAT ("Can we turn of[f] this token
552
+ // usage thing by default on all the bots"): rules come only from the owner's own
553
+ // words in a direct chat, so group chats never get junk "standing rule" posts,
554
+ // and token usage alerts are off by default and never go to a group chat.
555
+ // Canary on the sales bot only.
556
+ assert.strictEqual(pkg.version, "3.5.13", "release version must remain unchanged without explicit approval");
550
557
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
551
558
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
552
559
  }