switchroom 0.19.18 → 0.19.19

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 (38) hide show
  1. package/dist/agent-scheduler/index.js +2 -1
  2. package/dist/auth-broker/index.js +3 -1
  3. package/dist/cli/drive-write-pretool.mjs +48 -5
  4. package/dist/cli/ms-365-write-pretool.mjs +40 -2
  5. package/dist/cli/notion-write-pretool.mjs +2 -1
  6. package/dist/cli/switchroom.js +3392 -1569
  7. package/dist/host-control/main.js +12209 -11396
  8. package/dist/vault/approvals/kernel-server.js +60 -7
  9. package/dist/vault/broker/server.js +206 -76
  10. package/package.json +4 -3
  11. package/profiles/_base/start.sh.hbs +61 -1
  12. package/telegram-plugin/bridge/bridge.ts +14 -0
  13. package/telegram-plugin/dist/bridge/bridge.js +13 -0
  14. package/telegram-plugin/dist/gateway/gateway.js +1644 -1044
  15. package/telegram-plugin/dist/server.js +13 -0
  16. package/telegram-plugin/gateway/always-allow-persist-queue.ts +97 -11
  17. package/telegram-plugin/gateway/missed-approvals-store.ts +66 -17
  18. package/telegram-plugin/gateway/pending-card-store.ts +46 -16
  19. package/telegram-plugin/gateway/scoped-grant-store.ts +39 -14
  20. package/telegram-plugin/gateway/store-file.ts +244 -0
  21. package/telegram-plugin/hooks/tool-label-pretool.mjs +88 -2
  22. package/telegram-plugin/tests/bridge-tool-parity.test.ts +95 -0
  23. package/telegram-plugin/tests/store-atomic-write.test.ts +411 -0
  24. package/telegram-plugin/tests/tool-activity-summary.test.ts +9 -2
  25. package/telegram-plugin/tests/tool-label-pretool.test.ts +94 -0
  26. package/telegram-plugin/tests/worker-feed-repeat-steps.test.ts +147 -0
  27. package/telegram-plugin/worker-activity-feed.ts +51 -1
  28. package/vendor/hindsight-memory/scripts/drain_pending.py +668 -56
  29. package/vendor/hindsight-memory/scripts/lib/client.py +124 -0
  30. package/vendor/hindsight-memory/scripts/lib/pending.py +865 -33
  31. package/vendor/hindsight-memory/scripts/lib/retain_split.py +449 -0
  32. package/vendor/hindsight-memory/scripts/session_start.py +48 -0
  33. package/vendor/hindsight-memory/scripts/tests/test_client_document_exists.py +470 -0
  34. package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +2121 -0
  35. package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +430 -0
  36. package/vendor/hindsight-memory/scripts/tests/test_session_start_version_skew.py +204 -0
  37. package/vendor/hindsight-memory/tests/test_drain_pending.py +102 -6
  38. package/vendor/hindsight-memory/tests/test_pending.py +32 -7
@@ -142,6 +142,25 @@ export interface BotApiForWorkerFeed {
142
142
  /** Dispatch-time task description cap for the worker header. */
143
143
  const DESC_MAX = 80
144
144
 
145
+ /**
146
+ * Repeat marker appended to a narrative step that fired more than once in a
147
+ * row (`Running a command ·×3`). Rendered inline on the step line so a worker
148
+ * whose every step carries the SAME label still visibly advances instead of
149
+ * freezing the card. `·` is already the card's separator glyph.
150
+ */
151
+ const REPEAT_SUFFIX_RE = / ·×(\d+)$/
152
+
153
+ /** The step text without its `·×N` marker (identity for dedup comparisons). */
154
+ export function stripRepeatSuffix(line: string): string {
155
+ return line.replace(REPEAT_SUFFIX_RE, '')
156
+ }
157
+
158
+ /** How many times a step line has fired: 1 when it carries no marker. */
159
+ export function repeatCountOf(line: string): number {
160
+ const m = REPEAT_SUFFIX_RE.exec(line)
161
+ return m == null ? 1 : Number(m[1])
162
+ }
163
+
145
164
  /**
146
165
  * Thin adapter over the unified `renderStatusCard` primitive (emoji 🛠, label
147
166
  * 'Worker'): builds the header, passes raw narrative steps (the primitive runs
@@ -428,6 +447,13 @@ interface WorkerRow {
428
447
  * live render so the feed reads like the main agent's answer.
429
448
  */
430
449
  narrative: string[]
450
+ /**
451
+ * `view.toolCount` observed when the newest narrative step was last recorded
452
+ * or counted. The repeat counter (`·×N`) increments only when toolCount has
453
+ * MOVED — the watcher re-emits an unchanged view every tick, so counting
454
+ * label-equality alone would inflate the number with no work behind it.
455
+ */
456
+ lastNarrativeToolCount: number | null
431
457
  /** Last view for this worker (drives the heartbeat re-render + combined row). */
432
458
  lastView: WorkerActivityView | null
433
459
  /** Latest state observed for this worker; excluded from the running set once
@@ -794,10 +820,33 @@ export function createWorkerActivityFeed(opts: WorkerActivityFeedOpts): WorkerAc
794
820
  function accumulateNarrative(row: WorkerRow, view: WorkerActivityView): void {
795
821
  const line = view.latestSummary.trim()
796
822
  if (line.length === 0) return
823
+
824
+ // A REPEAT of the current step is counted, not dropped. A worker whose
825
+ // steps all render the same label (e.g. Bash calls with no description →
826
+ // "Running a command") used to hit the dedup below on every tool call and
827
+ // the card held ONE frozen line for the whole job — indistinguishable from
828
+ // a wedged worker. `·×N` makes the repetition visible and, because a new
829
+ // count resets stepStartedAtMs, the climbing `· Ns` step timer restarts
830
+ // with each real call.
831
+ //
832
+ // Gated on `view.toolCount`, NOT on the label: the watcher re-emits an
833
+ // UNCHANGED view on every tick, so counting label-equality alone would
834
+ // inflate the number with no work behind it. One increment per observed
835
+ // tool call, deterministically.
836
+ const last = row.narrative.length > 0 ? row.narrative[row.narrative.length - 1] : null
837
+ if (last != null && stripRepeatSuffix(last) === line) {
838
+ if (view.toolCount === row.lastNarrativeToolCount) return
839
+ row.lastNarrativeToolCount = view.toolCount
840
+ row.narrative[row.narrative.length - 1] = `${line} ·×${repeatCountOf(last) + 1}`
841
+ row.stepStartedAtMs = nowFn()
842
+ return
843
+ }
844
+
797
845
  // Dedup within the whole rolling window (the watcher re-emits the same
798
846
  // narrative across ticks, and a preamble + its tool label can repeat
799
847
  // non-adjacently — the A,B,A duplication observed on live cards).
800
- if (row.narrative.includes(line)) return
848
+ if (row.narrative.some((l) => stripRepeatSuffix(l) === line)) return
849
+ row.lastNarrativeToolCount = view.toolCount
801
850
  row.narrative.push(line)
802
851
  // The `→` current-step line just CHANGED — reset the per-step timer.
803
852
  row.stepStartedAtMs = nowFn()
@@ -1545,6 +1594,7 @@ export function createWorkerActivityFeed(opts: WorkerActivityFeedOpts): WorkerAc
1545
1594
  agentId,
1546
1595
  ordinal: ++g.workerOrdinalCounter,
1547
1596
  narrative: [],
1597
+ lastNarrativeToolCount: null,
1548
1598
  lastView: null,
1549
1599
  state: 'running',
1550
1600
  finished: false,