hyperloop 0.8.1__tar.gz → 0.9.1__tar.gz

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 (83) hide show
  1. {hyperloop-0.8.1 → hyperloop-0.9.1}/CHANGELOG.md +11 -0
  2. {hyperloop-0.8.1 → hyperloop-0.9.1}/PKG-INFO +1 -1
  3. {hyperloop-0.8.1 → hyperloop-0.9.1}/pyproject.toml +1 -1
  4. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/matrix_setup.py +107 -62
  5. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/config.py +3 -3
  6. {hyperloop-0.8.1 → hyperloop-0.9.1}/uv.lock +1 -1
  7. {hyperloop-0.8.1 → hyperloop-0.9.1}/.github/workflows/ci.yaml +0 -0
  8. {hyperloop-0.8.1 → hyperloop-0.9.1}/.github/workflows/release.yaml +0 -0
  9. {hyperloop-0.8.1 → hyperloop-0.9.1}/.gitignore +0 -0
  10. {hyperloop-0.8.1 → hyperloop-0.9.1}/.pre-commit-config.yaml +0 -0
  11. {hyperloop-0.8.1 → hyperloop-0.9.1}/.python-version +0 -0
  12. {hyperloop-0.8.1 → hyperloop-0.9.1}/CLAUDE.md +0 -0
  13. {hyperloop-0.8.1 → hyperloop-0.9.1}/LICENSE +0 -0
  14. {hyperloop-0.8.1 → hyperloop-0.9.1}/README.md +0 -0
  15. {hyperloop-0.8.1 → hyperloop-0.9.1}/base/implementer.yaml +0 -0
  16. {hyperloop-0.8.1 → hyperloop-0.9.1}/base/kustomization.yaml +0 -0
  17. {hyperloop-0.8.1 → hyperloop-0.9.1}/base/pm.yaml +0 -0
  18. {hyperloop-0.8.1 → hyperloop-0.9.1}/base/process-improver.yaml +0 -0
  19. {hyperloop-0.8.1 → hyperloop-0.9.1}/base/process.yaml +0 -0
  20. {hyperloop-0.8.1 → hyperloop-0.9.1}/base/rebase-resolver.yaml +0 -0
  21. {hyperloop-0.8.1 → hyperloop-0.9.1}/base/verifier.yaml +0 -0
  22. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/observability.md +0 -0
  23. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/prompts/checklist.md +0 -0
  24. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/prompts/checks/check_result_file.sh +0 -0
  25. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/prompts/rules.md +0 -0
  26. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/spec.md +0 -0
  27. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-001.md +0 -0
  28. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-002.md +0 -0
  29. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-003.md +0 -0
  30. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-004.md +0 -0
  31. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-005.md +0 -0
  32. {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-006.md +0 -0
  33. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/__init__.py +0 -0
  34. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/__main__.py +0 -0
  35. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/__init__.py +0 -0
  36. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/git_state.py +0 -0
  37. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/local.py +0 -0
  38. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/matrix_probe.py +0 -0
  39. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/probe.py +0 -0
  40. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/serial.py +0 -0
  41. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/structlog_probe.py +0 -0
  42. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/cli.py +0 -0
  43. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/compose.py +0 -0
  44. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/__init__.py +0 -0
  45. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/decide.py +0 -0
  46. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/deps.py +0 -0
  47. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/model.py +0 -0
  48. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/pipeline.py +0 -0
  49. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/logging.py +0 -0
  50. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/loop.py +0 -0
  51. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/__init__.py +0 -0
  52. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/pr.py +0 -0
  53. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/probe.py +0 -0
  54. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/runtime.py +0 -0
  55. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/serial.py +0 -0
  56. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/state.py +0 -0
  57. {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/pr.py +0 -0
  58. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/__init__.py +0 -0
  59. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/__init__.py +0 -0
  60. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/pr.py +0 -0
  61. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/probe.py +0 -0
  62. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/runtime.py +0 -0
  63. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/serial.py +0 -0
  64. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/state.py +0 -0
  65. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_cli.py +0 -0
  66. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_compose.py +0 -0
  67. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_config.py +0 -0
  68. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_decide.py +0 -0
  69. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_deps.py +0 -0
  70. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_e2e.py +0 -0
  71. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_fakes.py +0 -0
  72. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_git_state.py +0 -0
  73. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_local_runtime.py +0 -0
  74. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_loop.py +0 -0
  75. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_matrix_probe.py +0 -0
  76. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_model.py +0 -0
  77. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_pipeline.py +0 -0
  78. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_pr.py +0 -0
  79. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_probe.py +0 -0
  80. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_serial_agents.py +0 -0
  81. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_smoke.py +0 -0
  82. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_state_contract.py +0 -0
  83. {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_structlog_probe.py +0 -0
@@ -2,6 +2,17 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v0.9.1 (2026-04-15)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **matrix**: Proper UIA two-step registration flow
10
+ ([`97080f9`](https://github.com/jsell-rh/hyperloop/commit/97080f9d6980e914451a59d4b2453c7b5294ed30))
11
+
12
+
13
+ ## v0.9.0 (2026-04-15)
14
+
15
+
5
16
  ## v0.8.1 (2026-04-15)
6
17
 
7
18
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperloop
3
- Version: 0.8.1
3
+ Version: 0.9.1
4
4
  Summary: Orchestrator that walks tasks through composable process pipelines using AI agents
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperloop"
3
- version = "0.8.1"
3
+ version = "0.9.1"
4
4
  description = "Orchestrator that walks tasks through composable process pipelines using AI agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -11,18 +11,18 @@ auto-setup and cache.
11
11
  from __future__ import annotations
12
12
 
13
13
  import json
14
- import logging
15
14
  import secrets
16
15
  from typing import TYPE_CHECKING, cast
17
16
 
18
17
  import httpx
18
+ import structlog
19
19
 
20
20
  if TYPE_CHECKING:
21
21
  from pathlib import Path
22
22
 
23
23
  from hyperloop.config import MatrixConfig
24
24
 
25
- _log = logging.getLogger(__name__)
25
+ _log: structlog.stdlib.BoundLogger = structlog.get_logger()
26
26
 
27
27
  _CACHE_FILE = ".hyperloop/matrix-state.json"
28
28
 
@@ -53,6 +53,8 @@ def _ensure_gitignored(repo_path: Path) -> None:
53
53
  """Ensure .hyperloop/ is in the target repo's .gitignore."""
54
54
  gitignore = repo_path / ".gitignore"
55
55
  entry = ".hyperloop/"
56
+ if not repo_path.is_dir():
57
+ return
56
58
  if gitignore.is_file():
57
59
  content = gitignore.read_text()
58
60
  if entry in content.splitlines():
@@ -104,55 +106,52 @@ def _register_bot(
104
106
  username: str,
105
107
  password: str,
106
108
  ) -> tuple[str, str]:
107
- """Register a bot user. Returns (user_id, access_token).
109
+ """Register a bot user via UIA flow. Returns (user_id, access_token).
108
110
 
109
- Uses Synapse's shared-secret registration flow. If the user already
110
- exists (HTTP 400), falls back to login with the given password.
111
+ Matrix UIA (User-Interactive Authentication) registration:
112
+ 1. POST /register without auth server returns 401 with session + flows.
113
+ 2. POST /register with auth (type + token + session) → 200 with credentials.
114
+
115
+ If the user already exists (HTTP 400), falls back to login.
111
116
  """
112
117
  url = f"{homeserver}/_matrix/client/v3/register"
113
- body = {
118
+ base_body: dict[str, object] = {
114
119
  "username": username,
115
120
  "password": password,
116
- "auth": {
117
- "type": "m.login.registration_token",
118
- "token": registration_token,
119
- },
120
121
  "initial_device_display_name": "hyperloop",
121
122
  "inhibit_login": False,
122
123
  }
123
124
 
124
- resp = client.post(url, json=body)
125
+ # Step 1: request without auth to get session
126
+ resp = client.post(url, json=base_body)
125
127
 
126
128
  if resp.status_code == 200:
129
+ # Some servers accept registration without UIA
127
130
  data = resp.json()
128
131
  return str(data["user_id"]), str(data["access_token"])
129
132
 
130
- if resp.status_code == 401:
131
- # Server requires a different auth flow — try the flows it offers
132
- data = resp.json()
133
- flows = data.get("flows", [])
134
- session = data.get("session", "")
135
-
136
- # Check if m.login.registration_token is among the available flows
137
- for flow in flows:
138
- stages = flow.get("stages", [])
139
- if "m.login.registration_token" in stages:
140
- body["auth"] = {
141
- "type": "m.login.registration_token",
142
- "token": registration_token,
143
- "session": session,
144
- }
145
- resp = client.post(url, json=body)
146
- if resp.status_code == 200:
147
- data = resp.json()
148
- return str(data["user_id"]), str(data["access_token"])
149
-
150
133
  if resp.status_code == 400:
151
- # User likely already exists — try login
152
134
  return _login(client, homeserver, username, password)
153
135
 
136
+ # Step 2: server should return 401 with session + flows
137
+ if resp.status_code in (401, 403):
138
+ data = resp.json()
139
+ session = data.get("session", "")
140
+ if session:
141
+ base_body["auth"] = {
142
+ "type": "m.login.registration_token",
143
+ "token": registration_token,
144
+ "session": session,
145
+ }
146
+ resp = client.post(url, json=base_body)
147
+ if resp.status_code == 200:
148
+ data = resp.json()
149
+ return str(data["user_id"]), str(data["access_token"])
150
+ if resp.status_code == 400:
151
+ return _login(client, homeserver, username, password)
152
+
154
153
  resp.raise_for_status()
155
- msg = f"Unexpected registration response: {resp.status_code}"
154
+ msg = f"Registration failed: {resp.status_code} {resp.text}"
156
155
  raise RuntimeError(msg)
157
156
 
158
157
 
@@ -182,15 +181,18 @@ def _create_room(
182
181
  homeserver: str,
183
182
  access_token: str,
184
183
  room_name: str,
184
+ invite_user: str = "",
185
185
  ) -> str:
186
- """Create a private Matrix room. Returns the room_id."""
186
+ """Create a private Matrix room and optionally invite a user. Returns the room_id."""
187
187
  url = f"{homeserver}/_matrix/client/v3/createRoom"
188
- body = {
188
+ body: dict[str, object] = {
189
189
  "name": room_name,
190
190
  "topic": "hyperloop orchestrator notifications",
191
191
  "visibility": "private",
192
192
  "preset": "private_chat",
193
193
  }
194
+ if invite_user:
195
+ body["invite"] = [invite_user]
194
196
 
195
197
  resp = client.post(
196
198
  url,
@@ -213,15 +215,15 @@ def ensure_matrix_ready(
213
215
  ) -> tuple[str, str]:
214
216
  """Ensure Matrix credentials and room are available.
215
217
 
216
- Resolution order for **access_token**:
217
- 1. Explicit ``token_env`` env var
218
- 2. Cached token from ``.hyperloop/matrix-state.json``
219
- 3. Auto-register via ``registration_token_env``
218
+ **Access token** resolution:
219
+ 1. Explicit ``token_env`` env var → use directly.
220
+ 2. ``registration_token_env`` register a fresh disposable bot
221
+ (``hyperloop-{repo}-{random}``), deactivate the previous one.
220
222
 
221
- Resolution order for **room_id**:
222
- 1. Explicit ``room_id`` in config
223
- 2. Cached room_id
224
- 3. Auto-create via Matrix API
223
+ **Room ID** resolution:
224
+ 1. Explicit ``room_id`` in config.
225
+ 2. Cached room_id from ``.hyperloop/matrix-state.json``.
226
+ 3. Auto-create via Matrix API (invites ``invite_user``).
225
227
 
226
228
  Returns:
227
229
  (access_token, room_id) tuple. Either or both may be empty string
@@ -234,23 +236,21 @@ def ensure_matrix_ready(
234
236
 
235
237
  # --- Resolve access token ---
236
238
  explicit_token = os.environ.get(config.token_env) if config.token_env else ""
237
- cached_token = cache.get("access_token", "") if cache else ""
238
- access_token = explicit_token or cached_token
239
+ access_token = explicit_token
239
240
 
240
241
  if not access_token:
241
- # Try auto-registration
242
242
  registration_token = (
243
243
  os.environ.get(config.registration_token_env) if config.registration_token_env else ""
244
244
  )
245
245
  if registration_token:
246
246
  try:
247
- access_token = _auto_register(
247
+ access_token = _register_disposable_bot(
248
248
  config, repo_path, homeserver, registration_token, cache
249
249
  )
250
250
  except Exception:
251
- _log.exception("Matrix bot registration failed")
251
+ _log.exception("matrix_bot_registration_failed")
252
252
  else:
253
- _log.warning("Matrix: no access token and no registration token — skipping")
253
+ _log.warning("matrix_skipped", reason="no access token and no registration token")
254
254
  return "", ""
255
255
 
256
256
  if not access_token:
@@ -261,62 +261,107 @@ def ensure_matrix_ready(
261
261
  room_id = config.room_id or cached_room
262
262
 
263
263
  if not room_id:
264
+ _log.info("matrix_room_creating")
264
265
  try:
265
266
  room_id = _auto_create_room(config, repo_path, homeserver, access_token)
266
267
  except Exception:
267
- _log.exception("Matrix room creation failed")
268
+ _log.exception("matrix_room_creation_failed")
268
269
  return access_token, ""
269
270
 
271
+ # Ensure the bot has joined the room (it's a new user each run)
272
+ if access_token != explicit_token and room_id:
273
+ _log.info("matrix_room_joining", room_id=room_id)
274
+ try:
275
+ _join_room(httpx.Client(timeout=10.0), homeserver, access_token, room_id)
276
+ except Exception:
277
+ _log.exception("matrix_room_join_failed", room_id=room_id)
278
+
270
279
  return access_token, room_id
271
280
 
272
281
 
273
- def _auto_register(
282
+ def _register_disposable_bot(
274
283
  config: MatrixConfig,
275
284
  repo_path: Path,
276
285
  homeserver: str,
277
286
  registration_token: str,
278
287
  cache: dict[str, str] | None,
279
288
  ) -> str:
280
- """Register a bot user and cache credentials. Returns access_token."""
281
- repo_name = repo_path.name
282
- username = config.bot_username or f"hyperloop-{repo_name}"
289
+ """Register a fresh disposable bot user. Returns access_token.
283
290
 
284
- password = cache.get("password", "") if cache else ""
285
- if not password:
286
- password = secrets.token_urlsafe(32)
291
+ Each run gets a new identity (``hyperloop-{repo}-{random}``). The
292
+ previous bot is deactivated best-effort. Only the room_id is cached.
293
+ """
294
+ repo_name = repo_path.name
295
+ suffix = secrets.token_hex(4)
296
+ username = f"hyperloop-{repo_name}-{suffix}"
297
+ password = secrets.token_urlsafe(32)
287
298
 
288
299
  client = httpx.Client(timeout=30.0)
289
300
  try:
301
+ # Deactivate previous bot (best-effort)
302
+ prev_token = cache.get("access_token", "") if cache else ""
303
+ if prev_token:
304
+ _deactivate_user(client, homeserver, prev_token)
305
+
306
+ # Register new bot
290
307
  user_id, access_token = _register_bot(
291
308
  client, homeserver, registration_token, username, password
292
309
  )
293
310
 
311
+ # Cache room_id + new bot credentials
312
+ cached_room = cache.get("room_id", "") if cache else ""
294
313
  _save_cache(
295
314
  repo_path,
296
315
  homeserver=homeserver,
297
316
  user_id=user_id,
298
317
  access_token=access_token,
299
- room_id=config.room_id,
318
+ room_id=config.room_id or cached_room,
300
319
  password=password,
301
320
  )
302
321
 
303
- _log.info("Matrix bot registered: %s", user_id)
322
+ _log.info("matrix_bot_registered", user_id=user_id)
304
323
  return access_token
305
324
  finally:
306
325
  client.close()
307
326
 
308
327
 
328
+ def _deactivate_user(client: httpx.Client, homeserver: str, access_token: str) -> None:
329
+ """Deactivate the user associated with the given access token. Best-effort."""
330
+ import contextlib
331
+
332
+ url = f"{homeserver}/_matrix/client/v3/account/deactivate"
333
+ with contextlib.suppress(Exception):
334
+ client.post(
335
+ url,
336
+ json={"auth": {"type": "m.login.password"}},
337
+ headers={"Authorization": f"Bearer {access_token}"},
338
+ )
339
+
340
+
341
+ def _join_room(client: httpx.Client, homeserver: str, access_token: str, room_id: str) -> None:
342
+ """Join a room by ID."""
343
+ url = f"{homeserver}/_matrix/client/v3/join/{room_id}"
344
+ resp = client.post(url, json={}, headers={"Authorization": f"Bearer {access_token}"})
345
+ resp.raise_for_status()
346
+
347
+
309
348
  def _auto_create_room(
310
349
  config: MatrixConfig,
311
350
  repo_path: Path,
312
351
  homeserver: str,
313
352
  access_token: str,
314
353
  ) -> str:
315
- """Create a room and update the cache. Returns room_id."""
354
+ """Create a room, invite the configured user, and cache room_id. Returns room_id."""
316
355
  repo_name = repo_path.name
317
356
  client = httpx.Client(timeout=30.0)
318
357
  try:
319
- room_id = _create_room(client, homeserver, access_token, f"hyperloop-{repo_name}")
358
+ room_id = _create_room(
359
+ client,
360
+ homeserver,
361
+ access_token,
362
+ f"hyperloop-{repo_name}",
363
+ invite_user=config.invite_user,
364
+ )
320
365
 
321
366
  # Update cache with the new room_id
322
367
  cache = _load_cache(repo_path, homeserver) or {}
@@ -329,7 +374,7 @@ def _auto_create_room(
329
374
  password=cache.get("password", ""),
330
375
  )
331
376
 
332
- _log.info("Matrix room created: %s", room_id)
377
+ _log.info("matrix_room_created", room_id=room_id)
333
378
  return room_id
334
379
  finally:
335
380
  client.close()
@@ -31,7 +31,7 @@ class MatrixConfig:
31
31
  token_env: str
32
32
  verbose: bool
33
33
  registration_token_env: str # env var holding the registration token
34
- bot_username: str
34
+ invite_user: str # Matrix user ID to invite to auto-created rooms
35
35
 
36
36
 
37
37
  @dataclass(frozen=True)
@@ -144,7 +144,7 @@ def _flatten_yaml(raw: dict[str, object]) -> dict[str, object]:
144
144
  flat["matrix_token_env"] = mx.get("token_env", "")
145
145
  flat["matrix_verbose"] = mx.get("verbose", False)
146
146
  flat["matrix_registration_token_env"] = mx.get("registration_token_env", "")
147
- flat["matrix_bot_username"] = mx.get("bot_username", "")
147
+ flat["matrix_invite_user"] = mx.get("invite_user", "")
148
148
 
149
149
  return flat
150
150
 
@@ -208,7 +208,7 @@ def load_config(
208
208
  token_env=token_env,
209
209
  verbose=bool(values.get("matrix_verbose", False)),
210
210
  registration_token_env=registration_token_env,
211
- bot_username=str(values.get("matrix_bot_username", "")),
211
+ invite_user=str(values.get("matrix_invite_user", "")),
212
212
  )
213
213
 
214
214
  obs_cfg = ObservabilityConfig(
@@ -120,7 +120,7 @@ wheels = [
120
120
 
121
121
  [[package]]
122
122
  name = "hyperloop"
123
- version = "0.8.0"
123
+ version = "0.9.0"
124
124
  source = { editable = "." }
125
125
  dependencies = [
126
126
  { name = "httpx" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes