switchroom 0.16.47 → 0.17.0

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.
Files changed (106) hide show
  1. package/dist/agent-scheduler/index.js +3 -1
  2. package/dist/auth-broker/index.js +24 -8
  3. package/dist/cli/drive-write-pretool.mjs +0 -5
  4. package/dist/cli/notion-write-pretool.mjs +3 -1
  5. package/dist/cli/switchroom.js +1358 -1030
  6. package/dist/cli/ui/index.html +84 -12
  7. package/dist/host-control/main.js +53 -17
  8. package/dist/vault/approvals/kernel-server.js +4 -1
  9. package/dist/vault/broker/server.js +201 -56
  10. package/package.json +3 -3
  11. package/profiles/_base/cron-session.sh.hbs +1 -1
  12. package/profiles/_base/start.sh.hbs +54 -3
  13. package/skills/switchroom-architecture/telegram.md +8 -15
  14. package/skills/switchroom-cli/SKILL.md +4 -5
  15. package/skills/telegram-test-harness/SKILL.md +1 -1
  16. package/telegram-plugin/README.md +18 -29
  17. package/telegram-plugin/bridge/bridge.ts +1 -41
  18. package/telegram-plugin/bridge/tool-filter.ts +3 -4
  19. package/telegram-plugin/dist/bridge/bridge.js +8 -43
  20. package/telegram-plugin/dist/gateway/gateway.js +682 -773
  21. package/telegram-plugin/dist/server.js +8 -43
  22. package/telegram-plugin/gateway/busy-key-reaper.ts +113 -0
  23. package/telegram-plugin/gateway/disconnect-flush.ts +11 -0
  24. package/telegram-plugin/gateway/escalation-bridge-gate.ts +46 -0
  25. package/telegram-plugin/gateway/gate-parity-probe.ts +102 -0
  26. package/telegram-plugin/gateway/gateway.ts +518 -624
  27. package/telegram-plugin/gateway/inbound-delivery-confirm.ts +89 -7
  28. package/telegram-plugin/gateway/inbound-spool.ts +108 -10
  29. package/telegram-plugin/gateway/model-command.ts +51 -3
  30. package/telegram-plugin/gateway/pending-inbound-buffer.ts +26 -0
  31. package/telegram-plugin/gateway/represent-guard.ts +28 -11
  32. package/telegram-plugin/gateway/status-pin-store.ts +124 -45
  33. package/telegram-plugin/gateway/worker-feed-dispatch.ts +19 -0
  34. package/telegram-plugin/history.ts +5 -0
  35. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +1 -2
  36. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +9 -1
  37. package/telegram-plugin/registry/subagents-schema.ts +126 -1
  38. package/telegram-plugin/registry/turns-schema.ts +65 -1
  39. package/telegram-plugin/session-tail.ts +26 -4
  40. package/telegram-plugin/slot-banner-driver.ts +42 -2
  41. package/telegram-plugin/status-query-telemetry.ts +100 -0
  42. package/telegram-plugin/stream-reply-handler.ts +15 -16
  43. package/telegram-plugin/subagent-watcher.ts +182 -30
  44. package/telegram-plugin/tests/buffer-gate-broadened.test.ts +4 -10
  45. package/telegram-plugin/tests/busy-key-reaper.test.ts +191 -0
  46. package/telegram-plugin/tests/emission-authority-facade.test.ts +11 -17
  47. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +5 -26
  48. package/telegram-plugin/tests/escalation-bridge-gate.test.ts +38 -0
  49. package/telegram-plugin/tests/gate-parity-probe.test.ts +171 -0
  50. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +13 -0
  51. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +14 -11
  52. package/telegram-plugin/tests/inbound-delivery-confirm.test.ts +146 -0
  53. package/telegram-plugin/tests/inbound-spool.test.ts +143 -0
  54. package/telegram-plugin/tests/model-command.test.ts +54 -1
  55. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +5 -11
  56. package/telegram-plugin/tests/nested-worker-visibility-harness.test.ts +329 -0
  57. package/telegram-plugin/tests/pending-inbound-buffer.test.ts +53 -0
  58. package/telegram-plugin/tests/progress-update-redact.test.ts +99 -0
  59. package/telegram-plugin/tests/registry-turns.test.ts +67 -0
  60. package/telegram-plugin/tests/represent-guard.test.ts +42 -6
  61. package/telegram-plugin/tests/resume-inbound-builder.test.ts +1 -0
  62. package/telegram-plugin/tests/session-tail.test.ts +10 -1
  63. package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +246 -0
  64. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +0 -14
  65. package/telegram-plugin/tests/status-pin-store.test.ts +220 -5
  66. package/telegram-plugin/tests/status-query-telemetry.test.ts +115 -0
  67. package/telegram-plugin/tests/subagent-nested-dispatch.test.ts +209 -0
  68. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +37 -0
  69. package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +167 -0
  70. package/telegram-plugin/tests/subagent-watcher-env-thresholds.test.ts +46 -3
  71. package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +70 -0
  72. package/telegram-plugin/tests/tool-activity-summary.test.ts +16 -0
  73. package/telegram-plugin/tests/tool-filter.test.ts +1 -3
  74. package/telegram-plugin/tests/tool-label-pretool.test.ts +1 -4
  75. package/telegram-plugin/tests/turn-flush-safety.test.ts +222 -1
  76. package/telegram-plugin/tests/worker-activity-feed.test.ts +202 -9
  77. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +25 -0
  78. package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +295 -0
  79. package/telegram-plugin/tool-activity-summary.ts +19 -0
  80. package/telegram-plugin/turn-flush-safety.ts +16 -1
  81. package/telegram-plugin/uat/scenarios/jtbd-answer-pings.test.ts +8 -9
  82. package/telegram-plugin/uat/scenarios/jtbd-foreground-feed-visibility-dm.test.ts +1 -1
  83. package/telegram-plugin/uat/scenarios/jtbd-narration-intent-dm.test.ts +1 -1
  84. package/telegram-plugin/worker-activity-feed.ts +75 -15
  85. package/vendor/hindsight-memory/CHANGELOG.md +24 -0
  86. package/vendor/hindsight-memory/README.md +5 -0
  87. package/vendor/hindsight-memory/scripts/lib/client.py +31 -1
  88. package/vendor/hindsight-memory/scripts/lib/config.py +41 -2
  89. package/vendor/hindsight-memory/scripts/lib/content.py +4 -1
  90. package/vendor/hindsight-memory/scripts/lib/daemon.py +11 -2
  91. package/vendor/hindsight-memory/scripts/recall.py +74 -1
  92. package/vendor/hindsight-memory/scripts/retain.py +8 -1
  93. package/vendor/hindsight-memory/scripts/tests/test_config_client_casts.py +111 -0
  94. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +85 -1
  95. package/vendor/hindsight-memory/scripts/tests/test_recall_tag_filters.py +107 -0
  96. package/vendor/hindsight-memory/settings.json +4 -0
  97. package/vendor/hindsight-memory/tests/test_client.py +130 -0
  98. package/vendor/hindsight-memory/tests/test_config.py +47 -0
  99. package/vendor/hindsight-memory/tests/test_content.py +18 -0
  100. package/vendor/hindsight-memory/tests/test_hooks.py +62 -0
  101. package/telegram-plugin/gateway/error-envelope-card.ts +0 -64
  102. package/telegram-plugin/gateway/resolve-calling-subagent.ts +0 -78
  103. package/telegram-plugin/silent-reply.ts +0 -58
  104. package/telegram-plugin/tests/error-envelope-unlock-card.test.ts +0 -79
  105. package/telegram-plugin/tests/resolve-calling-subagent.test.ts +0 -269
  106. package/telegram-plugin/tests/silent-reply-guard.test.ts +0 -122
@@ -2392,8 +2392,20 @@
2392
2392
  return;
2393
2393
  }
2394
2394
 
2395
+ // Worst-first posture strip + sort — not-active (down) or auth-failed
2396
+ // agents are the ones that need attention; surface them first.
2397
+ const needsAttention = (a) => a.active !== 'active' || a.auth.authenticated === false;
2398
+ const attnAgents = agents.filter(needsAttention);
2399
+ const cleanAgents = agents.filter(a => !needsAttention(a));
2400
+ const posture = `
2401
+ <div class="ac-posture" style="grid-column:1/-1;margin:0 0 0.4rem;justify-content:flex-start">
2402
+ <span class="ac-stat attn"><span class="n">${attnAgents.length}</span><span class="l">need attention</span></span>
2403
+ <span class="ac-stat clean"><span class="n">${cleanAgents.length}</span><span class="l">running clean</span></span>
2404
+ </div>`;
2405
+ const sortedAgents = attnAgents.concat(cleanAgents);
2406
+
2395
2407
  container.className = 'agents-grid';
2396
- container.innerHTML = agents.map(a => `
2408
+ container.innerHTML = posture + sortedAgents.map(a => `
2397
2409
  <div class="agent-card" data-agent="${escapeHtml(a.name)}">
2398
2410
  <div class="card-header" onclick="toggleLogs('${escapeHtml(a.name)}')">
2399
2411
  <div class="status-dot ${statusClass(a)}" title="${escapeHtml(a.active)} · bridge heartbeat (not a claude-liveness signal)"></div>
@@ -2731,15 +2743,36 @@
2731
2743
  const updating = h.stale
2732
2744
  ? `<span style="color:var(--text-dim);font-size:.7rem;margin-left:.4rem">(updating…)</span>`
2733
2745
  : '';
2746
+
2747
+ // Worst-first posture strip — same "attention vs clean" pattern as
2748
+ // Fleet Health / Accounts. hostd counts as attention when the audit
2749
+ // log is missing/erroring OR any recent verb actually failed (not
2750
+ // the benign in-flight "started" state).
2751
+ const hostdHasFailure = !!(hd.recent && hd.recent.some(e =>
2752
+ e.result === 'error' || (e.exit_code != null && e.exit_code !== 0)));
2753
+ const services = [
2754
+ { key: 'broker', ok: !!b.reachable, name: 'auth-broker', dotOk: b.reachable,
2755
+ html: `<div class="agent-card"><div class="card-header" style="cursor:default">${dot(b.reachable)}<span class="agent-name">auth-broker</span></div><div style="padding:0 1.25rem 1rem">${brokerBody}</div></div>` },
2756
+ { key: 'hindsight', ok: hs.running !== false, name: 'hindsight', dotOk: hs.running,
2757
+ html: `<div class="agent-card"><div class="card-header" style="cursor:default">${dot(hs.running)}<span class="agent-name">hindsight</span></div><div style="padding:0 1.25rem 1rem">${hindsightBody}</div></div>` },
2758
+ ];
2759
+ const attnCount = services.filter(s => !s.ok).length + (hd.auditLogPresent && !hostdHasFailure ? 0 : 1);
2760
+ const cleanCount = services.length + 1 - attnCount;
2761
+ const posture = `
2762
+ <div class="ac-posture" style="margin:0 0 0.6rem;justify-content:flex-start">
2763
+ <span class="ac-stat attn"><span class="n">${attnCount}</span><span class="l">need attention</span></span>
2764
+ <span class="ac-stat clean"><span class="n">${cleanCount}</span><span class="l">clean</span></span>
2765
+ </div>`;
2766
+ // Sort the service cards worst-first (down services surface before
2767
+ // healthy ones) — the hostd card stays pinned below since it's a
2768
+ // wider table, not part of the two-up grid.
2769
+ const servicesSorted = services.slice().sort((a, b2) => (a.ok === b2.ok) ? 0 : a.ok ? 1 : -1);
2770
+
2734
2771
  container.innerHTML = `
2735
2772
  ${updating ? `<div style="margin-bottom:.4rem">System health ${updating}</div>` : ''}
2773
+ ${posture}
2736
2774
  <div class="agents-grid">
2737
- <div class="agent-card"><div class="card-header" style="cursor:default">
2738
- ${dot(b.reachable)}<span class="agent-name">auth-broker</span></div>
2739
- <div style="padding:0 1.25rem 1rem">${brokerBody}</div></div>
2740
- <div class="agent-card"><div class="card-header" style="cursor:default">
2741
- ${dot(hs.running)}<span class="agent-name">hindsight</span></div>
2742
- <div style="padding:0 1.25rem 1rem">${hindsightBody}</div></div>
2775
+ ${servicesSorted.map(s => s.html).join('')}
2743
2776
  </div>
2744
2777
  <div class="agent-card" style="margin-top:1rem"><div class="card-header" style="cursor:default">
2745
2778
  ${dot(hd.auditLogPresent)}<span class="agent-name">hostd — recent privileged verbs</span></div>
@@ -2848,6 +2881,25 @@
2848
2881
  const linear = data.linear || { configured: false, agents: [] };
2849
2882
  const agentNames = data.agentNames || [];
2850
2883
 
2884
+ // Worst-first: a config-only account (no live broker slot — never
2885
+ // actually connected) is the real "needs attention" signal here. A
2886
+ // brokerKnown account past its (short-lived) access-token expiry is
2887
+ // NOT broken — it auto-refreshes on next use via the refresh token —
2888
+ // so it must NOT be sorted/flagged as an attention item (matches the
2889
+ // "don't alarm on auto-refresh" fix in renderOAuthAccountCard above).
2890
+ const isConnBroken = (a) => a.brokerKnown === false;
2891
+ const worstFirst = (list) => list.slice().sort((a, b) => (isConnBroken(a) === isConnBroken(b)) ? 0 : isConnBroken(a) ? -1 : 1);
2892
+ const googleSorted = worstFirst(google);
2893
+ const microsoftSorted = worstFirst(microsoft);
2894
+ const oauthAttn = google.filter(isConnBroken).length + microsoft.filter(isConnBroken).length;
2895
+ const oauthClean = (google.length + microsoft.length) - oauthAttn;
2896
+ const posture = (google.length + microsoft.length) > 0
2897
+ ? `<div class="ac-posture" style="margin:0 0 1rem;justify-content:flex-start">
2898
+ <span class="ac-stat attn"><span class="n">${oauthAttn}</span><span class="l">need attention</span></span>
2899
+ <span class="ac-stat clean"><span class="n">${oauthClean}</span><span class="l">connected</span></span>
2900
+ </div>`
2901
+ : '';
2902
+
2851
2903
  // When a provider's fetch FAILED (not genuinely empty), its empty-state
2852
2904
  // must say "couldn't load", never "none configured" — otherwise a
2853
2905
  // transient blip reads as "you have no accounts".
@@ -2856,10 +2908,10 @@
2856
2908
  data.googleFailed
2857
2909
  ? "Couldn't load Google accounts — the data source was unreachable (retrying)."
2858
2910
  : 'No Google accounts. Add one under <code>google_accounts:</code> and run <code>switchroom auth google account add</code>.',
2859
- google.map(a => renderOAuthAccountCard(a, { showType: false, provider: 'google', agentNames })).join(''),
2911
+ googleSorted.map(a => renderOAuthAccountCard(a, { showType: false, provider: 'google', agentNames })).join(''),
2860
2912
  );
2861
2913
 
2862
- const msCards = microsoft.map(a => renderOAuthAccountCard(a, { showType: true, provider: 'microsoft', agentNames })).join('');
2914
+ const msCards = microsoftSorted.map(a => renderOAuthAccountCard(a, { showType: true, provider: 'microsoft', agentNames })).join('');
2863
2915
  const msEmpty = data.microsoftFailed
2864
2916
  ? "Couldn't load Microsoft accounts — the data source was unreachable (retrying)."
2865
2917
  : 'No Microsoft accounts yet — click <b>Connect a Microsoft account</b> above (or <code>/connect microsoft</code> from Telegram).';
@@ -2957,7 +3009,7 @@
2957
3009
  ? renderProblem(problemFor('connections-unreachable', {}))
2958
3010
  : '';
2959
3011
 
2960
- container.innerHTML = unreachableBanner + degradedBanner + googleSection + microsoftSection + notionSection + linearSection;
3012
+ container.innerHTML = unreachableBanner + degradedBanner + posture + googleSection + microsoftSection + notionSection + linearSection;
2961
3013
  }
2962
3014
 
2963
3015
  function renderSchedule(data) {
@@ -3050,7 +3102,27 @@
3050
3102
  </div>`;
3051
3103
  };
3052
3104
 
3053
- const cards = Object.keys(byAgent).sort().map(agent => {
3105
+ // Worst-first: an agent whose most recent fire for ANY of its crons
3106
+ // failed (non-zero exit) needs attention before agents whose fires
3107
+ // are all clean (or unfired). Posture strip mirrors the Fleet
3108
+ // Health / Accounts "need attention vs clean" pattern.
3109
+ const agentHasFailure = (agent) => {
3110
+ const agentFires = recent[agent] || [];
3111
+ return byAgent[agent].some(e => {
3112
+ const latest = agentFires.filter(f => f.promptKey === e.promptKey).slice(-1)[0];
3113
+ return latest && latest.exitCode !== 0;
3114
+ });
3115
+ };
3116
+ const agentNamesSorted = Object.keys(byAgent).sort();
3117
+ const attnAgentNames = agentNamesSorted.filter(agentHasFailure);
3118
+ const cleanAgentNames = agentNamesSorted.filter(a => !agentHasFailure(a));
3119
+ const posture = `
3120
+ <div class="ac-posture" style="margin:0 0 0.8rem;justify-content:flex-start">
3121
+ <span class="ac-stat attn"><span class="n">${attnAgentNames.length}</span><span class="l">need attention</span></span>
3122
+ <span class="ac-stat clean"><span class="n">${cleanAgentNames.length}</span><span class="l">clean</span></span>
3123
+ </div>`;
3124
+
3125
+ const cards = attnAgentNames.concat(cleanAgentNames).map(agent => {
3054
3126
  const agentFires = recent[agent] || [];
3055
3127
  const list = byAgent[agent].map(e => renderCron(e, agentFires)).join('');
3056
3128
  return `
@@ -3062,7 +3134,7 @@
3062
3134
  <div style="padding:.5rem 1.25rem 1rem">${list}</div>
3063
3135
  </div>`;
3064
3136
  }).join('');
3065
- container.innerHTML = degradedBanner + truncatedNote + cards;
3137
+ container.innerHTML = degradedBanner + truncatedNote + posture + cards;
3066
3138
  }
3067
3139
 
3068
3140
  function renderApprovals(data, grants) {
@@ -15903,7 +15903,9 @@ var SessionContinuitySchema = exports_external.object({
15903
15903
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
15904
15904
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
15905
15905
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
15906
- resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
15906
+ resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
15907
+ session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
15908
+ session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
15907
15909
  }).optional();
15908
15910
  var webhookDispatchRule = exports_external.object({
15909
15911
  description: exports_external.string().optional(),
@@ -17184,6 +17186,36 @@ var CONTAINER_DEFAULT_UTC_ZONES = new Set([
17184
17186
  "Universal"
17185
17187
  ]);
17186
17188
 
17189
+ // src/broker-common/peercred-path.ts
17190
+ function matchSocketName(socketPath, patterns, maxNameLen) {
17191
+ if (typeof socketPath !== "string" || socketPath.length === 0)
17192
+ return null;
17193
+ let name = null;
17194
+ for (const re of patterns) {
17195
+ const m = socketPath.match(re);
17196
+ if (m && typeof m[1] === "string") {
17197
+ name = m[1];
17198
+ break;
17199
+ }
17200
+ }
17201
+ if (name === null || name.length === 0)
17202
+ return null;
17203
+ if (maxNameLen !== undefined && name.length > maxNameLen)
17204
+ return null;
17205
+ return name;
17206
+ }
17207
+ function parseSocketIdentity(socketPath, spec) {
17208
+ const name = matchSocketName(socketPath, spec.patterns, spec.maxNameLen);
17209
+ if (name === null)
17210
+ return null;
17211
+ const operatorName = spec.operatorName ?? "operator";
17212
+ if (name === operatorName)
17213
+ return { kind: "operator" };
17214
+ if (spec.reservedNames.has(name))
17215
+ return null;
17216
+ return { kind: "agent", name };
17217
+ }
17218
+
17187
17219
  // src/vault/broker/peercred.ts
17188
17220
  var RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
17189
17221
  function isReservedAgentName(name) {
@@ -22179,17 +22211,10 @@ var SOCKET_PATH_CONTAINER_SUBDIR_RE = /^\/run\/switchroom\/hostd\/([a-zA-Z0-9][a
22179
22211
  var SOCKET_PATH_HOST_SUBDIR_RE = /\/\.switchroom\/hostd\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\/sock$/;
22180
22212
  var RESERVED_AGENT_NAMES2 = new Set(["operator", "hostd"]);
22181
22213
  function socketPathToIdentity(socketPath) {
22182
- if (typeof socketPath !== "string" || socketPath.length === 0)
22183
- return null;
22184
- const m = socketPath.match(SOCKET_PATH_CONTAINER_SUBDIR_RE) ?? socketPath.match(SOCKET_PATH_HOST_SUBDIR_RE);
22185
- if (!m)
22186
- return null;
22187
- const name = m[1];
22188
- if (name === "operator")
22189
- return { kind: "operator" };
22190
- if (RESERVED_AGENT_NAMES2.has(name))
22191
- return null;
22192
- return { kind: "agent", name };
22214
+ return parseSocketIdentity(socketPath, {
22215
+ patterns: [SOCKET_PATH_CONTAINER_SUBDIR_RE, SOCKET_PATH_HOST_SUBDIR_RE],
22216
+ reservedNames: RESERVED_AGENT_NAMES2
22217
+ });
22193
22218
  }
22194
22219
 
22195
22220
  // telegram-plugin/secret-detect/patterns.ts
@@ -22624,7 +22649,7 @@ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:f
22624
22649
  import { dirname as dirname4, join as join2 } from "node:path";
22625
22650
 
22626
22651
  // src/build-info.ts
22627
- var VERSION = "0.15.48";
22652
+ var VERSION = "0.17.0";
22628
22653
 
22629
22654
  // src/cli/resolve-version.ts
22630
22655
  function readPackageVersion() {
@@ -24478,19 +24503,30 @@ class HostdServer {
24478
24503
  const recoveryNote = recRes2.exit_code === 0 ? "rolled back successfully" : `rolled back but recovery reconcile also failed (exit ${recRes2.exit_code})`;
24479
24504
  await approval.finalize({
24480
24505
  outcome: "reconcile_failed_rolled_back",
24481
- detail: recoveryNote
24506
+ detail: `${recoveryNote}; reconcile stderr: ${tail(recRes.stderr, 512) || "(empty)"}`
24482
24507
  });
24483
- return this.reconcileFailedRolledBack(`reconcile exit ${recRes.exit_code}; ${recoveryNote}`, req, caller, started);
24508
+ return this.reconcileFailedRolledBack(`reconcile exit ${recRes.exit_code}; ${recoveryNote}`, req, caller, started, { primary: recRes, recovery: recRes2 });
24484
24509
  } finally {
24485
24510
  release();
24486
24511
  }
24487
24512
  }
24488
- reconcileFailedRolledBack(detail, req, caller, started) {
24513
+ reconcileFailedRolledBack(detail, req, caller, started, output) {
24489
24514
  const legacy = `E_RECONCILE_FAILED_ROLLED_BACK: ${detail}`;
24490
24515
  const built = err("E_RECONCILE_FAILED_ROLLED_BACK", "config write or reconcile failed; live file rolled back to snapshot").why(detail).fixOperatorAction("infra", [
24491
24516
  "inspect hostd logs + switchroom apply output to identify the underlying failure"
24492
24517
  ]).op("config_propose_edit").caller(caller.kind === "agent" ? "agent" : "operator").agentName(caller.kind === "agent" ? caller.name : undefined).build(req.request_id, Date.now() - started);
24493
- return { ...built, error: legacy };
24518
+ const resp = { ...built, error: legacy };
24519
+ if (output) {
24520
+ const recoveryStdout = output.recovery && output.recovery.exit_code !== 0 ? `
24521
+ --- recovery reconcile stdout ---
24522
+ ${output.recovery.stdout}` : "";
24523
+ const recoveryStderr = output.recovery && output.recovery.exit_code !== 0 ? `
24524
+ --- recovery reconcile stderr ---
24525
+ ${output.recovery.stderr}` : "";
24526
+ resp.stdout_tail = tail(output.primary.stdout + recoveryStdout);
24527
+ resp.stderr_tail = tail(output.primary.stderr + recoveryStderr);
24528
+ }
24529
+ return resp;
24494
24530
  }
24495
24531
  configApplyLock = Promise.resolve();
24496
24532
  async acquireConfigApplyLock() {
@@ -4151,7 +4151,9 @@ var init_schema = __esm(() => {
4151
4151
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
4152
4152
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
4153
4153
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
4154
- resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
4154
+ resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
4155
+ session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
4156
+ session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
4155
4157
  }).optional();
4156
4158
  webhookDispatchRule = exports_external.object({
4157
4159
  description: exports_external.string().optional(),
@@ -13250,6 +13252,7 @@ function openKernelDb(dbPath) {
13250
13252
  chmodSync(dbPath, 384);
13251
13253
  } catch {}
13252
13254
  db.run("PRAGMA journal_mode=WAL");
13255
+ db.run("PRAGMA busy_timeout=5000");
13253
13256
  db.run("PRAGMA foreign_keys=ON");
13254
13257
  migrateApprovalSchema(db);
13255
13258
  return db;