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.
- {hyperloop-0.8.1 → hyperloop-0.9.1}/CHANGELOG.md +11 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/PKG-INFO +1 -1
- {hyperloop-0.8.1 → hyperloop-0.9.1}/pyproject.toml +1 -1
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/matrix_setup.py +107 -62
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/config.py +3 -3
- {hyperloop-0.8.1 → hyperloop-0.9.1}/uv.lock +1 -1
- {hyperloop-0.8.1 → hyperloop-0.9.1}/.github/workflows/ci.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/.github/workflows/release.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/.gitignore +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/.pre-commit-config.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/.python-version +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/CLAUDE.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/LICENSE +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/README.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/base/implementer.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/base/kustomization.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/base/pm.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/base/process-improver.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/base/process.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/base/rebase-resolver.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/base/verifier.yaml +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/observability.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/prompts/checklist.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/prompts/checks/check_result_file.sh +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/prompts/rules.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/spec.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-001.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-002.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-003.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-004.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-005.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/specs/tasks/task-006.md +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/__init__.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/__main__.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/__init__.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/git_state.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/local.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/matrix_probe.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/probe.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/serial.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/adapters/structlog_probe.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/cli.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/compose.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/__init__.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/decide.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/deps.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/model.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/domain/pipeline.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/logging.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/loop.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/__init__.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/pr.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/probe.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/runtime.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/serial.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/ports/state.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/src/hyperloop/pr.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/__init__.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/__init__.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/pr.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/probe.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/runtime.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/serial.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/fakes/state.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_cli.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_compose.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_config.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_decide.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_deps.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_e2e.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_fakes.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_git_state.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_local_runtime.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_loop.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_matrix_probe.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_model.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_pipeline.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_pr.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_probe.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_serial_agents.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_smoke.py +0 -0
- {hyperloop-0.8.1 → hyperloop-0.9.1}/tests/test_state_contract.py +0 -0
- {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
|
|
@@ -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 =
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
217
|
-
1. Explicit ``token_env`` env var
|
|
218
|
-
2.
|
|
219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
247
|
+
access_token = _register_disposable_bot(
|
|
248
248
|
config, repo_path, homeserver, registration_token, cache
|
|
249
249
|
)
|
|
250
250
|
except Exception:
|
|
251
|
-
_log.exception("
|
|
251
|
+
_log.exception("matrix_bot_registration_failed")
|
|
252
252
|
else:
|
|
253
|
-
_log.warning("
|
|
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("
|
|
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
|
|
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
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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("
|
|
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
|
|
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(
|
|
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("
|
|
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
|
-
|
|
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["
|
|
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
|
-
|
|
211
|
+
invite_user=str(values.get("matrix_invite_user", "")),
|
|
212
212
|
)
|
|
213
213
|
|
|
214
214
|
obs_cfg = ObservabilityConfig(
|
|
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
|
|
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
|
|
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
|
|
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
|