Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Changelog

This file is the canonical Hermes Linux Stack release history from v0.5.2 onward.
Older component-specific history remains in the component release-note files and Git history.

The current runtime release is **v0.5.9**.

### Features — content production platform: agents, timeline renderer, operations console (2026-09-22)

- The Smart Router gains the four content production agents (Storyboard, Video
  Director, Media Planning, NotebookLM Recovery) as registered agents with
  deterministic normalizers, exposed on `GET /v1/content/agents` and
  `POST /v1/content/{storyboard,timeline,video-plan,media-plan,recover}`, so
  planning rides on the existing registry, orchestrator, approvals and audit
  trail. Release: Smart Router `0.6.2`.
- Media Studio gains the `timeline-video` driver: a validated timeline document
  renders to one MP4 with ffmpeg (per-scene fragments, transitions, narration
  track, brand chip), and `POST /timeline/validate` checks a document with the
  renderer's own normalizer without rendering. The renderer never receives a
  raw script. Release: Media Studio `0.5.1` (timeline driver in `0.5.0`).
- The operator panel becomes the Content Operations Center: Video Studio plans,
  validates, renders and tracks jobs (status, log tail, cancel, retry,
  download, preview), Hermes shows the control-plane agents, routing profiles
  and measured telemetry, Orchestration lists runs with steps, approvals and
  reviewer verdicts, and Knowledge lists bases and runs retrieval tests.
  Release: operator panel `0.5.0`.
- NotebookLM keeps its place as the research connector; its failures now feed
  the Recovery Agent instead of blind retries.
- Operator panel `0.5.1`: Video Studio reports the drivers the running worker
  loaded instead of trusting `.env`, and the exposure warnings only fire for
  services that are actually published (with a bind/state/port matrix in the
  Overview).
- Operator panel `0.5.2`: the n8n exposure note states that Hermes reaches
  n8n over the internal Docker network, so the host publish only adds LAN
  access and binding the port to loopback does not affect the agent.
- Smart Router `0.6.4` adds `POST /v1/content/scene`: the Storyboard Agent
  rewrites one scene of an existing storyboard (index, instruction, draft
  context) and the answer is repaired with the storyboard scene bounds, so the
  operator's scene editor regenerates a single scene instead of a whole plan.
- Operator panel `0.6.0` adds the Storyboard view: drafts planned by the
  agents are stored under `data/panel/storyboards/` and edited scene by scene
  (edit, reorder, add, remove, per-scene regenerate), validated against the
  renderer, approved or rejected with a note, and rendered through the
  `timeline-video` driver with the job tracked on the draft.
- Smart Router `0.6.4` also makes virtual API keys fully removable:
  `DELETE /control/api/keys/{id}?purge=true` deletes the key instead of the
  reversible revoke, answers `409 key_in_use` while ACL rules or budgets still
  reference it, and `cascade=true` removes those references in the same
  transaction. Access -> Users & Keys gains the Delete button and an Active
  column next to the existing Revoke action.
- Both consoles now document themselves. The Smart Router Docs page covers
  every navigation area, the complete HTTP API (OpenAI-compatible,
  Anthropic-compatible and content production surfaces), the virtual key
  lifecycle and ready-to-copy Codex and Claude client configurations. The
  operator panel gains a Docs view with one section per panel view plus the
  full `/api/` reference and curl automation examples.

### Features — NotebookLM Video Overviews for the Content Bot (2026-09-15)

- A new optional worker (`notebooklm-worker`, compose profile `notebooklm`)
  produces narrated overview videos with Google NotebookLM instead of a paid
  video API. It drives the NotebookLM web app in a real browser session
  (cdp against the operator's Chrome by default, or a container profile),
  adds the draft title and body plus the original link as sources, renders the
  Video Overview, and serves the mp4 as a job artifact.
- The media question gains a fifth choice, **NotebookLM video**, with three
  profiles (`technical_fa`, `educational_fa`, `news_fa`) and a documented
  length-bucket API (`short`, `standard`, `deep`). The ask message is edited
  as the job advances (`⏳ 📚 📤 🎬 ⬇️`), the finished video arrives as the
  usual preview, and a failed job reports the worker error with a retry button
  that reuses the stored profile.
- NotebookLM is an independent provider next to Media Studio: the button
  appears only when `CONTENT_NOTEBOOKLM_URL` is set, the bot keeps a shared
  bearer token with the worker, and Media Studio keeps generating images and
  recorded clips. `./manage.sh notebooklm-enable|status|login|disable` manages
  the profile, the token, and the sign-in, and `docs/NOTEBOOKLM-STUDIO.md`
  covers sessions, the API, profiles, selector calibration, and failures.
- The worker publishes as `afsharidevops/notebooklm-worker` through
  `.github/workflows/publish-notebooklm-worker.yml`; `data/notebooklm-worker`
  is a scoped backup section, and selector overrides load from
  `NOTEBOOKLM_SELECTORS_FILE` when the NotebookLM UI changes.
- Releases: Content Bot `0.4.3` (image and workflow tag, release notes in
  `content-bot/V0.4.3-RELEASE-NOTES.md`) and NotebookLM Worker `0.1.0`.

### Features — Content Bot scheduled proposals: media question and daily toggle (2026-09-14)

- The Content Bot media question now reaches scheduled proposals, not only
  operator-sent links. When Media Studio is configured
  (`CONTENT_MEDIA_STUDIO_URL`), every daily proposal is followed by the same
  four choices as an on-demand draft (text only, AI image, send your own
  image, or a video prompt), and scheduled routines can select
  `media: ask` in `editorial-policy.yaml`. `media: auto` (AI image) and
  `media: none` keep working unchanged.
- More than one proposal per day needs no code change: add one routine per
  slot, each with its own `id`, `time`, `count`, and `media` mode. Run markers
  are per routine, so a second daily slot is independent of the first.
- The built-in daily pass can be turned off with `pipeline.daily_enabled:
  false` in `editorial-policy.yaml` when the scheduled routines own the
  cadence. The default stays enabled, so existing deployments are unchanged,
  and the routines keep running either way.

### Features — Aparat video publishing for the Content Bot (2026-09-14)

- Aparat is an automatic channel now. Once a browser session is stored
  (`CONTENT_APARAT_TOKEN`, the `jwt` value of a signed-in aparat.com tab, or
  `CONTENT_APARAT_COOKIE`), a draft that carries a video publishes to Aparat
  the moment the platform is picked: the bot reserves an upload slot, sends
  the file in chunks, and submits the title, description, tags, and category.
  The publication row keeps the watch URL when Aparat returns one.
- Aparat is video-only and on demand. Text and photo drafts keep the
  copy-ready package, the chooser marks the entry `Aparat (needs a video)`
  until a video is attached, **All targets** skips it without a video, and
  picking it without one asks for the file and uploads it as soon as it
  arrives. The daily proposals and the scheduled routines never publish to
  Aparat on their own.
- The adapter reads the same per-destination adapted text the other channels
  use, so the title, description, and hashtags of the Aparat upload follow the
  draft language and the platform profile. `docs/APARAT-SETUP.md` covers the
  session copy step, the panel card, the category ids, and the failure modes;
  the panel gained an Aparat card with a session test, and
  `./manage.sh content-aparat-check` probes the stored session without
  uploading anything.
- The content-bot container now receives every `CONTENT_APARAT_*` key from the
  stack `.env` file, so a session stored by the panel (or by hand) reaches the
  bot after the container is recreated; a panel regression test keeps every
  platform-card key wired to its service.

### Fixes — Aparat uploader matches the web uploader and retries failed chunks (2026-09-14)

- The metadata call that finishes an Aparat upload now sends the body and the
  headers the Aparat web uploader sends: `video_pass` as a number, `watermark`
  with `watermark_bool`, `subtitle` as a list, `publish_date` as null, optional
  `duration` and `thumbnail`, and the `isNext`/`jsonType`/`domain`/`currentUrl`/
  `isRedesign` headers. The uploader form also adds `upload_base_url`,
  `uploadId`, and `video` right before it submits, and the endpoint answers
  400 (`upload_base_url: ...`) without them, so the client carries them too.
- The upload server is read from the nested `data.attributes` object Aparat
  returns; the flat `data.server` key the code read before does not exist, so
  an upload stopped right after `upload_config`. A 3 s private upload now runs
  end to end against the live service with the stored session.
- `request_multipart` and `request_multipart_many` return the `(status, body)`
  pair their callers unpack again; they leaked the internal
  `(status, body, headers)` triple, so every Aparat chunk and every Telegram
  file send (photo, video, document) failed with "too many values to unpack".
- A refused chunk is uploaded again up to three times with a 1 s, 2 s, 4 s
  backoff, the `chunksdone` close of the upload is retried the same way, and a
  `4xx` rejection fails on the first answer. `AparatClient.publish` takes a
  duration and a thumbnail (image bytes or a `data:image/...` URL), the Aparat
  channel forwards both from the draft `meta`, and the uploader logs its steps
  (file, size, chunk, retry, metadata answer) for debugging. Content Bot 0.4.2
  carries the change; no new environment key is required.

### Fixes — Buffered chat clients and streaming upstreams (2026-09-14)

- A Chat Completions request that omits `stream` is now forwarded with an
  explicit `stream: false` instead of relying on the upstream default, and a
  `text/event-stream` body that an upstream returns for a buffered request is
  collapsed into one `chat.completion` JSON body. Internal callers that post to
  the router's own `/v1/chat/completions` (the Orchestrator planner, executor,
  reviewer, and team synthesis) no longer fail with "agent returned no
  completion" when the gateway streams by default. Streaming clients are
  unchanged.
- Smart Router release: `afsharidevops/hermes-smart-router:0.6.1`.

### Features — Multi-agent Orchestrator and the LocalLab router console (2026-09-14)

- The Operations Center gained an **Orchestrator** page and the matching
  `/api/orchestrations` API. A planner turns one operator task into a validated
  machine-readable plan, the supervisor executes the steps through the
  registered agents, a sensitive step pauses the run in `awaiting_approval`
  until an operator approves or rejects it, and a reviewer pass records a
  verdict with an optional rollback suggestion. The planner reuses the recent
  completed runs as history, so repeating a task stays consistent with what was
  done before. Runs and steps persist in the existing Operations DB
  (`v60_agent_runs`, `v60_agent_run_steps`); the compatibility filename
  `control-v0.5.2.sqlite3` is unchanged and the in-place schema marker moves to
  `0.6.0`.
