Skip to content

Instantly share code, notes, and snippets.

@niquola
Last active April 4, 2026 18:02
Show Gist options
  • Select an option

  • Save niquola/951fdea163297093606a84b052c981eb to your computer and use it in GitHub Desktop.

Select an option

Save niquola/951fdea163297093606a84b052c981eb to your computer and use it in GitHub Desktop.
Wsjet: гипермедиа-виджеты для взаимодействия агента и человека

Wsjet: гипермедиа-виджеты для агентных воркспейсов

TL;DR

Агент создаёт скрипт → в UI появляется ⚡ виджет → пользователь взаимодействует → всё в браузере, без серверов. Скрипт на любом языке, который говорит HTML через stdout. CGI 2026.

Архитектура

graph TB
    subgraph Browser["Браузер"]
        UI["Workspace UI<br/>(htmx + Tailwind)"]
        Chat["Чат с агентом"]
        Sidebar["⚡ Сайдбар"]
    end

    subgraph Platform["Платформа"]
        WM["wsmanager<br/>(публичный API)"]
        WL["wmlet<br/>(per-workspace runtime)"]
    end

    subgraph Workspace["Воркспейс (файловая система)"]
        Script["*.wsjet.ts/py/sh<br/>(CGI скрипт)"]
        Data[".tasks.json<br/>.config.json<br/>(данные)"]
        Agent["Claude Agent<br/>(ACP)"]
    end

    Sidebar -->|"htmx GET /jet/{ws}/{name}/"| WM
    Chat -->|"fetch /jet/..."| WM
    WM -->|"proxy"| WL
    WL -->|"bun/python/bash<br/>env: METHOD, PATH<br/>stdin: POST body"| Script
    Script -->|"stdout → HTML"| WL
    WL -->|"HTML фрагмент"| UI
    Script <-->|"read/write JSON"| Data
    Agent <-->|"read/write JSON"| Data

    Agent -->|"bun .skills/wsjet/cli.ts show"| WL
    WL -->|"WS event {type:wsjet}"| Chat

    Script -->|"POST /dispatch?topic=claude"| WL
    WL -->|"submit_run"| Agent

    style Browser fill:#E8F4FD,stroke:#005FB8
    style Platform fill:#F8F8F8,stroke:#E5E5E5
    style Workspace fill:#FFF,stroke:#E5E5E5
Loading

Поток данных

sequenceDiagram
    participant U as Пользователь
    participant UI as Браузер (htmx)
    participant WM as wsmanager
    participant WL as wmlet
    participant S as wsjet скрипт
    participant A as Claude Agent

    Note over A: Агент создаёт config.wsjet.ts

    A->>WL: bun .skills/wsjet/cli.ts show config.wsjet.ts --topic=claude
    WL->>UI: WS event {type:"wsjet", jet:"config"}
    UI->>WM: fetch GET /jet/ws/config/
    WM->>WL: proxy GET /jet/config/
    WL->>S: bun config.wsjet.ts<br/>env: METHOD=GET, PATH=/
    S->>WL: stdout: HTML с формой
    WL->>WM: HTML
    WM->>UI: HTML
    UI->>U: ⚡ Форма конфигурации в чате

    U->>UI: Заполняет форму, нажимает Submit
    UI->>WM: htmx POST /jet/ws/config/save (form data)
    WM->>WL: proxy POST
    WL->>S: bun config.wsjet.ts<br/>env: METHOD=POST, PATH=/save<br/>stdin: form data
    S->>S: Сохраняет .config.json
    S->>WL: POST /dispatch?topic=claude&text=User configured DB
    WL->>A: submit_run: "User configured DB"
    S->>WL: stdout: HTML "✅ Saved!"
    WL->>UI: HTML
    UI->>U: Обновлённый виджет

    A->>A: Читает .config.json, продолжает работу
Loading

Протокол dispatch (обратная связь)

graph LR
    subgraph "Wsjet скрипт"
        F["Обработка<br/>формы"]
        D["Dispatch"]
        R["Ответ HTML"]
    end

    subgraph "wmlet"
        DP["/dispatch endpoint"]
        SR["submit_run"]
    end

    subgraph "Agent"
        T["Топик: claude"]
        Q["Очередь runs"]
    end

    F -->|"1. Сохраняет данные"| R
    F -->|"2. curl POST /dispatch"| DP
    DP -->|"3. Создаёт run"| SR
    SR -->|"4. В очередь"| Q
    Q -->|"5. Агент получает сообщение"| T
    R -->|"6. HTML → пользователь"| F

    style F fill:#FFF,stroke:#005FB8
    style DP fill:#F8F8F8,stroke:#E5E5E5
    style T fill:#E8F4FD,stroke:#005FB8
Loading

Три контекста — один протокол

