Release version management: single VERSION file as source of truth
- VERSION at repo root (0.1.2); bump it to cut a release - App: generated AppVersion.kt (config-cache-safe Gradle task with VERSION as declared input) shown in Settings; sent to the gateway via X-Iris-App-Version header on the SSE open - Gateway: reports its own version in hello.ack server_caps.app_version (read from the repo-root VERSION via the plugin symlink); stores the app's version in the device registry caps (merge, not overwrite, so an old app reconnecting without the header doesn't wipe it) - Settings: app + gateway version rows, mismatch hint, and a best-effort Gitea latest-release check (ReleaseCheck) with an 'update available' hint - Release workflow: reads VERSION from the repo (no manual input), with a guard against an empty file - Docs: frames.schema.json + 04-wire-protocol.md updated for app_version
This commit is contained in:
1 parent
f3f1b37221
commit
fb980d12b4
17 files changed
+422
-37
No files matched your search
@@ -135,6 +135,7 @@ from .setup import (
|
||||
validate_config,
|
||||
)
|
||||
from .tool_frames import ToolProgressHandlers
|
||||
from .version import plugin_version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -778,6 +779,9 @@ class IrisAdapter(
|
||||
self._push.server_url if isinstance(self._push, NtfyBackend) else ""
|
||||
),
|
||||
"pickers": True, # picker.choice / picker.select (slash-command menus)
|
||||
# Release version from the repo-root VERSION file (version.py);
|
||||
# the app shows it in Settings and hints on app/gateway mismatch.
|
||||
"app_version": plugin_version(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -490,11 +490,20 @@ class HttpServer:
|
||||
device_name = (handler.headers.get("X-Iris-Device-Name") or "").strip()[:120]
|
||||
fcm_token = handler.headers.get("X-Iris-Fcm-Token") or None
|
||||
ntfy_topic = handler.headers.get("X-Iris-Ntfy-Topic") or None
|
||||
# App release version (the repo-root VERSION baked into the build);
|
||||
# stored in the device registry's caps JSON so `hermes` can see which
|
||||
# app version each device runs (old-version awareness). A missing
|
||||
# header (old app build) must not wipe a previously stored version,
|
||||
# so merge over the existing caps instead of replacing them.
|
||||
app_version = (handler.headers.get("X-Iris-App-Version") or "").strip()[:40]
|
||||
try:
|
||||
existing_caps = dict(self._devices.get(device_id) or {}).get("caps") or {}
|
||||
if app_version:
|
||||
existing_caps["app_version"] = app_version
|
||||
self._devices.upsert(
|
||||
device_id,
|
||||
device_name or device_id,
|
||||
None,
|
||||
existing_caps or None,
|
||||
fcm_token,
|
||||
ntfy_topic,
|
||||
)
|
||||
@@ -523,7 +532,12 @@ class HttpServer:
|
||||
# lifecycle is the primary "is the device connected?" signal
|
||||
# for debugging flaky links — a gap here is invisible at the
|
||||
# gateway's default log level.
|
||||
logger.info("iris: SSE stream opened: %s (cursor=%d)", device_id, cursor)
|
||||
logger.info(
|
||||
"iris: SSE stream opened: %s (cursor=%d, app_version=%s)",
|
||||
device_id,
|
||||
cursor,
|
||||
app_version or "?",
|
||||
)
|
||||
# 1. Catch-up from the outbox (id = cursor; the envelope also
|
||||
# carries the cursor for the app's push dedupe).
|
||||
max_cursor = cursor
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Version discovery -- the repo-root ``VERSION`` file is the single source
|
||||
of truth for the release version ("everything from here on out is vX.Y.Z"
|
||||
= bump ``VERSION`` and commit).
|
||||
|
||||
The plugin lives at ``<repo>/gateway-plugin`` (installed into
|
||||
``~/.hermes/plugins/iris`` as a symlink in production), so the ``VERSION``
|
||||
file is one directory up. The value is advertised to the app in
|
||||
``hello.ack`` (``server_caps.app_version``) so the app can show which
|
||||
gateway version it is talking to.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
_FALLBACK = "unknown"
|
||||
|
||||
|
||||
def plugin_version() -> str:
|
||||
"""The release version from ``<repo>/VERSION``, or ``"unknown"``."""
|
||||
candidate = Path(__file__).resolve().parent.parent / "VERSION"
|
||||
try:
|
||||
version = candidate.read_text().strip()
|
||||
except OSError:
|
||||
return _FALLBACK
|
||||
return version or _FALLBACK
|
||||
Reference in new issue
Block a user