sneakoscope 7.3.0 → 7.6.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 (283) hide show
  1. package/README.md +129 -10
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/codex-app-command.js +1 -1
  5. package/dist/cli/command-manifest-lite.js +4 -1
  6. package/dist/cli/command-registry.js +2 -0
  7. package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
  8. package/dist/cli/install-helpers-codex-lb-config.js +114 -9
  9. package/dist/cli/install-helpers-install-support.js +6 -16
  10. package/dist/cli/install-helpers.js +788 -600
  11. package/dist/cli/router.js +30 -2
  12. package/dist/commands/codex-app.js +37 -1
  13. package/dist/commands/codex-lb.js +491 -300
  14. package/dist/config/skills-manifest.json +57 -71
  15. package/dist/core/align/align-route.js +127 -0
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
  17. package/dist/core/codex-app/codex-app-restart.js +83 -0
  18. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
  19. package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
  20. package/dist/core/codex-app/menubar/constants.js +5 -0
  21. package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
  22. package/dist/core/codex-app/menubar/installer.js +18 -0
  23. package/dist/core/codex-app/multi-provider-router.js +22 -10
  24. package/dist/core/codex-app/openrouter-activate.js +26 -1
  25. package/dist/core/codex-app.js +118 -26
  26. package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
  27. package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
  28. package/dist/core/codex-control/codex-task-runner.js +2 -2
  29. package/dist/core/codex-lb/capability-runner.js +204 -0
  30. package/dist/core/codex-lb/capability-types.js +1 -0
  31. package/dist/core/codex-lb/codex-lb-env.js +9 -13
  32. package/dist/core/codex-lb/codex-lb-setup.js +45 -8
  33. package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
  34. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
  35. package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
  36. package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
  37. package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
  38. package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
  39. package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
  40. package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
  41. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
  42. package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
  43. package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
  44. package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
  45. package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
  46. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
  47. package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
  48. package/dist/core/codex-lb/desktop-controller.js +1410 -0
  49. package/dist/core/codex-lb/desktop-mode.js +32 -0
  50. package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
  51. package/dist/core/codex-lb/desktop-service.js +613 -0
  52. package/dist/core/codex-lb/legacy-migration.js +575 -0
  53. package/dist/core/codex-lb/migration-receipt.js +246 -0
  54. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
  55. package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
  56. package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
  57. package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
  58. package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
  59. package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
  60. package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
  61. package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
  62. package/dist/core/codex-native/core-skill-manifest.js +22 -6
  63. package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
  64. package/dist/core/commands/align-command.js +220 -0
  65. package/dist/core/commands/image-ux-review-command.js +280 -68
  66. package/dist/core/commands/naruto-command.js +257 -8
  67. package/dist/core/commands/ppt-command.js +2 -2
  68. package/dist/core/commands/telegram-command.js +402 -34
  69. package/dist/core/commands/triwiki-command.js +11 -1
  70. package/dist/core/commands/triwiki-graph-command.js +160 -0
  71. package/dist/core/commands/wiki-command.js +100 -20
  72. package/dist/core/doctor/skill-legacy-surface.js +1 -3
  73. package/dist/core/errors/message.js +3 -0
  74. package/dist/core/evidence/evidence-freshness.js +14 -0
  75. package/dist/core/feature-fixtures.js +9 -1
  76. package/dist/core/feature-registry.js +5 -3
  77. package/dist/core/fsx.js +7 -3
  78. package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
  79. package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
  80. package/dist/core/hooks-runtime/hook-context.js +15 -2
  81. package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
  82. package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
  83. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
  84. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  85. package/dist/core/hooks-runtime.js +33 -89
  86. package/dist/core/image-ux-review/callout-extraction.js +6 -0
  87. package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
  88. package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
  89. package/dist/core/image-ux-review.js +230 -12
  90. package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
  91. package/dist/core/imagegen/imagegen-capability.js +10 -4
  92. package/dist/core/imagegen/require-imagegen.js +29 -5
  93. package/dist/core/init/skills.js +25 -13
  94. package/dist/core/init.js +1 -1
  95. package/dist/core/install/installed-package-smoke.js +157 -4
  96. package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
  97. package/dist/core/managed-path-safety.js +8 -10
  98. package/dist/core/mcp-config/backup.js +2 -4
  99. package/dist/core/mcp-config/config-reader.js +2 -4
  100. package/dist/core/mcp-config/scope.js +3 -5
  101. package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
  102. package/dist/core/naruto/context-graph-advisor.js +0 -0
  103. package/dist/core/perf/release-latency-slo.js +12 -6
  104. package/dist/core/pipeline-internals/runtime-core.js +78 -0
  105. package/dist/core/pipeline-internals/runtime-gates.js +5 -2
  106. package/dist/core/policy/latest-version-guidance.js +113 -0
  107. package/dist/core/ppt.js +8 -8
  108. package/dist/core/preflight/parallel-preflight-engine.js +1 -1
  109. package/dist/core/proof/auto-finalize.js +98 -6
  110. package/dist/core/proof/route-proof-gate.js +12 -1
  111. package/dist/core/proof/route-proof-policy.js +8 -8
  112. package/dist/core/release/gate-manifest.js +32 -1
  113. package/dist/core/release/npm-stage-contract.js +21 -0
  114. package/dist/core/release/package-size-budget.js +2 -2
  115. package/dist/core/release/release-gate-contract.js +5 -0
  116. package/dist/core/release/release-proof-io.js +1 -3
  117. package/dist/core/release/release-real-contract.js +5 -4
  118. package/dist/core/release/release-stamp-proof.js +1 -3
  119. package/dist/core/release/stage-publish.js +27 -0
  120. package/dist/core/remote/audit-idempotency.js +50 -18
  121. package/dist/core/remote/codex-public-activity.js +283 -0
  122. package/dist/core/remote/index.js +1 -0
  123. package/dist/core/remote/ssh-worker-client.js +4 -1
  124. package/dist/core/remote/worker.js +206 -27
  125. package/dist/core/research/research-adversarial-review.js +1 -3
  126. package/dist/core/research/research-claim-synthesizer.js +1 -3
  127. package/dist/core/research/research-falsification-runner.js +1 -3
  128. package/dist/core/research/research-source-evidence.js +1 -3
  129. package/dist/core/responses-stream.js +65 -19
  130. package/dist/core/routes/constants.js +1 -1
  131. package/dist/core/routes/design-policy.js +7 -2
  132. package/dist/core/routes/dollar-manifest-lite.js +2 -4
  133. package/dist/core/routes/evidence.js +1 -1
  134. package/dist/core/routes.js +30 -10
  135. package/dist/core/safety/command-contract/contracts.js +2 -0
  136. package/dist/core/safety/command-contract/types.js +1 -1
  137. package/dist/core/search/context-graph-seeds.js +205 -0
  138. package/dist/core/search/context.js +0 -0
  139. package/dist/core/search/provider.js +2 -2
  140. package/dist/core/secret-redaction.js +6 -0
  141. package/dist/core/subagents/naruto-help-contract.js +2 -1
  142. package/dist/core/subagents/naruto-host-credentials.js +161 -0
  143. package/dist/core/subagents/official-subagent-preparation.js +4 -1
  144. package/dist/core/subagents/official-subagent-prompt.js +44 -2
  145. package/dist/core/subagents/official-subagent-runner.js +26 -8
  146. package/dist/core/subagents/triwiki-attention.js +135 -123
  147. package/dist/core/telegram/bot-api.js +85 -10
  148. package/dist/core/telegram/center-snapshot.js +718 -0
  149. package/dist/core/telegram/config.js +158 -31
  150. package/dist/core/telegram/hub.js +288 -33
  151. package/dist/core/telegram/index.js +2 -0
  152. package/dist/core/telegram/ingress-spool.js +419 -0
  153. package/dist/core/telegram/ledgers.js +188 -29
  154. package/dist/core/telegram/messages.js +26 -6
  155. package/dist/core/telegram/owner-lock.js +10 -7
  156. package/dist/core/telegram/runtime-projection.js +38 -3
  157. package/dist/core/telegram/runtime.js +298 -63
  158. package/dist/core/telegram/service.js +137 -8
  159. package/dist/core/telegram/setup.js +291 -73
  160. package/dist/core/text/strings.js +3 -0
  161. package/dist/core/triwiki/code-pack.js +12 -120
  162. package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
  163. package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
  164. package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
  165. package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
  166. package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
  167. package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
  168. package/dist/core/triwiki/context-graph/contracts.js +236 -0
  169. package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
  170. package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
  171. package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
  172. package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
  173. package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
  174. package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
  175. package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
  176. package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
  177. package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
  178. package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
  179. package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
  180. package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
  181. package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
  182. package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
  183. package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
  184. package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
  185. package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
  186. package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
  187. package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
  188. package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
  189. package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
  190. package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
  191. package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
  192. package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
  193. package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
  194. package/dist/core/triwiki/context-graph/graph-index.js +143 -0
  195. package/dist/core/triwiki/context-graph/ids.js +0 -0
  196. package/dist/core/triwiki/context-graph/lint/index.js +0 -0
  197. package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
  198. package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
  199. package/dist/core/triwiki/context-graph/paths.js +116 -0
  200. package/dist/core/triwiki/context-graph/profiles.js +78 -0
  201. package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
  202. package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
  203. package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
  204. package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
  205. package/dist/core/triwiki/context-graph/projections/index.js +7 -0
  206. package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
  207. package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
  208. package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
  209. package/dist/core/triwiki/context-graph/query/explain.js +0 -0
  210. package/dist/core/triwiki/context-graph/query/index.js +178 -0
  211. package/dist/core/triwiki/context-graph/query/load.js +116 -0
  212. package/dist/core/triwiki/context-graph/query/pack.js +117 -0
  213. package/dist/core/triwiki/context-graph/query/rank.js +142 -0
  214. package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
  215. package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
  216. package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
  217. package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
  218. package/dist/core/triwiki/context-graph/query-types.js +29 -0
  219. package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
  220. package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
  221. package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
  222. package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
  223. package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
  224. package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
  225. package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
  226. package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
  227. package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
  228. package/dist/core/verification/context-graph-affected.js +271 -0
  229. package/dist/core/version.js +1 -1
  230. package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
  231. package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
  232. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
  233. package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
  234. package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
  235. package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
  236. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
  237. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
  238. package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
  239. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
  240. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
  241. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
  242. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
  243. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
  244. package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
  245. package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
  246. package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
  247. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
  248. package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
  249. package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
  250. package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
  251. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
  252. package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
  253. package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
  254. package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
  255. package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
  256. package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
  257. package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
  258. package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
  259. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
  260. package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
  261. package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
  262. package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
  263. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
  264. package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
  265. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
  266. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
  267. package/dist/scripts/context-graph-check.js +191 -0
  268. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
  269. package/dist/scripts/gate-policy-audit-check.js +1 -1
  270. package/dist/scripts/helper-dedup-check.js +0 -2
  271. package/dist/scripts/installed-package-smoke-check.js +4 -1
  272. package/dist/scripts/latest-version-guidance-check.js +74 -0
  273. package/dist/scripts/release-metadata-check.js +21 -4
  274. package/dist/scripts/release-real-check.js +7 -0
  275. package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
  276. package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
  277. package/package.json +8 -3
  278. package/release-gates.v2.json +176 -1
  279. package/runtime-required-scripts.json +30 -0
  280. package/schemas/triwiki/context-graph.schema.json +119 -0
  281. package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
  282. package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
  283. package/dist/core/triwiki/code-index-scanner.js +0 -309
@@ -0,0 +1,447 @@
1
+ import Cocoa
2
+
3
+ extension RemoteTelegramViewController {
4
+ @objc func acknowledgeDeadLetters() {
5
+ guard let window = view.window, deadLetterIngressCount > 0 else { return }
6
+ AlertFactory.confirmSheet(
7
+ window: window,
8
+ title: "Acknowledge failed Telegram ingress?",
9
+ message: "This permanently retires \(deadLetterIngressCount) failed ingress record\(deadLetterIngressCount == 1 ? "" : "s"). It does not claim the Codex effect ran and it does not replay the original text. Keep the Hub stopped, acknowledge, restart it, then resend each failed request from the paired Telegram chat.",
10
+ destructive: true,
11
+ actionTitle: "Acknowledge"
12
+ ) { [weak self] confirmed in
13
+ guard let self = self, confirmed else { return }
14
+ self.runMutation(
15
+ args: [
16
+ "telegram", "dead-letters", "acknowledge", "--yes",
17
+ "--project-root", AppRuntime.projectRoot,
18
+ "--json"
19
+ ],
20
+ kind: "telegram-dead-letters-acknowledge",
21
+ summary: "Acknowledge failed Telegram ingress"
22
+ )
23
+ }
24
+ }
25
+
26
+ func checkStatus(refreshConversation: Bool) {
27
+ guard !busy, !statusInFlight else { return }
28
+ statusInFlight = true
29
+ generation += 1
30
+ let requestGeneration = generation
31
+ checkButton?.isEnabled = false
32
+ readinessStatus.stringValue = "Checking Codex App, CLI, Git, Telegram, and Hub state…"
33
+ if !setupReady {
34
+ ControlKit.setBadge(setupBadge, text: "Checking Telegram setup…", tone: .busy)
35
+ setupStatus.stringValue = "Checking Keychain, bot identity, pairing, and dedicated sessions…"
36
+ }
37
+ var remote: [String: Any]?
38
+ var telegram: [String: Any]?
39
+ var telegramFailure: String?
40
+ let group = DispatchGroup()
41
+ group.enter()
42
+ processClient.run(
43
+ ["remote", "readiness", "--project-root", AppRuntime.projectRoot, "--json"],
44
+ timeout: NativeView.statusTimeout
45
+ ) { [weak self] result in
46
+ if result.code == 0, !result.truncated {
47
+ remote = self?.json(result.output)
48
+ }
49
+ group.leave()
50
+ }
51
+ group.enter()
52
+ processClient.run(
53
+ ["telegram", "status", "--project-root", AppRuntime.projectRoot, "--json"],
54
+ timeout: ProcessClient.telegramStatusTimeout
55
+ ) { [weak self] result in
56
+ if !result.timedOut,
57
+ !result.emptyOutput,
58
+ !result.truncated,
59
+ let payload = self?.json(result.output),
60
+ payload["schema"] as? String == "sks.telegram-status.v1" {
61
+ telegram = payload
62
+ } else {
63
+ telegramFailure = Self.telegramStatusFailureMessage(result)
64
+ }
65
+ group.leave()
66
+ }
67
+ group.notify(queue: .main) { [weak self] in
68
+ guard let self = self, self.generation == requestGeneration else { return }
69
+ self.statusInFlight = false
70
+ self.render(remote: remote, telegram: telegram)
71
+ if let telegramFailure = telegramFailure {
72
+ ControlKit.setBadge(
73
+ self.setupReady ? self.connectionBadge : self.setupBadge,
74
+ text: "Telegram live status unavailable",
75
+ tone: .warning
76
+ )
77
+ if self.setupReady {
78
+ self.connectionStatus.stringValue = "\(telegramFailure) The linked session and locally loaded conversation remain available."
79
+ } else {
80
+ self.setupStatus.stringValue = telegramFailure
81
+ }
82
+ }
83
+ if !self.hubRunning,
84
+ let startupFailure = TelegramHubSupervisor.latestStartupFailureMessage {
85
+ ControlKit.setBadge(self.hubBadge, text: "Telegram Hub startup needs attention", tone: .error)
86
+ self.hubStatus.stringValue = startupFailure
87
+ }
88
+ self.updateButtons()
89
+ if self.setupReady {
90
+ if refreshConversation || self.loadedSessionId != self.selectedSessionId {
91
+ self.refreshSnapshot(force: true)
92
+ } else {
93
+ self.refreshLive(force: true)
94
+ }
95
+ } else {
96
+ self.clearSnapshot()
97
+ }
98
+ }
99
+ }
100
+
101
+ static func telegramStatusFailureMessage(_ result: ProcessResult) -> String {
102
+ if result.timedOut || result.output.contains("native_process_timeout") {
103
+ return "Telegram status exceeded its bounded native deadline. Retry after the current probe finishes or check network access to api.telegram.org."
104
+ }
105
+ if result.emptyOutput || result.output.contains("native_process_empty_output") {
106
+ return "Telegram status returned no JSON. Retry, then open Diagnostics if the CLI remains unavailable."
107
+ }
108
+ if result.truncated {
109
+ return "Telegram status exceeded the native output budget. Open Diagnostics and retry."
110
+ }
111
+ return "Telegram status returned an invalid response. Retry, then open Diagnostics if it persists."
112
+ }
113
+
114
+ func refreshSnapshot(force: Bool = false) {
115
+ guard setupReady, !busy, !snapshotInFlight else { return }
116
+ if !force {
117
+ guard view.window?.isVisible == true else { return }
118
+ }
119
+ liveGeneration += 1
120
+ liveInFlight = false
121
+ snapshotInFlight = true
122
+ snapshotGeneration += 1
123
+ let requestGeneration = snapshotGeneration
124
+ let requestSessionId = selectedSessionId
125
+ let requestCursor = conversationCursor
126
+ let requestSortDirection = conversationSortDirection
127
+ updateButtons()
128
+ ControlKit.setBadge(liveSourceBadge, text: "Refreshing real Codex thread…", tone: .busy)
129
+ snapshotStatus.stringValue = "Reading the selected App Server thread and bounded live activity…"
130
+ var args = ["telegram", "center-snapshot", "--project-root", AppRuntime.projectRoot, "--json"]
131
+ if let selectedSessionId = selectedSessionId {
132
+ args += ["--session-id", selectedSessionId]
133
+ }
134
+ if let conversationCursor = conversationCursor {
135
+ args += ["--cursor", conversationCursor]
136
+ }
137
+ args += ["--sort-direction", conversationSortDirection]
138
+ processClient.run(
139
+ args,
140
+ timeout: 30,
141
+ maxOutputBytes: 896 * 1024,
142
+ logOutput: false
143
+ ) { [weak self] result in
144
+ guard let self = self,
145
+ self.snapshotGeneration == requestGeneration,
146
+ self.selectedSessionId == requestSessionId,
147
+ self.conversationCursor == requestCursor,
148
+ self.conversationSortDirection == requestSortDirection else { return }
149
+ self.snapshotInFlight = false
150
+ self.updateButtons()
151
+ guard result.code == 0,
152
+ !result.truncated,
153
+ let snapshot = self.json(result.output),
154
+ self.validFullSnapshot(snapshot, sessionId: requestSessionId) else {
155
+ ControlKit.setBadge(self.liveSourceBadge, text: "Conversation refresh unavailable", tone: .warning)
156
+ self.snapshotStatus.stringValue = result.truncated
157
+ ? "The private snapshot exceeded the native output budget. Open Diagnostics and retry after the thread is compacted."
158
+ : self.loadedSessionId == nil
159
+ ? "The linked conversation could not be loaded. Retry the conversation refresh or open Diagnostics."
160
+ : "The last loaded conversation and activity remain visible. Retry the conversation refresh or open Diagnostics."
161
+ return
162
+ }
163
+ self.renderSnapshot(snapshot)
164
+ }
165
+ }
166
+
167
+ func refreshLive(force: Bool = false) {
168
+ guard setupReady,
169
+ !busy,
170
+ !statusInFlight,
171
+ !snapshotInFlight,
172
+ !liveInFlight,
173
+ isViewingLatestConversationPage else { return }
174
+ if !force {
175
+ guard view.window?.isVisible == true else { return }
176
+ }
177
+ let requestSourceSignature = centerLiveSourceSignature()
178
+ let requestWasCatchUp = liveCatchUpInProgress
179
+ liveSourceFileSignature = requestSourceSignature
180
+ liveInFlight = true
181
+ liveGeneration += 1
182
+ let requestGeneration = liveGeneration
183
+ let requestSessionId = selectedSessionId
184
+ var args = ["telegram", "center-live", "--project-root", AppRuntime.projectRoot, "--json"]
185
+ if let selectedSessionId = selectedSessionId {
186
+ args += ["--session-id", selectedSessionId]
187
+ }
188
+ processClient.run(
189
+ args,
190
+ timeout: 10,
191
+ maxOutputBytes: 320 * 1024,
192
+ logOutput: false
193
+ ) { [weak self] result in
194
+ guard let self = self,
195
+ self.liveGeneration == requestGeneration,
196
+ self.selectedSessionId == requestSessionId else { return }
197
+ self.liveInFlight = false
198
+ guard result.code == 0,
199
+ !result.truncated,
200
+ let live = self.json(result.output),
201
+ self.validLiveSnapshot(live, sessionId: requestSessionId) else {
202
+ self.liveCatchUpInProgress = false
203
+ self.renderLiveFailure()
204
+ return
205
+ }
206
+ self.renderLiveSnapshot(live)
207
+ let completionSourceSignature = self.centerLiveSourceSignature()
208
+ if Self.shouldRequestImmediateLiveCatchUp(
209
+ requestSignature: requestSourceSignature,
210
+ completionSignature: completionSourceSignature,
211
+ wasCatchUp: requestWasCatchUp
212
+ ) {
213
+ self.liveCatchUpInProgress = true
214
+ self.liveSourceFileSignature = nil
215
+ self.refreshLive(force: true)
216
+ } else if completionSourceSignature != requestSourceSignature {
217
+ self.liveCatchUpInProgress = false
218
+ self.liveSourceFileSignature = nil
219
+ } else {
220
+ self.liveCatchUpInProgress = false
221
+ self.liveSourceFileSignature = requestSourceSignature
222
+ }
223
+ guard let revision = live["conversation_revision"] as? String,
224
+ !revision.isEmpty,
225
+ revision != self.loadedConversationRevision,
226
+ revision != self.liveTriggeredConversationRevision,
227
+ self.isCompletedTurn(live["binding"] as? [String: Any]) else { return }
228
+ self.liveTriggeredConversationRevision = revision
229
+ self.refreshSnapshot(force: true)
230
+ }
231
+ }
232
+
233
+ func startLivePolling() {
234
+ liveTimer?.invalidate()
235
+ liveSourceFileSignature = nil
236
+ liveCatchUpInProgress = false
237
+ liveTimer = Timer.scheduledTimer(withTimeInterval: 2.5, repeats: true) { [weak self] _ in
238
+ guard let self = self else { return }
239
+ guard self.view.window?.isVisible == true else {
240
+ self.liveTimer?.invalidate()
241
+ self.liveTimer = nil
242
+ return
243
+ }
244
+ guard self.isViewingLatestConversationPage else { return }
245
+ let sourceSignature = self.centerLiveSourceSignature()
246
+ guard sourceSignature != self.liveSourceFileSignature else { return }
247
+ self.refreshLive()
248
+ }
249
+ if let liveTimer = liveTimer {
250
+ RunLoop.main.add(liveTimer, forMode: .common)
251
+ }
252
+ }
253
+
254
+ func resetConversationPagination() {
255
+ conversationCursor = nil
256
+ conversationSortDirection = "desc"
257
+ isViewingLatestConversationPage = true
258
+ olderConversationCursor = nil
259
+ olderConversationSortDirection = "desc"
260
+ newerConversationCursor = nil
261
+ newerConversationSortDirection = "asc"
262
+ updateButtons()
263
+ }
264
+
265
+ func clearSnapshot() {
266
+ invalidateCenterRequests()
267
+ clearLoadedCenterState()
268
+ resetConversationPagination()
269
+ ControlKit.setBadge(liveSourceBadge, text: "Codex thread not loaded", tone: .neutral)
270
+ snapshotStatus.stringValue = "Complete Telegram setup to load a linked conversation."
271
+ renderConversationPlaceholder("Connect Telegram to load a Codex conversation.")
272
+ renderActivityPlaceholder("Public work activity will appear after a linked Codex turn starts.")
273
+ }
274
+
275
+ func clearLoadedCenterState() {
276
+ loadedSessionId = nil
277
+ loadedConversationRevision = nil
278
+ loadedLiveActivityDigest = nil
279
+ liveTriggeredConversationRevision = nil
280
+ loadedTranscriptRows = []
281
+ loadedHistoryActivityRows = []
282
+ loadedLiveActivityRows = []
283
+ loadedThreadId = nil
284
+ loadedThreadStatus = "unknown"
285
+ loadedConversationPageTurnCount = 0
286
+ loadedSnapshotTruncated = false
287
+ loadedLiveActivityState = "pending"
288
+ loadedLiveActivityError = nil
289
+ renderedTranscriptSignature = nil
290
+ renderedActivitySignature = nil
291
+ liveSourceFileSignature = nil
292
+ liveCatchUpInProgress = false
293
+ }
294
+
295
+ func applyConversationPage(_ page: [String: Any]?) {
296
+ guard let page = page else {
297
+ resetConversationPagination()
298
+ return
299
+ }
300
+ isViewingLatestConversationPage = page["is_latest"] as? Bool == true
301
+ olderConversationCursor = page["older_cursor"] as? String
302
+ olderConversationSortDirection = validConversationSortDirection(
303
+ page["older_sort_direction"] as? String,
304
+ fallback: "desc"
305
+ )
306
+ newerConversationCursor = page["newer_cursor"] as? String
307
+ newerConversationSortDirection = validConversationSortDirection(
308
+ page["newer_sort_direction"] as? String,
309
+ fallback: "asc"
310
+ )
311
+ updateButtons()
312
+ }
313
+
314
+ private func validConversationSortDirection(_ value: String?, fallback: String) -> String {
315
+ guard let value = value, value == "asc" || value == "desc" else { return fallback }
316
+ return value
317
+ }
318
+
319
+ private func isCompletedTurn(_ binding: [String: Any]?) -> Bool {
320
+ guard let status = binding?["last_turn_status"] as? String else { return false }
321
+ return status == "completed" || status == "failed" || status == "interrupted"
322
+ }
323
+
324
+ private func validFullSnapshot(_ payload: [String: Any], sessionId: String?) -> Bool {
325
+ guard payload["schema"] as? String == "sks.telegram-center-snapshot.v1",
326
+ payload["ok"] as? Bool == true,
327
+ (payload["selected_session_id"] as? String) == sessionId,
328
+ let state = payload["state"] as? String,
329
+ ["ready", "awaiting_first_message"].contains(state),
330
+ payload["conversation_revision"] is String,
331
+ payload["live_activity_digest"] is String,
332
+ payload["transcript"] is [[String: Any]],
333
+ payload["activity"] is [[String: Any]] else { return false }
334
+ if state == "ready" {
335
+ return payload["thread"] is [String: Any]
336
+ && payload["conversation_page"] is [String: Any]
337
+ }
338
+ return true
339
+ }
340
+
341
+ private func validLiveSnapshot(_ payload: [String: Any], sessionId: String?) -> Bool {
342
+ guard payload["schema"] as? String == "sks.telegram-center-live.v1",
343
+ payload["ok"] as? Bool == true,
344
+ (payload["selected_session_id"] as? String) == sessionId,
345
+ let state = payload["state"] as? String,
346
+ ["ready", "awaiting_first_message"].contains(state),
347
+ payload["binding"] is [String: Any],
348
+ payload["conversation_revision"] is String,
349
+ payload["live_activity_digest"] is String,
350
+ payload["activity"] is [[String: Any]] else { return false }
351
+ return true
352
+ }
353
+
354
+ func invalidateCenterRequests() {
355
+ snapshotGeneration += 1
356
+ liveGeneration += 1
357
+ snapshotInFlight = false
358
+ liveInFlight = false
359
+ liveCatchUpInProgress = false
360
+ updateButtons()
361
+ }
362
+
363
+ static func shouldRequestImmediateLiveCatchUp(
364
+ requestSignature: String,
365
+ completionSignature: String,
366
+ wasCatchUp: Bool
367
+ ) -> Bool {
368
+ completionSignature != requestSignature && !wasCatchUp
369
+ }
370
+
371
+ func centerLiveSourceSignature() -> String {
372
+ let remoteRoot = URL(fileURLWithPath: AppRuntime.projectRoot, isDirectory: true)
373
+ .appendingPathComponent(".sneakoscope", isDirectory: true)
374
+ .appendingPathComponent("remote", isDirectory: true)
375
+ let names = [
376
+ "events.json",
377
+ "events-health.json",
378
+ "codex-session-bindings.json"
379
+ ]
380
+ return names.map { name in
381
+ let url = remoteRoot.appendingPathComponent(name)
382
+ guard let attributes = try? FileManager.default.attributesOfItem(atPath: url.path) else {
383
+ return "\(name):missing"
384
+ }
385
+ let modified = (attributes[.modificationDate] as? Date)?.timeIntervalSince1970 ?? 0
386
+ let size = (attributes[.size] as? NSNumber)?.uint64Value ?? 0
387
+ return "\(name):\(modified):\(size)"
388
+ }.joined(separator: "|")
389
+ }
390
+
391
+ func setupGuidance(
392
+ token: Bool,
393
+ botVerified: Bool,
394
+ longPollReady: Bool,
395
+ pairing: Bool,
396
+ sessions: Int,
397
+ fallback: String?
398
+ ) -> String {
399
+ if !token || !botVerified {
400
+ return "Next: Create or regenerate the bot in @BotFather, send /start in its private chat, then connect the token here."
401
+ }
402
+ if !pairing {
403
+ return "Next: Send /start to the bot from the private Telegram account you want to pair, then reconnect."
404
+ }
405
+ if sessions == 0 {
406
+ return "Next: Reconnect the bot to register a dedicated coding session for this project."
407
+ }
408
+ if !longPollReady {
409
+ return "Next: Remove the bot’s existing webhook, send /start again, then reconnect."
410
+ }
411
+ return fallback.map { "Next: \($0)" } ?? "Next: Complete the highlighted setup step, then refresh status."
412
+ }
413
+
414
+ func userFacingThreadStatus(_ status: String) -> String {
415
+ switch status {
416
+ case "idle": return "ready"
417
+ case "active", "inProgress": return "working"
418
+ case "notLoaded": return "stored history available"
419
+ case "systemError": return "needs attention"
420
+ case "awaiting_first_message": return "awaiting first message"
421
+ case "unknown": return "status unavailable"
422
+ default: return "thread available"
423
+ }
424
+ }
425
+
426
+ func setupFailureMessage(_ output: String) -> String {
427
+ if output.contains("telegram_pairing_start_not_found") {
428
+ return "No private /start message was found. Open the new bot, send /start from the Telegram account you want to pair, then retry."
429
+ }
430
+ if output.contains("telegram_pairing_ambiguous") {
431
+ return "More than one private /start was pending, so SKS refused to guess the operator. Use only the intended account, send /start again, then retry."
432
+ }
433
+ if output.contains("telegram_webhook_conflict") {
434
+ return "This bot still has a webhook from another service. Remove that webhook there, send /start again, then retry."
435
+ }
436
+ if output.contains("telegram_hub_must_be_stopped_before_setup") {
437
+ return "Stop the Telegram Hub before reconnecting the bot or rotating its token."
438
+ }
439
+ if output.contains("telegram_keychain_readback_failed") || output.contains("telegram_keychain_snapshot_failed") {
440
+ return "The token could not be read back from macOS Keychain for the Hub. Approve the Keychain prompt, then retry."
441
+ }
442
+ if output.contains("telegram_bot_auth_failed") || output.contains("\"error_code\":401") || output.contains("Unauthorized") {
443
+ return "Telegram rejected the token. Copy the current token from @BotFather and try again."
444
+ }
445
+ return "Setup failed. Confirm the BotFather token and private /start message, then retry. The token was not written to logs."
446
+ }
447
+ }