- Approval gating is deterministic: the planner flag, a dangerous-operation
  pattern (`kubectl delete`/`drain`, `terraform destroy`/`apply`, `iptables`,
  `DROP TABLE`/`DATABASE`, `TRUNCATE`, `docker rm`/`rmi`/`volume rm`/`system prune`,
  `systemctl stop`/`disable`/`mask`, `mkfs`, `dd if=`, `chmod 777`, `fdisk`,
  `userdel`, `kill -9`, `git push --force`, `DELETE FROM`, `shutdown`/`reboot`),
  or a step tool that is registered as high risk or missing from the plugin
  registry. Tool names stay declarative: the run record and the audit log show
  the intent, and execution remains with the Execution Broker.
- `SMART_ROUTER_ORCHESTRATOR_APPROVAL_MODE` (`auto`), `SMART_ROUTER_ORCHESTRATOR_PLANNER_TIER`
  (`standard`), and `SMART_ROUTER_ORCHESTRATOR_REVIEWER_TIER` (`strong`) control
  the flow in `.env` and in `deploy/helm/hermes-linux-stack`. See
  [docs/ORCHESTRATION.md](docs/ORCHESTRATION.md).
- The Smart Router Operations Center now shares the Content Console design
  language: the LocalLab gradient palette, glass surfaces, gradient accents,
  and the LocalLab mark on the login and sidebar brand.
- Smart Router release: `afsharidevops/hermes-smart-router:0.6.0`.

### Features — Public service links in the operator console (2026-09-13)

- The console **Overview -> Endpoints** card now links every publishable
  service through the public origin recorded in `.env` (`PANEL_PUBLIC_URL`,
  `SMART_ROUTER_PUBLIC_URL`, `MEDIA_STUDIO_PUBLIC_URL`, `N8N_PUBLIC_URL`,
  `INSTAGRAM_MEDIA_PUBLIC_BASE_URL`, `S3_PUBLIC_BASE_URL`,
  `S3_PUBLIC_CONSOLE_URL`) and falls back to the LAN bind address when a
  service has no public URL yet, so one console works from the LAN and from
  outside it. Loopback placeholders such as `NINEROUTER_PUBLIC_BASE_URL=
  http://localhost:20128` count as "not published" and keep the bind fallback.
  The object-storage view resolves `S3_PUBLIC_BASE_URL` and
  `S3_PUBLIC_CONSOLE_URL` the same way, the row for the active router backend
  follows `COMPOSE_PROFILES`, and the Environment editor gained **Add key** so
  a key that is not in `.env` yet can be created from the browser.
- `./manage.sh domains` (also in the panel menu) prints one block per service:
  the recorded or suggested host name derived from `STACK_BASE_DOMAIN`, the
  `.env` key and value to record, the LAN `bind:port` target, a warning when
  the bind is still loopback, and the ready-to-paste Caddy block - including
  the GET-only root redirect the RustFS console needs.
- `install.sh` records `PANEL_PUBLIC_URL` when the operator publishes the
  panel, points at `SMART_ROUTER_PUBLIC_URL` and `MEDIA_STUDIO_PUBLIC_URL` when
  a base domain is known, and prints the `./manage.sh domains` checklist in the
  final summary. Panel tests grew from 93 to 96.

### Features — Platforms view in the operator console (2026-09-13)

- The console gained a **Platforms** section: one card per publishing platform
  (Telegram, Bale, Eitaa, Instagram, the AI writer, Media Studio, the platform
  chooser, and the package-only YouTube/Aparat/LinkedIn entries). Each card
  lists the bot token, the channel or business id, and the API **base URL**
  that client dials, marks the required fields, and reports the platform state
  (`ready`, `incomplete`, `not configured`, `package handover`, or `on`/`off`
  for the chooser switch) without ever returning a stored secret to the
  browser.
- **Test connection** asks the provider with the values currently in the form,
  so a token can be validated before it is saved: `getMe` for Telegram, Bale,
  and Eitaa, the Graph node lookup for Instagram, `/v1/models` with a
  `/health` fallback for the writer, and `/healthz` for Media Studio. **Save**
  writes only the edited `.env` lines through the line-preserving editor, and
  **Apply changes** recreates the containers so `content-bot` reads them.
- `panel/platforms.py` holds the registry, the validation, and the tests;
  `EnvStore` gained `value()`/`is_secret()` for single-key reads. Panel tests
  grew from 73 to 93.

### Features — manage.sh channel setup for Bale and Eitaa (2026-09-13)

- `./manage.sh content-connect-bale` and `./manage.sh content-connect-eitaa`
  store the token, the destination, and the API base in `.env`, optionally
  check the token with `getMe` (`--verify`), publish one test message through
  the running adapter (`--test`), and recreate `content-bot` so the draft
  chooser flips from `(no token)` to `(auto)` in one command. Running either
  command without flags prompts for the values.
- `./manage.sh content-channels` reports the state of both channels without
  printing a token, and `content-status` prints the same lines. `manage.sh
  content` gained the matching menu entries.

### Docs — Bale and Eitaa setup guide (2026-09-13)

- `docs/BALE-EITAA-SETUP.md` walks through creating the bot with Bale's
  BotFather, preparing the channel, verifying the token with `getMe` and
  `sendMessage` before touching the stack, the EitaaYar equivalent, the `.env`
  keys, and the failure modes worth knowing (wrong token, wrong channel id, a
  bot that is not an administrator, oversized media).

### Features — Bale and Eitaa publish automatically (2026-09-13)

- The per-draft platform chooser is no longer package-only. A platform whose
  profile runs in `auto` mode is published the moment the operator picks it,
  caption and media included: `Bale` uses the Telegram-shaped Bot API
  (`https://tapi.bale.ai`) and `Eitaa` uses the EitaaYar gateway
  (`https://eitaayar.ir/api`), each configured with a token plus a destination
  chat id. Instagram keeps the manual package, and the package platforms
  (YouTube, Aparat, LinkedIn) are unchanged.
- Channels that are still missing a token are marked `(no token)` in the
  chooser and fall back to the package, and every automatic publish records
  its target on the draft so a post can never be sent to the same channel
  twice.

### Features — Per-post upload packages for LinkedIn, YouTube, and Aparat (2026-09-13)

- Every enabled manual platform is offered per draft under **More platforms...**,
  so a post that suits LinkedIn or Shorts can be handed over without touching
  the others. LinkedIn joins the built-in profiles with its own title and
  description limits, and all of them stay overridable in the `platforms:`
  section of `editorial-policy.yaml`.
- Published drafts are archived in a small bounded copy, and the post-publish
  message keeps the **More platforms...** button, so a package can still be
  requested after the post went out instead of only before the approval.

### Fixes — Instagram manual mode hands over the post package (2026-09-13)

- `INSTAGRAM_AUTO_PUBLISH=false` used to switch Instagram off instead of
  switching it to manual: the Telegram buttons disappeared, and approving a
  draft with Instagram as a target was refused outright, so the operator had no
  way to collect a copy-ready package from the chat. The approval path now
  treats the Instagram half as a hand-off: **Approve to Instagram** sends the
  package, **Approve to Telegram + Instagram** publishes to the channel and
  sends the package, and approving a media draft to Telegram alone sends the
  package with the same step. Drafts carry an **Instagram package** button
  whenever automatic publishing is off, and the console actions behave the same
  way instead of reporting "skipped".

### Fixes — Smart Router image publishes on its own again (2026-09-12)

- `.github/workflows/publish-smart-router.yml` only ran on
  `workflow_dispatch`, so Smart Router changes merged to `main` never reached
  Docker Hub and deployments kept pulling a stale image. The workflow now also
  triggers on pushes that touch `smart-router/**` or itself, and it derives the
  image version from `smart-router/pyproject.toml` when no manual version is
  given, so the published tags can no longer drift from the package version.
  A manual run still accepts an explicit version and rejects a value that does
  not match the package version.

### Features — Codex and Claude Code client protocols (2026-09-12)

- `POST /v1/responses` now speaks the Responses API properly instead of
  flattening it to plain chat: instructions become the system message,
  `function_call` / `function_call_output` history round-trips as assistant
  tool calls and tool results, tool definitions keep their JSON schema, and the
  stream emits `response.created` / `output_item.added` /
  `output_text.delta` / `function_call_arguments.delta` / `output_item.done` /
  `response.completed` with `sequence_number` and Responses-shaped usage. A
  broken upstream stream ends with `response.failed` rather than a truncated
  `response.completed`. Codex connects with `wire_api = "responses"` and runs
  its tool loop through the router.
- New `POST /v1/messages` implements the Anthropic Messages API for Claude
  Code: system blocks, text/image/tool_use/tool_result content, tools and
  `tool_choice`, streaming `message_start` … `message_stop` events with
  `text_delta` and `input_json_delta` blocks, and Anthropic usage reporting.
  `POST /v1/messages/count_tokens` returns a local estimate, and `GET
  /v1/models` answers in the Anthropic shape for clients that ask with
  `anthropic-version` or `x-api-key`.
- `examples/clients/` ships a ready-to-copy Codex `config.toml`, a Claude
  Codex environment file, and a verification checklist.
- `/v1/chat/completions`, `/v1/responses`, and `/v1/messages` share one routing
  path, so tiering, budget enforcement, sticky sessions, and telemetry apply to
  every protocol. `docs/SMART-ROUTER-CLIENT-API.md` documents both client
  configurations end to end.

### Fixes — panel config edits no longer lock the bot out (2026-09-12)

- `ConfigStore.write` staged edits in a `0600` temporary file and kept that
  mode after `os.replace`, so editing `editorial-policy.yaml` in the panel made
  it unreadable for the bot container, which runs under a different uid and
  crashed every maintenance cycle with `PermissionError`. Configuration files
  are now written world readable (`0644`, existing bits preserved), matching
  the shipped defaults and the read-only mounts of the bot, router, and n8n
  containers.

### Features — Instagram manual mode and the post package (2026-09-12)

- `INSTAGRAM_AUTO_PUBLISH` (default `true`) decides whether Instagram publishes
  through the Graph API. With `false` the two Instagram approval buttons
  disappear from the Telegram keyboards and the operator console, and the
  queued `publish_ig` / `publish_both` actions answer with a skip message that
  names the switch. Credentials, the media host, and the token refresh are
  untouched, so setting it back to `true` restores the API path.
- The operator console gains a **Post package** action for every publishable
  draft. The bot re-sends the stored media as an untouched Telegram document
  and follows it with the exact Instagram caption inside a `<pre>` block, which
  Telegram renders with a copy button, so a manual post needs no text
  selection. Text-only drafts receive the caption package alone.
- `/api/instagram` reports the new switch as `auto_publish`, and the Overview
  card shows `auto publish on` or `manual package` next to the token state.
- `.env.example` and `docs/INSTAGRAM-SETUP.md` document
  `INSTAGRAM_AUTO_PUBLISH` and `INSTAGRAM_DISABLE_REFRESH`, including the
  manual checklist for posting from the Instagram app.
- `content-bot` adds `TelegramApi.send_document` for full-quality file
  hand-offs, and `/status` prints the Instagram automatic-publishing state.

### Fixes — RustFS console route behind a reverse proxy (2026-09-11)

- The console signs in with a signed `POST /` (`Action=AssumeRole`) against the
  host that serves it. A router route that redirects `/` to
  `/rustfs/console/` for every method answers that POST with a 302 and login
  fails without a useful message. The installer wizard and `./manage.sh
  s3-guide` now print a route that keeps the redirect for `GET` only, and
  `docs/S3-STORAGE.md` explains the requirement plus the `Host` header rule
  behind signed requests.
- The installer asks directly whether the console should be published, derives
  `S3_PUBLIC_CONSOLE_URL` from the host name it receives, and prints the
  matching Caddy block, so the console URL recorded in `.env` always matches
  the published route.
- `install.sh` asks once for the zone the published services live in and stores
  it as `STACK_BASE_DOMAIN` (for example `stack.example.com`). The panel, the
  Instagram media host, the S3 API and the console are then suggested as
  `<service>.<zone>`, and `./manage.sh s3-guide` prints the same names,
  preferring the public origins already recorded in `.env`.
- Examples, installer defaults, fixtures, and the Firefox extension id use the
  `example.com` placeholder zone instead of the operator's own domain, so a
  public checkout never names the real zone.

### Fixes — Content Bot startup resilience (2026-09-11)

- The first Telegram call of the Content Bot runs while the container starts,
  which can race a resolver or an outbound route that is not ready yet. A
  single `Temporary failure in name resolution` there used to end the process,
  leaving the recovery to the container restart policy and printing a full
  traceback. Startup now retries five times with a growing delay and logs one
  warning per attempt, while a rejected bot token still fails immediately.

  ```text
  WARNING content_bot: Telegram unreachable at startup (attempt 1/5): [Errno -3]
  Temporary failure in name resolution; retrying in 3s
  ```

- `docs/CONTENT-PRODUCTION-GUIDE.md` documents both the startup retries and the
  `Telegram connection problem, retrying` warning, including the host resolver
  check to run when the message repeats.

- All Telegram API methods now convert transient connection errors to
  ``TelegramNetworkError`` (a subclass of ``TelegramError``), so existing
  ``except TelegramError`` guards in ``_startup`` (``delete_webhook``,
  ``set_my_commands``) no longer crash when the outbound route stalls between
  container startup and the first API call. The startup retry logic in
  ``_startup_identity`` distinguishes ``TelegramNetworkError`` from a rejected
  token and retries the former, while a bad token still fails immediately.

## Content Manager — fork of Hermes Linux Stack v0.5.9 (2026-09-07)

This repository is **Content Manager**: a fork of the Hermes Linux Stack v0.5.9
platform (upstream unchanged) extended with a deterministic daily
content-production layer. The upstream changelog below documents the inherited
platform; this section tracks the fork additions.

### Fork release — Content Manager v0.4.1 (2026-09-14)

- Added Aparat as an automatic video channel (`content_bot/aparat.py`,
  `AparatChannel`): the bot reserves an upload slot, sends the clip in chunks,
  and submits the title, description, tags, and category through the Aparat
  upload API of a stored browser session (`CONTENT_APARAT_TOKEN` or
  `CONTENT_APARAT_COOKIE`). Aparat is on demand and video-only: the daily
  proposals and the scheduled routines never publish to it, the chooser marks
  the entry `Aparat (needs a video)` until a clip is attached, **All targets**
  skips it without one, and picking it without a video asks for the file and
  uploads it as soon as it arrives. Text and photo drafts keep the copy-ready
  package. `docs/APARAT-SETUP.md` covers the session copy step, the category
  ids, and the failure modes; the panel gained an Aparat card with a session
  test, and `./manage.sh content-aparat-check` probes the stored session
  without uploading anything.
- Scheduled proposals of the Content Bot now carry the same media question as
  an on-demand draft, each routine selects its own `media` mode (`none`,
  `auto`, or `ask`), and the built-in daily pass can be switched off with
  `pipeline.daily_enabled: false` when the routines own the cadence. The
  default stays enabled, so existing deployments are unchanged.
- Added `docs/SMART-ROUTER-USER-GUIDE.md`, the complete operator guide for the
  Smart Router console, the OpenAI-compatible API, model and alias selection,
  routing profiles, the Orchestrator, and the client integrations.
- A stored Aparat session that is really the text `undefined` or `null` - what
  the browser console hands back when `copy(...)` is used without reading the
  value - or that still carries its printed quotes, is discarded instead of
  failing with a puzzling `401` later. `docs/APARAT-SETUP.md` explains the
  console answers, and the card test names the placeholder it found.
- An image job that fails because the endpoint has no images API now says so:
  the Media Studio driver names `MEDIA_STUDIO_WRITER_BASE_URL`, the panel's
  Media Studio test probes the image endpoint before an operator presses
  **AI image**, `./manage.sh media-status` warns when the endpoint is the
  stack chat gateway, and `docs/MEDIA-STUDIO.md` gained a troubleshooting
  table (the Smart Router serves chat completions only, so an image model and
  an image-capable provider are required).
- Added the Media Studio `api-video` driver: it submits
  `POST <base>/videos/generations`, polls `GET <base>/videos/<id>`, downloads
  the finished clip, and bakes in the brand chip, so a deployment without a
  Google session can answer the video button of the Content Bot through a
  video-capable provider of the gateway (`xai/grok-imagine-video`,
  `openrouter/google/veo-3.1`, `vertex/veo-3.1-fast-generate-preview`). A chat
  combo such as `ai` or `ai-strong` is rejected by the gateway with "Combos are
  not supported for video generation", and both the driver and the panel test
  say so instead of failing silently. New keys:
  `MEDIA_STUDIO_VIDEO_BASE_URL`, `MEDIA_STUDIO_VIDEO_API_KEY`,
  `MEDIA_STUDIO_VIDEO_MODEL`, `MEDIA_STUDIO_VIDEO_PROVIDER`,
  `MEDIA_STUDIO_VIDEO_DURATION`, `MEDIA_STUDIO_VIDEO_ASPECT_RATIO`,
  `MEDIA_STUDIO_VIDEO_RESOLUTION`, `MEDIA_STUDIO_VIDEO_POLL_SECONDS`,
  `MEDIA_STUDIO_VIDEO_TIMEOUT_SECONDS`; the video endpoint and key fall back to
  the writer values, and `MEDIA_STUDIO_DRIVERS` now ships
  `api-image,api-video,flow-video,video-edit`.
- Content Bot 0.4.1, operator panel 0.4.1, and Media Studio 0.4.0. The Helm
  chart moves to 0.6.4 with the same image tags.

### Fork release — Content Manager v0.4.0 (2026-09-14)

- Added LinkedIn as an automatic channel (`content_bot/linkedin.py`): posts are
  created through `POST /rest/posts`, an attached image goes through
  `initializeUpload` plus a binary `PUT`, transient failures (`429`/`5xx`) are
  retried with backoff, and the created post URN is kept as the publication's
  `remote_id`. Each configured account - a personal profile or a company page -
  is its own channel on the draft.
- Social accounts now live in `social-accounts.yaml` inside the policy
  directory (`CONTENT_SOCIAL_ACCOUNTS_FILE`), with the `CONTENT_LINKEDIN_*`
  environment block as the single-account fallback; tokens are never
  hardcoded, and an account without one is skipped with a log line.
- Added the per-destination content adapter (`content_bot/adapt.py`): the draft
  body is rewritten for each target according to a tone profile
  (`linkedin_personal`, `linkedin_company`, `telegram`), profiles are
  overridable in the `tones:` section of `editorial-policy.yaml`, the rewrite
  prompt lives in `prompt-templates/<tone>.md`, and any failure - or
  `CONTENT_ADAPT_ENABLED=false` - publishes the untouched draft text. Only the
  body is adapted, so title, source link, and captions keep the existing
  behavior.
- Added multi-target publishing with a publication ledger: drafts may carry
  `targets:`, `publishing.targets` supplies defaults, **All targets** reaches
  every automatic channel, and every attempt is a row (`id`, `content_id`,
  `platform`, `account`, `status`, `remote_id`, `published_at`, `error`) in
  `data/content-bot/state.json`. One destination failing never blocks the
  others, and the adapted text is cached so a retry never re-runs the writer.
- The operator console gained a LinkedIn card (token, type, author URN or
  person/organization id, API base and version) whose **Test connection**
  reserves one image upload slot to validate the token and the author without
  publishing. `./manage.sh content-connect-linkedin` stores the same values,
  and `content-channels`/`content-status` report the account state.
- Added `docs/LINKEDIN-SETUP.md` (app, scopes, three-legged OAuth, author id,
  account file, tones, targets, publish flow, token rotation, troubleshooting)
  and extended the production guide and panel documentation. Content Bot
  0.4.0 and operator panel 0.4.0; Media Studio stays 0.3.0.

### Fork release — Content Manager v0.3.0 (2026-09-11)

- Added `panel/`: an optional operator console (Compose profile `panel`) for
  stack status, validated YAML/JSON editing with backups, `.env` edits, service
  logs, and a fixed whitelist of compose actions. It stays bound to loopback,
  authenticates with a token stored outside the repository, and never returns a
  secret to the browser.
- The panel lists the live drafts and applies the same decisions as the
  Telegram keyboard (text only, AI image, publish, discard). The bot keeps the
  authoritative `state.json`, so the panel appends validated requests to
  `data/content-bot/panel-actions.requests.jsonl` and the bot drains them from
  its main loop, reporting each outcome in the console and in Telegram.
- Added the shared tool registry (`content/config/tools.json`) and scheduled
  per-platform routines (research, draft, media, queue) driven by the policy
  cadence, plus the "edit or publish as-is" choice for operator-recorded
  clips.
- The Instagram long-lived token now refreshes itself: the bot extends it
  weekly, stores the result in `data/content-bot/instagram-token.json`, keeps
  publishing with the previous token when the Graph API refuses, and notifies
  the operator once instead of failing quietly. `/instagram` and the console
  card show the remaining days and can force a refresh.
- The brand mark is now the LocalLab gradient pill (cyan to blue to violet to
  magenta over navy) with a halo, highlight sweep, hairline border, and hexagon
  mark, rendered with Pillow only. `MEDIA_STUDIO_BRAND_STYLE=chip` restores the
  previous flat chip, and the `video-edit` driver bakes the same mark into
  prepared clips through ffmpeg.
- Added the optional `ig-media` compose profile: nginx serves
  `data/content-bot/media` read-only on loopback while a cloudflared sidecar
  publishes it, so the stack provides its own public media host instead of
  depending on a hand-started process. `./manage.sh instagram-media-enable`,
  `-status`, and `-disable` manage it, and the bot resolves the public base URL
  from a pinned `media-base-url.txt`, a non-quick-tunnel
  `INSTAGRAM_MEDIA_PUBLIC_BASE_URL`, or the live tunnel log, rebuilding the
  Graph client when the hostname changes. Setting `IG_MEDIA_TUNNEL_TOKEN`
  switches the same profile set to a named Cloudflare tunnel, which keeps one
  permanent hostname on a domain you own instead of a random quick tunnel, and
  the quick tunnel now lives in its own `ig-media-quick` profile so nginx can
  stay up behind a reverse proxy (`./manage.sh instagram-media-tunnel-off`).
  Installing the content stack on a server can publish the media directory on
  a Caddy domain straight from the wizard, which needs no tunnel at all.
- Documented quick-tunnel versus stable media URLs, since a tunnel hostname
  that changes on restart breaks Instagram publishing with a confusing
  media-processing error.

- Section backups: `./manage.sh backup --only SECTION[,...]` archives only the
  named part of the stack (`env`, `secrets`, `hermes`, `router`, `content`,
  `media`, `panel`, `n8n`, `openwebui`, `s3`, `caddy`, `execution`, `state`,
  `compose`). Archives carry a manifest and a `.meta.json` sidecar with the
  section list, so `backup-list` shows what each archive contains and
  `restore` knows it must copy those paths without replacing the rest of the
  stack. `./manage.sh backup-sections` lists the names, and the maintenance
  menu gained a "create a section backup" entry.
- Restores are portable across servers: when an archive records a different
  checkout root, absolute `*_HOST_PATH`/`*_STACK_PATH` values in the restored
  `.env` are rewritten to the current checkout
  (`--no-relocate` disables it). Full archives still replace `.env` and
  `data/`; partial archives merge, keep a pre-restore safety backup, and put
  the previous state back when validation or readiness fails.
- `install.sh` now asks whether a reverse proxy on another host publishes the
  operator panel and suggests the detected LAN IPv4 address for the bind,
  which is exactly what a router-side reverse proxy reaches; the final summary
  prints the resulting URL and the proxy target. The same wizard already
  suggested the LAN address for the Instagram media host, and the Helm chart
  documentation repeats the pattern for cluster ingress.
- Added `tests/test-stack-ops.sh` section coverage (partial backup, partial
  restore, path relocation, `backup-list` metadata) and wired the file into
  the Validate workflow, which had not been running it.
- Helm chart review: the chart did not render at all (the escaped quotes in
  the router PostgreSQL and Redis URLs were an invalid template), fixed now.
  The chart gained an optional in-cluster 9router/OmniRoute upstream, optional
  Content Bot, operator panel, Media Studio, and media file server components,
  a generic ingress (with `ingress.tls: false` for an external reverse proxy),
  image pull secrets, and `helm lint`/render coverage in
  `tests/test-helm-chart.sh`. `examples/helm/content-stack-values.yaml` enables
  the whole stack as an overlay, and `docs/HELM.md` documents the values,
  component secrets, publishing the chart (GHCR OCI, GitHub Pages,
  ChartMuseum, other OCI registries, Artifact Hub), and the free registry
  options. `.github/workflows/publish-helm-chart.yml` and
  `scripts/helm-publish.sh` publish the packaged chart on release tags.
- Added `docs/BACKUP-RESTORE.md`: sections, full versus partial restores, the
  cross-server procedure, what to re-check after a restore, and why the
  single-host Compose stack does not need PostgreSQL (PostgreSQL and Redis
  exist for the multi-replica Kubernetes deployment).
- Object storage: the stack now has one shared S3 block
  (`S3_STORAGE_BACKEND=rustfs|external|off` plus the `S3_*` endpoint, bucket,
  region, and credential variables) that every consumer reads. The optional
  `rustfs` Compose profile runs RustFS - an Apache-2.0 S3-compatible server -
  in-stack with its S3 API on 9000, its console on 9001 under
  `/rustfs/console`, data under `data/rustfs/`, and an unprivileged identity
  pinned to the operator (root-managed installs keep 10001:10001). Point any
  other provider (AWS S3, Cloudflare R2, Backblaze B2, MinIO, Arvan S3, ...)
  at the same block with `./manage.sh s3-enable --external`. Open WebUI
  consumes it natively (`STORAGE_PROVIDER=s3` with path-style addressing);
  the Content Bot, Media Studio, n8n free tier, and Hermes Agent keep their
  local storage and the supported matrix is documented rather than implied.
  `./manage.sh s3-status`, `s3-enable [--rustfs|--external] [--bind-ip IP]`,
  `s3-disable`, `s3-verify [--create-bucket]` (signed requests, no cloud CLI),
  `s3-keys [--show-secrets|--rotate]`, `s3-guide`, a storage menu group,
  `logs rustfs`, and the `s3` backup section cover day-two operations.
  `install.sh` asks for the backend, generates or preserves the credentials,
  suggests the LAN address when a reverse proxy on another host publishes the
  API or console, and records the public origins. The Helm chart gained an
  optional `components.rustfs` (PVC, Deployment, Services for the API and
  console) and the ingress can publish it. `docs/S3-STORAGE.md` documents the
  consumer matrix, the configuration reference, the ArvanCloud-to-Caddy
  route, the security notes, and troubleshooting; `data/rustfs` is gitignored
  and `tests/test-s3-storage.sh` covers the configuration, the Compose
  rendering, and the manage.sh commands.
- Install-time public-route checklists: every wizard step that needs a domain
  now prints the two manual steps together where the decision is made (DNS A
  record to the router public IP plus the Caddy site block with the exact
  proxy target), the operator panel stores `PANEL_COOKIE_SECURE=true` when a
  public hostname is given, the Instagram media host records
  `INSTAGRAM_MEDIA_PUBLIC_BASE_URL` from the answer, re-runs that keep an
  existing bind address repeat the route reminder, and the final summary
  lists the DNS records to create for every selected hostname.
- The operator panel gained **Object storage** and **Backups** views, so the
  two features that only existed on the command line are now visible where the
  rest of the stack is managed. Object storage reports the shared `S3_*` block,
  the per-service consumer matrix, and the bundled RustFS state, with **Run
  status** and **Verify endpoint** buttons for `s3-status` and `s3-verify`
  (the verify runs against the in-network endpoint the containers use, not the
  host loopback value). Backups lists the archives with their `.meta.json`
  metadata - creation time, full or partial, section list, stack version - and
  **Create backup** runs `manage.sh backup --label panel --no-pause` after a
  confirmation, and a **Partial backup** picker whose section names come from
  `./manage.sh backup-sections`, so `backup --only SECTION[,...]` is available
  from the console as well (the action takes the section list as a validated
  parameter, and the panel builds a command only for names the CLI publishes).
  Because the panel runs as the operator uid and cannot read
  every data directory, that action starts a throwaway container from the
  panel image with the Docker socket, checkout, and backup directory mounted,
  runs the same `manage.sh backup` inside it, and hands the `hermes-stack-*`
  files back to the backup directory owner (stack-ops writes them as `0600`
  because they carry `.env` secrets). `panel-enable` now also creates the
  `<checkout>-backups` directory and mounts it, the panel image installs GNU
  `tar` for the archive step, `manage.sh s3-verify` accepts an endpoint
  override so a caller inside the stack network can test the in-network
  endpoint, and `docs/PANEL.md` documents both views.
- The panel's **Media jobs** card now dials the Compose service address
  (`media-studio:<MEDIA_STUDIO_PORT>`) instead of the published host bind, which
  a process inside the stack network cannot reach; `MEDIA_STUDIO_INTERNAL_URL`
  overrides the derived address.
- The README opens with five operator-console screens captured from the panel
  image against a seeded demo stack (`docs-site/assets/content-console-*-v0.3.0.png`):
  stack status, pipeline state, object storage, backups, and the action
  whitelist.
- Release pipeline fix: the Helm publish workflow built its GHCR destination
  from `github.repository_owner` (`Afsharidevops`), and the OCI distribution
  spec requires a lowercase repository path, so the release tag failed with
  `invalid_reference: invalid repository`. The workflow now lowercases the
  owner before `helm push` and refuses an uppercase reference outright, and
  `scripts/helm-publish.sh` lowercases `--registry` the same way.
  `tests/test-helm-chart.sh` covers both paths. The workflow also attaches the
  packaged chart to the GitHub release for the tag, which is what
  `docs/HELM.md` already described, and skips the attachment when the tag has
  no release.

### Fork release — Content Manager v0.2.0 (2026-09-09)

- Added `media-studio/`: a single-worker media job service with API image
  generation through the selected writer gateway and optional Google
  Flow/Gemini drivers driven over CDP. The `locallab-flow-unlock` extension
  keeps `flow.google.com` usable on a laptop without stack components, and
  `MEDIA_STUDIO_BLOCK_GEO_REDIRECT` / `MEDIA_STUDIO_FREEZE_ON_READY` keep a
  container profile usable where Flow enforces the unsupported-country
  redirect. Flow credit prompts are auto-approved and the download quality
  selector is calibrated automatically.
- The Content Bot accepts topics as well as links: a topic is searched and
  drafted by the writer, and proposals can attach AI-generated or
  operator-supplied images plus a video prompt flow through Media Studio.
- Unified media publishing posts the draft and its media together with
  caption continuation when the post exceeds one Telegram message, keeps the
  title bold and RTL-safe, appends the source link once at the end, and
  repairs mojibake writer replies.
- A configurable corner brand chip (`MEDIA_STUDIO_BRAND_TEXT`) is stamped on
  AI-generated and operator-uploaded images before approval.
- `main` now supports both router backends (`9router` and `omniroute`) as
  Compose profiles with install-time selection and backend switching; the
  `hermes-omniroute-linux-stack` branch is obsolete. Installer options 5/6/7
  keep the standalone and combined content pipeline router-free.
- Component images advance to `0.2.0`: `afsharidevops/content-bot:0.2.0` and
  `afsharidevops/media-studio:0.2.0` (plus `:latest`).

### Features — scheduled per-platform routines (2026-09-10)

- `editorial-policy.yaml` gains an optional `routines:` list. Each enabled
  routine runs one research -> draft -> media -> queue pass per cadence period
  (daily at `time`, or weekly on or after `weekday`), drafts up to `count` of
  the ranked candidates, and drops them into the Telegram approval queue as
  "Scheduled proposal" drafts.
- `media: auto` submits one AI image per queued draft through Media Studio and
  attaches it when the job finishes; `media: none` queues text only. Drafts
  are still queued when Media Studio is not configured, and a failed image job
  is reported to the operator (scheduled drafts have no media question).
- Run markers live in `data/content-bot/state.json` under `routine_last_run`,
  so a restart never repeats a period; `/status` and `/help` report the active
  routines. Routines share the discovery feeds, dedupe/filter/scoring rules,
  and the category-mix rule with the daily proposal; the shipped default is
  `routines: []` and the daily flow is unchanged.

### Features — operator panel for the stack (2026-09-10)

- Added `panel/`: an optional operator console served by the new `content-panel`
  container (Compose profile `panel`). It reads stack status, health, image
  tags, published host ports, `data/` disk usage, pipeline counters, drafts, and
  Media Studio jobs, and warns when an internal endpoint such as n8n/MCP is
  published beyond loopback.
- The console edits the four managed configuration files through validated
  editors that keep a timestamped backup before every atomic write and can
  restore a previous version. `.env` keys are editable in place with secret
  values masked, and state-changing requests require a CSRF header on top of the
  HMAC-derived HttpOnly session cookie.
- Actions are a fixed whitelist (apply changes, restart Content Bot / Media
  Studio / Smart Router, pull published images, status summaries, doctor) and
  never accept a free-form command; `PANEL_ACTIONS_ENABLED=false` serves
  read-only views.
- The panel is a separate container on purpose: the Content Bot image keeps its
  read-only root filesystem and unprivileged uid, while the panel mounts the
  Docker socket and writes repository files as the stack owner. It is bound to
  loopback by default, is enabled or disabled with `./manage.sh panel-enable` /
  `panel-disable`, and stores its operator token in `data/panel/token`.
- `.env.example`, `docker-compose.yml`, `install.sh`, and `manage.sh` carry the
  new `PANEL_*` settings, the panel menu, and the `panel-*` commands;
  `.github/workflows/publish-panel.yml` publishes the image and `docs/PANEL.md`
  documents the security model, remote-access guidance, and the configuration
  reference.

### Features — shared tool registry for the bot, router, and n8n (2026-09-10)

- `content/config/tools.json` is the single source of truth for the remote
  tools the content stack calls (MCP servers, OpenAPI services, plain HTTP
  endpoints). Each entry lists the kind, address, capabilities, allowed
  consumers, and the *name* of the environment variable that holds the
  credential; secrets are never stored in the file, and `${ENV_NAME}`
  placeholders are substituted by each runtime, so one registry works for a
  combined server and for a split deployment.
- The Content Bot loads the registry from its policy directory and exposes
  `/tools` (entries for the bot with credential state and warnings) plus a
  one-line summary in `/status`. A missing or invalid file never stops the
  bot.
- The Smart Router serves `GET /v1/tools` behind the same client auth as
  `/v1/models`, read from `SMART_ROUTER_TOOLS_REGISTRY`; a broken file answers
  with `tools_registry_invalid` and HTTP 500.
- The n8n bootstrap reads the same file (mounted at `/tools` by
  `./manage.sh bootstrap-n8n` or the `N8N_TOOLS_REGISTRY` override) and adds a
  tool summary with the count of missing credential variables to its
  reconcile result.
- Media Studio publishes `GET /openapi.json` (no token, no secrets) so the
  registry entry can point at a live spec, including the `video-edit` driver
  and `POST /uploads`.
- `docs/TOOL-REGISTRY.md` documents the schema, the per-consumer wiring, and
  how to add a tool; `.env.example`, `docker-compose.yml`, `install.sh`, and
  the CI validation workflow ship the new file and settings.

### Features — edit or publish an operator-sent video (2026-09-10)

- When the operator sends a recorded clip to the bot, the media question now
  asks **Edit it** or **Publish as-is** instead of attaching the file
  straight away. **Publish as-is** keeps the recording untouched; **Edit it**
  uploads the clip to Media Studio and runs the new offline `video-edit`
  driver, which re-encodes it to an MP4 with H.264/AAC and faststart, caps
  the long side at 1920 px with the aspect ratio preserved, fixes rotation,
  strips container metadata, and can trim with
  `MEDIA_STUDIO_VIDEO_EDIT_MAX_SECONDS`.
- Media Studio gained `POST /uploads` (raw video bytes in, upload id out),
  the `video-edit` driver and registry entry, and ffmpeg resolution that
  prefers `MEDIA_STUDIO_FFMPEG`, then a system binary, then the
  `imageio-ffmpeg` wheel, so the image needs no extra system package.
  Stored uploads are pruned after `MEDIA_STUDIO_UPLOAD_TTL_SECONDS`.
- A failed edit job never loses the recording: the original file is restored
  as the draft media, the bot explains the failure, and **Approve** publishes
  the clip unchanged. Clips above the 20 MB Bot API limit stay on Telegram
  and skip the question because they cannot be copied into Media Studio.
- `CONTENT_MEDIA_VIDEO_EDIT_DRIVER`, the `MEDIA_STUDIO_VIDEO_EDIT_*` set,
  `MEDIA_STUDIO_UPLOAD_TTL_SECONDS`, and `MEDIA_STUDIO_FFMPEG` are documented
  in `.env.example`, `docker-compose.yml`, and `docs/MEDIA-STUDIO.md`.

### Changes — manual platform packages are opt-in (2026-09-10)

- The copy-ready upload packages (YouTube, Aparat) and their **More
  platforms...** button are hidden unless `CONTENT_PLATFORMS_ENABLED=true`, so
  the live bot stays on Telegram and Instagram only. The packages, the
  `platforms:` policy section, and their callbacks are unchanged behind the
  switch; a callback that arrives while it is off answers with the setting
  name instead of sending a package.
- `.env.example`, `docker-compose.yml`, and
  `docs/CONTENT-PRODUCTION-GUIDE.md` document the switch; the shipped default
  is off.

### Features — Firefox build and ZIP packages for Flow Unlock (2026-09-10)

- `extensions/locallab-flow-unlock-firefox/` is the Flow unlock for Firefox
  140+ as an independent package: the same page-world response patch and
  freeze guard as the Chrome extension, with a Firefox MV3 manifest (event
  page instead of a service worker) and a blocking `webRequest` listener that
  sends a navigation on the unsupported-country route back to the app root of
  the same account and cancels other resources on that route, replacing the
  `declarativeNetRequest` rule set. The package lints clean for AMO.
- `extensions/package-flow-unlock.sh` builds
  `locallab-flow-unlock-chrome-<version>.zip` and
  `locallab-flow-unlock-firefox-<version>.zip` with a per-browser README, so a
  machine can install the unlock without the repository layout. Builds use
  fixed timestamps and are byte-identical for unchanged sources.
- `tests/test-flow-unlock-rules.sh` now validates both manifests and keeps the
  shared scripts byte-identical across the two packages;
  `tests/test-flow-unlock-package.sh` builds the ZIPs on every CI run and
  checks their layout.

### Fixes — oversized video uploads stay on Telegram (2026-09-10)

- A video above the 20 MB Bot API download limit no longer fails with
  `Could not download the file: Telegram getFile HTTP 400`. The bot keeps the
  Telegram `file_id` instead of the bytes, re-sends the clip as the media
  preview, and publishes it to the channel by id, so a reel created in Flow or
  on a phone can be attached without re-encoding it.
- Files kept on Telegram live there only: Instagram approvals and the platform
  upload packages need the file itself and now say so, asking for a copy under
  20 MB instead of failing silently or shipping a package without media.
- Bot API failures include Telegram's own description (for example the
  `getFile` limit message) instead of only the HTTP status.

### Improvements — friendlier copy and RTL-safe captions (2026-09-10)

- The writer prompt now asks for a warm, conversational, second-person voice
  with short everyday sentences and explicitly rules out formal, official, and
  news-desk phrasing, so drafts read like a note from a friend.
- Instagram captions start with an invisible right-to-left mark and mark every
  Latin-led paragraph. Instagram renders the account name inline before the
  caption, and without the mark a Latin account name flips the first paragraph
  to LTR and scrambles the Persian title. Telegram and Instagram now share the
  same bidi helpers in `content_bot/rtl.py`.
- Writer titles keep the English product name inside the sentence; the prompt
  already required a Persian opening word and now enforces it together with the
  friendlier voice.

### Improvements — segmented video prompt package and character choice (2026-09-10)

- "My video (get a prompt)" now builds a reel package instead of one prompt:
  a scene setting and the exact Persian narration line per ten-second
  segment, ready for the Google Flow Extend flow. Short packages arrive in
  one message and longer ones as one copy-ready message per segment.
- The flow asks whether the reel should use the saved Flow character
  (`CONTENT_VIDEO_CHARACTER`, for example `@mohammad`) or run as a pure-AI
  promo with no character. Character reels repeat the character block on
  every segment, AI promo reels describe the shot only and list the Persian
  line as an optional voiceover.
- `CONTENT_VIDEO_CHARACTER_PROMPT` overrides the built-in character
  description, `CONTENT_VIDEO_ASPECT` sets the reel frame (default
  `9:16 vertical`), and `CONTENT_VIDEO_SEGMENT_SECONDS` sets the clip length
  Flow generates per segment (default 10). The writer drafts the narration
  lines and falls back to the post text when the writer backend is down.
- Video script requests use a larger token budget (at least 2600) with low
  reasoning effort; reasoning-heavy routers otherwise spend the whole budget
  on hidden reasoning and return empty content, which silently pushed every
  reel package back to the post-text fallback.

### Fixes — Flow unlock for multi-account URLs and long-poll timeouts (2026-09-10)

- The `locallab-flow-unlock` rules only matched the bare
  `/unsupported-country` path. With several Google accounts signed in the
  block page is reached through `/u/<n>/unsupported-country`, which the rules
  missed; the rule set now covers the account-scoped paths, `flow.google-*`
  hosts, and the `labs.google` tool path, and the companion filter file was
  extended the same way. The content script also watches the address bar and
  restarts the app root (at most twice per tab) when the web app swaps the
  route client-side, where no request exists for the rule set to block.
- The recovery navigation is no longer cancelled by the freeze that targets
  the block page: scheduled `window.stop()` calls are cleared before bouncing,
  which previously left the tab sitting on the block view.
- New `background.js` service worker: when a blocked navigation still strands
  a tab on Chrome's `ERR_BLOCKED_BY_CLIENT` page, the tab is returned to the
  app root up to twice a minute. The block view itself is server-decided for a
  signed-in session, so the extension can only avoid the dead end, not bypass
  the verdict.
- Recovery navigations keep the `/u/<n>/` account prefix. Restarting the app
  on the bare root silently fell back to the first signed-in Google account,
  which looked like the browser switching accounts on its own.
- The create-button matcher used by the freeze step is back to its original
  form: the wider selectors added earlier could fire the freeze during the app
  bootstrap and abort data the dashboard needs.
- The Content Bot polling loop survives transient network failures: Telegram
  long polls that time out through a slow route are logged as a warning and
  retried after a short backoff instead of dumping a full traceback into the
  container log. The long-poll HTTP read timeout is now derived from the poll
  timeout instead of a fixed 35 seconds.

### Fixes — Flow app-config response patch (2026-09-10)

- The `locallab-flow-unlock` extension no longer depends on winning a race
  against the region check. Flow decides the unsupported-country verdict inside
  the page, from two batchexecute answers, and both are now rewritten in
  flight: `VideoFxService.GetFlowAppConfig` (`cPZSdc`) carries the country flag
  in field 31 and the age flag in field 32, `AiSandbox.CheckToolAvailability`
  (`KV2T2d`) carries the per-tool status in field 1. Only those fields change
  and the rest of the answer passes through untouched.
- The patch runs in the page world (`unlock.js`, `world: MAIN`, loaded at
  `document_start`) and handles both encodings the backend uses, the JSON
  protocol-buffer form and the base64 one, so the dashboard renders for an
  account the Flow backend marks as an unsupported country.
- `freeze.js` stands down as soon as the config answer is known to be patched
  (the page carries a `data-locallab-flow-config="patched"` marker), which
  keeps the freeze a fallback for tabs where the patch did not apply.
- The declarative rules are anchored to the `/unsupported-country` path
  (`regexFilter`) instead of matching the string anywhere in the URL. The old
  wildcard filters also blocked the app's own `batchexecute` calls, because
  their `source-path` parameter contains the blocked route.
- Rebuilt answers keep the batchexecute framing valid: each frame's length
  token counts the JSON plus the newlines around it in UTF-16 units (not
  bytes) and moves with the frame, and the closing `e` frame's value is
  re-computed to the byte size of the rewritten answer, so the page accepts a
  patched response instead of staying on the block route.
- `tests/test-flow-unlock-patch.mjs` covers both payload encodings, the
  omitted-field case, the length-token and closing-size framing, and the
  pass-through of unrelated RPCs; the rules test now also guards the second
  content script and the anchored filters.

### Fixes — Instagram API version default (2026-09-10)

- The default Graph API version moves from `v23.0` to `v26.0`, matching the
  version the Meta dashboard now shows. Live checks against
  `graph.instagram.com` confirm `v26.0` is the newest accepted version
  (`v27.0` is rejected as unknown) and that account lookup, media listing,
  and publishing all keep working on it.

### Fixes — Instagram approval flow and Media Studio reachability (2026-09-10)

- Media previews now carry the Instagram approval buttons: photo and video
  previews show them inline, and albums (user photo collections) send a
  follow-up message with the same buttons, because Telegram albums cannot hold
  an inline keyboard.
- The photo-collection preview offers the **Done** button that its own ask
  message describes; every upload replaces the previous preview and both
  preview messages are removed when a draft is replaced, rejected, or
  published.
- `Approve to Instagram` publishes to Instagram only. Previously it also posted
  to the Telegram channel, which made it identical to the combined button, and
  `content_bot/bot.py` never imported the Instagram module, so every Instagram
  approval failed with a `NameError`.
- The callback query is acknowledged before the slow Instagram publish instead
  of after it, so Graph API processing can no longer fail the update with a
  stale callback id.
- Media Studio listens on every container interface so `content-bot` can reach
  `http://media-studio:8850`; the published host port still follows
  `MEDIA_STUDIO_BIND_IP`.

### Improvements — platform upload packages (2026-09-10)

- Draft and media previews gained a **More platforms...** button that opens a
  chooser for platforms that need a human upload step. Picking a platform
  sends a copy-ready package: the title cut to the platform limit, the
  description with the source link and hashtags, the direct upload URL, and
  the stored media file re-sent for a quick download.
- Packages are driven by the `platforms:` section of `editorial-policy.yaml`:
  YouTube and Aparat ship as defaults, any key can be overridden, added, or
  set to `null` to hide its button. Telegram and Instagram keep publishing
  through their APIs; a package never consumes the draft or counts toward the
  daily limit.

### Fork additions — content layer v0.1.0

- Added the `content/` Python layer for deterministic discovery normalization,
  deduplication, filtering, and candidate scoring; see `content/README.md`.
- Added owner-editable policy under `content/config/` (`editorial-policy.yaml`,
  `categories.yaml`). Persian copy is produced at runtime, never stored.
- `install.sh` now provisions the Content Manager workspace: it seeds a
  gitignored working copy of the policy under `data/content-manager/config`,
  defaults the n8n timezone to `Asia/Tehran`, and reports the content layer at
  the end of install.
- Quickstart and installer identity now point at this repository.

### Fork additions — Media Studio worker v0.1.0 (2026-09-09)

- Added `media-studio/`: a single-worker job service with a JSON API on
  `127.0.0.1:8850`, JSON-backed job state, artifact storage, and pluggable
  drivers.
- Added the `api-image` driver (OpenAI-compatible `/images/generations`) so
  media generation works with the existing router or hosted writer API,
  without any Google subscription.
- Added Google drivers (`flow-video`, `gemini-image`) that drive a signed-in
  Chrome over CDP (operator desktop) or a persistent container profile; both
  support the Flow unsupported-country region workaround
  (`MEDIA_STUDIO_BLOCK_GEO_REDIRECT`, `MEDIA_STUDIO_FREEZE_ON_READY`).
- Added `extensions/locallab-flow-unlock/`: a standalone Chrome extension and uBlock
  filter that keep `flow.google.com` usable on a laptop without any stack
  component (`docs/FLOW-UNLOCK-STANDALONE.md`).
- `install.sh` gained a Media Studio wizard and `--media-reconfigure`;
  `manage.sh` gained the `media` menu group and `media-status`,
  `media-guide`, and `media-configure` commands; the compose profile is
  `media` with image `afsharidevops/media-studio:0.1.0`.
- Documentation: `docs/MEDIA-STUDIO.md` (install, sessions, API, selector
  calibration) and `docs/publishing/MEDIA-STUDIO-DOCKERHUB.md`.

### Fork additions — selectable router backend: 9router or OmniRoute (2026-09-09)

- `install.sh` now asks on fresh installs whether to use the `9router` or the
  `omniroute` backend profile and can switch an existing install between them
  without losing data; the `hermes-omniroute-linux-stack` branch is obsolete.
- Added the OmniRoute Compose service (dashboard on 20128, OpenAI-compatible
  API on 20129, profile `omniroute`) and a backend-agnostic
  `router-upstream-probe` one-shot that waits for the selected backend before
  the Smart Router starts.
- OmniRoute installs default the Smart Router route profiles, observe model,
  n8n hosted-chat model, Hermes provider, and Open WebUI connection to the
  `auto/best-*` aliases (`http://omniroute:20129/v1`); 9router installs keep
  the `combo-*` defaults. Switching backends resets Smart Router upstream
  URLs, route profiles, and stale upstream keys.
- `manage.sh` n8n provisioning now creates, stores, and validates a dedicated
  OmniRoute API key for the hosted-chat router credential; `set-router-mode`,
  `set-backend-api-key`, `verify-n8n`, `reconcile-n8n`, and the n8n MCP
  commands all work with either backend.
- The standalone pipeline options (5/6/7) and the combined Content Bot +
  Media Studio wiring described below are unchanged and remain router-free
  when no backend is installed.

### Fork additions — combined standalone content pipeline install (2026-09-09)

- `install.sh` fresh-install option 7 installs the Content Bot and Media
  Studio together as one content pipeline with no router, Hermes, n8n, or
  Open WebUI. Options 5 and 6 ask whether the other pipeline service should
  be added too, then walk through both configuration wizards in one run.
- A combined install writes the Content Bot -> Media Studio link into `.env`:
  `CONTENT_MEDIA_STUDIO_URL=http://media-studio:8850`, the Media Studio API
  token mirrored into `CONTENT_MEDIA_STUDIO_TOKEN`, and image/video driver
  names that match the enabled Media Studio drivers. Reconfiguring either
  service keeps the link in sync, so a rotated Media Studio API token is
  propagated to the Content Bot.
- `manage.sh` gained `pipeline-status`: a combined status view of the Content
  Bot, Media Studio, and their API link (URL, token sync, driver coverage).
- Documentation: `docs/CONTENT-PRODUCTION-GUIDE.md`, `docs/MEDIA-STUDIO.md`,
  and the README describe the one-server content pipeline install.

### Fork additions — Content Bot Telegram MVP (2026-09-07)


- Added `content/content_pipeline/score.py`: deterministic weighted scoring,
  configured penalties, freshness mapping, and candidate ranking.
- Added `content/config/sources.yaml`: seeded RSS/Atom discovery template for
  the daily run.
- Added the `content-bot/` service (Compose profile `content`): Telegram
  long-polling bot with on-demand link drafts, Approve/Reject inline buttons,
  channel publishing, per-day caps, duplicate-post protection, and a daily
  editorial scheduler driven by the working-copy policy.
- `install.sh` now offers the Content Bot interactively, asks for the bot
  token, operator IDs, publish channel, and writer model, and provisions the
  unprivileged container, secrets, and state directory automatically.
- `manage.sh` gained `content-status`, `content-connect-instagram`, the
  interactive Content Bot group, and `./manage.sh logs content`.
- Added `.github/workflows/publish-content-bot.yml`: pushing Content Bot
  source to `main` builds and publishes
  `afsharidevops/content-bot:0.1.0` (plus `:latest`) to Docker Hub.
  `install.sh` now pulls that published image instead of building the
  container locally, and the daily scheduler honors `daily_proposal_time`
  from `editorial-policy.yaml`.
- Added `docs/CONTENT-PRODUCTION-GUIDE.md` and
  `docs/INSTAGRAM-SETUP.md`; Instagram remains a guided pending checklist.

### Fork additions — editorial loop and standalone deployment (2026-09-09)

- Draft behavior is now policy-driven: on-demand freshness and the daily
  proposal cap are read from the working-copy editorial policy, and
  operator-sent links are never blocked by the freshness window or the daily
  cap.
- Added the reply-based revision loop: replying to a proposal with edit notes
  and pressing Reject revises the draft in place; pressing Reject without
  notes discards it. Empty or unchanged writer revisions are surfaced to the
  operator instead of re-posting the original draft.
- Draft titles render bold with RTL-safe ordering, and daily feed parsing is
  hardened to HTML titles and links instead of raw page text.
- Seeded `content/config/sources.yaml` with vetted RSS/Atom feeds per category
  and an in-file guide for adding and pre-testing new feeds.
- Added standalone Content Bot deployment: installer option 5 provisions only
  the Content Bot against an external OpenAI-compatible writer API (base URL,
  key, model), and `./install.sh --content-reconfigure` or
  `./manage.sh content-configure` reconfigures it while preserving existing
  components and data.
- Writer calls tolerate null or truncated responses and honor
  `CONTENT_WRITER_MODEL=auto`, `CONTENT_WRITER_MAX_TOKENS`, and
  `CONTENT_WRITER_REASONING_EFFORT`.
- Added the operator `/forget-link <url>` command (plus `/forget_link` alias)
  so a previously published link can be drafted again; the Telegram command
  menu is registered on startup in the default and private-chat scopes.

### Fork additions — media handoff, uploads, RTL-safe copy (2026-09-09)

- The media question after a draft now offers four choices: text only, an
  AI-generated image, a photo the operator uploads, or a ready-to-use video
  prompt so the operator creates the clip elsewhere and uploads it.
- Upload handling is guarded per draft: a Cancel button, wrong-kind rejection,
  size checks, and blocked new drafts while an upload is pending; uploaded
  files are stored locally, and uploaded photos are branded by Media Studio
  like AI images while uploaded videos stay unchanged.
- Media preview buttons now match the media source: retry options for
  AI-generated media and Approve/Reject options for operator uploads.
- Mixed Persian/Latin post bodies render RTL-safe in Telegram: every line that
  needs it carries a right-to-left mark, and writer prompts require paragraphs
  to open with a Persian word so Latin product names never flip the line.
- Writer replies that arrive as legacy-charset mojibake are repaired
  automatically; replies that cannot be repaired are refused instead of being
  published as garbage.

### Fork additions — configurable corner brand chip (2026-09-09)

- Added `media-studio/media_studio/branding.py`: a translucent rounded chip
  with the configured brand label is drawn in a corner of every raster
  artifact produced by an image driver. The chip is disabled when the label
  is blank, and jobs can opt out with `"params": {"brand": false}`.
- Media Studio exposes `POST /brand` (raw image bytes in, branded bytes out);
  the Content Bot calls it for every uploaded photo so operator media carries
  the same brand as AI images. Branding failures never fail a job or an
  upload; the original file is kept.
- Configuration: `MEDIA_STUDIO_BRAND_LABEL` (default `Locallab`, blank
  disables) and `MEDIA_STUDIO_BRAND_POSITION` (default `bottom-right`, also
  `bottom-left`, `top-right`, `top-left`), wired through `docker-compose.yml`
  and `.env.example`.
- Media Studio gained Pillow as a runtime dependency for the overlay.

---

## Hermes Linux Stack — v0.5.9 Changelog — 2026-08-13

### v0.5.9 release focus

Visual Flow Connections across Workflow, Agent, Router Pipeline, and Knowledge Pipeline studios, while preserving Hermes execution trust boundaries.

### v0.5.9 release acceptance

Automated candidate, API/persistence, regression, Compose, manifest, and full Smart Router smoke validation passed during release preparation. The release owner explicitly requested early finalization and waived the remaining manual browser light/dark rendering and mouse/trackpad interaction gate. The waived checks are not represented as passed.


- Fixed direct Users, Policies, and Plugins create APIs returning HTTP 500 after commit because audit logging dereferenced expired SQLAlchemy rows outside their sessions.

- Added the v0.5.9 visual-flow implementation work without changing the runtime release version.
- Updated Hermes stack plugins for the current plugin registration API.
- Persist Telegram enablement for `stack-execution-policy` and `stack-package-policy` during installation.
- Updated `manage.sh` runtime diagnostics to verify the current stack plugin toolset names.
- Added regression coverage for Telegram plugin-toolset persistence and runtime registration.
- Implemented v0.5.9 Phase 1 shared graph contract for Workflow and Knowledge studios: port-aware edges, legacy graph normalization, typed compatibility, named outputs, drag-to-connect preview/target validation, selectable/deletable edges, dirty state, and server-side duplicate/self/cycle checks.
- Completed the v0.5.9 visual-flow development candidate across all four studios: Agent and Router shared-canvas integration, persisted agent composition graphs, legacy graph synthesis, named Router branch outputs (classifier/condition/capability/health/approval), branch-aware runtime traversal, edge reconnection, drop-on-empty quick add, undo/redo, pan/zoom/fit controls, keyboard/pointer accessibility, explicit save states, and backward-compatible port-aware persistence.
- Preserved the execution trust boundary: visual orchestration and Telegram UX do not grant Docker, SSH, signing-key, or Execution Admin authority.

---

## Hermes Linux Stack — v0.5.8 Changelog

### Release focus

v0.5.8 is the visual-building and operator-UX release for the 9router branch. It keeps the v0.5.7 trust-separated execution design while making browser connectivity failures diagnosable and adding visual studios for workflows, agents, router pipelines, and knowledge pipelines.

### Execution & Approvals reliability

- Execution Broker target advances to `0.1.3`.
- The Operations Center now probes the Execution Admin `/health` endpoint before attempting key authentication. Network/bind/CORS failures are reported separately from key failures.
- Added `./manage.sh configure-execution-admin-browser ORIGIN [PRIVATE_IPV4]` to configure a private bind plus exact browser origin without printing the Execution Admin key.
- Execution Admin CORS preflight supports the Private Network Access response header for explicitly allowed origins.
- The key remains browser-memory-only and is still sent directly from the operator browser to Execution Admin; Smart Router does not receive it.
- Wildcard/public bind shortcuts are intentionally refused by the helper.

### Operations Center redesign

- Reworked dark and light palettes around semantic surface/text/border tokens.
- Reorganized navigation into Observe, Build, Tools, Routing, Access, and System.
- Added Dify-inspired visual interaction patterns without copying Dify branding or assets.
- Added Workflow Studio with draggable nodes, visible edges, node inspector, references, and save/edit lifecycle.
- Added Agent Studio with visible Input → Knowledge → Agent → Tools → Answer path and configuration inspector.
- Added Router Pipeline Studio for conditions, capability/health filters, scoring, load balance, route, retry, fallback, and approval stages.
- Added Knowledge Pipeline Studio and persistent `v58_knowledge_pipelines` registry.
- Added Publish & Monitor workspace that links API, Flight Deck, agent testing, and runtime status.
- Improved Flight Deck light-mode surfaces, sidebar, controls, and status pills.

### Data and API

- Smart Router runtime/control schema marker advances in place to `0.5.8`.
- Compatibility database filename remains `control-v0.5.2.sqlite3`; persistent Operations Center state is upgraded in place.
- New Operations API routes:
  - `GET/POST /control/api/knowledge-pipelines`
  - `PUT/DELETE /control/api/knowledge-pipelines/{id}`
- Knowledge pipeline graph validation accepts only managed ingestion/indexing node types and validates node IDs, edge references, and graph limits.

### Image targets

- Smart Router: `afsharidevops/hermes-smart-router:0.5.8`
- Execution Broker: `afsharidevops/hermes-execution-broker:0.1.3`
- Intended platforms: `linux/amd64`, `linux/arm64`

### Validation performed in packaging environment

- Smart Router pytest suite: 96 passed per branch.
- Execution Admin focused unittest suite: 5 passed per branch.
- `manage.sh` UX tests: passed per branch.
- `bash -n` for `manage.sh` and `install.sh`: passed.
- Python compilation for modified Smart Router UI/control files: passed.
- Operations Center JavaScript syntax (`node --check`): passed.

The broader root Python test discovery also contains environment-dependent tests requiring `ssh-keygen` and a fully usable npm prefix. Those checks could not complete in this packaging environment and should be run in the normal CI/release host before production publication.

### Security invariant

UI convenience must not collapse execution authority into Smart Router. Execution Admin continues to exclude the Ed25519 approval-signing private key, Docker socket, and SSH private credentials, while Smart Router continues to exclude the Execution Admin key and execution approval secrets.

### Post-release private-ingress hotfix

- Execution Admin now joins a dedicated `execution-admin-ingress-net` in addition to the internal `execution-control-net`.
- The control network remains `internal: true`; only Execution Admin receives the host-ingress bridge.
- This allows `${EXECUTION_ADMIN_BIND_IP}:${EXECUTION_ADMIN_PORT}:8752` to be actually published on Docker while preserving broker isolation.
- Smart Router and Execution Broker application images are unchanged by this Compose-only networking fix.
- Smart Router publishing documentation/workflow now uses only the plain version tag and `latest`; no `v<version>` or SHA alias is emitted.
- `MANIFEST.sha256` is regenerated from repository-tracked release files, removing stale ignored cache entries that are not present in a fresh clone.

---

## Hermes Linux Stack — v0.5.7 Changelog

### Focus

v0.5.7 integrates secure execution administration with the v0.5.6 Operations Center without collapsing the execution-broker trust boundaries.

### Execution & Approvals

- Added **System → Execution & Approvals** to Hermes Operations Center.
- Added `execution-admin` mode to Hermes Execution Broker `0.1.2`.
- Operations Center talks directly from the operator browser to the separate Execution Admin endpoint with a separate admin key.
- Smart Router backend does not receive the Execution Admin key or dedicated Telegram approval-bot token.
- Execution Admin does not mount the Ed25519 approval signing key, Docker socket, or SSH private credentials.
- Added live redacted health for approver, Docker broker and SSH broker.
- Added live enable/disable policy for already-deployed `local`, `docker`, and `ssh` execution capabilities.
- Added Telegram execution approver management, constrained to IDs already present in `TELEGRAM_ALLOWED_USERS`.
- Added write-only dedicated approval-bot token replacement. The token is never returned by the API.
- Added protection preventing the execution approval bot from reusing the Hermes Telegram bot token when the Hermes token hash is synchronized.
- Added broker control-secret rotation from the separate admin boundary.
- Added redacted SSH profile listing without exposing private keys/passwords.
- Added execution-admin audit events.
- Every execution policy/admin mutation increments the policy generation, invalidating older pending capabilities/approvals.

### Dynamic execution policy

- Added `EXECUTION_FEATURES_FILE` support to the Hermes execution policy plugin and execution brokers.
- Added `EXECUTION_POLICY_GENERATION_FILE` support to Docker, SSH and approver modes.
- Policy files are bind-mounted and rewritten in place to preserve host ownership and permissions.
- Existing environment variables remain compatibility fallbacks.
- First-time broker deployment remains a host `manage.sh` operation; the UI does not need Docker-socket authority.

### Management commands

Added:

```text
./manage.sh enable-execution-admin
./manage.sh disable-execution-admin
./manage.sh execution-admin-status
./manage.sh show-execution-admin-key
./manage.sh rotate-execution-admin-key
```

The existing execution commands remain supported.

### Security defaults

- Execution Admin binds to `127.0.0.1:8752` by default.
- Browser CORS uses an exact allowlist (`EXECUTION_ADMIN_ALLOWED_ORIGINS`); wildcard origins are not used.
- The admin credential is separate from Smart Router authentication.
- Operations Center keeps the Execution Admin key only in page memory; it is not written to localStorage.
- Bot token readback is not implemented.
- Signing-key rotation and SSH credential creation/removal remain local operator/CLI operations.

### v0.5.6 platform foundations retained

v0.5.7 retains the v0.5.6 light/dark UI, lifecycle fixes, hybrid vector RAG/pgvector support, Flight Deck traces, guardrails, router pipelines, workflows, prompt versioning, datasets/evaluations, model catalog, marketplace/onboarding and HA foundations.

### Compatibility

- Smart Router runtime/schema marker: `0.5.7`.
- Existing `control-v0.5.2.sqlite3` compatibility filename is preserved and upgraded in place.
- Smart Router image target: `afsharidevops/hermes-smart-router:0.5.7`.
- Execution Broker image target: `afsharidevops/hermes-execution-broker:0.1.2`.
- Branches: `main` (9router) and `hermes-omniroute-linux-stack` (OmniRoute).

---

## Hermes Linux Stack v0.5.6 — Platform Foundations

v0.5.6 advances Hermes from an operator-focused Smart Router into a broader self-hosted AI infrastructure platform while preserving the existing `control-v0.5.2.sqlite3` compatibility filename and explicit-model pass-through behavior.

### Major additions

- Light/dark themes in Hermes Operations Center and Flight Deck, persisted in the browser.
- Consistent lifecycle UX: Agents, Teams, and Groups use reversible Enable/Disable controls; permanent deletion is a separate destructive action. Group purge protects ACL references unless explicit cascade deletion is requested. Plugins and Skills have explicit enable/disable and permanent removal controls.
- Hybrid Knowledge/RAG with lexical + vector retrieval, embedding indexing, score fusion/reranking, PostgreSQL `pgvector` acceleration where available, and a deterministic portable vector fallback for offline/development use.
- Full request trace records for request/auth/authorization/guardrails/RAG/classification/routing/retry/fallback/result stages, visible in Flight Deck and Operations Center.
- Guardrail engine foundations for prompt-injection indicators, PII indicators, content deny rules, tool allow-lists, and high-risk tool confirmation policy. Modes: `off`, `audit`, `enforce`.
- Advanced router pipeline definitions with conditions, route stages, load balancing, retries, and fallback model chains.
- Workflow graph registry and visual workflow preview for Agent/Team orchestration.
- Prompt registry with immutable versions, activation/rollback, notes, and history.
- Evaluation datasets, dataset items, and A/B evaluation-run definitions.
- Model catalog synchronized from the upstream `/models` endpoint with capability/context/pricing/health/latency metadata where available.
- Plugin/Skill marketplace view built on the safe catalog/registry model.
- First-run Operations Center onboarding flow.
- PostgreSQL + pgvector + Redis + two-router HA Compose example and smoke/load-test tooling.
- Enterprise identity readiness page. OIDC remains the completed interactive login path; LDAP/SAML/SCIM are connector/provisioning foundations and require deployment-specific integration before production use.
- Static public-docs starter with release screenshots and demo deployment example.

### Compatibility and non-regression rules

- `model=auto` enters Smart Router automatic selection.
- Explicit upstream model IDs pass through unchanged.
- Existing SQLite data is upgraded in place; the compatibility DB filename is not renamed simply because the software version changes.
- Catalog plugin installation does not download/execute arbitrary untrusted code. Lifecycle state, permissions metadata, endpoint configuration, and safe registration remain controlled by the operator.

### Production notes

For real semantic vector RAG, configure an embeddings endpoint and use PostgreSQL with the `vector` extension. The built-in deterministic embedding fallback is useful for tests/offline operation but is not a substitute for a production embedding model. Run the HA smoke and load-test scripts against your own infrastructure before calling a deployment production-HA certified.

---

## Hermes Linux Stack v0.5.5

### Operations Center runtime controls, agent lifecycle, skills, groups, and built-in docs

v0.5.5 is an operator-control and usability release built on v0.5.4. It preserves the existing Smart Router data directory and the default `control-v0.5.2.sqlite3` compatibility filename while upgrading the Operations Center schema in place to `0.5.5`.

#### Smart Router / Operations Center

- Live UI editing for `router_mode`: `observe` or `route`.
- Live UI editing for `router_policy`: `heuristic`, `calibrated`, or `learned`.
- UI control for HA mode with a guard that requires Redis before HA can be enabled.
- Runtime UI settings persist in the Operations database and override startup environment values until **Reset to environment** is used.
- System page now reports the schema version and explains why the default DB filename can remain `control-v0.5.2.sqlite3`.
- OIDC login button corrected to the actual `/api/auth/oidc/start` route.
- ACL UI corrected to use backend subject type `virtual_key`; `group` subjects are now exposed.

#### Agents

- Create Agent errors are shown instead of failing silently.
- Agent names and referenced Knowledge/Plugin/Skill IDs are validated.
- Agents can be edited in the UI.
- Agents can be disabled reversibly or permanently deleted.
- Agent forms use existing Knowledge, Skill, and Plugin records instead of requiring blind CSV IDs.

#### Skills and plugins

- New reusable Skill registry with curated suggestions for Linux, Docker, networking, MikroTik, automation safety, and incident response.
- Skills can be installed from the built-in catalog or registered manually, including commercial/license metadata without storing license secrets.
- Skills assigned to an agent are injected into that agent's system context during runs.
- Suggested Plugin catalog can install safe registry templates for GitHub MCP, read-only PostgreSQL, Kubernetes observation, and MikroTik observation.
- Plugin catalog installation does not download or execute arbitrary code and installs suggested entries disabled by default.

#### Access groups

- New Access → Groups menu.
- Groups contain Operations Center usernames.
- ACL rules with `subject_type=group` now resolve real group membership.

#### Help and documentation

- New built-in **Docs** menu covers routing, system controls, users/keys/groups/ACLs, Knowledge, Memory, Agents, Skills, Plugins, Teams, upgrades, backups, and troubleshooting examples.
- Memory page now explains scope behavior and warns against using memory as a secret store.

#### Database compatibility

The default filename remains:

```text
sqlite:////data/control-v0.5.2.sqlite3
```

This is intentional. v0.5.5 creates its new tables in the same database and updates the `schema_versions` marker to `0.5.5`, preserving v0.5.1/v0.5.2-era users, routes, keys, policies, budgets, knowledge, memory, agents, teams, plugins, ACLs, audit, and outcome records.

New v0.5.5 tables include:

```text
v55_runtime_settings
v55_access_groups
v55_skills
v55_agent_skills
```

Back up the persistent Smart Router database before any production upgrade.

---

## v0.5.4 change summary — 9router

- Fixed the hidden 200,000 TPM ceiling on `SMART_ROUTER_CLIENT_API_KEY`; trusted stack-client RPM/TPM/daily limits are explicit (2,000,000 TPM default).
- Local quota 429 responses now identify Smart Router as the source, include current/limit/estimated values, and return `Retry-After`.
- Redis/HA quota checks are atomic: denied requests no longer consume additional quota.
- Virtual API-key RPM/TPM/daily limits can be edited in-place in `/control/` without rotating the key.
- Visible **Control Plane** naming is replaced by **Hermes Operations Center**; `/control/` and legacy `SMART_ROUTER_CONTROL_*` variables stay compatible.
- RAG knowledge tables can share the Operations DB or use a separate SQLite/PostgreSQL database through `SMART_ROUTER_KNOWLEDGE_DATABASE_URL`.
- The Operations Center shows RAG storage health/backend and labels the built-in retriever accurately as lexical.
- The persistent default filename remains `control-v0.5.2.sqlite3` so upgrades preserve state.
- Runtime/package/image/chart defaults are `0.5.4`.

---

## v0.5.3 change summary — 9router

### User experience

- `./manage.sh` now opens a grouped interactive manager by default.
- Added dedicated Services, Smart Router, Hermes, n8n, Execution, Maintenance, and Security menu groups.
- Smart Router mode/policy/time-window/feature choices are shown as numbered choices instead of requiring memorized values.
- Direct v0.5.2 commands remain backward compatible for scripts and automation.

### Hermes Smart Router Flight Deck

- Redesigned `/dashboard` as the Hermes Flight Deck with access state, time-window and auto-refresh selectors, routing-flow explanation, telemetry quality, route mix, and explicit zero/pricing states.
- Redesigned `/control/` navigation into Observe, Routing, Access, Intelligence, and System groups.
- Added dropdown/select controls for common finite-value choices such as roles, budget scopes, ACL effects, agent profiles, team strategy, plugin kind/risk, and API-key tiers.
- Dashboard and Control Plane link to each other.

### Compatibility

- Runtime/package version is 0.5.3.
- Fresh installs pin `afsharidevops/hermes-smart-router:0.5.3`.
- Existing v0.5.2 Control Plane SQLite/schema naming is intentionally preserved for in-place data compatibility.
- Corrected the installer status message so it reports the selected Smart Router mode instead of always saying observation mode.

---

## v0.5.2 change summary — main

- fixed router-info endpoint and retained compatibility alias
- hardened installer secret rotation and Smart Router client-key synchronization
- removed fixed Docker GID assumption
- enabled control/client auth by default and disabled Open WebUI signup by default
- made application image tags `.env`-overrideable while retaining `latest`/`main` defaults
- added provider health/circuit breakers, shared Redis state/stickiness, OIDC/ACL/secrets foundations, outcome capture and provider quality metadata
- added doctor/backup/restore/rollback/image-lock operations
- added HA Compose and Helm foundations plus security CI
- reconciled package version/documentation to Smart Router v0.5.2

See `docs/HERMES-SMART-ROUTER-v0.5.2-IMPLEMENTATION-STATUS.md` for items still open and not claimed complete.

### Post-review cleanup

- Replaced the Docker execution GID 0 fallback with fail-closed sentinel GID 65534; `install.sh` still detects the actual Docker socket GID when available.
- Removed a dead duplicate `/router/info` handler definition.
- Deduplicated Redis/Authlib dependency constraints to their prior effective versions.
- Removed duplicate long-range planning documentation from the active release tree.
- Updated smoke validation for configurable Smart Router image tags (`latest` by default, pinnable via `.env`).