graph TB
    CGI["CGI протокол<br/>env vars + stdin → stdout HTML"]

    subgraph "Контекст 1: Сайдбар"
        S1["Клик ⚡ tasks"]
        S2["htmx GET /jet/ws/tasks/"]
        S3["HTML в #file-content"]
    end

    subgraph "Контекст 2: Чат"
        C1["Агент: show tasks.wsjet"]
        C2["WS event → fetch /jet/ws/tasks/"]
        C3["HTML в чат-бабл"]
    end

    subgraph "Контекст 3: API"
        A1["curl /jet/ws/tasks/add"]
        A2["POST с form data"]
        A3["HTML ответ"]
    end

    S2 --> CGI
    C2 --> CGI
    A2 --> CGI

    CGI -->|"stdout"| S3
    CGI -->|"stdout"| C3
    CGI -->|"stdout"| A3
Loading

Проблема

Чат с агентом — это текст. Но половина задач требует UI:

  • «Выбери 3 таблицы из 20» — чекбоксы
  • «Одобри эти 5 изменений» — кнопки
  • «Настрой параметры» — форма
  • «Вот данные» — таблица или график

Обычные решения плохие: поднять сервер (порты, lifecycle), iframe (изоляция), вшить в платформу (каждый виджет = доработка).

Wsjet = CGI + htmx

Wsjet — это любой скрипт, который читает запрос из env vars и пишет HTML в stdout. Язык не важен.

#!/bin/bash
# hello.wsjet.sh
echo "<h1>Hello ${QUERY_STRING#name=}!</h1>"
echo '<form hx-get="/jet/ws/hello/" hx-target="#wsjet-hello" hx-swap="innerHTML">'
echo '  <input name="name" /><button>Go</button>'
echo '</form>'
#!/usr/bin/env python3
# tasks.wsjet.py
import os, json
tasks = json.load(open(".tasks.json")) if os.path.exists(".tasks.json") else []
print(f"<h2>Tasks ({len(tasks)})</h2>")
for t in tasks:
    print(f'<div>{"✅" if t["done"] else "⬜"} {t["title"]}</div>')
// dashboard.wsjet.ts
const data = await Bun.file(".metrics.json").json();
console.log(`<table>${data.map(r =>
  `<tr><td>${r.name}</td><td>${r.value}</td></tr>`
).join("")}</table>`);

Нет сервера. Нет порта. Нет фреймворка.

CGI-протокол

Переменная Что
REQUEST_METHOD GET, POST
PATH_INFO путь (/, /add)
QUERY_STRING параметры
WORKSPACE_DIR корень воркспейса
WSJET_ID id контейнера для htmx
DISPATCH_URL URL для обратной связи с агентом
stdin тело POST

Вывод: HTML в stdout. Платформа оборачивает в <div id="wsjet-{name}">.

Hypermedia-driven

htmx вставляет HTML прямо в DOM. Формы внутри wsjet работают через htmx. Замкнутый цикл:

Клик → htmx GET → скрипт → HTML → htmx вставляет
Submit → htmx POST → скрипт → HTML → htmx заменяет

Никакого клиентского JS (кроме htmx). Никакого state management. HTML туда, HTML обратно.

Скрипты и графики

<script> теги выполняются. Внешние библиотеки грузятся последовательно:

console.log(`
  <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-lite@5"></script>
  <script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
  <div id="vis"></div>
  <script>
    vegaEmbed('#vis', ${JSON.stringify(spec)});
  </script>
`);

Dispatch: обратная связь

Wsjet может отправить сообщение агенту (из любого языка):

curl -X POST "$DISPATCH_URL?topic=claude&text=User+approved"

Dispatch — опционален. Wsjet сам решает когда и что сообщить агенту.

Жизненный цикл виджета

stateDiagram-v2
    [*] --> Created: Агент создаёт .wsjet файл
    Created --> Visible: Появляется ⚡ в сайдбаре
    Created --> Embedded: Агент показывает в чате

    Visible --> Rendered: Пользователь кликает
    Embedded --> Rendered: fetch + вставка HTML

    Rendered --> Interaction: Пользователь заполняет форму
    Interaction --> Rendered: htmx POST → новый HTML
    Interaction --> Dispatched: POST /dispatch

    Dispatched --> AgentNotified: submit_run в топик
    AgentNotified --> [*]: Агент продолжает работу

    Rendered --> [*]: Пользователь уходит
Loading

Почему CGI снова работает

2026: Bun стартует за 10ms. Python — 30ms. Для виджетов — мгновенно.

HTML — универсальный формат. Не нужен JSON + React.

htmx заменяет JavaScript. Три атрибута вместо фреймворка.

Эпоха Технология Модель
1993 CGI Perl/C → HTML
2004 PHP .php → HTML
2010 SPA JSON API + React
2026 Wsjet любой скрипт → HTML + htmx

Полный круг.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment