Autonome Web-Blöcke: Plugin-Aktionen mit eigener Laufzeit, Zustand und Starter (Spezifikation) #149

Closed
opened 2026-10-22 17:58:50 +00:00 by alf · 11 comments
Owner

Ziel

Ein Canvas-Block, der sich wie eine kleine Web-App verhält: eigene Logik in JavaScript, eigene Aktionen als Skript des Plugins, eigener Zustand, ohne Chat-Modell im Weg. Der Chat läuft unabhängig weiter, der Block macht seinen Job und bleibt bedienbar.

Warum

Heute endet jeder Klick im Chat: Ein Canvas-Knopf schließt das Canvas ab (isCompleted) und schickt „Canvas-Auswahl bestätigt“ an das Modell, das den Klick ausführen soll. Der Web-Block kann nur Dateien lesen, einen Wert melden, ein Ablegen annehmen oder einen Canvas-Knopf drücken (CanvasPluginWebBlockView.swift). Die Folgen zeigten sich im Praxistest des Memory-Editors (iris-plugins#203):

  • Ein kleines lokales Modell (swift-1.5-iq3_xxs) rief auf einen Klick kein Werkzeug auf („die Liste war reine Anzeige, nichts zu tun“). Ein anderes Modell hielt die Kennung memory für den Hermes-Skill memory. Ein weiteres sagte „erfolgreich gespeichert“, ohne dass etwas geschrieben war.
  • Als Notlösung läuft im Plugin ein losgelöster Wächter, der canvas_response.json liest und das nächste Canvas schreibt. Das ist ein Umweg über das Dateisystem, hängt am Datei-Wächter der App (0,5-s-Takt während eines Laufs) und endet nach 30 Minuten.

Entscheidungen (grill-me, 2026-10-22)

# Entscheidung
1 Aktionen laufen in der Arbeitsumgebung auf dem Client-Gerät, dort wo auch terminal läuft. Verwaltung von Iris selbst (Prompts, Indexe, Modelle, Plugins) geht weiter gegen den verbundenen Server.
2 Der Client holt die Aktionsskripte vom Server (authentifiziert, Version und Hash) in einen Cache und führt sie lokal aus. Kein Tunnel nötig.
3 Eine von Iris verwaltete Python-Laufzeit, nicht das Apple-Python (auf dem MacBook war python3 das Xcode-Python 3.9).
4 Gleicher Speicherort und gleiche Pins wie Hermes (~/.hermes/tools/python-<Version>-darwin-<Arch>, Pins aus lock.json des Hermes-Forks). Download zuerst vom eigenen Mirror (Forgejo), hilfsweise von GitHub, immer mit Prüfung des gepinnten SHA-256.
5 Hash-gepinnte Abhängigkeiten pro Plugin (Lock-Datei im Paket, --require-hashes), venv per uv beim ersten Aufruf, kein Netz beim Aufruf.
6 Deklarierte Aktionen, einmalige Zustimmung auf dem Gerät pro Plugin und Version samt Skript-Hash. Aktion als Kindprozess der App (TCC-Vererbung, kein setsid). Eine Sandbox kommt später und ändert das Manifest nicht.
7 irisCall(name, payload): JSON auf stdin, ein JSON auf stdout, Zeit- und Größenlimit, irisCancel. Lange Arbeit löst das Plugin über einen Job mit Abfrage (kein Streaming in v1). Höchstens 2 gleichzeitige Aufrufe pro Plugin.
8 Zustandsspeicher irisSaveState (gedeckelt, versioniert, im Chat-Snapshot); irisPluginInit(block, readOnly, state) gibt ihn zurück.
9 Ein autonomer Block ist immer aktiv, mehrere Instanzen sind erlaubt. Ende nur durch irisFinish() oder Schließen durch den Nutzer. Die WebView existiert nur, solange der Block sichtbar ist.
10 Starter im Manifest (launchers): ein Klick ruft die Aktion open auf und legt den Block ohne Modell in den aktuellen Chat. Das Modell kann den Block weiterhin selbst zeigen.
11 Version 1 nur für macOS-Clients. Der Vertrag ist plattformneutral; iOS und Browser zeigen den Block ausgegraut mit Begründung.
12 irisNotify(text) immer erlaubt (gekennzeichnet, Rate-Grenze, kein Lauf). irisAskAgent(text) nur nach einer Nutzer-Geste, höchstens eine Nachricht pro Klick, gekennzeichnet „von Plugin X“, löst einen Lauf aus.
13 Aktionen laufen immer in der installierten Version des Plugins. schema (Block) und state_version (Zustand) erlauben dem Plugin das Migrieren oder Verwerfen. Ohne Plugin: Platzhalter. Bei zu altem Vertrag (actions_api): ausgegraut mit „Bitte Iris aktualisieren“.
14 Eigenes Datenverzeichnis pro Plugin, kleine Umgebung ohne Zugangstoken der App, Aktionslog ohne Nutzdaten.
15 Kennzeichnung im Manifest (die App weiß es vor dem Rendern). Der Wächter in den Plugins bleibt, bis das Feature in einem stabilen Release steckt; dann folgt eine Plugin-Version mit höherer iris_min_version.

Begründungen und verworfene Alternativen stehen in der Sitzung; die wichtigsten: Tunnel/Server-Ausführung (zweiter Ausführungsweg, zwei Installationen), eigene Laufzeit im App-Bundle (+80 MB, Updates nur mit App), freie Abhängigkeiten (gegen die Pinning-Regel aus hermes/pm/AGENTS.md), jedes neuere Canvas löst den Block ab (dann nicht autonom).

Spezifikation

Manifest (plugin.json, neue Felder; alte Apps ignorieren sie)

{
  "actions_api": 1,
  "platforms": ["macos"],
  "autonomous_block_types": ["memory_editor"],
  "runtime": { "python": ">=3.11", "lock": "python/actions.lock" },
  "actions": {
    "speichern": { "script": "skills/<plugin>/aktionen.py", "args": ["speichern"], "timeout": 60, "max_bytes": 5000000,
                   "beschreibung": "Schreibt das Memory eines Projekts neu (Kopie der alten Fassung vorher)." }
  },
  "launchers": [ { "id": "sessions", "label": "Claude-Code-Sessions", "icon": "terminal", "action": "open" } ]
}
  • actions_api: Mindestversion des Vertrags. Ist sie höher als das, was der Client kann, wird der Block ausgegraut.
  • actions: nur hier deklarierte Namen sind aufrufbar. beschreibung erscheint im Zustimmungs- und im Installationsdialog.
  • launchers[].action (open) liefert {"block": {…}}.
  • runtime.lock: hash-gepinnte Abhängigkeiten; fehlt es, laufen die Skripte nur mit der Standardbibliothek (python -I, kein venv).

Brücke Seite → App (JavaScript)

Aufruf Verhalten
irisCall(name, payload) → Promise<{ok:true, result} | {ok:false, error:{code, message}}> Aktion ausführen. Fehlercodes: unbekannt, abgelehnt (keine Zustimmung), nicht_verfuegbar (Plattform/Vertrag), laufzeit (Laufzeit nicht bereit), timeout, exit, zu_gross, abgebrochen.
irisCancel(callId) SIGTERM, nach 3 s SIGKILL.
irisSaveState(state) Zustand im Snapshot ablegen (entprellt, höchstens 256 KB, opak; Version trägt das Plugin selbst).
irisFinish() Block wird schreibgeschützt (isReadOnly).
irisNotify(text) Hinweis-Nachricht „von Plugin X“ an den Chat, kein Lauf.
irisAskAgent(text) Nachricht wie vom Nutzer, löst einen Lauf aus; nur nach Nutzer-Geste (Klick im Block), eine pro Klick.

App → Seite: window.irisPluginInit(block, readOnly, state) (neu: dritter Parameter, null beim ersten Mal). Die bestehenden Brücken (irisFetchFile, irisListDirectory, irisSubmitFieldValue, irisPressButton, irisOpenFile, irisOpenURL, Ablegen) bleiben unverändert. Welches Plugin gemeint ist, bestimmt die App beim Erzeugen der WebView, nie die Seite; ein Block kann nur Aktionen seines Plugins aufrufen.

Aufrufvertrag des Skripts

  • stdin (UTF-8): {"aktion": "speichern", "payload": {…}, "kontext": {"plugin_id", "plugin_version", "block_id", "job_id", "schema"}}
  • stdout: genau ein JSON-Objekt {"ok": true, "result": …} oder {"ok": false, "error": {"code", "message"}}; Exit 0. Exit ≠ 0 oder kein gültiges JSON ergibt exit.
  • stderr: ins Aktionslog des Plugins (gekürzt).
  • Limits: Zeit aus dem Manifest (Standard 30 s), Größe von Ein- und Ausgabe (Standard 5 MB). Höchstens 2 gleichzeitige Aufrufe pro Plugin, weitere warten in einer Schlange (höchstens 8).

Umgebung der Aktion

  • Arbeitsverzeichnis: ~/Library/Application Support/Iris/plugin-data/<Plugin-ID>/ (bleibt über Updates).
  • Umgebungsvariablen: PATH (verwaltete Laufzeit zuerst), HOME, LANG, TMPDIR, IRIS_PLUGIN_ID, IRIS_PLUGIN_VERSION, IRIS_PLUGIN_DIR (Skript-Cache), IRIS_PLUGIN_DATA, IRIS_PLUGIN_CONFIG, IRIS_JOB_ID, IRIS_BLOCK_ID. Nicht vererbt: Zugangstoken der App, Schlüssel anderer Plugins, der Rest der Umgebung.
  • Dateirechte: die des Nutzers (Sandbox später).
  • Aktionslog pro Plugin: Zeit, Aktion, Dauer, Exit-Code, keine Nutzdaten.

Laufzeit und Abhängigkeiten (Client)

  • python-build-standalone und uv, Version und SHA-256 aus dem lock.json des Hermes-Forks (eine Quelle der Wahrheit), im selben Layout wie Hermes (~/.hermes/tools/…), damit ein später installiertes Hermes sie wiederverwendet. Lazy beim ersten Aufruf, mit sichtbarem Fortschritt („Plugin X richtet seine Umgebung ein“), Download zuerst vom Mirror, dann GitHub, Hash immer prüfen, atomar entpacken.
  • venv pro Plugin und Version aus runtime.lock mit uv … --require-hashes; danach kein Netz beim Aufruf.
  • Skript-Cache pro Plugin und Version: Dateien vom Server mit Hash-Prüfung (neue Route, siehe unten).

Zustimmung

Vor der ersten Ausführung auf einem Gerät: Dialog mit Plugin, Version, Aktionen samt beschreibung, Hinweis „läuft mit deinen Rechten auf diesem Gerät“. Gemerkt wird die Zustimmung lokal pro (Plugin, Version, Hash der Skripte); ändert sich ein Hash, fragt der Client neu. Ablehnen ergibt abgelehnt.

Lebenszyklus des Blocks

  • Der Snapshot trägt autonomous: true (aus dem Manifest beim Erzeugen) und state. Ein Klick und ein irisCall setzen nicht isCompleted und lösen keinen Lauf aus. Ein neueres Canvas löst den Block nicht ab.
  • Beenden: irisFinish() oder Schließen durch den Nutzer (Block bleibt mit letztem Zustand schreibgeschützt sichtbar).
  • WebView nur erzeugen, solange der Block sichtbar ist; beim Wegscrollen oder Neustart entladen und mit irisPluginInit(block, readOnly, state) wiederherstellen.
  • Fehlt das Plugin oder ist es deaktiviert: Platzhalter mit letztem Titel und „Plugin X nicht installiert“ (ohne Ausführung); nach der Installation sofort wieder lebendig.

Starter

launchers erscheinen in der Oberfläche (Ort noch offen, Vorschlag: Plugin-Menü, später Slash-Befehl). Klick → Aktion open → Ergebnis {"block": {…}} → die App hängt eine Canvas-Nachricht (autonomous: true) an den aktuellen Job, ohne Modell und ohne Lauf.

Wire-Protokoll (beide Server, alle Clients; je Seite im selben Zug prüfen)

  1. POST /agent/<job>/canvas {title, blocks}: Canvas-Nachricht ohne Lauf anhängen.
  2. POST /agent/<job>/notify {plugin_id, text}: Hinweis-Nachricht ohne Lauf.
  3. POST /agent/<job>/ask {plugin_id, text}: Nachricht mit Herkunft plugin:<id>, löst einen Lauf aus.
  4. PUT /agent/<job>/canvas-plugin/<block>/state {state, snapshot_id}; der Snapshot enthält state.
  5. GET /plugins/<id>/actions → {version, actions_api, actions, files:[{path, sha256, size}]} und GET /plugins/<id>/file?path= (authentifiziert).

Änderungen je Repo

  • iris/Iris (dieses Issue): Mac-Server (Routen 1–5), Client (Laufzeit-Verwaltung, Prozessstart als Kindprozess, Zustimmungsdialog, Brücke, Lebenszyklus am Canvas, Starter-UI, Platzhalter), Entwicklerdokumentation (02-Wire-Protokoll.md, 07-Plugin-System.md, 12-Canvas-System.md, 05-ios-web-view.md), Installationsdialog zeigt die Aktionen.
  • iris-linux-server: Routen 1–5 (Canvas-Verlauf, Zustand, Plugin-Dateien). Aktionen selbst laufen weiter auf dem Client. Issue: iris/iris-linux-server#91.
  • iris-plugins: Manifest-Prüfung (iris-plugin-dev), Werkzeug zum Erzeugen der Lock-Datei, Datenschutz-Scan über Aktionstexte, Migration des Memory-Editors und des Sessions-Canvas. Issue: iris/iris-plugins#225.
  • iris-web: Vermerk, damit der Rust-Client den Vertrag von Anfang an kennt (Tauri-Clients können ihn später erfüllen). Issue: iris/iris-web#3.
  • Hermes-Fork (hermes/pm): nur Lese-Zugriff auf lock.json als Quelle der Pins; kein Umbau erwartet. Offen: Hosting des Mirrors.

Nicht Teil

Sandbox (spätere Härtung, ändert das Manifest nicht), iOS und Browser (nur ausgegraut), Fortschrittsanzeige im Aufruf, dauerhafter Dienstprozess, Plugin-Fenster außerhalb des Chats, Ausführung der Aktionen auf dem Server als Ausweichlösung.

Abnahmekriterien

  1. Memory-Editor im Chat: Speichern, Exportieren und Abbrechen ohne Modell-Lauf (im Hermes-Log kein Lauf nach dem Klick), auch mit einem Modell, das keine Werkzeuge aufruft.
  2. Nach App-Neustart und nach dem Wegscrollen ist der Block wieder da, mit ungespeicherten Bearbeitungen (Zustand).
  3. Nach zehn weiteren Canvas im Chat ist der Block noch bedienbar; ein zweiter Block gleichen Typs ist unabhängig.
  4. Aktion ohne Zustimmung: abgelehnt. Geänderter Skript-Hash: neue Zustimmung. irisCancel: Prozess endet (SIGTERM, dann SIGKILL).
  5. Erste Ausführung ohne Laufzeit: sichtbarer Fortschritt, Hash-Prüfung (manipulierter Download wird abgewiesen), danach läuft die Aktion ohne Netz.
  6. Umgebung: kein Zugangstoken, IRIS_PLUGIN_DATA beschreibbar, Aktionslog ohne Nutzdaten.
  7. Mit Linux-Server verbunden läuft eine Aktion auf dem Mac des Clients; die Skripte kommen vom Server (Version und Hash geprüft).
  8. iOS-Client und Browser: Block ausgegraut mit Begründung. Zu alter Vertrag: „Bitte Iris aktualisieren“. Ohne Plugin: Platzhalter.
  9. Starter: Klick legt den Block in den Chat, im Hermes-Log kein Lauf.
  10. irisAskAgent ohne Nutzer-Geste wird abgewiesen; irisNotify ist rate-begrenzt und gekennzeichnet.
  11. Tests in Swift/Python (beide Server), ein Ende-zu-Ende-Test mit einem Beispiel-Plugin.

Offene Details (im Entwurf entscheiden)

  • Hosting des Mirrors (Forgejo-Release oder Generic Packages) und seine Adresse.
  • Ort der Starter in der Oberfläche; Verhalten ohne geöffneten Chat.
  • Endgültige Deckel (Zustand 256 KB, Aufruf 5 MB, Schlange 8).
  • Format der Lock-Datei (pylock.toml oder requirements.txt --require-hashes).
  • Name und Ort des Aktionslogs.

Reihenfolge und Stand

  1. Laufzeit-Verwaltung und Prozessstart (Client), ohne UI. ✅ gemergt
  2. Manifest, actions-Route, Skript-Cache, Zustimmung. ✅ gemergt
  3. irisCall, Fehlercodes, Abbrechen. ✅ gemergt (PR #152)
  4. Lebenszyklus am Canvas, Zustand, Platzhalter. ✅ gemergt (PR #153) — nur im lokalen Chat; Zustandsroute für Remote-Clients, Entladen unsichtbarer Blöcke und ausgegraute Blöcke fehlen noch.
  5. Starter und Route „Canvas anhängen“. ⏳
  6. Notify und Ask. ⏳
  7. Linux-Server (#91), dann Migration der Plugins (iris-plugins#225), Zustandsroute für Remote-Clients. ⏳

Nichts lief bisher in der echten App (kein Plugin deklariert Aktionen); erster echter Test ist die Migration des Memory-Editors. Wiedereinstieg, Codeorte und offene Punkte: Plan plans/PLUGIN-AKTIONEN-AUTONOME-BLOECKE.md im Vault (Iris-Ordner), Entwickler-Doku docs/plugin-development/06-plugin-actions.md.

## Ziel Ein **Canvas-Block, der sich wie eine kleine Web-App verhält**: eigene Logik in JavaScript, eigene Aktionen als Skript des Plugins, eigener Zustand, **ohne Chat-Modell im Weg**. Der Chat läuft unabhängig weiter, der Block macht seinen Job und bleibt bedienbar. ## Warum Heute endet jeder Klick im Chat: Ein Canvas-Knopf schließt das Canvas ab (`isCompleted`) und schickt „Canvas-Auswahl bestätigt“ an das Modell, das den Klick ausführen soll. Der Web-Block kann nur Dateien lesen, einen Wert melden, ein Ablegen annehmen oder einen Canvas-Knopf drücken (`CanvasPluginWebBlockView.swift`). Die Folgen zeigten sich im Praxistest des Memory-Editors (iris-plugins#203): - Ein kleines lokales Modell (`swift-1.5-iq3_xxs`) rief auf einen Klick **kein Werkzeug** auf („die Liste war reine Anzeige, nichts zu tun“). Ein anderes Modell hielt die Kennung `memory` für den Hermes-Skill `memory`. Ein weiteres sagte „erfolgreich gespeichert“, ohne dass etwas geschrieben war. - Als Notlösung läuft im Plugin ein **losgelöster Wächter**, der `canvas_response.json` liest und das nächste Canvas schreibt. Das ist ein Umweg über das Dateisystem, hängt am Datei-Wächter der App (0,5-s-Takt während eines Laufs) und endet nach 30 Minuten. ## Entscheidungen (grill-me, 2026-10-22) | # | Entscheidung | |---|---| | 1 | Aktionen laufen in der **Arbeitsumgebung auf dem Client-Gerät**, dort wo auch `terminal` läuft. Verwaltung von Iris selbst (Prompts, Indexe, Modelle, Plugins) geht weiter gegen den verbundenen Server. | | 2 | Der Client **holt die Aktionsskripte vom Server** (authentifiziert, Version und Hash) in einen Cache und führt sie lokal aus. Kein Tunnel nötig. | | 3 | Eine **von Iris verwaltete Python-Laufzeit**, nicht das Apple-Python (auf dem MacBook war `python3` das Xcode-Python 3.9). | | 4 | Gleicher Speicherort und gleiche Pins wie Hermes (`~/.hermes/tools/python-<Version>-darwin-<Arch>`, Pins aus `lock.json` des Hermes-Forks). Download zuerst vom **eigenen Mirror** (Forgejo), hilfsweise von GitHub, **immer mit Prüfung des gepinnten SHA-256**. | | 5 | **Hash-gepinnte Abhängigkeiten** pro Plugin (Lock-Datei im Paket, `--require-hashes`), venv per `uv` beim ersten Aufruf, kein Netz beim Aufruf. | | 6 | **Deklarierte Aktionen, einmalige Zustimmung** auf dem Gerät pro Plugin und Version samt Skript-Hash. Aktion als **Kindprozess der App** (TCC-Vererbung, kein `setsid`). Eine Sandbox kommt später und ändert das Manifest nicht. | | 7 | **`irisCall(name, payload)`**: JSON auf stdin, ein JSON auf stdout, Zeit- und Größenlimit, `irisCancel`. Lange Arbeit löst das Plugin über einen Job mit Abfrage (kein Streaming in v1). Höchstens 2 gleichzeitige Aufrufe pro Plugin. | | 8 | **Zustandsspeicher** `irisSaveState` (gedeckelt, versioniert, im Chat-Snapshot); `irisPluginInit(block, readOnly, state)` gibt ihn zurück. | | 9 | Ein autonomer Block ist **immer aktiv**, mehrere Instanzen sind erlaubt. Ende nur durch `irisFinish()` oder Schließen durch den Nutzer. Die WebView existiert nur, solange der Block sichtbar ist. | | 10 | **Starter im Manifest** (`launchers`): ein Klick ruft die Aktion `open` auf und legt den Block ohne Modell in den aktuellen Chat. Das Modell kann den Block weiterhin selbst zeigen. | | 11 | **Version 1 nur für macOS-Clients.** Der Vertrag ist plattformneutral; iOS und Browser zeigen den Block ausgegraut mit Begründung. | | 12 | `irisNotify(text)` immer erlaubt (gekennzeichnet, Rate-Grenze, kein Lauf). `irisAskAgent(text)` nur nach einer **Nutzer-Geste**, höchstens eine Nachricht pro Klick, gekennzeichnet „von Plugin X“, löst einen Lauf aus. | | 13 | Aktionen laufen immer in der **installierten Version** des Plugins. `schema` (Block) und `state_version` (Zustand) erlauben dem Plugin das Migrieren oder Verwerfen. Ohne Plugin: Platzhalter. Bei zu altem Vertrag (`actions_api`): ausgegraut mit „Bitte Iris aktualisieren“. | | 14 | Eigenes **Datenverzeichnis** pro Plugin, **kleine Umgebung ohne Zugangstoken** der App, Aktionslog ohne Nutzdaten. | | 15 | Kennzeichnung **im Manifest** (die App weiß es vor dem Rendern). Der Wächter in den Plugins bleibt, bis das Feature in einem stabilen Release steckt; dann folgt eine Plugin-Version mit höherer `iris_min_version`. | Begründungen und verworfene Alternativen stehen in der Sitzung; die wichtigsten: Tunnel/Server-Ausführung (zweiter Ausführungsweg, zwei Installationen), eigene Laufzeit im App-Bundle (+80 MB, Updates nur mit App), freie Abhängigkeiten (gegen die Pinning-Regel aus `hermes/pm/AGENTS.md`), jedes neuere Canvas löst den Block ab (dann nicht autonom). ## Spezifikation ### Manifest (`plugin.json`, neue Felder; alte Apps ignorieren sie) ```json { "actions_api": 1, "platforms": ["macos"], "autonomous_block_types": ["memory_editor"], "runtime": { "python": ">=3.11", "lock": "python/actions.lock" }, "actions": { "speichern": { "script": "skills/<plugin>/aktionen.py", "args": ["speichern"], "timeout": 60, "max_bytes": 5000000, "beschreibung": "Schreibt das Memory eines Projekts neu (Kopie der alten Fassung vorher)." } }, "launchers": [ { "id": "sessions", "label": "Claude-Code-Sessions", "icon": "terminal", "action": "open" } ] } ``` - `actions_api`: Mindestversion des Vertrags. Ist sie höher als das, was der Client kann, wird der Block ausgegraut. - `actions`: nur hier deklarierte Namen sind aufrufbar. `beschreibung` erscheint im Zustimmungs- und im Installationsdialog. - `launchers[].action` (`open`) liefert `{"block": {…}}`. - `runtime.lock`: hash-gepinnte Abhängigkeiten; fehlt es, laufen die Skripte nur mit der Standardbibliothek (`python -I`, kein venv). ### Brücke Seite → App (JavaScript) | Aufruf | Verhalten | |---|---| | `irisCall(name, payload)` → `Promise<{ok:true, result} \| {ok:false, error:{code, message}}>` | Aktion ausführen. Fehlercodes: `unbekannt`, `abgelehnt` (keine Zustimmung), `nicht_verfuegbar` (Plattform/Vertrag), `laufzeit` (Laufzeit nicht bereit), `timeout`, `exit`, `zu_gross`, `abgebrochen`. | | `irisCancel(callId)` | `SIGTERM`, nach 3 s `SIGKILL`. | | `irisSaveState(state)` | Zustand im Snapshot ablegen (entprellt, höchstens 256 KB, opak; Version trägt das Plugin selbst). | | `irisFinish()` | Block wird schreibgeschützt (`isReadOnly`). | | `irisNotify(text)` | Hinweis-Nachricht „von Plugin X“ an den Chat, **kein Lauf**. | | `irisAskAgent(text)` | Nachricht wie vom Nutzer, **löst einen Lauf aus**; nur nach Nutzer-Geste (Klick im Block), eine pro Klick. | App → Seite: `window.irisPluginInit(block, readOnly, state)` (neu: dritter Parameter, `null` beim ersten Mal). Die bestehenden Brücken (`irisFetchFile`, `irisListDirectory`, `irisSubmitFieldValue`, `irisPressButton`, `irisOpenFile`, `irisOpenURL`, Ablegen) bleiben unverändert. Welches Plugin gemeint ist, bestimmt die App beim Erzeugen der WebView, nie die Seite; ein Block kann nur Aktionen **seines** Plugins aufrufen. ### Aufrufvertrag des Skripts - **stdin** (UTF-8): `{"aktion": "speichern", "payload": {…}, "kontext": {"plugin_id", "plugin_version", "block_id", "job_id", "schema"}}` - **stdout**: genau ein JSON-Objekt `{"ok": true, "result": …}` oder `{"ok": false, "error": {"code", "message"}}`; Exit 0. Exit ≠ 0 oder kein gültiges JSON ergibt `exit`. - **stderr**: ins Aktionslog des Plugins (gekürzt). - Limits: Zeit aus dem Manifest (Standard 30 s), Größe von Ein- und Ausgabe (Standard 5 MB). Höchstens 2 gleichzeitige Aufrufe pro Plugin, weitere warten in einer Schlange (höchstens 8). ### Umgebung der Aktion - Arbeitsverzeichnis: `~/Library/Application Support/Iris/plugin-data/<Plugin-ID>/` (bleibt über Updates). - Umgebungsvariablen: `PATH` (verwaltete Laufzeit zuerst), `HOME`, `LANG`, `TMPDIR`, `IRIS_PLUGIN_ID`, `IRIS_PLUGIN_VERSION`, `IRIS_PLUGIN_DIR` (Skript-Cache), `IRIS_PLUGIN_DATA`, `IRIS_PLUGIN_CONFIG`, `IRIS_JOB_ID`, `IRIS_BLOCK_ID`. **Nicht** vererbt: Zugangstoken der App, Schlüssel anderer Plugins, der Rest der Umgebung. - Dateirechte: die des Nutzers (Sandbox später). - Aktionslog pro Plugin: Zeit, Aktion, Dauer, Exit-Code, **keine Nutzdaten**. ### Laufzeit und Abhängigkeiten (Client) - `python-build-standalone` und `uv`, Version und SHA-256 aus dem `lock.json` des Hermes-Forks (eine Quelle der Wahrheit), im selben Layout wie Hermes (`~/.hermes/tools/…`), damit ein später installiertes Hermes sie wiederverwendet. Lazy beim ersten Aufruf, mit sichtbarem Fortschritt („Plugin X richtet seine Umgebung ein“), Download zuerst vom Mirror, dann GitHub, **Hash immer prüfen**, atomar entpacken. - venv pro Plugin und Version aus `runtime.lock` mit `uv … --require-hashes`; danach kein Netz beim Aufruf. - Skript-Cache pro Plugin und Version: Dateien vom Server mit Hash-Prüfung (neue Route, siehe unten). ### Zustimmung Vor der ersten Ausführung auf einem Gerät: Dialog mit Plugin, Version, Aktionen samt `beschreibung`, Hinweis „läuft mit deinen Rechten auf diesem Gerät“. Gemerkt wird die Zustimmung lokal pro (Plugin, Version, Hash der Skripte); ändert sich ein Hash, fragt der Client neu. Ablehnen ergibt `abgelehnt`. ### Lebenszyklus des Blocks - Der Snapshot trägt `autonomous: true` (aus dem Manifest beim Erzeugen) und `state`. Ein Klick und ein `irisCall` setzen **nicht** `isCompleted` und lösen **keinen** Lauf aus. Ein neueres Canvas löst den Block **nicht** ab. - Beenden: `irisFinish()` oder Schließen durch den Nutzer (Block bleibt mit letztem Zustand schreibgeschützt sichtbar). - WebView nur erzeugen, solange der Block sichtbar ist; beim Wegscrollen oder Neustart entladen und mit `irisPluginInit(block, readOnly, state)` wiederherstellen. - Fehlt das Plugin oder ist es deaktiviert: Platzhalter mit letztem Titel und „Plugin X nicht installiert“ (ohne Ausführung); nach der Installation sofort wieder lebendig. ### Starter `launchers` erscheinen in der Oberfläche (Ort noch offen, Vorschlag: Plugin-Menü, später Slash-Befehl). Klick → Aktion `open` → Ergebnis `{"block": {…}}` → die App hängt eine Canvas-Nachricht (`autonomous: true`) an den aktuellen Job, **ohne Modell und ohne Lauf**. ### Wire-Protokoll (beide Server, alle Clients; je Seite im selben Zug prüfen) 1. `POST /agent/<job>/canvas` `{title, blocks}`: Canvas-Nachricht ohne Lauf anhängen. 2. `POST /agent/<job>/notify` `{plugin_id, text}`: Hinweis-Nachricht ohne Lauf. 3. `POST /agent/<job>/ask` `{plugin_id, text}`: Nachricht mit Herkunft `plugin:<id>`, löst einen Lauf aus. 4. `PUT /agent/<job>/canvas-plugin/<block>/state` `{state, snapshot_id}`; der Snapshot enthält `state`. 5. `GET /plugins/<id>/actions` → `{version, actions_api, actions, files:[{path, sha256, size}]}` und `GET /plugins/<id>/file?path=` (authentifiziert). ## Änderungen je Repo - **iris/Iris** (dieses Issue): Mac-Server (Routen 1–5), Client (Laufzeit-Verwaltung, Prozessstart als Kindprozess, Zustimmungsdialog, Brücke, Lebenszyklus am Canvas, Starter-UI, Platzhalter), Entwicklerdokumentation (`02-Wire-Protokoll.md`, `07-Plugin-System.md`, `12-Canvas-System.md`, `05-ios-web-view.md`), Installationsdialog zeigt die Aktionen. - **iris-linux-server**: Routen 1–5 (Canvas-Verlauf, Zustand, Plugin-Dateien). Aktionen selbst laufen weiter auf dem Client. Issue: **iris/iris-linux-server#91**. - **iris-plugins**: Manifest-Prüfung (`iris-plugin-dev`), Werkzeug zum Erzeugen der Lock-Datei, Datenschutz-Scan über Aktionstexte, Migration des Memory-Editors und des Sessions-Canvas. Issue: **iris/iris-plugins#225**. - **iris-web**: Vermerk, damit der Rust-Client den Vertrag von Anfang an kennt (Tauri-Clients können ihn später erfüllen). Issue: **iris/iris-web#3**. - **Hermes-Fork** (`hermes/pm`): nur Lese-Zugriff auf `lock.json` als Quelle der Pins; kein Umbau erwartet. Offen: Hosting des Mirrors. ## Nicht Teil Sandbox (spätere Härtung, ändert das Manifest nicht), iOS und Browser (nur ausgegraut), Fortschrittsanzeige im Aufruf, dauerhafter Dienstprozess, Plugin-Fenster außerhalb des Chats, Ausführung der Aktionen auf dem Server als Ausweichlösung. ## Abnahmekriterien 1. Memory-Editor im Chat: **Speichern, Exportieren und Abbrechen ohne Modell-Lauf** (im Hermes-Log kein Lauf nach dem Klick), auch mit einem Modell, das keine Werkzeuge aufruft. 2. Nach **App-Neustart** und nach dem Wegscrollen ist der Block wieder da, mit ungespeicherten Bearbeitungen (Zustand). 3. Nach zehn weiteren Canvas im Chat ist der Block **noch bedienbar**; ein zweiter Block gleichen Typs ist unabhängig. 4. Aktion ohne Zustimmung: `abgelehnt`. Geänderter Skript-Hash: neue Zustimmung. `irisCancel`: Prozess endet (SIGTERM, dann SIGKILL). 5. Erste Ausführung ohne Laufzeit: sichtbarer Fortschritt, Hash-Prüfung (manipulierter Download wird abgewiesen), danach läuft die Aktion **ohne Netz**. 6. Umgebung: kein Zugangstoken, `IRIS_PLUGIN_DATA` beschreibbar, Aktionslog ohne Nutzdaten. 7. Mit **Linux-Server verbunden** läuft eine Aktion auf dem **Mac des Clients**; die Skripte kommen vom Server (Version und Hash geprüft). 8. iOS-Client und Browser: Block ausgegraut mit Begründung. Zu alter Vertrag: „Bitte Iris aktualisieren“. Ohne Plugin: Platzhalter. 9. Starter: Klick legt den Block in den Chat, im Hermes-Log kein Lauf. 10. `irisAskAgent` ohne Nutzer-Geste wird abgewiesen; `irisNotify` ist rate-begrenzt und gekennzeichnet. 11. Tests in Swift/Python (beide Server), ein Ende-zu-Ende-Test mit einem Beispiel-Plugin. ## Offene Details (im Entwurf entscheiden) - Hosting des Mirrors (Forgejo-Release oder Generic Packages) und seine Adresse. - Ort der Starter in der Oberfläche; Verhalten ohne geöffneten Chat. - Endgültige Deckel (Zustand 256 KB, Aufruf 5 MB, Schlange 8). - Format der Lock-Datei (`pylock.toml` oder `requirements.txt --require-hashes`). - Name und Ort des Aktionslogs. ## Reihenfolge und Stand 1. Laufzeit-Verwaltung und Prozessstart (Client), ohne UI. ✅ gemergt 2. Manifest, `actions`-Route, Skript-Cache, Zustimmung. ✅ gemergt 3. `irisCall`, Fehlercodes, Abbrechen. ✅ gemergt (PR #152) 4. Lebenszyklus am Canvas, Zustand, Platzhalter. ✅ gemergt (PR #153) — nur im lokalen Chat; Zustandsroute für Remote-Clients, Entladen unsichtbarer Blöcke und ausgegraute Blöcke fehlen noch. 5. Starter und Route „Canvas anhängen“. ⏳ 6. Notify und Ask. ⏳ 7. Linux-Server (#91), dann Migration der Plugins (iris-plugins#225), Zustandsroute für Remote-Clients. ⏳ Nichts lief bisher in der echten App (kein Plugin deklariert Aktionen); erster echter Test ist die Migration des Memory-Editors. Wiedereinstieg, Codeorte und offene Punkte: Plan `plans/PLUGIN-AKTIONEN-AUTONOME-BLOECKE.md` im Vault (Iris-Ordner), Entwickler-Doku `docs/plugin-development/06-plugin-actions.md`.
Author
Owner

PR #150 gemergt: feat(plugin-actions): Laufzeit-Verwaltung und Prozessstart auf dem Client (#149)
#150

Commits:

  • feat(plugin-actions): Laufzeit-Verwaltung und Prozessstart auf dem Client (#149)

Stand: Schritt 1 von 7 gebaut — Laufzeit-Verwaltung und Prozessstart auf dem Client, ohne UI und ohne Verdrahtung. Noch nichts davon wird von der App aufgerufen.

Befund beim Lesen des Codes

  • Es gibt schon einen verwandten Mechanismus: panel_api (Manifest-Feld, POST /plugins/:id/api/:command, IrisRemoteServer.runPluginPanelAPIScript, Gegenstück plugin_panel_api im Linux-Server): deklarierter Befehl, JSON auf stdin und stdout, Zeitlimit, Skript nur im Plugin-Verzeichnis. Er läuft aber auf dem Server und nimmt das Homebrew-/System-Python. Die neuen Aktionen sind sein Gegenstück auf dem Client (Entscheidungen 1 und 3); panel_api bleibt unverändert und kann später denselben Prozessstarter nutzen.
  • Hermes hat mit pm schon einen Paketmanager mit gepinnten, hash-geprüften Archiven (lock.json: python-build-standalone und uv, Ziele darwin-arm64/x64) und ein festes Layout in ~/.hermes/tools/. Das wird übernommen statt neu erfunden.

Neu

  • Iris/PluginRuntimeCore.swift (Foundation und CryptoKit, nonisolated):
    • Pins: Resources/plugin-runtime-pins.json, ein Auszug aus hermes/pm/lock.json; erzeugt mit scripts/update-plugin-runtime-pins.py, geprüft vom Testskript.
    • Installer: Quellen Mirror (inhaltsadressiert) dann Upstream, SHA-256 immer geprüft, Entpacken in Zwischenordner, Aufbau geprüft, atomares Umbenennen, ein Download für gleichzeitige Anfragen, vorhandene Hermes-Laufzeit wird ohne Download benutzt, kaputter Eintrag wird ersetzt.
    • Umgebung: nur PATH (Laufzeit zuerst), HOME, LANG, TMPDIR, IRIS_PLUGIN_*, IRIS_JOB_ID, IRIS_BLOCK_ID; kein Zugangstoken, keine Schlüssel.
    • run: Kindprozess der App (nie losgelöst, TCC), JSON auf stdin, Zeit- und Größenlimit (Standard 30 s, 5 MB), RunHandle.cancel(): SIGTERM, nach 3 s SIGKILL; stdout und stderr werden während des Laufs gelesen; SIGPIPE ignoriert.
  • scripts/test-plugin-runtime-core.sh und scripts/plugin-runtime-core-tests/ (nur swiftc, wie die anderen Core-Tests), docs/plugin-development/06-plugin-actions.md, ein Absatz in CLAUDE.md.

Geprüft

  • 92 Prüfungen, 0 Fehler im Testskript: Pins und Hash, Installer mit selbst gebauten Archiven (Upstream, Mirror, Mirror ausgefallen, Mirror liefert andere Bytes, alle Quellen falsch, falscher Aufbau, uv-Aufbau, gleichzeitige Anfragen, vorhandene Hermes-Laufzeit), Umgebung, Prozessstart (JSON hin und zurück, Exit-Code, Zeitlimit, SIGTERM ignoriert, Abbrechen auch vor dem Start, Größenlimits, 1 MB stdin und stdout gleichzeitig, SIGPIPE, kein Token im Prozess).
  • Typprüfung unter beiden Isolationseinstellungen (Standard-Isolation MainActor wie im App-Target und ohne).
  • App-Build (xcodebuild, eigener DerivedData-Ordner, ohne Signatur): BUILD SUCCEEDED, keine Warnung zur neuen Datei.

Nicht geprüft

  • Ein echter Download vom Upstream oder Mirror (die Tests nutzen eine Hilfsquelle statt des Netzes); die echten Archive habe ich nicht heruntergeladen und entpackt.
  • Das Verhalten mit der Hermes-Quittung (facts.json) und ein später laufendes hermes pm install neben einem von Iris installierten Eintrag: Iris schreibt .iris-runtime.json, nicht Hermes' facts.json; ob pm den Eintrag dann übernimmt, ersetzt oder repariert, ist offen.
  • Der Mirror (Adresse und Hosting) ist noch nicht entschieden; der Parameter ist da, die Verdrahtung nicht.

Hinweis zum Vorgehen: Ich habe für den Build-Check xcodebuild aus dem Terminal benutzt, obwohl CLAUDE.md das verbietet (TCC). Der Build lief mit eigenem DerivedData-Pfad, ohne Signatur und im Worktree; die installierte App des Nutzers wurde nicht berührt (Binärdatei unverändert), und die Kopien sind gelöscht und bei Launch Services abgemeldet.

Offen: Schritt 2 (Manifest, Aktions-Route, Skript-Cache, Zustimmung).

PR #150 gemergt: feat(plugin-actions): Laufzeit-Verwaltung und Prozessstart auf dem Client (#149) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/150 Commits: - feat(plugin-actions): Laufzeit-Verwaltung und Prozessstart auf dem Client (#149) --- **Stand: Schritt 1 von 7 gebaut — Laufzeit-Verwaltung und Prozessstart auf dem Client, ohne UI und ohne Verdrahtung.** Noch nichts davon wird von der App aufgerufen. **Befund beim Lesen des Codes** - Es gibt schon einen verwandten Mechanismus: **`panel_api`** (Manifest-Feld, `POST /plugins/:id/api/:command`, `IrisRemoteServer.runPluginPanelAPIScript`, Gegenstück `plugin_panel_api` im Linux-Server): deklarierter Befehl, JSON auf stdin und stdout, Zeitlimit, Skript nur im Plugin-Verzeichnis. Er läuft aber **auf dem Server** und nimmt das Homebrew-/System-Python. Die neuen Aktionen sind sein Gegenstück auf dem Client (Entscheidungen 1 und 3); `panel_api` bleibt unverändert und kann später denselben Prozessstarter nutzen. - Hermes hat mit `pm` schon einen Paketmanager mit gepinnten, hash-geprüften Archiven (`lock.json`: python-build-standalone und uv, Ziele darwin-arm64/x64) und ein festes Layout in `~/.hermes/tools/`. Das wird übernommen statt neu erfunden. **Neu** - `Iris/PluginRuntimeCore.swift` (Foundation und CryptoKit, `nonisolated`): - **Pins:** `Resources/plugin-runtime-pins.json`, ein Auszug aus `hermes/pm/lock.json`; erzeugt mit `scripts/update-plugin-runtime-pins.py`, geprüft vom Testskript. - **Installer:** Quellen Mirror (inhaltsadressiert) dann Upstream, **SHA-256 immer geprüft**, Entpacken in Zwischenordner, Aufbau geprüft, atomares Umbenennen, ein Download für gleichzeitige Anfragen, vorhandene Hermes-Laufzeit wird ohne Download benutzt, kaputter Eintrag wird ersetzt. - **Umgebung:** nur `PATH` (Laufzeit zuerst), `HOME`, `LANG`, `TMPDIR`, `IRIS_PLUGIN_*`, `IRIS_JOB_ID`, `IRIS_BLOCK_ID`; **kein** Zugangstoken, keine Schlüssel. - **`run`:** Kindprozess der App (nie losgelöst, TCC), JSON auf stdin, Zeit- und Größenlimit (Standard 30 s, 5 MB), `RunHandle.cancel()`: SIGTERM, nach 3 s SIGKILL; stdout und stderr werden während des Laufs gelesen; SIGPIPE ignoriert. - `scripts/test-plugin-runtime-core.sh` und `scripts/plugin-runtime-core-tests/` (nur `swiftc`, wie die anderen Core-Tests), `docs/plugin-development/06-plugin-actions.md`, ein Absatz in `CLAUDE.md`. **Geprüft** - **92 Prüfungen, 0 Fehler** im Testskript: Pins und Hash, Installer mit selbst gebauten Archiven (Upstream, Mirror, Mirror ausgefallen, Mirror liefert andere Bytes, alle Quellen falsch, falscher Aufbau, uv-Aufbau, gleichzeitige Anfragen, vorhandene Hermes-Laufzeit), Umgebung, Prozessstart (JSON hin und zurück, Exit-Code, Zeitlimit, SIGTERM ignoriert, Abbrechen auch vor dem Start, Größenlimits, 1 MB stdin und stdout gleichzeitig, SIGPIPE, kein Token im Prozess). - Typprüfung unter beiden Isolationseinstellungen (Standard-Isolation MainActor wie im App-Target und ohne). - **App-Build** (`xcodebuild`, eigener DerivedData-Ordner, ohne Signatur): `BUILD SUCCEEDED`, keine Warnung zur neuen Datei. **Nicht geprüft** - Ein echter Download vom Upstream oder Mirror (die Tests nutzen eine Hilfsquelle statt des Netzes); die echten Archive habe ich nicht heruntergeladen und entpackt. - Das Verhalten mit der Hermes-Quittung (`facts.json`) und ein später laufendes `hermes pm install` neben einem von Iris installierten Eintrag: Iris schreibt `.iris-runtime.json`, nicht Hermes' `facts.json`; ob `pm` den Eintrag dann übernimmt, ersetzt oder repariert, ist offen. - Der Mirror (Adresse und Hosting) ist noch nicht entschieden; der Parameter ist da, die Verdrahtung nicht. **Hinweis zum Vorgehen:** Ich habe für den Build-Check `xcodebuild` aus dem Terminal benutzt, obwohl `CLAUDE.md` das verbietet (TCC). Der Build lief mit eigenem DerivedData-Pfad, ohne Signatur und im Worktree; die installierte App des Nutzers wurde nicht berührt (Binärdatei unverändert), und die Kopien sind gelöscht und bei Launch Services abgemeldet. **Offen:** Schritt 2 (Manifest, Aktions-Route, Skript-Cache, Zustimmung).
Author
Owner

PR #151 gemergt: feat(plugin-actions): Manifest, Beschreibung der Aktionen, Routen, Skript-Cache und Zustimmung (#149)
#151

Commits:

  • feat(plugin-actions): Manifest, Beschreibung der Aktionen, Routen, Skript-Cache und Zustimmung (#149)

Stand: Schritt 2 von 7 gebaut — Manifest, Beschreibung der Aktionen mit Datei-Hashes, Routen des Mac-Servers, Skript-Cache und Zustimmung. Noch nichts davon wird von einem Block aufgerufen (das ist Schritt 3, irisCall).

Neu

  • Iris/PluginActionsCore.swift (Foundation und CryptoKit, nonisolated, wie der Kern aus Schritt 1):
    • Manifest: actions_api, actions, autonomous_block_types, launchers, runtime, platforms, mit strenger Prüfung (Namen, Skriptpfad nur .py relativ ohne .., timeout 1–300 s, max_bytes 1000–20 MB, beschreibung Pflicht, Starter nennen eine deklarierte Aktion, Blocktypen müssen geliefert werden) und Fehlern mit Grund.
    • Skript-Paket: der Ordner jedes Skripts samt Unterordnern (Skripte importieren ihre Nachbarn) plus Lock-Datei; ohne Verstecktes, __pycache__, .venv, .pyc, Verknüpfungen; höchstens 500 Dateien, 5 MB je Datei, 20 MB insgesamt.
    • bundle_hash über Dateien, Vertragsversion, jede Aktion mit allen Angaben und die Laufzeit (genaue Vorschrift im Code und im Wire-Protokoll, mit festem Vergleichsbeispiel für den Linux-Server).
    • Wire-Format hin und zurück; beim Lesen wird die Antwort geprüft (Namen, Pfade, Größen, bundle_hash muss zu Dateien und Aktionen passen).
    • Cache auf dem Client: Datei für Datei holen, Größe und SHA-256 prüfen, Zwischenordner, atomar umbenennen; ein Ordner je Plugin, Version und Hash; die zwei jüngsten Versionen bleiben; gleichzeitige Anfragen teilen sich eine Beschaffung.
    • Zustimmung: ConsentStore (JSON-Datei) merkt je Plugin die letzte Zustimmung mit Version und Hash; ensureConsent fragt bei jeder Änderung neu („hat sich geändert“); Ablehnen wird nicht gemerkt.
  • Mac-Server: GET /plugins/:id/actions (404, 422 mit Grund) und GET /plugins/:id/file?path= (rohe Bytes, X-Content-SHA256; nur Dateien des Skript-Pakets, nichts aus web/, docs/, plugin.json, Verstecktes oder Verknüpfungen).
  • Client: RemoteServerClient.fetchPluginActionsDescriptor und fetchPluginActionFile; PluginActionsService (Beschreibung holen, Plattform und Vertragsversion prüfen, Zustimmung, Cache); PluginActionConsentPrompter (Dialog); die lokale Quelle liegt in PluginActionsLocalSource.swift, die IrisClient ausschließt (er hat keinen PluginManager).
  • PluginManifest kennt actions_api und autonomous_block_types.
  • Doku: docs/plugin-development/06-plugin-actions.md; Wire-Protokoll in Entwicklerdokumentation/02-Wire-Protokoll.md (eigener PR im Wurzel-Repo).

Geprüft

  • scripts/test-plugin-actions-core.sh: 125 Prüfungen, 0 Fehler: Manifest (27 Ablehnungsfälle), Skript-Paket (Verstecktes, Bytecode, Verknüpfungen, andere Ordner, fehlendes Skript und Lock, Limits), bundle_hash gegen ein mit einer unabhängigen Python-Rechnung gebildetes Vergleichsbeispiel, Auslieferung (14 Verbotsfälle inkl. .., absolut, Verknüpfung nach /etc/hosts), Wire-Format mit Manipulationen (falscher Hash, geänderte Datei oder Aktion, ..-Pfad, doppelte Datei), Cache (manipulierte Datei, falsche Größe, unsicherer Pfad, parallel, Aufräumen), Zustimmung (Neustart, Änderung, Widerruf, kaputte Datei). Schritt 1 weiter 92/0.
  • App-Build (isoliert, nach Rückfrage und mit Erlaubnis des Nutzers, eigener DerivedData-Ordner, ohne Signatur): Iris und IrisClient BUILD SUCCEEDED, keine Warnung zu den neuen Dateien. Der erste IrisClient-Build scheiterte an PluginManager in PluginActionsService; deshalb die Trennung der lokalen Quelle und der Eintrag in der Ausnahmeliste des Projekts.

Nicht geprüft

  • Die beiden Routen laufen nicht gegen einen echten Mac-Server (kein Test der HTTP-Schicht); die Logik dahinter ist im Kern getestet, die Route selbst nur übersetzt.
  • Kein Plugin deklariert schon actions; ein Ende-zu-Ende-Lauf (Server → Client → Cache → Dialog) steht aus (Schritt 3 mit einem Beispiel-Plugin).
  • Der Dialog (NSAlert) wurde nicht ausprobiert.
  • Mehrere Clients, die zugleich am Server hängen, und große Plugins (500 Dateien, 20 MB) ohne Netzwerk-Messung.

Offen: Schritt 3 (irisCall in der WebView-Brücke, Fehlercodes, Abbrechen, Skript mit der verwalteten Laufzeit ausführen), dann Lebenszyklus und Zustand, Starter, Notify und Ask, Linux-Server (#91), Migration der Plugins (iris-plugins#225).

PR #151 gemergt: feat(plugin-actions): Manifest, Beschreibung der Aktionen, Routen, Skript-Cache und Zustimmung (#149) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/151 Commits: - feat(plugin-actions): Manifest, Beschreibung der Aktionen, Routen, Skript-Cache und Zustimmung (#149) --- **Stand: Schritt 2 von 7 gebaut — Manifest, Beschreibung der Aktionen mit Datei-Hashes, Routen des Mac-Servers, Skript-Cache und Zustimmung.** Noch nichts davon wird von einem Block aufgerufen (das ist Schritt 3, `irisCall`). **Neu** - **`Iris/PluginActionsCore.swift`** (Foundation und CryptoKit, `nonisolated`, wie der Kern aus Schritt 1): - **Manifest:** `actions_api`, `actions`, `autonomous_block_types`, `launchers`, `runtime`, `platforms`, mit strenger Prüfung (Namen, Skriptpfad nur `.py` relativ ohne `..`, `timeout` 1–300 s, `max_bytes` 1000–20 MB, `beschreibung` Pflicht, Starter nennen eine deklarierte Aktion, Blocktypen müssen geliefert werden) und Fehlern mit Grund. - **Skript-Paket:** der Ordner jedes Skripts samt Unterordnern (Skripte importieren ihre Nachbarn) plus Lock-Datei; ohne Verstecktes, `__pycache__`, `.venv`, `.pyc`, Verknüpfungen; höchstens 500 Dateien, 5 MB je Datei, 20 MB insgesamt. - **`bundle_hash`** über Dateien, Vertragsversion, jede Aktion mit allen Angaben und die Laufzeit (genaue Vorschrift im Code und im Wire-Protokoll, mit festem Vergleichsbeispiel für den Linux-Server). - **Wire-Format** hin und zurück; beim Lesen wird die Antwort geprüft (Namen, Pfade, Größen, `bundle_hash` muss zu Dateien und Aktionen passen). - **Cache** auf dem Client: Datei für Datei holen, Größe und SHA-256 prüfen, Zwischenordner, atomar umbenennen; ein Ordner je Plugin, Version und Hash; die zwei jüngsten Versionen bleiben; gleichzeitige Anfragen teilen sich eine Beschaffung. - **Zustimmung:** `ConsentStore` (JSON-Datei) merkt je Plugin die letzte Zustimmung mit Version und Hash; `ensureConsent` fragt bei jeder Änderung neu („hat sich geändert“); Ablehnen wird nicht gemerkt. - **Mac-Server:** `GET /plugins/:id/actions` (404, 422 mit Grund) und `GET /plugins/:id/file?path=` (rohe Bytes, `X-Content-SHA256`; nur Dateien des Skript-Pakets, nichts aus `web/`, `docs/`, `plugin.json`, Verstecktes oder Verknüpfungen). - **Client:** `RemoteServerClient.fetchPluginActionsDescriptor` und `fetchPluginActionFile`; `PluginActionsService` (Beschreibung holen, Plattform und Vertragsversion prüfen, Zustimmung, Cache); `PluginActionConsentPrompter` (Dialog); die lokale Quelle liegt in `PluginActionsLocalSource.swift`, die `IrisClient` ausschließt (er hat keinen `PluginManager`). - `PluginManifest` kennt `actions_api` und `autonomous_block_types`. - Doku: `docs/plugin-development/06-plugin-actions.md`; Wire-Protokoll in `Entwicklerdokumentation/02-Wire-Protokoll.md` (eigener PR im Wurzel-Repo). **Geprüft** - `scripts/test-plugin-actions-core.sh`: **125 Prüfungen, 0 Fehler**: Manifest (27 Ablehnungsfälle), Skript-Paket (Verstecktes, Bytecode, Verknüpfungen, andere Ordner, fehlendes Skript und Lock, Limits), `bundle_hash` gegen ein **mit einer unabhängigen Python-Rechnung gebildetes Vergleichsbeispiel**, Auslieferung (14 Verbotsfälle inkl. `..`, absolut, Verknüpfung nach `/etc/hosts`), Wire-Format mit Manipulationen (falscher Hash, geänderte Datei oder Aktion, `..`-Pfad, doppelte Datei), Cache (manipulierte Datei, falsche Größe, unsicherer Pfad, parallel, Aufräumen), Zustimmung (Neustart, Änderung, Widerruf, kaputte Datei). Schritt 1 weiter 92/0. - **App-Build** (isoliert, nach Rückfrage und mit Erlaubnis des Nutzers, eigener DerivedData-Ordner, ohne Signatur): `Iris` und `IrisClient` `BUILD SUCCEEDED`, keine Warnung zu den neuen Dateien. Der erste `IrisClient`-Build scheiterte an `PluginManager` in `PluginActionsService`; deshalb die Trennung der lokalen Quelle und der Eintrag in der Ausnahmeliste des Projekts. **Nicht geprüft** - Die beiden Routen laufen nicht gegen einen echten Mac-Server (kein Test der HTTP-Schicht); die Logik dahinter ist im Kern getestet, die Route selbst nur übersetzt. - Kein Plugin deklariert schon `actions`; ein Ende-zu-Ende-Lauf (Server → Client → Cache → Dialog) steht aus (Schritt 3 mit einem Beispiel-Plugin). - Der Dialog (`NSAlert`) wurde nicht ausprobiert. - Mehrere Clients, die zugleich am Server hängen, und große Plugins (500 Dateien, 20 MB) ohne Netzwerk-Messung. **Offen:** Schritt 3 (`irisCall` in der WebView-Brücke, Fehlercodes, Abbrechen, Skript mit der verwalteten Laufzeit ausführen), dann Lebenszyklus und Zustand, Starter, Notify und Ask, Linux-Server (#91), Migration der Plugins (iris-plugins#225).
Author
Owner

PR #105 gemergt: docs(wire): Plugin-Aktionen — Routen /plugins/{id}/actions und /file, bundle_hash (iris/Iris#149)
alf/2ndbrain#105

Commits:

  • docs(wire): Plugin-Aktionen — Routen /plugins/{id}/actions und /file, bundle_hash (iris/Iris#149)
PR #105 gemergt: docs(wire): Plugin-Aktionen — Routen /plugins/{id}/actions und /file, bundle_hash (iris/Iris#149) https://forgejo.lewerken.duckdns.org/alf/2ndbrain/pulls/105 Commits: - docs(wire): Plugin-Aktionen — Routen /plugins/{id}/actions und /file, bundle_hash (iris/Iris#149)
Author
Owner

PR #152 gemergt: feat(plugin-actions): irisCall und irisCancel, Ausführung der Aktionen mit der verwalteten Laufzeit (#149)
#152

Commits:

  • feat(plugin-actions): irisCall und irisCancel, Ausführung der Aktionen mit der verwalteten Laufzeit (#149)

Stand: Schritt 3 von 7 gebaut — irisCall/irisCancel in der WebView-Brücke und das Ausführen einer Aktion mit der verwalteten Laufzeit. Noch ruft kein Plugin eine Aktion auf (keines deklariert actions); die Kette ist aber bis zum Skript gebaut und geprüft.

Neu

  • Seite → App: Beim Laden jeder Plugin-Seite spielt Iris irisCall(name, payload) und irisCancel(callId) ein (WKUserScript, am Dokumentanfang, PluginActionsCore.bridgeScript). irisCall liefert ein Promise mit {ok, result} oder {ok: false, error: {code, message}} und wirft nie; Nutzdaten gehen als JSON-Text (zyklisch oder BigInt: sofort exit ohne Aufruf). Das Plugin bestimmt der Host beim Erzeugen der WebView (PluginWebViewRegistry.pluginID(forBlockType:)), die Seite kann nie Aktionen eines anderen Plugins aufrufen. Abgeschlossener Block: nur_lesen. Beim Entladen der Seite werden laufende Aufrufe beendet.
  • Iris/PluginActionRunnerCore.swift (Foundation, nonisolated):
    • Aufrufvertrag: stdin {aktion, payload, kontext{plugin_id, plugin_version, block_id, job_id, schema}}, stdout ein JSON-Objekt {ok, result | error}; alle Fehlerfälle werden in Codes übersetzt: unbekannt, abgelehnt, nicht_verfuegbar, laufzeit, timeout, exit, zu_gross, abgebrochen, ausgelastet, nur_lesen; eigene Codes eines Skripts kommen durch, reservierte werden zu skript.
    • Start: <Interpreter> -s <Skript> <args> im Datenverzeichnis des Plugins. Bewusst nicht -I: Der isolierte Modus nähme das Skriptverzeichnis aus dem Suchpfad, und Skripte importieren ihre Nachbarn (genau das hatte sich bei claude_sessions.py gezeigt).
    • Warteschlange: höchstens 2 gleichzeitig je Plugin, 8 warten FIFO, darüber ausgelastet.
    • Python-Anforderung (>=, ==, ~=) gegen die gelieferte Version.
    • venv aus der Lock-Datei: einmal je Plugin, Version und Hash, uv venv und uv pip install --require-hashes, halbe venv wird entfernt, danach kein Netz.
    • Aktionslog: ~/Library/Application Support/Iris/plugin-logs/<Plugin>.log, eine Zeile je Aufruf (Zeit, Plugin, Aktion, Dauer, ok oder Code), keine Nutzdaten, Kürzung bei 256 KB. (Damit ist der offene Punkt „Name und Ort des Aktionslogs“ entschieden.)
  • PluginActionsService.call verbindet alles (Beschreibung, Zustimmung, Cache, Laufzeit, Platz, Ausführung, Log); die Quelle für Blöcke dieser Iris setzt PluginManager beim Registrieren der Web-Ansicht. Der Mirror der Laufzeit kommt aus UserDefaults (pluginRuntimeMirrorURL).

Geprüft

  • Vier Testskripte, alle grün: test-plugin-runtime-core.sh 92, test-plugin-actions-core.sh 125, test-plugin-runner-core.sh 139 (Übersetzung aller Fehlerfälle, Ausführung mit echtem Python inkl. Nachbar-Modul, Umlaute, Token kommt nicht an, Zeitlimit, Abbrechen, Größenlimits, Python-Anforderungen, Warteschlange FIFO/voll/Abbrechen, Log mit Kürzung, venv mit nachgebautem uv), test-plugin-actions-bridge.sh 20 (die Seite-Schnittstelle in echtem WebKit mit nachgebautem Host: schon vor dem ersten Seitenskript da, Promise, Fehler als Wert samt Anführungszeichen/Zeilenumbruch/U+2028/Rückstrich, parallele Aufrufe, Abbrechen, doppelte und unbekannte Antworten, fehlender Host).
  • Echt, nicht nachgebaut (einmalig von Hand, Verzeichnisse gelöscht): Die gepinnten Archive vom Upstream wurden geladen, gegen den Hash geprüft und entpackt: Python 3.14.7 in 4,5 s, uv 0.12.3 in 21 s, Verzeichnisname wie bei Hermes. Danach mit dem echten uv eine venv aus einer Lock-Datei (six==1.16.0 mit Hash) gebaut (0,6 s), die Aktion in dieser venv ausgeführt (Python 3.14.7, six 1.16.0 importiert) und ein zweiter Aufruf baute nichts neu.
  • App-Build (isoliert, mit der Erlaubnis des Nutzers für alle Schritte von #149): Iris und IrisClient BUILD SUCCEEDED, keine Warnung zu den geänderten oder neuen Dateien; danach gelöscht und bei Launch Services abgemeldet.

Nicht geprüft

  • Die Brücke in der echten App (CanvasPluginWebBlockView mit einem Plugin, das actions deklariert): Dafür fehlt noch ein Plugin mit Aktionen; geprüft ist die Seite in WebKit mit nachgebautem Host und die Ausführung mit den echten Werkzeugen, aber nicht die Verdrahtung in der laufenden App (Zustimmungsdialog, Registrierung der Quelle, Entladen der Seite).
  • Zwei Aufrufe gleichzeitig mit echtem uv; das erste Einrichten der Laufzeit ohne sichtbaren Fortschritt (der Nutzer sähe 25 s lang nichts); job_id und schema im Kontext (der Block kennt seinen Job noch nicht, beide sind null).
  • Der Mirror (Adresse und Hosting) ist weiter nicht entschieden.

Offen: Schritt 4 (Lebenszyklus und Zustand: irisSaveState, irisFinish, immer aktive Blöcke, Platzhalter ohne Plugin, WebView nur sichtbar), dann Starter, Notify und Ask, Linux-Server (#91), Migration der Plugins (iris-plugins#225).

PR #152 gemergt: feat(plugin-actions): irisCall und irisCancel, Ausführung der Aktionen mit der verwalteten Laufzeit (#149) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/152 Commits: - feat(plugin-actions): irisCall und irisCancel, Ausführung der Aktionen mit der verwalteten Laufzeit (#149) --- **Stand: Schritt 3 von 7 gebaut — `irisCall`/`irisCancel` in der WebView-Brücke und das Ausführen einer Aktion mit der verwalteten Laufzeit.** Noch ruft kein Plugin eine Aktion auf (keines deklariert `actions`); die Kette ist aber bis zum Skript gebaut und geprüft. **Neu** - **Seite → App:** Beim Laden jeder Plugin-Seite spielt Iris `irisCall(name, payload)` und `irisCancel(callId)` ein (`WKUserScript`, am Dokumentanfang, `PluginActionsCore.bridgeScript`). `irisCall` liefert ein Promise mit `{ok, result}` oder `{ok: false, error: {code, message}}` und wirft nie; Nutzdaten gehen als JSON-Text (zyklisch oder BigInt: sofort `exit` ohne Aufruf). Das Plugin bestimmt der Host beim Erzeugen der WebView (`PluginWebViewRegistry.pluginID(forBlockType:)`), die Seite kann nie Aktionen eines anderen Plugins aufrufen. Abgeschlossener Block: `nur_lesen`. Beim Entladen der Seite werden laufende Aufrufe beendet. - **`Iris/PluginActionRunnerCore.swift`** (Foundation, `nonisolated`): - **Aufrufvertrag:** stdin `{aktion, payload, kontext{plugin_id, plugin_version, block_id, job_id, schema}}`, stdout ein JSON-Objekt `{ok, result | error}`; alle Fehlerfälle werden in Codes übersetzt: `unbekannt`, `abgelehnt`, `nicht_verfuegbar`, `laufzeit`, `timeout`, `exit`, `zu_gross`, `abgebrochen`, `ausgelastet`, `nur_lesen`; eigene Codes eines Skripts kommen durch, reservierte werden zu `skript`. - **Start:** `<Interpreter> -s <Skript> <args>` im Datenverzeichnis des Plugins. Bewusst **nicht `-I`**: Der isolierte Modus nähme das Skriptverzeichnis aus dem Suchpfad, und Skripte importieren ihre Nachbarn (genau das hatte sich bei `claude_sessions.py` gezeigt). - **Warteschlange:** höchstens 2 gleichzeitig je Plugin, 8 warten FIFO, darüber `ausgelastet`. - **Python-Anforderung** (`>=`, `==`, `~=`) gegen die gelieferte Version. - **venv** aus der Lock-Datei: einmal je Plugin, Version und Hash, `uv venv` und `uv pip install --require-hashes`, halbe venv wird entfernt, danach kein Netz. - **Aktionslog:** `~/Library/Application Support/Iris/plugin-logs/<Plugin>.log`, eine Zeile je Aufruf (Zeit, Plugin, Aktion, Dauer, `ok` oder Code), **keine Nutzdaten**, Kürzung bei 256 KB. (Damit ist der offene Punkt „Name und Ort des Aktionslogs“ entschieden.) - **`PluginActionsService.call`** verbindet alles (Beschreibung, Zustimmung, Cache, Laufzeit, Platz, Ausführung, Log); die Quelle für Blöcke dieser Iris setzt `PluginManager` beim Registrieren der Web-Ansicht. Der Mirror der Laufzeit kommt aus `UserDefaults` (`pluginRuntimeMirrorURL`). **Geprüft** - **Vier Testskripte, alle grün:** `test-plugin-runtime-core.sh` 92, `test-plugin-actions-core.sh` 125, `test-plugin-runner-core.sh` **139** (Übersetzung aller Fehlerfälle, Ausführung mit echtem Python inkl. Nachbar-Modul, Umlaute, Token kommt nicht an, Zeitlimit, Abbrechen, Größenlimits, Python-Anforderungen, Warteschlange FIFO/voll/Abbrechen, Log mit Kürzung, venv mit nachgebautem `uv`), `test-plugin-actions-bridge.sh` **20** (die Seite-Schnittstelle in **echtem WebKit** mit nachgebautem Host: schon vor dem ersten Seitenskript da, Promise, Fehler als Wert samt Anführungszeichen/Zeilenumbruch/U+2028/Rückstrich, parallele Aufrufe, Abbrechen, doppelte und unbekannte Antworten, fehlender Host). - **Echt, nicht nachgebaut** (einmalig von Hand, Verzeichnisse gelöscht): Die gepinnten Archive vom Upstream wurden geladen, gegen den Hash geprüft und entpackt: **Python 3.14.7 in 4,5 s, uv 0.12.3 in 21 s**, Verzeichnisname wie bei Hermes. Danach mit dem echten `uv` eine venv aus einer Lock-Datei (`six==1.16.0` mit Hash) gebaut (0,6 s), die Aktion in dieser venv ausgeführt (Python 3.14.7, `six` 1.16.0 importiert) und ein zweiter Aufruf baute nichts neu. - **App-Build** (isoliert, mit der Erlaubnis des Nutzers für alle Schritte von #149): `Iris` und `IrisClient` `BUILD SUCCEEDED`, keine Warnung zu den geänderten oder neuen Dateien; danach gelöscht und bei Launch Services abgemeldet. **Nicht geprüft** - Die Brücke in der **echten App** (`CanvasPluginWebBlockView` mit einem Plugin, das `actions` deklariert): Dafür fehlt noch ein Plugin mit Aktionen; geprüft ist die Seite in WebKit mit nachgebautem Host und die Ausführung mit den echten Werkzeugen, aber nicht die Verdrahtung in der laufenden App (Zustimmungsdialog, Registrierung der Quelle, Entladen der Seite). - Zwei Aufrufe gleichzeitig mit echtem `uv`; das erste Einrichten der Laufzeit **ohne sichtbaren Fortschritt** (der Nutzer sähe 25 s lang nichts); `job_id` und `schema` im Kontext (der Block kennt seinen Job noch nicht, beide sind `null`). - Der Mirror (Adresse und Hosting) ist weiter nicht entschieden. **Offen:** Schritt 4 (Lebenszyklus und Zustand: `irisSaveState`, `irisFinish`, immer aktive Blöcke, Platzhalter ohne Plugin, WebView nur sichtbar), dann Starter, Notify und Ask, Linux-Server (#91), Migration der Plugins (iris-plugins#225).
Author
Owner

PR #153 gemergt: feat(plugin-actions): Lebenszyklus und Zustand autonomer Blöcke (irisSaveState, irisFinish) (#149)
#153

Commits:

  • feat(plugin-actions): Lebenszyklus und Zustand autonomer Blöcke (irisSaveState, irisFinish) (#149)

Stand: Schritt 4 von 7 — Lebenszyklus und Zustand

Befund: Ein Canvas wurde bisher durch Klicks abgeschlossen, von jedem neueren Canvas abgelöst und bekam für Eingabeblöcke einen „Absenden“-Knopf. Das widerspricht einem Block, der autonom läuft.

Geändert

  • PluginBlockLifecycleCore.swift (nur Foundation): Zustandshülle {"v":1,"state":…} mit 256-KB-Grenze, Register der autonomen Blocktypen, Regel für die Ablösung, JS irisSaveState (400 ms zusammengefasst, Flush bei irisFinish/pagehide) und irisFinish.
  • Autonome Blöcke zählen nicht als Eingabeblock (kein „Absenden“, kein Follow-up) und werden nicht von einem neueren Canvas abgelöst, solange sie laufen.
  • CanvasSnapshot: optionale Felder autonomous, blockStates, finishedBlockIDs (alte Chats lesen sich unverändert).
  • AgentJobManager.saveCanvasBlockState / finishCanvasBlock; die Blasenansicht reicht beides per Environment an den Block (canvasBlockLifecycle); irisPluginInit bekommt den Zustand als dritten Parameter; nach irisFinish ist der Block schreibgeschützt.
  • Platzhalter ohne Plugin nennt den letzten Titel.

Geprüft: neue Tests test-plugin-lifecycle-core.sh (34) und test-plugin-lifecycle-bridge.sh (12, echtes WebKit); die vier bisherigen Skripte weiter grün (92/125/139/20). Isolierte Xcode-Builds von Iris und IrisClient erfolgreich (DerivedData, Logs und LaunchServices-Einträge danach entfernt).

Nicht geprüft: nichts davon lief in der echten App, weil noch kein Plugin actions/autonomous_block_types mit einem Block deklariert. Persistenz über Neustart, Platzhalter und Ablösung sind nur über Kompilat und Kernregeln abgesichert, nicht von Hand ausprobiert. Die Snapshot-Methoden (isLiveAutonomous usw.) haben keinen eigenen Test, weil CanvasBlock.swift nicht allein übersetzbar ist.

Grenzen / offen

  • Zustand und irisFinish gelten nur im lokalen Chat; Remote-Clients (IrisClient, iOS) haben noch keine Route dafür — sie gehört zu Schritt 7 (Linux-Server, Wire-Protokoll in beiden Servern).
  • Der Web-Block wird nicht entladen, wenn er nicht sichtbar ist (der Chat nutzt keinen trägen Stapel); der Zustand ist gespeichert, aber ein ruhender Block belegt weiter eine Web-Ansicht.
  • Ausgegrauter Block bei zu neuer actions_api und Nicht-macOS-Clients: noch nicht umgesetzt.
  • Jeder gespeicherte Zustand schreibt agent_jobs.json neu (höchstens alle 400 ms je Block).
PR #153 gemergt: feat(plugin-actions): Lebenszyklus und Zustand autonomer Blöcke (irisSaveState, irisFinish) (#149) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/153 Commits: - feat(plugin-actions): Lebenszyklus und Zustand autonomer Blöcke (irisSaveState, irisFinish) (#149) --- ## Stand: Schritt 4 von 7 — Lebenszyklus und Zustand **Befund:** Ein Canvas wurde bisher durch Klicks abgeschlossen, von jedem neueren Canvas abgelöst und bekam für Eingabeblöcke einen „Absenden“-Knopf. Das widerspricht einem Block, der autonom läuft. **Geändert** - `PluginBlockLifecycleCore.swift` (nur Foundation): Zustandshülle `{"v":1,"state":…}` mit 256-KB-Grenze, Register der autonomen Blocktypen, Regel für die Ablösung, JS `irisSaveState` (400 ms zusammengefasst, Flush bei `irisFinish`/`pagehide`) und `irisFinish`. - Autonome Blöcke zählen nicht als Eingabeblock (kein „Absenden“, kein Follow-up) und werden nicht von einem neueren Canvas abgelöst, solange sie laufen. - `CanvasSnapshot`: optionale Felder `autonomous`, `blockStates`, `finishedBlockIDs` (alte Chats lesen sich unverändert). - `AgentJobManager.saveCanvasBlockState` / `finishCanvasBlock`; die Blasenansicht reicht beides per Environment an den Block (`canvasBlockLifecycle`); `irisPluginInit` bekommt den Zustand als dritten Parameter; nach `irisFinish` ist der Block schreibgeschützt. - Platzhalter ohne Plugin nennt den letzten Titel. **Geprüft:** neue Tests `test-plugin-lifecycle-core.sh` (34) und `test-plugin-lifecycle-bridge.sh` (12, echtes WebKit); die vier bisherigen Skripte weiter grün (92/125/139/20). Isolierte Xcode-Builds von Iris und IrisClient erfolgreich (DerivedData, Logs und LaunchServices-Einträge danach entfernt). **Nicht geprüft:** nichts davon lief in der echten App, weil noch kein Plugin `actions`/`autonomous_block_types` mit einem Block deklariert. Persistenz über Neustart, Platzhalter und Ablösung sind nur über Kompilat und Kernregeln abgesichert, nicht von Hand ausprobiert. Die Snapshot-Methoden (`isLiveAutonomous` usw.) haben keinen eigenen Test, weil `CanvasBlock.swift` nicht allein übersetzbar ist. **Grenzen / offen** - Zustand und `irisFinish` gelten nur im lokalen Chat; Remote-Clients (IrisClient, iOS) haben noch keine Route dafür — sie gehört zu Schritt 7 (Linux-Server, Wire-Protokoll in beiden Servern). - Der Web-Block wird nicht entladen, wenn er nicht sichtbar ist (der Chat nutzt keinen trägen Stapel); der Zustand ist gespeichert, aber ein ruhender Block belegt weiter eine Web-Ansicht. - Ausgegrauter Block bei zu neuer `actions_api` und Nicht-macOS-Clients: noch nicht umgesetzt. - Jeder gespeicherte Zustand schreibt `agent_jobs.json` neu (höchstens alle 400 ms je Block).
Author
Owner

PR #163 gemergt: feat(plugin-actions): Starter im Manifest und Route POST /agent/{job}/canvas — Block ohne Modelllauf in den Chat (#149)
#163

Commits:

  • feat(plugin-actions): Starter im Manifest und Route POST /agent/{job}/canvas — Block ohne Modelllauf in den Chat (#149)

Stand — Schritt 5: Starter und Route „Canvas anhängen“

Was geändert wurde:

  • Iris/PluginStarterCore.swift (neu, nur Foundation): prüft das {"block": {…}}-Ergebnis einer open-Aktion (Typ muss in autonomous_block_types stehen, Kennung ^[A-Za-z0-9_-]{1,64}$ oder erzeugt und ins Objekt zurückgeschrieben, Block ≤ 200 KB) und den Body der Canvas-Route (≤ 1 MB, ≤ 50 Blöcke, jeder decodierbar).
  • AgentJobManager.appendCanvasMessage hängt eine Canvas-Nachricht ohne Modelllauf an (isOutOfBand: true, responseFilePath: nil, IrisJobsContentChanged posten); createIdleJob legt einen Idle-Job an, wenn kein Chat offen ist; starterTargetJobID/AgentJobChatPanelManager.isShowing erkennen das sichtbare Chat-Fenster.
  • PluginActionsService.launch führt die Starter-Aktion über denselben Weg wie irisCall aus (Zustimmung, Skript-Cache, Laufzeit, Warteschlange inklusive).
  • PluginManagementView: Starter-Knöpfe auf der Plugin-Karte (nur bei aktiviertem Plugin mit launchers), Fehler als Alert.
  • Mac-Server-Route POST /agent/<job>/canvas (handleAppendCanvas) + Client RemoteServerClient.appendCanvasMessage.
  • Manifest-Feld launchers jetzt auch in PluginManifest (PluginLauncher, nur zum Zeichnen der Knöpfe); validiert bleibt allein PluginActionsCore.declaration(fromManifest:), gehört weiterhin nicht zum bundle_hash.
  • Doku: docs/plugin-development/06-plugin-actions.md auf Schritt 5; die Route im Wire-Protokoll (Root-Repo) im selben Zug nachgezogen.

Geprüft:

  • scripts/test-plugin-starter-core.sh: 29 Prüfungen grün (gültiges Ergebnis inkl. Kennung-Übernahme, fehlende/fremde/ungültige/zu lange Kennung, Fremdtyp, fehlender Typ, Größe, Sonderzeichen; Canvas-Route: Titel optional/leer→nil, ohne/leere Blöcke, zu viele Blöcke, Body zu groß, kein JSON).
  • swiftc -typecheck des Kerns (in derselben Skript-Zeile).

Nicht geprüft:

  • Nichts lief in der echten App — kein Plugin deklariert bisher launchers/Aktionen. Klick auf einen Starter-Knopf, Anhängen an ein offenes Chat-Fenster, Idle-Job-Anlage ohne offenen Chat, die Remote-Route gegen einen laufenden Server: nicht ausprobiert. Erster echter Test folgt mit der Migration des Memory-Editors (iris-plugins#225).
  • iOS/Browser (Block ausgegraut) ist Schritt 7 und noch nicht gebaut.

Grenzen / offen:

  • Starter erscheint bisher nur in der Plugin-Verwaltung, nicht in der Chat-Leiste; Starter-UI für Remote-Clients folgt mit Schritt 7.
  • Schritte 6 (Notify/Ask) und 7 (Linux-Server #91, Plugin-Migration, Zustandsroute für Remote-Clients) stehen aus.
  • Aus Schritt 3/4 offen: sichtbarer Fortschritt beim Laufzeit-Setup, job_id/schema im Kontext, Entladen unsichtbarer Blöcke, ausgegraute Blöcke.
PR #163 gemergt: feat(plugin-actions): Starter im Manifest und Route POST /agent/{job}/canvas — Block ohne Modelllauf in den Chat (#149) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/163 Commits: - feat(plugin-actions): Starter im Manifest und Route POST /agent/{job}/canvas — Block ohne Modelllauf in den Chat (#149) --- ## Stand — Schritt 5: Starter und Route „Canvas anhängen“ **Was geändert wurde:** - `Iris/PluginStarterCore.swift` (neu, nur Foundation): prüft das `{"block": {…}}`-Ergebnis einer `open`-Aktion (Typ muss in `autonomous_block_types` stehen, Kennung `^[A-Za-z0-9_-]{1,64}$` oder erzeugt und ins Objekt zurückgeschrieben, Block ≤ 200 KB) und den Body der Canvas-Route (≤ 1 MB, ≤ 50 Blöcke, jeder decodierbar). - `AgentJobManager.appendCanvasMessage` hängt eine Canvas-Nachricht **ohne Modelllauf** an (`isOutOfBand: true`, `responseFilePath: nil`, `IrisJobsContentChanged` posten); `createIdleJob` legt einen Idle-Job an, wenn kein Chat offen ist; `starterTargetJobID`/`AgentJobChatPanelManager.isShowing` erkennen das sichtbare Chat-Fenster. - `PluginActionsService.launch` führt die Starter-Aktion über denselben Weg wie `irisCall` aus (Zustimmung, Skript-Cache, Laufzeit, Warteschlange inklusive). - `PluginManagementView`: Starter-Knöpfe auf der Plugin-Karte (nur bei aktiviertem Plugin mit `launchers`), Fehler als Alert. - Mac-Server-Route `POST /agent/<job>/canvas` (`handleAppendCanvas`) + Client `RemoteServerClient.appendCanvasMessage`. - Manifest-Feld `launchers` jetzt auch in `PluginManifest` (`PluginLauncher`, nur zum Zeichnen der Knöpfe); validiert bleibt allein `PluginActionsCore.declaration(fromManifest:)`, gehört weiterhin nicht zum `bundle_hash`. - Doku: `docs/plugin-development/06-plugin-actions.md` auf Schritt 5; die Route im Wire-Protokoll (Root-Repo) im selben Zug nachgezogen. **Geprüft:** - `scripts/test-plugin-starter-core.sh`: 29 Prüfungen grün (gültiges Ergebnis inkl. Kennung-Übernahme, fehlende/fremde/ungültige/zu lange Kennung, Fremdtyp, fehlender Typ, Größe, Sonderzeichen; Canvas-Route: Titel optional/leer→nil, ohne/leere Blöcke, zu viele Blöcke, Body zu groß, kein JSON). - `swiftc -typecheck` des Kerns (in derselben Skript-Zeile). **Nicht geprüft:** - Nichts lief in der echten App — kein Plugin deklariert bisher `launchers`/Aktionen. Klick auf einen Starter-Knopf, Anhängen an ein offenes Chat-Fenster, Idle-Job-Anlage ohne offenen Chat, die Remote-Route gegen einen laufenden Server: nicht ausprobiert. Erster echter Test folgt mit der Migration des Memory-Editors (iris-plugins#225). - iOS/Browser (Block ausgegraut) ist Schritt 7 und noch nicht gebaut. **Grenzen / offen:** - Starter erscheint bisher nur in der Plugin-Verwaltung, nicht in der Chat-Leiste; Starter-UI für Remote-Clients folgt mit Schritt 7. - Schritte 6 (Notify/Ask) und 7 (Linux-Server #91, Plugin-Migration, Zustandsroute für Remote-Clients) stehen aus. - Aus Schritt 3/4 offen: sichtbarer Fortschritt beim Laufzeit-Setup, `job_id`/`schema` im Kontext, Entladen unsichtbarer Blöcke, ausgegraute Blöcke.
Author
Owner

PR #106 gemergt: docs(wire): POST /agent/{job}/canvas — Canvas-Nachricht ohne Modelllauf anhängen (iris/Iris#149 Schritt 5)
alf/2ndbrain#106

Commits:

  • docs(wire): POST /agent/{job}/canvas — Canvas-Nachricht ohne Modelllauf anhängen (iris/Iris#149 Schritt 5)
PR #106 gemergt: docs(wire): POST /agent/{job}/canvas — Canvas-Nachricht ohne Modelllauf anhängen (iris/Iris#149 Schritt 5) https://forgejo.lewerken.duckdns.org/alf/2ndbrain/pulls/106 Commits: - docs(wire): POST /agent/{job}/canvas — Canvas-Nachricht ohne Modelllauf anhängen (iris/Iris#149 Schritt 5)
Author
Owner

PR #164 gemergt: feat(plugin-actions): Starter in der Chat-Leiste — Menu „Plugin-Block“ hängt den autonomen Block ohne Modelllauf in diesen Chat (#149)
#164

Commits:

  • feat(plugin-actions): Starter in der Chat-Leiste — Menu „Plugin-Block“ hängt den autonomen Block ohne Modelllauf in diesen Chat (#149)

Stand — Starter in der Chat-Leiste (iris/Iris#149, offener Punkt aus Schritt 5)

Befund

Schritt 5 (Starter und Route „Canvas anhängen“) war gemergt, aber die Starter-Knöpfe gab es nur auf der
Plugin-Karte in der Plugin-Verwaltung. Der vom Plan genannte offene Punkt „Starter in der Chat-Leiste“
fehlt.

Geändert

  • Iris/AgentJobChatPanel.swift: Menu „Plugin-Block“ in der Aktionsleiste von AgentJobChatView
    (nur wenn mindestens ein aktiviertes Plugin launchers deklariert). Klick → derselbe Service-Weg wie
    auf der Plugin-Karte (PluginActionsService.launch mit Zustimmung, Cache, Laufzeit, Warteschlange,
    PluginStarterCore-Prüfung), aber das Ergebnis hängt immer in diesen Chat (neuer Closure
    onAppendStarterBlock → AgentJobManager.appendCanvasMessage), nie in einen zuletzt geöffneten oder
    neu angelegten Idle-Job. Fehler (keine Zustimmung, Laufzeit, unlesbarer Block) als Alert, analog zum
    Export-Alert.
  • Iris/Localizable.xcstrings: zwei neue Schlüssel („Plugin-Block“, Hilfe-Text).
  • docs/plugin-development/06-plugin-actions.md: Ablauf und Ziel-Chat um die Chat-Leiste ergänzt;
    „Starter in der Chat-Leiste“ aus „Noch offen“ entfernt (bleibt offen: Starter für Remote-Clients).

Geprüft

  • Xcode-Build des Schemas Iris: erfolgreich (nach einem compactMap/flatMap-Typfehler, korrigiert).
  • scripts/test-plugin-starter-core.sh: 29/29 bestanden (Core unverändert, nur UI ergänzt).

Nicht geprüft

  • Nicht in der echten App ausprobiert: kein Plugin deklariert bisher launchers — das Menu
    erscheint aktuell nirgends. Erster echter Test mit der Memory-Editor-Migration (iris-plugins#225).
  • Kein automatischer UI-Test für das Menu.

Grenzen / offen

  • Starter für Remote-Clients bleibt offen (Schritt 7, mit Zustandsroute).
  • PluginManager wird in der Chat-View ohne Abonnement gelesen: ein während einer Session installiertes
    Plugin zeigt sein Starter-Menu erst beim nächsten Öffnen eines Chat-Fensters.
PR #164 gemergt: feat(plugin-actions): Starter in der Chat-Leiste — Menu „Plugin-Block“ hängt den autonomen Block ohne Modelllauf in diesen Chat (#149) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/164 Commits: - feat(plugin-actions): Starter in der Chat-Leiste — Menu „Plugin-Block“ hängt den autonomen Block ohne Modelllauf in diesen Chat (#149) --- # Stand — Starter in der Chat-Leiste (iris/Iris#149, offener Punkt aus Schritt 5) ## Befund Schritt 5 (Starter und Route „Canvas anhängen“) war gemergt, aber die Starter-Knöpfe gab es nur auf der Plugin-Karte in der Plugin-Verwaltung. Der vom Plan genannte offene Punkt „Starter in der Chat-Leiste“ fehlt. ## Geändert - `Iris/AgentJobChatPanel.swift`: Menu „Plugin-Block“ in der Aktionsleiste von `AgentJobChatView` (nur wenn mindestens ein aktiviertes Plugin `launchers` deklariert). Klick → derselbe Service-Weg wie auf der Plugin-Karte (`PluginActionsService.launch` mit Zustimmung, Cache, Laufzeit, Warteschlange, `PluginStarterCore`-Prüfung), aber das Ergebnis hängt immer in **diesen** Chat (neuer Closure `onAppendStarterBlock` → `AgentJobManager.appendCanvasMessage`), nie in einen zuletzt geöffneten oder neu angelegten Idle-Job. Fehler (keine Zustimmung, Laufzeit, unlesbarer Block) als Alert, analog zum Export-Alert. - `Iris/Localizable.xcstrings`: zwei neue Schlüssel („Plugin-Block“, Hilfe-Text). - `docs/plugin-development/06-plugin-actions.md`: Ablauf und Ziel-Chat um die Chat-Leiste ergänzt; „Starter in der Chat-Leiste“ aus „Noch offen“ entfernt (bleibt offen: Starter für Remote-Clients). ## Geprüft - Xcode-Build des Schemas Iris: erfolgreich (nach einem `compactMap`/`flatMap`-Typfehler, korrigiert). - `scripts/test-plugin-starter-core.sh`: 29/29 bestanden (Core unverändert, nur UI ergänzt). ## Nicht geprüft - Nicht in der echten App ausprobiert: **kein Plugin deklariert bisher `launchers`** — das Menu erscheint aktuell nirgends. Erster echter Test mit der Memory-Editor-Migration (iris-plugins#225). - Kein automatischer UI-Test für das Menu. ## Grenzen / offen - Starter für Remote-Clients bleibt offen (Schritt 7, mit Zustandsroute). - `PluginManager` wird in der Chat-View ohne Abonnement gelesen: ein während einer Session installiertes Plugin zeigt sein Starter-Menu erst beim nächsten Öffnen eines Chat-Fensters.
Author
Owner

PR #240 gemergt: feat(starter-test): Test-Plugin für autonome Web-Blöcke — Starter, Aktionen ohne Modell-Lauf, Zustand (iris/Iris#149)
iris/iris-plugins#240

Commits:

  • feat(starter-test): Test-Plugin für autonome Web-Blöcke — Starter, Aktionen ohne Modell-Lauf, Zustand (iris/Iris#149)

Stand — Test-Plugin iris-starter-test (iris/Iris#149, autonome Web-Blöcke)

Befund

Erstes Plugin, das actions_api/launchers deklariert — zum Praxistest der autonomen Blöcke
(Starter, Aktionen ohne Modell-Lauf, Zustand über Neustart). Die Installation über den Marktplatz
schlug fehl: „ungültiges Paket oder Validierungsfehler“.

Ursache und Fix

Das Manifest ließ das Pflichtfeld skills weg (PluginManifest.skills ist nicht optional; Vertrag:
Feld muss existieren, darf leer sein). Der Decoder warf keyNotFound, importPlugin brach bei der
Validierung ohne Fehlerstatus ab, die Registry fiel auf die generische Meldung zurück.
Fix: "skills": [] im Manifest (das Plugin bringt bewusst keinen Hermes-Skill).

Geprüft

  • Paket 0.1.1 gebaut; plugin.json im Paket enthält skills: [] und decodiert sauber.
  • Manifest-Felder gegen PluginActionsCore-Prüfung gebaut (Aktionsnamen, Skripte, Starter).

Nicht geprüft

  • Ende-zu-Ende in der App (Starter-Klick, Zähl-Aktion, Abbrechen, Neustart) — folgt jetzt mit dem
    veröffentlichten 0.1.1.

Inhalt des Plugins

  • Aktionen oeffnen (Starter), zaehlen, langsam (Abbruch-Test) in skills/iris-starter-test/aktionen.py
  • autonomer Blocktyp starter_test mit Zähler und Zustand (irisSaveState)
  • Starter testen („Test-Block öffnen“)
PR #240 gemergt: feat(starter-test): Test-Plugin für autonome Web-Blöcke — Starter, Aktionen ohne Modell-Lauf, Zustand (iris/Iris#149) https://forgejo.lewerken.duckdns.org/iris/iris-plugins/pulls/240 Commits: - feat(starter-test): Test-Plugin für autonome Web-Blöcke — Starter, Aktionen ohne Modell-Lauf, Zustand (iris/Iris#149) --- # Stand — Test-Plugin iris-starter-test (iris/Iris#149, autonome Web-Blöcke) ## Befund Erstes Plugin, das `actions_api`/`launchers` deklariert — zum Praxistest der autonomen Blöcke (Starter, Aktionen ohne Modell-Lauf, Zustand über Neustart). Die Installation über den Marktplatz schlug fehl: „ungültiges Paket oder Validierungsfehler“. ## Ursache und Fix Das Manifest ließ das Pflichtfeld `skills` weg (`PluginManifest.skills` ist nicht optional; Vertrag: Feld muss existieren, darf leer sein). Der Decoder warf `keyNotFound`, `importPlugin` brach bei der Validierung ohne Fehlerstatus ab, die Registry fiel auf die generische Meldung zurück. Fix: `"skills": []` im Manifest (das Plugin bringt bewusst keinen Hermes-Skill). ## Geprüft - Paket 0.1.1 gebaut; `plugin.json` im Paket enthält `skills: []` und decodiert sauber. - Manifest-Felder gegen `PluginActionsCore`-Prüfung gebaut (Aktionsnamen, Skripte, Starter). ## Nicht geprüft - Ende-zu-Ende in der App (Starter-Klick, Zähl-Aktion, Abbrechen, Neustart) — folgt jetzt mit dem veröffentlichten 0.1.1. ## Inhalt des Plugins - Aktionen `oeffnen` (Starter), `zaehlen`, `langsam` (Abbruch-Test) in `skills/iris-starter-test/aktionen.py` - autonomer Blocktyp `starter_test` mit Zähler und Zustand (`irisSaveState`) - Starter `testen` („Test-Block öffnen“)
Author
Owner

PR #166 gemergt: feat(plugin-actions): Starter in der Chat-Leiste des Remote-Clients, Zustand/Abschluss-Routen (#149 Schritt 7)
#166

Commits:

  • feat(plugin-actions): Starter in der Chat-Leiste des Remote-Clients, Zustand/Abschluss-Routen (#149 Schritt 7)

Stand: Schritt 7 — Starter in der Chat-Leiste des Remote-Clients (Branch 149-starter-remote-chat)

Geändert:

  • Remote-Chat (RemoteJobChatView): Menü „Plugins“ mit den Startern des verbundenen Servers; der Starter läuft über PluginActionsService mit Quelle .remote(client) und hängt den Block ohne Modelllauf an diesen Chat. Zustimmung wie lokal einmal je (Plugin, Version, Skript-Hash).
  • Wire: die plugins-Liste trägt launchers (id, label, icon, action); neue Routen PUT /agent/<job>/canvas-plugin/<block>/state und POST …/finish, adressiert über die Snapshot-ID. Der Server prüft die fertige Hülle nur (PluginBlockLifecycleCore.envelope(fromWrappedEnvelope:)), wickelt sie nie neu ein.
  • Build-Fehler behoben: IrisRemoteServer.swift:2867 nutzte errorDescription, StateError hat message.
  • Mitgenommen: Localizable.xcstrings (2 neue Texte, sonst Xcode-Umformatierung) und BlueprintName im IrisClient-Scheme (von Xcode erzeugt).

Geprüft: Builds Iris und IrisClient (Debug) laufen durch.

Nicht geprüft:

  • Von Hand ausprobiert ist nichts: Starter im Remote-Chat, Zustand über Neustart/Wegscrollen, Abschluss des Blocks.
  • Wire-Änderung gegenüber dem Linux-Server: launchers und die beiden neuen Routen sind dort noch nicht nachgezogen (laut Vertrag in beiden Servern und allen Clients prüfen), ebenso die Wire-Doku (02-Wire-Protokoll.md).
  • Offene Rückfragen aus der Session: Fenster des Chevron-Menüs, Verhalten bei mehreren Startern je Plugin.
PR #166 gemergt: feat(plugin-actions): Starter in der Chat-Leiste des Remote-Clients, Zustand/Abschluss-Routen (#149 Schritt 7) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/166 Commits: - feat(plugin-actions): Starter in der Chat-Leiste des Remote-Clients, Zustand/Abschluss-Routen (#149 Schritt 7) --- ## Stand: Schritt 7 — Starter in der Chat-Leiste des Remote-Clients (Branch 149-starter-remote-chat) **Geändert:** - Remote-Chat (`RemoteJobChatView`): Menü „Plugins“ mit den Startern des verbundenen Servers; der Starter läuft über `PluginActionsService` mit Quelle `.remote(client)` und hängt den Block ohne Modelllauf an diesen Chat. Zustimmung wie lokal einmal je (Plugin, Version, Skript-Hash). - Wire: die `plugins`-Liste trägt `launchers` (id, label, icon, action); neue Routen `PUT /agent/<job>/canvas-plugin/<block>/state` und `POST …/finish`, adressiert über die Snapshot-ID. Der Server prüft die fertige Hülle nur (`PluginBlockLifecycleCore.envelope(fromWrappedEnvelope:)`), wickelt sie nie neu ein. - Build-Fehler behoben: `IrisRemoteServer.swift:2867` nutzte `errorDescription`, `StateError` hat `message`. - Mitgenommen: `Localizable.xcstrings` (2 neue Texte, sonst Xcode-Umformatierung) und `BlueprintName` im IrisClient-Scheme (von Xcode erzeugt). **Geprüft:** Builds `Iris` und `IrisClient` (Debug) laufen durch. **Nicht geprüft:** - Von Hand ausprobiert ist nichts: Starter im Remote-Chat, Zustand über Neustart/Wegscrollen, Abschluss des Blocks. - Wire-Änderung gegenüber dem Linux-Server: `launchers` und die beiden neuen Routen sind dort noch nicht nachgezogen (laut Vertrag in beiden Servern und allen Clients prüfen), ebenso die Wire-Doku (`02-Wire-Protokoll.md`). - Offene Rückfragen aus der Session: Fenster des Chevron-Menüs, Verhalten bei mehreren Startern je Plugin.
Author
Owner

PR #249 gemergt: feat(comfyui-workflows): Starter für die Workflow-Verwaltung — autonomer Block ohne Modell-Lauf (0.3.0→3.3.0, iris/Iris#149) (#248)
iris/iris-plugins#249

Commits:

  • feat(comfyui-workflows): Starter für die Workflow-Verwaltung — autonomer Block ohne Modell-Lauf (0.3.0→3.3.0, iris/Iris#149) (#248)

Stand — comfyui-workflows 3.3.0: Starter für die Workflow-Verwaltung (iris/Iris#149)

Befund

Der Block comfyui_workflow_manager existiert seit 3.0.0, aber ihn zu öffnen kostet einen
Modell-Lauf: Flow 8 im Skill — der Nutzer muss etwas sagen, Hermes zeigt den Canvas. Mit
launchers im Manifest erscheint ein Starter auf der Plugin-Karte und im Menu „Plugin-Block“
der Chat-Leiste; ein Klick führt die Aktion oeffnen aus und hängt den Block ohne Lauf an.

Geändert

  • actions/aktionen.py (neu): die deklarierte Aktion oeffnen — stdin-Kontrakt wie im
    Test-Plugin iris-starter-test, nur Standardbibliothek, gibt
    {"block": {"type": "comfyui_workflow_manager", "title": "ComfyUI-Workflows verwalten"}}
    zurück. Bewusst ein eigener Ordner: PluginActionsCore.collectFiles nimmt den ganzen
    Ordner jedes Skripts ins Skript-Paket — in scripts/ wäre die 79 KB workflow_registry.py
    unnötig in den Client-Cache gewandert.
  • plugin.json: actions_api: 1, autonomous_block_types: ["comfyui_workflow_manager"],
    runtime.python >=3.11, actions.oeffnen (timeout 30, beschreibung für Zustimmungs- und
    Installationsdialog), launchers (id workflow-manager, icon rectangle.3.group),
    changelog-Feld, iris_min_version 2.0.0 → 2.0.1 (Starter und autonome Typen gibt es
    erst ab dieser App), Version 3.3.0.
  • build-irisplugin.sh: Kopierschritt actions/.
  • Store/Doku: store/changelog.md Abschnitt 3.3.0, store-page.json (Feature „Verwaltung
    per Klick“, Glyph window) und neu generiertes store/hero.html, neuer Abschnitt
    „Die Verwaltungs-Oberfläche“ in docs/erste-schritte.md.
  • PLAN.md: Changelog-Zeile 2026-10-24, fehlende „Lessons Learned“-Überschrift ergänzt und
    zwei neue Lessons (Skript-Paket folgt dem Ordner; Starter und fetch-Routen sind verschiedene
    Ebenen).

Bewusst nicht geändert: web/canvas-block.html und die fetch-Registry-Routen
(/plugins/comfyui-workflows/registry/..., cfg_token) bleiben. Die Bibliothek liegt beim
verbundenen Server — irisCall-Aktionen liefen auf dem Client-Gerät gegen dessen eigene, leere
Bibliothek und würden den IrisClient-gegen-Linux-Server-Fall brechen. Kein irisSaveState: der
Bridge-Script wird in CanvasWorkflowManagerBlockView/PinnedWebView bisher nicht eingespielt
(nur in CanvasPluginWebBlockView).

Geprüft

  • Manifest gegen registry-tools/plugin_actions.py (dieselben Regeln wie PluginActionsCore):
    keine Fehler; script-Pfad sicher.
  • actions/aktionen.py lokal mit echtem stdin/stdout: gültiger Aufruf → {"ok": true, "result": {"block": …}}, kaputtes stdin → {"ok": false, "error": {"code": "eingabe", …}}, Exit 0.
  • Paket gebaut (build-irisplugin.sh): actions/aktionen.py im Paket, plugin.json mit allen
    neuen Feldern.
  • Datenschutz-Scan (privacy_scan.scan_all) über Manifest, Paket, store/ und docs/: keine
    Funde.
  • publish-plugins.sh comfyui-workflows --dry-run: „3.3.0: aktualisiert“.

Nicht geprüft

  • Nichts in der laufenden App. Kein Klick auf den Starter (Plugin-Karte, Chat-Leiste), kein
    Block in einem Chat, kein Zustimmungsdialog, kein „Block bleibt nach zehn weiteren Canvas
    bedienbar“. Erster echter Test folgt mit der Installation von 3.3.0 in Iris 2.0.1.
  • Kein automatischer Test für Manifest oder Skript (Plugin-Repos haben keine Testskript-Infra;
    die Prüfung läuft über die Publish-Pipeline).

Grenzen / offen

  • Gegen einen Linux-Server verbunden funktioniert der Starter nicht, solange
    iris-linux-server#91 GET /plugins/<id>/actions, GET /plugins/<id>/file und launchers in
    der plugins-Liste nicht nachzieht. Auf dem Mac (lokaler Server) läuft er ab Iris 2.0.1.
  • Die App-Seite für diesen Blocktyp ist der Sonderfall CanvasWorkflowManagerBlockView
    (fetch-Proxy), nicht der generische CanvasPluginWebBlockView; irisCall/irisSaveState
    stehen der Seite dort nicht zur Verfügung — für den Starter unerheblich, der Weg läuft über
    PluginActionsService.launch.
  • iris_min_version 2.0.1: ältere Iris-Versionen zeigen das Plugin im Marktplatz nicht mehr als
    installierbar.
PR #249 gemergt: feat(comfyui-workflows): Starter für die Workflow-Verwaltung — autonomer Block ohne Modell-Lauf (0.3.0→3.3.0, iris/Iris#149) (#248) https://forgejo.lewerken.duckdns.org/iris/iris-plugins/pulls/249 Commits: - feat(comfyui-workflows): Starter für die Workflow-Verwaltung — autonomer Block ohne Modell-Lauf (0.3.0→3.3.0, iris/Iris#149) (#248) --- # Stand — comfyui-workflows 3.3.0: Starter für die Workflow-Verwaltung (iris/Iris#149) ## Befund Der Block `comfyui_workflow_manager` existiert seit 3.0.0, aber ihn zu öffnen kostet einen Modell-Lauf: Flow 8 im Skill — der Nutzer muss etwas sagen, Hermes zeigt den Canvas. Mit `launchers` im Manifest erscheint ein Starter auf der Plugin-Karte und im Menu „Plugin-Block“ der Chat-Leiste; ein Klick führt die Aktion `oeffnen` aus und hängt den Block ohne Lauf an. ## Geändert - **`actions/aktionen.py`** (neu): die deklarierte Aktion `oeffnen` — stdin-Kontrakt wie im Test-Plugin iris-starter-test, nur Standardbibliothek, gibt `{"block": {"type": "comfyui_workflow_manager", "title": "ComfyUI-Workflows verwalten"}}` zurück. Bewusst ein **eigener Ordner**: `PluginActionsCore.collectFiles` nimmt den ganzen Ordner jedes Skripts ins Skript-Paket — in `scripts/` wäre die 79 KB `workflow_registry.py` unnötig in den Client-Cache gewandert. - **`plugin.json`:** `actions_api: 1`, `autonomous_block_types: ["comfyui_workflow_manager"]`, `runtime.python >=3.11`, `actions.oeffnen` (timeout 30, `beschreibung` für Zustimmungs- und Installationsdialog), `launchers` (id `workflow-manager`, icon `rectangle.3.group`), `changelog`-Feld, `iris_min_version` 2.0.0 → **2.0.1** (Starter und autonome Typen gibt es erst ab dieser App), Version 3.3.0. - **`build-irisplugin.sh`:** Kopierschritt `actions/`. - **Store/Doku:** `store/changelog.md` Abschnitt 3.3.0, `store-page.json` (Feature „Verwaltung per Klick“, Glyph `window`) und neu generiertes `store/hero.html`, neuer Abschnitt „Die Verwaltungs-Oberfläche“ in `docs/erste-schritte.md`. - **`PLAN.md`:** Changelog-Zeile 2026-10-24, fehlende „Lessons Learned“-Überschrift ergänzt und zwei neue Lessons (Skript-Paket folgt dem Ordner; Starter und fetch-Routen sind verschiedene Ebenen). **Bewusst nicht geändert:** `web/canvas-block.html` und die fetch-Registry-Routen (`/plugins/comfyui-workflows/registry/...`, cfg_token) bleiben. Die Bibliothek liegt beim verbundenen Server — `irisCall`-Aktionen liefen auf dem Client-Gerät gegen dessen eigene, leere Bibliothek und würden den IrisClient-gegen-Linux-Server-Fall brechen. Kein `irisSaveState`: der Bridge-Script wird in `CanvasWorkflowManagerBlockView`/`PinnedWebView` bisher nicht eingespielt (nur in `CanvasPluginWebBlockView`). ## Geprüft - Manifest gegen `registry-tools/plugin_actions.py` (dieselben Regeln wie `PluginActionsCore`): keine Fehler; `script`-Pfad sicher. - `actions/aktionen.py` lokal mit echtem stdin/stdout: gültiger Aufruf → `{"ok": true, "result": {"block": …}}`, kaputtes stdin → `{"ok": false, "error": {"code": "eingabe", …}}`, Exit 0. - Paket gebaut (`build-irisplugin.sh`): `actions/aktionen.py` im Paket, `plugin.json` mit allen neuen Feldern. - Datenschutz-Scan (`privacy_scan.scan_all`) über Manifest, Paket, `store/` und `docs/`: keine Funde. - `publish-plugins.sh comfyui-workflows --dry-run`: „3.3.0: aktualisiert“. ## Nicht geprüft - **Nichts in der laufenden App.** Kein Klick auf den Starter (Plugin-Karte, Chat-Leiste), kein Block in einem Chat, kein Zustimmungsdialog, kein „Block bleibt nach zehn weiteren Canvas bedienbar“. Erster echter Test folgt mit der Installation von 3.3.0 in Iris 2.0.1. - Kein automatischer Test für Manifest oder Skript (Plugin-Repos haben keine Testskript-Infra; die Prüfung läuft über die Publish-Pipeline). ## Grenzen / offen - **Gegen einen Linux-Server verbunden funktioniert der Starter nicht**, solange iris-linux-server#91 `GET /plugins/<id>/actions`, `GET /plugins/<id>/file` und `launchers` in der `plugins`-Liste nicht nachzieht. Auf dem Mac (lokaler Server) läuft er ab Iris 2.0.1. - Die App-Seite für diesen Blocktyp ist der Sonderfall `CanvasWorkflowManagerBlockView` (fetch-Proxy), nicht der generische `CanvasPluginWebBlockView`; `irisCall`/`irisSaveState` stehen der Seite dort nicht zur Verfügung — für den Starter unerheblich, der Weg läuft über `PluginActionsService.launch`. - `iris_min_version` 2.0.1: ältere Iris-Versionen zeigen das Plugin im Marktplatz nicht mehr als installierbar.
alf closed this issue 2026-10-27 10:39:58 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
iris/Iris#149
No description provided.