switchroom 0.16.46 → 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 (109) hide show
  1. package/dist/agent-scheduler/index.js +83 -81
  2. package/dist/auth-broker/index.js +104 -88
  3. package/dist/cli/autoaccept-poll.js +8 -8
  4. package/dist/cli/drive-write-pretool.mjs +10 -15
  5. package/dist/cli/notion-write-pretool.mjs +85 -83
  6. package/dist/cli/skill-validate-pretool.mjs +91 -91
  7. package/dist/cli/switchroom.js +1720 -1392
  8. package/dist/cli/ui/index.html +84 -12
  9. package/dist/host-control/main.js +209 -173
  10. package/dist/vault/approvals/kernel-server.js +86 -83
  11. package/dist/vault/broker/server.js +284 -139
  12. package/package.json +3 -3
  13. package/profiles/_base/cron-session.sh.hbs +1 -1
  14. package/profiles/_base/start.sh.hbs +54 -3
  15. package/skills/switchroom-architecture/telegram.md +8 -15
  16. package/skills/switchroom-cli/SKILL.md +4 -5
  17. package/skills/telegram-test-harness/SKILL.md +1 -1
  18. package/telegram-plugin/README.md +18 -29
  19. package/telegram-plugin/bridge/bridge.ts +1 -41
  20. package/telegram-plugin/bridge/tool-filter.ts +3 -4
  21. package/telegram-plugin/dist/bridge/bridge.js +120 -155
  22. package/telegram-plugin/dist/gateway/gateway.js +1127 -1029
  23. package/telegram-plugin/dist/server.js +168 -203
  24. package/telegram-plugin/gateway/busy-key-reaper.ts +113 -0
  25. package/telegram-plugin/gateway/disconnect-flush.ts +11 -0
  26. package/telegram-plugin/gateway/escalation-bridge-gate.ts +46 -0
  27. package/telegram-plugin/gateway/gate-parity-probe.ts +102 -0
  28. package/telegram-plugin/gateway/gateway.ts +566 -631
  29. package/telegram-plugin/gateway/inbound-delivery-confirm.ts +89 -7
  30. package/telegram-plugin/gateway/inbound-spool.ts +108 -10
  31. package/telegram-plugin/gateway/model-command.ts +51 -3
  32. package/telegram-plugin/gateway/pending-inbound-buffer.ts +26 -0
  33. package/telegram-plugin/gateway/represent-guard.ts +28 -11
  34. package/telegram-plugin/gateway/status-pin-store.ts +124 -45
  35. package/telegram-plugin/gateway/worker-feed-dispatch.ts +19 -0
  36. package/telegram-plugin/history.ts +5 -0
  37. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +1 -2
  38. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +9 -1
  39. package/telegram-plugin/registry/subagents-schema.ts +126 -1
  40. package/telegram-plugin/registry/turns-schema.ts +65 -1
  41. package/telegram-plugin/session-tail.ts +26 -4
  42. package/telegram-plugin/slot-banner-driver.ts +42 -2
  43. package/telegram-plugin/status-query-telemetry.ts +100 -0
  44. package/telegram-plugin/stream-reply-handler.ts +15 -16
  45. package/telegram-plugin/subagent-watcher.ts +182 -30
  46. package/telegram-plugin/tests/buffer-gate-broadened.test.ts +4 -10
  47. package/telegram-plugin/tests/busy-key-reaper.test.ts +191 -0
  48. package/telegram-plugin/tests/emission-authority-facade.test.ts +11 -17
  49. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +5 -26
  50. package/telegram-plugin/tests/escalation-bridge-gate.test.ts +38 -0
  51. package/telegram-plugin/tests/gate-parity-probe.test.ts +171 -0
  52. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +13 -0
  53. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +14 -11
  54. package/telegram-plugin/tests/inbound-delivery-confirm.test.ts +146 -0
  55. package/telegram-plugin/tests/inbound-spool.test.ts +143 -0
  56. package/telegram-plugin/tests/model-command.test.ts +54 -1
  57. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +5 -11
  58. package/telegram-plugin/tests/nested-worker-visibility-harness.test.ts +329 -0
  59. package/telegram-plugin/tests/pending-inbound-buffer.test.ts +53 -0
  60. package/telegram-plugin/tests/progress-update-redact.test.ts +99 -0
  61. package/telegram-plugin/tests/registry-turns.test.ts +67 -0
  62. package/telegram-plugin/tests/represent-guard.test.ts +42 -6
  63. package/telegram-plugin/tests/resume-inbound-builder.test.ts +1 -0
  64. package/telegram-plugin/tests/session-tail.test.ts +10 -1
  65. package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +246 -0
  66. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +0 -14
  67. package/telegram-plugin/tests/status-pin-store.test.ts +220 -5
  68. package/telegram-plugin/tests/status-query-telemetry.test.ts +115 -0
  69. package/telegram-plugin/tests/subagent-nested-dispatch.test.ts +209 -0
  70. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +37 -0
  71. package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +167 -0
  72. package/telegram-plugin/tests/subagent-watcher-env-thresholds.test.ts +46 -3
  73. package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +70 -0
  74. package/telegram-plugin/tests/tool-activity-summary.test.ts +16 -0
  75. package/telegram-plugin/tests/tool-filter.test.ts +1 -3
  76. package/telegram-plugin/tests/tool-label-pretool.test.ts +1 -4
  77. package/telegram-plugin/tests/turn-flush-safety.test.ts +222 -1
  78. package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +46 -0
  79. package/telegram-plugin/tests/worker-activity-feed.test.ts +202 -9
  80. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +25 -0
  81. package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +295 -0
  82. package/telegram-plugin/tool-activity-summary.ts +19 -0
  83. package/telegram-plugin/turn-flush-safety.ts +16 -1
  84. package/telegram-plugin/uat/scenarios/jtbd-answer-pings.test.ts +8 -9
  85. package/telegram-plugin/uat/scenarios/jtbd-foreground-feed-visibility-dm.test.ts +1 -1
  86. package/telegram-plugin/uat/scenarios/jtbd-narration-intent-dm.test.ts +1 -1
  87. package/telegram-plugin/worker-activity-feed.ts +75 -15
  88. package/vendor/hindsight-memory/CHANGELOG.md +24 -0
  89. package/vendor/hindsight-memory/README.md +5 -0
  90. package/vendor/hindsight-memory/scripts/lib/client.py +31 -1
  91. package/vendor/hindsight-memory/scripts/lib/config.py +41 -2
  92. package/vendor/hindsight-memory/scripts/lib/content.py +4 -1
  93. package/vendor/hindsight-memory/scripts/lib/daemon.py +11 -2
  94. package/vendor/hindsight-memory/scripts/recall.py +74 -1
  95. package/vendor/hindsight-memory/scripts/retain.py +8 -1
  96. package/vendor/hindsight-memory/scripts/tests/test_config_client_casts.py +111 -0
  97. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +85 -1
  98. package/vendor/hindsight-memory/scripts/tests/test_recall_tag_filters.py +107 -0
  99. package/vendor/hindsight-memory/settings.json +4 -0
  100. package/vendor/hindsight-memory/tests/test_client.py +130 -0
  101. package/vendor/hindsight-memory/tests/test_config.py +47 -0
  102. package/vendor/hindsight-memory/tests/test_content.py +18 -0
  103. package/vendor/hindsight-memory/tests/test_hooks.py +62 -0
  104. package/telegram-plugin/gateway/error-envelope-card.ts +0 -64
  105. package/telegram-plugin/gateway/resolve-calling-subagent.ts +0 -78
  106. package/telegram-plugin/silent-reply.ts +0 -58
  107. package/telegram-plugin/tests/error-envelope-unlock-card.test.ts +0 -79
  108. package/telegram-plugin/tests/resolve-calling-subagent.test.ts +0 -269
  109. 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) {