switchroom 0.15.41 → 0.15.43

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.
@@ -11114,7 +11114,8 @@ var SubagentSchema = exports_external.object({
11114
11114
  var SessionSchema = exports_external.object({
11115
11115
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
11116
11116
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
11117
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
11117
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
11118
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
11118
11119
  }).optional();
11119
11120
  var SessionContinuitySchema = exports_external.object({
11120
11121
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -11114,7 +11114,8 @@ var SubagentSchema = exports_external.object({
11114
11114
  var SessionSchema = exports_external.object({
11115
11115
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
11116
11116
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
11117
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
11117
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
11118
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
11118
11119
  }).optional();
11119
11120
  var SessionContinuitySchema = exports_external.object({
11120
11121
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -11862,7 +11862,8 @@ var SubagentSchema = exports_external.object({
11862
11862
  var SessionSchema = exports_external.object({
11863
11863
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
11864
11864
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
11865
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
11865
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
11866
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
11866
11867
  }).optional();
11867
11868
  var SessionContinuitySchema = exports_external.object({
11868
11869
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -13678,7 +13678,8 @@ var init_schema = __esm(() => {
13678
13678
  SessionSchema = exports_external.object({
13679
13679
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
13680
13680
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
13681
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
13681
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
13682
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
13682
13683
  }).optional();
13683
13684
  SessionContinuitySchema = exports_external.object({
13684
13685
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -31678,6 +31679,46 @@ async function runMcpSecretChecks(config, deps = {}) {
31678
31679
  return results;
31679
31680
  }
31680
31681
 
31682
+ // src/cli/doctor-context.ts
31683
+ function fmtK(n) {
31684
+ return n >= 1000 ? `${(n / 1000).toFixed(n >= 1e4 ? 0 : 1)}k` : `${n}`;
31685
+ }
31686
+ function contextRow(agent, read) {
31687
+ const name = `context:${agent}`;
31688
+ if (read.kind === "absent") {
31689
+ return { name, status: "skip", detail: "no snapshot yet (agent idle since boot, or stopped)" };
31690
+ }
31691
+ if (read.kind === "unreadable") {
31692
+ return { name, status: "skip", detail: read.msg };
31693
+ }
31694
+ const s = read.snapshot;
31695
+ if (s.state === "unknown") {
31696
+ return { name, status: "skip", detail: "occupancy not yet measured" };
31697
+ }
31698
+ if (s.cap == null || s.pct == null) {
31699
+ return { name, status: "ok", detail: `${fmtK(s.occupancy)} tok (no cap \u2014 native compaction)` };
31700
+ }
31701
+ const pctLabel = `${Math.round(s.pct * 100)}%`;
31702
+ const detail = `${fmtK(s.occupancy)}/${fmtK(s.cap)} (${pctLabel})`;
31703
+ if (s.state === "tight") {
31704
+ return {
31705
+ name,
31706
+ status: "warn",
31707
+ detail: `${detail} \u2014 tight; a /compact is near`,
31708
+ fix: `Working context is ${pctLabel} of the cap. Normal if mid-large-task (it compacts automatically); if persistent, consider raising session.max_context_tokens or trimming this agent's tool/prose surface.`
31709
+ };
31710
+ }
31711
+ return { name, status: "ok", detail };
31712
+ }
31713
+ async function runContextChecks(config, deps = {}) {
31714
+ const agents = Object.keys(config.agents ?? {});
31715
+ if (agents.length === 0)
31716
+ return [];
31717
+ const read = deps.readSnapshot ?? (async () => ({ kind: "absent" }));
31718
+ const results = await Promise.all(agents.sort().map(async (a) => contextRow(a, await read(a))));
31719
+ return results;
31720
+ }
31721
+
31681
31722
  // src/cli/doctor-credentials-migration.ts
31682
31723
  import {
31683
31724
  existsSync as realExistsSync5,
@@ -34082,6 +34123,24 @@ function registerDoctorCommand(program3) {
34082
34123
  title: "MCP Connections (auth)",
34083
34124
  results: await runMcpSecretChecks(config, { vaultAclReader })
34084
34125
  },
34126
+ {
34127
+ title: "Context Headroom",
34128
+ results: await runContextChecks(config, {
34129
+ readSnapshot: async (agent) => {
34130
+ try {
34131
+ const { execFileSync: execFileSync19 } = await import("node:child_process");
34132
+ const out = execFileSync19("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/context-occupancy.json"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 });
34133
+ return { kind: "ok", snapshot: JSON.parse(out) };
34134
+ } catch (err) {
34135
+ const msg = err.message ?? String(err);
34136
+ if (/No such|cannot|not running|exited|No such container/i.test(msg)) {
34137
+ return { kind: "absent" };
34138
+ }
34139
+ return { kind: "unreadable", msg: `snapshot read failed: ${msg}` };
34140
+ }
34141
+ }
34142
+ })
34143
+ },
34085
34144
  { title: "MFF Skill", results: await checkMff(passphrase, vaultPath, config) },
34086
34145
  { title: "Webkite", results: runWebkiteChecks(config) },
34087
34146
  { title: "Cron Session", results: runCronSessionChecks(config) }
@@ -50708,8 +50767,8 @@ import { existsSync, readFileSync } from "node:fs";
50708
50767
  import { dirname, join } from "node:path";
50709
50768
 
50710
50769
  // src/build-info.ts
50711
- var VERSION = "0.15.41";
50712
- var COMMIT_SHA = "01f41098";
50770
+ var VERSION = "0.15.43";
50771
+ var COMMIT_SHA = "5480a4c3";
50713
50772
 
50714
50773
  // src/cli/resolve-version.ts
50715
50774
  function readPackageVersion() {
@@ -68833,6 +68892,9 @@ var HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true";
68833
68892
  var HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150;
68834
68893
  var HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4;
68835
68894
  var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
68895
+ var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
68896
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12;
68897
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 3;
68836
68898
  var HINDSIGHT_DEFAULT_MEM_LIMIT = "4g";
68837
68899
  var HINDSIGHT_DEFAULT_MEM_RESERVATION = "2g";
68838
68900
  var HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000;
@@ -68917,7 +68979,13 @@ function startHindsight(ports) {
68917
68979
  "-e",
68918
68980
  `HINDSIGHT_API_RERANKER_LOCAL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT}`,
68919
68981
  "-e",
68920
- `HINDSIGHT_API_RECALL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT}`
68982
+ `HINDSIGHT_API_RECALL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT}`,
68983
+ "-e",
68984
+ `HINDSIGHT_API_REFLECT_WALL_TIMEOUT=${HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S}`,
68985
+ "-e",
68986
+ `HINDSIGHT_API_CONSOLIDATION_LLM_BATCH_SIZE=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE}`,
68987
+ "-e",
68988
+ `HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS}`
68921
68989
  ];
68922
68990
  const args = [
68923
68991
  "run",
@@ -68965,6 +69033,29 @@ function stopHindsight() {
68965
69033
  execFileSync16("docker", ["rm", "switchroom-hindsight"], { stdio: "pipe" });
68966
69034
  } catch {}
68967
69035
  }
69036
+ function pullHindsightImage() {
69037
+ execFileSync16("docker", ["pull", HINDSIGHT_IMAGE], { stdio: "inherit" });
69038
+ }
69039
+ function getRunningHindsightPorts() {
69040
+ const readPort = (containerPort) => {
69041
+ try {
69042
+ const out = execFileSync16("docker", ["port", "switchroom-hindsight", `${containerPort}/tcp`], { stdio: "pipe", encoding: "utf-8" });
69043
+ const m = out.split(`
69044
+ `).map((l) => l.trim()).find((l) => l.length > 0);
69045
+ if (!m)
69046
+ return null;
69047
+ const port = Number(m.slice(m.lastIndexOf(":") + 1));
69048
+ return Number.isInteger(port) && port > 0 ? port : null;
69049
+ } catch {
69050
+ return null;
69051
+ }
69052
+ };
69053
+ const apiPort = readPort(HINDSIGHT_DEFAULT_API_PORT);
69054
+ if (apiPort === null)
69055
+ return null;
69056
+ const uiPort = readPort(HINDSIGHT_DEFAULT_UI_PORT) ?? (apiPort === HINDSIGHT_DEFAULT_API_PORT ? HINDSIGHT_DEFAULT_UI_PORT : 19999);
69057
+ return { apiPort, uiPort };
69058
+ }
68968
69059
  function getHindsightStatus() {
68969
69060
  try {
68970
69061
  const output = execFileSync16("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
@@ -68992,6 +69083,9 @@ function generateHindsightComposeSnippet() {
68992
69083
  ` - HINDSIGHT_API_RERANKER_MAX_CANDIDATES=${HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES}`,
68993
69084
  ` - HINDSIGHT_API_RERANKER_LOCAL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT}`,
68994
69085
  ` - HINDSIGHT_API_RECALL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT}`,
69086
+ ` - HINDSIGHT_API_REFLECT_WALL_TIMEOUT=${HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S}`,
69087
+ ` - HINDSIGHT_API_CONSOLIDATION_LLM_BATCH_SIZE=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE}`,
69088
+ ` - HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS}`,
68995
69089
  ` mem_limit: ${HINDSIGHT_DEFAULT_MEM_LIMIT}`,
68996
69090
  ` mem_reservation: ${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
68997
69091
  ` pids_limit: ${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
@@ -69195,7 +69289,7 @@ Cross-agent reflection plan
69195
69289
  }
69196
69290
  console.log();
69197
69291
  }));
69198
- memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").action(async (opts) => {
69292
+ memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current.").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").action(async (opts) => {
69199
69293
  if (opts.status) {
69200
69294
  if (!isDockerAvailable()) {
69201
69295
  console.log(source_default.red(" Docker is not available."));
@@ -69236,24 +69330,41 @@ Cross-agent reflection plan
69236
69330
  `));
69237
69331
  process.exit(1);
69238
69332
  }
69239
- if (isHindsightRunning()) {
69333
+ const recreate = opts.recreate === true;
69334
+ const reusePorts = recreate ? getRunningHindsightPorts() : null;
69335
+ if (isHindsightRunning() && !recreate) {
69240
69336
  console.log(source_default.green(`
69241
69337
  Hindsight container is already running (switchroom-hindsight).
69242
69338
  `));
69243
69339
  return;
69244
69340
  }
69341
+ if (recreate) {
69342
+ console.log(source_default.gray(" Pulling latest Hindsight image..."));
69343
+ try {
69344
+ pullHindsightImage();
69345
+ } catch (err) {
69346
+ console.error(source_default.red(`
69347
+ Failed to pull Hindsight image: ${err.message}
69348
+ `));
69349
+ process.exit(1);
69350
+ }
69351
+ }
69245
69352
  if (isHindsightContainerExists()) {
69246
- console.log(source_default.gray(" Removing stopped switchroom-hindsight container..."));
69353
+ console.log(source_default.gray(" Removing existing switchroom-hindsight container..."));
69247
69354
  stopHindsight();
69248
69355
  }
69249
69356
  let ports;
69250
- try {
69251
- ports = await pickHindsightPorts();
69252
- } catch (err) {
69253
- console.error(source_default.red(`
69357
+ if (reusePorts) {
69358
+ ports = reusePorts;
69359
+ } else {
69360
+ try {
69361
+ ports = await pickHindsightPorts();
69362
+ } catch (err) {
69363
+ console.error(source_default.red(`
69254
69364
  ${err.message}
69255
69365
  `));
69256
- process.exit(1);
69366
+ process.exit(1);
69367
+ }
69257
69368
  }
69258
69369
  if (ports.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
69259
69370
  console.log(source_default.yellow(` Port ${HINDSIGHT_DEFAULT_API_PORT} is already in use; ` + `using ${ports.apiPort}/${ports.uiPort} instead.`));
@@ -74629,6 +74740,17 @@ var DASHBOARD_CACHE_TTL = {
74629
74740
  approvals: 15000,
74630
74741
  grants: 15000
74631
74742
  };
74743
+ function readContextOccupancy(agentsDir, name) {
74744
+ try {
74745
+ const raw = readFileSync44(resolve28(agentsDir, name, "context-occupancy.json"), "utf-8");
74746
+ const s = JSON.parse(raw);
74747
+ if (typeof s?.occupancy !== "number")
74748
+ return null;
74749
+ return s;
74750
+ } catch {
74751
+ return null;
74752
+ }
74753
+ }
74632
74754
  function readLastTurnAt(agentsDir, name) {
74633
74755
  try {
74634
74756
  const db = openTurnsDb(resolve28(agentsDir, name));
@@ -74663,6 +74785,7 @@ async function cachedFleetStatus(now, fetchImpl = fetchFleetStatusViaHostd) {
74663
74785
  }
74664
74786
  async function handleGetAgents(config, deps = {}) {
74665
74787
  const readLastTurn = deps.readLastTurn ?? readLastTurnAt;
74788
+ const readContext = deps.readContext ?? readContextOccupancy;
74666
74789
  const statuses = getAllAgentStatuses(config);
74667
74790
  const authStatuses = getAllAuthStatuses(config);
74668
74791
  const agentsDir = resolveAgentsDir(config);
@@ -74695,7 +74818,8 @@ async function handleGetAgents(config, deps = {}) {
74695
74818
  expiresAt: auth?.expiresAt
74696
74819
  },
74697
74820
  memoryCollection: collection,
74698
- lastTurnAt: readLastTurn(agentsDir, name)
74821
+ lastTurnAt: readLastTurn(agentsDir, name),
74822
+ context: readContext(agentsDir, name)
74699
74823
  });
74700
74824
  }
74701
74825
  return agents;
@@ -78047,6 +78171,26 @@ function planUpdate(opts) {
78047
78171
  throw new Error("switchroom webd install failed");
78048
78172
  }
78049
78173
  });
78174
+ let memoryBackendHindsight;
78175
+ if (typeof opts.memoryBackendHindsight === "boolean") {
78176
+ memoryBackendHindsight = opts.memoryBackendHindsight;
78177
+ } else {
78178
+ try {
78179
+ memoryBackendHindsight = loadConfig().memory?.backend === "hindsight";
78180
+ } catch {
78181
+ memoryBackendHindsight = false;
78182
+ }
78183
+ }
78184
+ steps.push({
78185
+ name: "refresh-hindsight",
78186
+ description: "switchroom memory setup --recreate \u2014 pull latest hindsight image + recreate the memory singleton (standalone container, reusing its current port)",
78187
+ skipReason: !memoryBackendHindsight ? "memory.backend is not hindsight \u2014 no hindsight singleton to refresh" : opts.skipImages ? "--skip-images flag set" : undefined,
78188
+ run: () => {
78189
+ const r = runner(process.execPath, [scriptPath, "memory", "setup", "--recreate"]);
78190
+ if (r.status !== 0)
78191
+ throw new Error("switchroom memory setup --recreate failed");
78192
+ }
78193
+ });
78050
78194
  steps.push({
78051
78195
  name: "sync-bundled-skills",
78052
78196
  description: "Sync shipped skills/ to ~/.switchroom/skills/_bundled/ (host-stable pool dir).",
@@ -122,6 +122,15 @@
122
122
 
123
123
  .meta-item span { color: var(--text); }
124
124
 
125
+ /* Context-headroom gauge (RFC context-headroom-surface). */
126
+ .ctx-gauge { display: inline-flex; align-items: center; gap: 0.4rem; }
127
+ .ctx-text { color: var(--text); font-variant-numeric: tabular-nums; }
128
+ .ctx-bar {
129
+ display: inline-block; width: 48px; height: 6px; border-radius: 3px;
130
+ background: var(--bg-dim, rgba(255,255,255,0.12)); overflow: hidden;
131
+ }
132
+ .ctx-fill { display: block; height: 100%; border-radius: 3px; }
133
+
125
134
  .scope-toggle {
126
135
  cursor: pointer;
127
136
  color: var(--text);
@@ -1659,6 +1668,27 @@
1659
1668
  return 'inactive';
1660
1669
  }
1661
1670
 
1671
+ // Context-headroom gauge (RFC context-headroom-surface): occupancy/cap
1672
+ // with a fill bar, amber when tight (≥80%). '—' when no snapshot yet.
1673
+ function fmtTok(n) {
1674
+ return n >= 1000 ? (n / 1000).toFixed(n >= 10000 ? 0 : 1) + 'k' : String(n);
1675
+ }
1676
+ function formatContext(ctx) {
1677
+ if (!ctx || ctx.state === 'unknown' || typeof ctx.occupancy !== 'number') {
1678
+ return '<span style="color:var(--text-dim)">—</span>';
1679
+ }
1680
+ if (ctx.cap == null || ctx.pct == null) {
1681
+ return `<span>${fmtTok(ctx.occupancy)} <span style="color:var(--text-dim)">(no cap)</span></span>`;
1682
+ }
1683
+ const pct = Math.max(0, Math.min(1, ctx.pct));
1684
+ const tight = ctx.state === 'tight';
1685
+ const col = tight ? 'var(--amber, #d99)' : 'var(--green)';
1686
+ return `<span class="ctx-gauge" title="${fmtTok(ctx.occupancy)} / ${fmtTok(ctx.cap)} tokens — ${Math.round(pct * 100)}% of cap${tight ? ' (tight — /compact near)' : ''}">`
1687
+ + `<span class="ctx-text">${fmtTok(ctx.occupancy)}/${fmtTok(ctx.cap)} (${Math.round(pct * 100)}%)</span>`
1688
+ + `<span class="ctx-bar"><span class="ctx-fill" style="width:${(pct * 100).toFixed(0)}%;background:${col}"></span></span>`
1689
+ + `</span>`;
1690
+ }
1691
+
1662
1692
  function formatUptime(timestamp) {
1663
1693
  if (!timestamp) return '--';
1664
1694
  const d = new Date(timestamp);
@@ -1693,6 +1723,7 @@
1693
1723
  <div class="meta-item"><label>Uptime </label><span>${formatUptime(a.uptime)}</span></div>
1694
1724
  <div class="meta-item"><label>Mem </label><span>${a.memory || '--'}</span></div>
1695
1725
  <div class="meta-item"><label>Last turn </label><span>${a.lastTurnAt ? formatTimestamp(a.lastTurnAt) : '—'}</span></div>
1726
+ <div class="meta-item meta-context"><label>Context </label>${formatContext(a.context)}</div>
1696
1727
  <div class="meta-item"><label>Profile </label><span>${escapeHtml(a.extends)}</span></div>
1697
1728
  <div class="meta-item"><label>Auth </label><span>${a.auth.authenticated ? '✓' : '✗'}</span></div>
1698
1729
  <div class="meta-item"><label>Account </label><span>${a.primaryAccount ? escapeHtml(a.primaryAccount) : '<span style="color:var(--text-dim)">default</span>'}</span></div>
@@ -13849,7 +13849,8 @@ var SubagentSchema = exports_external.object({
13849
13849
  var SessionSchema = exports_external.object({
13850
13850
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
13851
13851
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
13852
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
13852
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
13853
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
13853
13854
  }).optional();
13854
13855
  var SessionContinuitySchema = exports_external.object({
13855
13856
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -11457,7 +11457,8 @@ var init_schema = __esm(() => {
11457
11457
  SessionSchema = exports_external.object({
11458
11458
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
11459
11459
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
11460
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
11460
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
11461
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
11461
11462
  }).optional();
11462
11463
  SessionContinuitySchema = exports_external.object({
11463
11464
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -11457,7 +11457,8 @@ var init_schema = __esm(() => {
11457
11457
  SessionSchema = exports_external.object({
11458
11458
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
11459
11459
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
11460
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
11460
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
11461
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
11461
11462
  }).optional();
11462
11463
  SessionContinuitySchema = exports_external.object({
11463
11464
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.15.41",
3
+ "version": "0.15.43",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {