release: public alpha 0.6.0 baseline

This commit is contained in:
JWeBot Contributors
2026-10-15 03:58:31 +08:00
commit d4242ae07c
238 changed files with 37724 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
# SDK API Reference
Package:
```text
jwebot-sdk
```
Current API version:
```text
1
```
## Event
`Event` is the normalized incoming message exposed to extension code.
Important fields:
- `event_id`
- `observed_at`
- `sender`
- `text`
- `group_fingerprint`
- `mention_bot`
- `confidence`
- `sender_confidence`
- `body_confidence`
- `source`
`sender` is an untrusted display label. `group_fingerprint` binds kernel
delivery safety but is not a selectable target.
## ReplyPlan
```python
ReplyPlan.text("visible reply")
ReplyPlan.no_reply()
```
Maximum visible text length is 900 characters.
## Fact
```python
Fact(kind="topic", text="likes servers", confidence=0.8)
```
Facts are candidates. The kernel still applies scope, sensitivity, evidence,
expiry, and deduplication policy.
## ExtensionResult
```python
ExtensionResult(
reply=ReplyPlan.text("visible reply"),
facts=(Fact(...),),
metadata={"source": "plugin-id"},
)
```
Limits:
- at most 12 facts;
- at most 16 metadata entries;
- short string metadata only.
## SingleGroupContext
Protocols exposed through the context:
- `commands`
- `storage`
- `http`
- `services`
- `facts`
- `scheduler`
- `logger`
Not exposed:
- GUI sender;
- group selector;
- raw OCR or screenshots;
- ledger or SQLite;
- filesystem;
- environment;
- subprocess or shell;
- arbitrary sockets or HTTP;
- credentials.
## CommandRegistry
```python
context.commands.register("/hello", handler)
```
The registered command must be declared in `plugin.toml`. The Kernel catalog
detects conflicts during startup and freezes before runtime routing.
User-visible `description` and `help` metadata also live in `plugin.toml`;
bundled `/功能列表` output is generated from the reviewed metadata rather than
from handler code.
## ApprovedHttpClient
```python
context.http.request(
"approved-profile",
method="GET",
path="/bounded/path",
query={"key": "value"},
)
```
The kernel owns host allowlists, credentials, proxy, timeout, cache, and
response limits. API version 1 does not expose a general-purpose URL client.
## ApprovedServiceBroker
```python
context.services.call(
"reviewed-profile",
operation="execute",
payload={"bounded": "data"},
)
```
Reviewed local adapters use named profiles to request Kernel-owned behavior without
receiving raw Ledger, provider, process, or GUI objects. Undeclared or
unconfigured profiles fail closed.
+111
View File
@@ -0,0 +1,111 @@
# Extension Author Guide
JWeBot extensions are reviewed Python packages for one preselected
WeChat group. They do not receive WeChat GUI access or server credentials.
## Create A Command Extension
From the repository root:
```sh
./tools/create-plugin.py weather-command /weather \
--author FriendName \
--description "查询指定城市天气" \
--help-entry "/weather 城市"
```
This creates:
```text
extensions/friends/weather-command/
plugin.toml
README.md
src/weather_command.py
tests/test_plugin.py
```
The generator refuses invalid IDs, invalid command names, and existing
destinations. The required author and user-facing description/help metadata are
recorded in `plugin.toml`. The Kernel uses that metadata to generate help.
## Implement Behavior
The handler receives:
```python
def handle(event: Event, arguments: str) -> ExtensionResult:
...
```
Return:
```python
ExtensionResult(reply=ReplyPlan.text("result"))
```
Use `ReplyPlan.no_reply()` when no visible response is appropriate.
Do not return raw strings or call a sender.
## Request Capabilities
Declare only what the extension needs:
```toml
author = "FriendName"
description = "查询指定城市天气"
help = ["/weather 城市"]
[capabilities]
commands = ["/weather"]
storage = false
network_profiles = []
```
Network access requires a separately reviewed named profile. Never put an API
key in source, tests, manifest, logs, or README.
The plugin entrypoint registers exactly the commands declared in the manifest.
Registration freezes at startup; runtime command mutation is rejected.
## Test Locally
Validate the manifest:
```sh
./tools/validate-plugin.py extensions/friends/weather-command/plugin.toml
```
Run the repository gate:
```sh
./tools/check-local.sh
```
On Python earlier than 3.11, install `requirements-dev.txt` so TOML validation
uses `tomli`.
## Submit
1. Create a branch.
2. Add the extension, tests, and README.
3. Run the local gate.
4. Open a PR in the Git hosting repository.
5. Explain every requested command, storage permission, and network profile.
6. Wait for capability and code review.
Merge does not imply immediate production activation. A reviewed publisher
must sign the canonical release record with a key that is not available to
extension code. Production provisioning and isolated-host activation remain
separately controlled steps.
## Review Checklist
- Uses only imports from `jwebot_sdk` plus Python pure-data helpers.
- Returns structured plans.
- Does not read environment variables or files.
- Does not use subprocess, sockets, SSH, Docker, VNC, or GUI APIs.
- Does not accept or construct a target group.
- Does not log message bodies or secrets.
- Has deterministic tests for success and failure behavior.
- Manifest matches registered commands exactly.
+163
View File
@@ -0,0 +1,163 @@
# Capability Catalog
Status: API version 1.
Capabilities are denied by default and declared in `plugin.toml`.
## Commands
Manifest field:
```toml
commands = ["/hello"]
```
Rules:
- command names begin with one `/`;
- one extension owns a reviewed namespace;
- duplicates and undeclared registrations are rejected;
- command extensions declare at least one command;
- enrichment and scheduled extensions cannot declare commands.
Command plugins also carry non-capability display metadata:
```toml
description = "Return a local greeting"
help = ["/hello [name]"]
```
The Kernel uses signed description/help metadata for automatic help and Host
health validation. Display metadata never grants storage, network, or service
access.
## Storage
Manifest field:
```toml
storage = true
```
The host exposes only a private key/value namespace for that extension.
It does not expose:
- filesystem paths;
- another extension's state;
- the edge ledger;
- OCR history;
- environment variables.
Storage quotas and persistence format remain kernel-owned.
## Approved Network Profiles
Manifest field:
```toml
network_profiles = ["steam-api"]
```
An approved profile owns:
- allowed hosts and paths;
- credentials;
- proxy policy;
- timeout;
- cache;
- response-size limit;
- error mapping.
Extensions receive profile names, not secrets. The current Steam behavior is the
reference: Mihomo is mandatory and proxy failure never falls back to direct
network access.
No network profile is currently part of the public example extension.
## Approved Service Profiles
Manifest field:
```toml
service_profiles = ["coin"]
```
Service profiles are narrow Kernel-owned operations used by reviewed local
adapters. They do not expose raw Ledger or provider objects.
Current profiles:
- `person-memory`
- `dice`
- `coin`
- `random-number`
- `draw-lot`
- `daily-fortune`
- `guess-number`
- `rock-paper-scissors`
- `scan-ranking`
- `repeat-ranking`
- `history-search`
- `server-status`
- `jwebot-version`
- `debug-telemetry`
- `steam-presence`
`person-memory`, `server-status`, `jwebot-version`, and `debug-telemetry` are
official-authored. The others are authored by JWeBot. Kernel direct commands
(`/功能列表`, `/帮助`, `/help`, `/余额`) have no service profile. Other
extensions do not receive these profiles by default. Every
profile request requires capability review, and an unavailable broker fails
closed.
## Facts
`FactWriter` and `ExtensionResult.facts` accept bounded fact candidates.
The kernel still controls:
- sensitive-content filtering;
- group and person scope;
- evidence requirements;
- expiry;
- deduplication;
- cloud projection.
An extension cannot directly edit profile memory.
## Scheduling
The scheduler accepts a named future intent. The kernel owns the clock, durable
queue, idempotency key, target group binding, and eventual delivery checks.
API version 1 does not permit arbitrary cron expressions or direct timers owned
by extension code.
## Logging
The extension logger accepts short messages and structured fields.
Logs must not contain:
- message bodies;
- group names;
- reply text;
- credentials;
- environment values;
- raw HTTP payloads.
## Explicitly Unavailable
The SDK intentionally has no capability for:
- GUI or VNC;
- group selection;
- raw OCR;
- SQLite;
- filesystem;
- environment;
- subprocess;
- SSH or Docker;
- unrestricted sockets or HTTP;
- direct model credentials.
+149
View File
@@ -0,0 +1,149 @@
# Command Registration And Execution Paths
Status: authoritative local registration and routing contract for
`alpha 0.6.x`.
## Three Execution Paths
Every accepted message resolves to exactly one named execution path:
| Execution path | Input | Runtime | Reply behavior |
|---|---|---|---|
| `local-command` | Registered `/command` | Kernel command or reviewed plugin | Synchronous local reply, except explicitly asynchronous Steam |
| `deepseek` | Ordinary `@JWeBot` message | Direct DeepSeek client | Fast ACK, background generation, ACK-gated final reply |
| `openclaw` | `@JWeBot #task` | OpenClaw cloud runner | Fast ACK, durable cloud outbox, ACK-gated final reply |
The compatibility `route` values remain `local`, `chat`, and `cloud` in stored
events. API responses and telemetry also include the explicit
`execution_path`, so operators and plugin authors do not need to infer the
actual provider from a legacy storage label.
Unknown slash commands remain on `local-command` and fail with a local command
error. They never fall through to DeepSeek or OpenClaw.
## Fast Local Ingress
The local command path is:
1. XDamage reports a relevant message-region change.
2. OpenCV segments incoming bubbles without running OCR over the whole screen.
3. The OCR engine reuses exact visual-fingerprint matches from a bounded LRU.
4. Only new or changed sender/body crops use RapidOCR.
5. Both recognition-only and detection fallback paths are prewarmed at startup.
6. A command event is written to an atomic, fsynced spool file.
7. The in-process responder is signalled immediately after durable replacement.
8. Edge durably records and executes the local command.
9. The guarded GUI sender revalidates the selected group and sends the reply.
The queue directory remains the recovery source of truth. The wake signal is
only a latency optimization; a separate responder process or a missed signal
still discovers work through the bounded one-second fallback scan.
Exact image bytes, crop dimensions, and sender pixels participate in the OCR
cache key. Identical repeated messages remain separate visible occurrences
because each bubble position still produces its own `IncomingMessage`.
Changed pixels force fresh OCR. The cache is bounded and periodically
revalidated.
## Bundled Plugin Registration
Bundled command metadata has one source:
```text
packages/jwebot-official-extensions/
src/jwebot_official_extensions/descriptors.py
```
Each `LocalExtensionDescriptor` declares:
- `extension_id`;
- `author`;
- user-visible `description`;
- user-visible `help_entries`;
- owned internal `command_kinds`.
Aliases are resolved from the same inventory. Run:
```sh
./tools/sync-local-extensions.py
```
The generator writes matching:
- `plugin.toml`;
- SDK adapter source;
- package README;
- package registration test;
- in-process compatibility adapter.
`--check` is part of the repository gate and rejects generated drift.
## Friend Plugin Registration
Create a command plugin with explicit user-facing metadata:
```sh
./tools/create-plugin.py weather-command /weather \
--author FriendName \
--description "查询指定城市天气" \
--help-entry "/weather 城市"
```
The command must also be registered in the plugin entrypoint:
```python
def plugin(context):
context.commands.register("/weather", handle)
```
Startup fails if registered commands differ from signed manifest commands.
The catalog freezes after startup, so command ownership cannot change during
request processing.
## Automatic Help
`/功能列表`, `/帮助`, and `/help` are Kernel commands. Their visible output is
generated from:
- Kernel command descriptors;
- each bundled plugin's `description`;
- each bundled plugin's `help_entries`;
- the fixed DeepSeek and OpenClaw path descriptions.
There is no separate hand-maintained help list. Adding or changing a bundled
plugin command without updating its descriptor changes generated files and
fails the sync gate.
Signed Host health responses include description and help metadata. Edge
compares them with the startup inventory together with author, commands, and
service profiles before activating the Host.
## Manifest Metadata
Command manifests must declare:
```toml
description = "查询指定城市天气"
help = ["/weather 城市"]
```
`description` and `help` must be declared together. Help entries are bounded,
single-line command usage strings beginning with `/`. Bundled plugins require
both fields.
User-visible help text is metadata only. It grants no capability and cannot
change routing ownership.
## Official Operational Plugins
Operational slash commands are implemented through official reviewed plugin
descriptors unless they are strictly Kernel-only. The current official
operational commands are:
- `/状态` and `/status`: server, Edge, OCR, and queue status.
- `/version`: installed JWeBot package versions.
- `/debug`: recent privacy-safe telemetry p50/p95/max summary.
These commands delegate to Kernel-owned service profiles through generated
adapters. They do not give plugin code direct access to raw SQLite, systemd,
journal, GUI, shell, or secrets.
+176
View File
@@ -0,0 +1,176 @@
# Configuration Guide
JWeBot configuration is a versioned profile package compiled into runtime
files. Source checkout paths and Git history never contain a populated profile.
## Storage
Defaults:
| Purpose | Path |
|---|---|
| Profiles | `~/.config/jwebot/profiles/` |
| Active profile | `~/.config/jwebot/current` |
| Generated runtime | `~/.config/jwebot/current/generated/` |
| Private state | `~/.local/state/jwebot/profiles/<profile>/` |
Override roots with `JWEBOT_CONFIG_ROOT` and `JWEBOT_CONFIG_STATE_ROOT`.
## Lifecycle
Create:
```sh
./tools/jwebot-config.py init personal
```
Validate and activate:
```sh
./tools/jwebot-config.py validate personal
./tools/jwebot-config.py activate personal
```
Import:
```sh
./tools/jwebot-config.py import /protected/profile.tar.gz --activate
```
Redacted export:
```sh
./tools/jwebot-config.py export personal \
--output /tmp/personal-redacted.tar.gz
```
The compiler validates every component, writes a temporary `generated`
directory, records SHA-256 digests in `manifest.json`, and renames it
atomically. Services never read partially generated config.
## Components
### Bot
`components/bot.toml` controls:
- `JWEBOT_BOT_MENTION`;
- the private identity note written to `JWEBOT_PRIVATE_CONTEXT_FILE`;
- cloud agent name;
- bounded remote memory path.
### Providers
`components/providers.toml` controls:
- DeepSeek base URL;
- optional cloud host;
- Steam owner ID;
- mandatory loopback Steam proxy.
API keys belong only in `secrets/runtime.env`:
```ini
JWEBOT_EDGE_DEEPSEEK_API_KEY=
JWEBOT_EDGE_STEAM_API_KEY=
```
Steam uses `JWEBOT_EDGE_STEAM_ID64` and
`JWEBOT_EDGE_STEAM_PROXY_URL`. No source file contains a fixed friend roster.
### Runtime
`components/runtime.toml` controls:
- Edge and local extension enablement;
- telemetry and OCR prewarm;
- delivery enablement;
- extension mode and transport;
- publisher principal;
- Host timeout and fallback;
- GUI sender timeout.
### Debug
`components/debug.toml` generates:
- `JWEBOT_DEBUG_ENABLED`;
- `JWEBOT_DEBUG_CAPTURE_PAYLOADS`;
- `JWEBOT_DEBUG_INCIDENT_RETENTION_DAYS`;
- `JWEBOT_DEBUG_SESSION_RETENTION_DAYS`;
- `JWEBOT_DEBUG_INCIDENT_ROOT`;
- `JWEBOT_DEBUG_SESSION_ROOT`.
Message bodies, sender names, raw group identifiers, provider payloads, and
environment values remain forbidden in telemetry and public incident records.
Schemas live in `config/schemas/`.
### Notification
`components/notification.toml` compiles to the file referenced by
`JWEBOT_HEALTH_NOTIFICATION_CONFIG`.
The adapter is an absolute argv array. JWeBot does not invoke a shell. The
notification body is supplied on stdin, and a zero exit status confirms
delivery. Provider credentials belong in the secret environment.
### Deployment
`components/deployment.toml` generates root-only values:
- `JWEBOT_RUNTIME_USER`;
- `JWEBOT_RUNTIME_HOME`;
- `JWEBOT_RUNTIME_PYTHON`;
- `JWEBOT_PRODUCTION_BACKUP_ROOT`.
The generated file is `current/generated/root.env`. Root deployment tooling
may source only this validated file.
### Private Context
`context/people.json` contains person aliases, optional identity facts, display
IDs, and clock targets. It compiles to `JWEBOT_PRIVATE_CONTEXT_FILE`.
Each person may also define a private `steam_id64` and `steam_aliases`. When at
least one mapping is present, Steam status and playtime are strictly scoped to
those mapped people and use their group display IDs. With no private mapping,
the provider falls back to public friend discovery.
This file is local-only PII. Default export removes all entries.
### Overrides
`overrides/runtime.env` accepts non-secret `JWEBOT_*` and `WECHAT_*` values.
`overrides/root.env` accepts deployment overrides. Secret-shaped keys are
rejected from runtime overrides and must live under `secrets/`.
## Legacy Migration
```sh
./tools/jwebot-config.py migrate-legacy personal \
--repository /path/to/old/checkout \
--environment /protected/legacy/environment \
--activate
```
The migration:
1. archives root-level debug, incident, report, and operations material;
2. converts static aliases and bot identity into private context;
3. separates secret environment keys from non-secret overrides;
4. compiles and activates the profile;
5. leaves the source checkout unchanged.
## Runtime Compatibility
The Edge CLI still reads the former
`~/.config/wechat-health-watch/environment` only as a compatibility fallback.
New systemd units use the generated profile directly.
## Validation
```sh
./tools/check-config.sh
./tools/check-secrets.py
PATH="$PWD/.venv/bin:$PATH" ./tools/check-local.sh
```
+72
View File
@@ -0,0 +1,72 @@
# Configuration Inventory
This inventory defines ownership and persistence boundaries. It intentionally
contains no real host, user, address, account, person, or credential value.
## Committed
| Path | Contents |
|---|---|
| `config/templates/default/` | Empty, public profile template. |
| `config/schemas/` | Profile, private-context, incident, and debug-event schemas. |
| `deploy/systemd/` | Generic service units with no credentials. |
| `deploy/defaults/` | Safe runtime defaults for the generic `jwebot` account. |
| `examples/` | Synthetic SDK and plugin examples. |
## Local Configuration
| Path | Mode | Contents |
|---|---:|---|
| `~/.config/jwebot/` | `0700` | Configuration root. |
| `profiles/<id>/` | `0700` | Source profile package. |
| `profiles/<id>/secrets/` | `0700` | API keys and notification secrets. |
| `profiles/<id>/context/` | `0700` | Private person and bot context. |
| `profiles/<id>/generated/` | `0700` | Atomic runtime output. |
| `current` | symlink | Active profile. |
Generated files are mode `0600`.
## Local State
| Path | Contents |
|---|---|
| `~/.local/state/jwebot/profiles/<id>/debug/sessions/` | Local debugger sessions. |
| `~/.local/state/jwebot/profiles/<id>/incidents/` | Privacy-safe incident records. |
| `~/.local/state/jwebot/profiles/<id>/telemetry/` | Optional bounded telemetry exports. |
| `~/.local/state/jwebot/profiles/<id>/archives/` | Migrated legacy debug/reports. |
| `~/.local/state/jwebot/profiles/<id>/root/` | Protected root-operation evidence. |
| `~/.local/state/jwebot-edge/` | Edge socket and SQLite ledger. |
| `~/.local/state/wechat-adaptive-ocr/` | OCR queue, history, and failed events. |
## Secret Keys
The config compiler recognizes these explicit secrets:
- `JWEBOT_EDGE_DEEPSEEK_API_KEY`;
- `JWEBOT_EDGE_STEAM_API_KEY`;
- `JWEBOT_HEALTH_NOTIFICATION_TOKEN`.
Additional names ending in `_KEY`, `_TOKEN`, `_PASSWORD`, or `_SECRET` are
treated as sensitive by export and scanning tools.
## Runtime Files
| Generated file | Consumer |
|---|---|
| `runtime.env` | Edge, OCR, health monitor, and user services. |
| `root.env` | Bootstrap and coordinated root deployer. |
| `private-context.json` | Kernel identity/alias registry only. |
| `notification.json` | Health notification adapter only. |
| `manifest.json` | Digest and generation metadata. |
Plugins receive none of these files or environment values.
## Never Commit
- populated profiles or generated output;
- debug payloads, incident archives, OCR history, screenshots, or message logs;
- SQLite, WAL, sockets, or process files;
- private person aliases, display names, SteamIDs, or group identifiers;
- host addresses, SSH aliases, usernames, or absolute personal home paths;
- API keys, passwords, tokens, private keys, or signing keys;
- configuration exports created with `--include-private`.
+185
View File
@@ -0,0 +1,185 @@
# Extension Host
Status: signed-release Host and local Edge routing integration implemented.
The Edge supports `in-process`, `prefer-host`, and `host-only` execution modes.
The production default remains `in-process`; the Host is not installed or
enabled on production host.
## Process Model
One host process:
1. receives an explicit verified release directory;
2. re-verifies installed release content, record, and publisher signature;
3. checks the publisher against an explicit OpenSSH allowed-signers file;
4. re-validates manifest and source policy;
5. loads one plugin entrypoint;
6. freezes declared command registration;
7. reads one bounded JSON request from stdin;
8. invokes one command;
9. exchanges bounded service requests with the Kernel when declared;
10. writes one final JSON response to stdout;
11. exits.
Plugin stdout is redirected away from the protocol stream.
Plugin stdout and stderr are bounded before reaching the parent pipe.
## Request
```json
{
"op": "invoke_command",
"command": "/hello",
"arguments": "world",
"event": {
"version": 1,
"event_id": "event-1",
"observed_at": "2026-10-14T10:00:00+08:00",
"sender": "JWeBot",
"text": "@JWeBot /hello world",
"confidence": 1.0,
"sender_confidence": 1.0,
"body_confidence": 1.0,
"side": "incoming",
"mention_bot": true,
"group_fingerprint": "opaque-fingerprint",
"source": "rapidocr-local"
}
}
```
The request contains no delivery target. The group fingerprint remains an
opaque safety binding for the Kernel.
## Response
```json
{
"status": "ok",
"api_version": "1",
"plugin_id": "hello-command",
"result": {
"api_version": "1",
"reply": {"kind": "text", "text": "hello JWeBot world"},
"facts": [],
"metadata": {}
}
}
```
Errors return a short error class and do not include plugin exception text.
Declared service calls use an intermediate JSON-line exchange:
```json
{
"status": "service_request",
"call_id": "1",
"profile": "coin",
"operation": "execute",
"payload": {"command": "/翻硬币"}
}
```
The parent validates the profile, performs the Kernel-owned operation, and
returns a matching `service_result`. Calls, message sizes, and total wall time
are bounded. An undeclared profile, mismatched call ID, malformed result, or
unavailable broker fails closed.
## Context Capabilities
The prototype provides:
- manifest-bound command registration;
- declared, bounded Kernel service profiles;
- bounded fact collection;
- hashed logger messages with field names only.
The following brokers intentionally fail closed until implemented:
- storage;
- approved HTTP;
- scheduler.
No GUI sender, group selector, raw ledger, environment, shell, SSH, Docker, or
arbitrary network client is supplied.
## Environment Handling
Before loading extension code, the CLI removes environment variables except
`LANG`, `LC_ALL`, and `TZ`.
This reduces accidental secret exposure but is not a security sandbox.
The parent interpreter also sets `PYTHONDONTWRITEBYTECODE=1`, and the loader
disables bytecode writes while importing a plugin, so verified release
directories cannot acquire unrecorded `__pycache__` files.
## Required Production Isolation
Before Edge integration, the host still needs an OS-enforced launcher with:
- dedicated dynamic or unprivileged identity;
- read-only verified release directory;
- private writable state directory;
- no inherited secrets;
- no network by default;
- restricted address families;
- no new privileges and empty capability set;
- process, memory, CPU, and wall-time limits;
- parent-enforced timeout and kill;
- fixed Unix-socket or pipe protocol.
Static source checks and Python-level context restrictions do not replace these
controls.
Repository templates:
```text
deploy/systemd/system/jwebot-extension-host@.service
deploy/systemd/system/jwebot-extension-host@.socket
```
They specify a fixed unprivileged `jwebot-ext` identity, private AF_UNIX-only
network namespace, empty capabilities, read-only system and release paths,
private devices/tmp, syscall filtering, and CPU/memory/process/runtime limits.
The service refuses to start unless
`/etc/jwebot-extension-host/allowed_signers` is readable and passes that trust
file to the Host. No private signing key belongs on the target.
The templates are not installed or enabled. A later provisioning phase must
create the service identity, install the Host virtual environment, prepare
release ownership and public-key trust file, run `systemd-analyze verify`, and
test the socket protocol on the target Debian version.
## Parent Client
`jwebot_kernel.ExtensionHostClient` starts one Host in a new process group with:
- an explicit verified release directory;
- `python -I` by default;
- only `LANG`, `LC_ALL`, and `TZ` inherited;
- closed unrelated file descriptors;
- bounded request/response/stderr sizes;
- a parent-enforced wall-time timeout;
- process-group kill on timeout.
The parent discards Host stderr from protocol results and returns generic
transport errors. It services only reviewed broker callbacks supplied by the
Kernel.
At Edge startup, signed manifests are loaded from `<state-root>/<plugin-id>/current`,
checked against the fixed ownership inventory, registered into one catalog,
and frozen. Host health responses must exactly match the signed author,
commands, service profiles, plugin ID, and API version.
`prefer-host` may use the in-process adapter only when a signed release is
missing or its Host fails startup health. Once a Host invocation starts, the
Kernel never replays that command through another path because the broker may
already have committed state. `host-only` requires every reviewed plugin to be
signed, installed, and healthy before Edge startup succeeds.
`jwebot_kernel.UnixExtensionHostClient` matches the systemd socket template. It
uses the same bounded JSON-line service exchange and applies one end-to-end
socket deadline. Socket absence, timeout, malformed JSON, service protocol
violations, and Host error status all fail closed.
+153
View File
@@ -0,0 +1,153 @@
# Plugin Contract
Status: authoritative for the `alpha 0.6.x` extension API.
## Scope
JWeBot extensions run for one configured, preselected WeChat group. They do not
discover groups, choose a destination, switch chats, or send messages directly.
The kernel owns:
- OCR event ordering and deduplication;
- the durable ledger and outboxes;
- acknowledgement ordering;
- model and approved network credentials;
- selected-group fingerprint verification;
- the GUI interaction lock and final WeChat delivery.
Extensions return structured intent. The kernel decides whether and when that
intent becomes a user-visible action.
## API Version
The current extension API is:
```text
api_version = "1"
```
An unsupported API version fails manifest validation before extension code is
loaded.
## Event Input
Extensions receive `jwebot_sdk.Event`, a normalized incoming event containing:
- event ID and observation timestamp;
- untrusted display sender label;
- bounded message text;
- confidence values;
- the current group fingerprint;
- mention flag and source metadata.
The group fingerprint is evidence for kernel delivery safety. It is not a group
selector and must not be used to discover or address another chat.
Display labels are conversational context only. They never grant permission.
## Result Output
An extension returns `jwebot_sdk.ExtensionResult`:
```python
ExtensionResult(
reply=ReplyPlan.text("hello"),
facts=(Fact(kind="topic", text="likes servers"),),
metadata={"source": "example"},
)
```
Limits:
- reply text: at most 900 characters;
- facts: at most 12;
- fact text: at most 120 characters;
- metadata: at most 16 short string entries.
`ReplyPlan.no_reply()` explicitly requests no visible response.
The extension cannot select a target group, draft GUI input, or press Send.
## Extension Types
### Command
Registers one or more reviewed slash-command namespaces. It may return one
reply plan and bounded facts.
### Enrichment
Consumes a normalized event off the reply-critical path. It may emit bounded
facts but cannot register commands or send replies by itself.
### Scheduled
Creates a scheduled intent that re-enters the kernel's ordered event path. It
never delivers directly.
Passive automatic reply extensions are not supported in API version 1.
## Manifest
Every extension has `plugin.toml`:
```toml
id = "hello-command"
author = "example"
description = "Return a local greeting"
help = ["/hello [name]"]
version = "1.0.0"
api_version = "1"
entrypoint = "hello_command:plugin"
type = "command"
[capabilities]
commands = ["/hello"]
storage = false
network_profiles = []
```
Unknown manifest keys and unknown capability names are rejected.
`author` is required, bounded, included in release metadata, and reviewed
against the extension's ownership directory.
Command plugins must declare bounded `description` and `help` metadata. The two
fields appear together; help entries begin with `/` and feed the
Kernel-generated `/功能列表`.
## Lifecycle
1. CI validates source, tests, manifest, API version, and capabilities.
2. Review approves the requested command and network namespaces.
3. CI builds an immutable artifact and canonical release record.
4. A reviewed publisher signs that exact release record with OpenSSH `sshsig`.
5. The target verifies the publisher, artifact digest, and API version.
6. The extension host re-verifies installed content and the publisher signature.
7. The extension host starts the approved release under resource limits.
8. The kernel registers approved manifests during startup and rejects conflicts.
9. The kernel freezes the extension catalog before runtime routing begins.
10. The kernel supplies a `SingleGroupContext`.
11. The extension registers only its declared behavior.
12. Invocation returns an `ExtensionResult` before its deadline.
13. The kernel validates and orders any resulting action.
The local Host, signed activation, and rollback contracts are implemented.
Provisioning and production enablement on production host remain separate work.
Static source validation rejects obvious access to processes, sockets, files,
dynamic execution, and Python object-introspection escape hatches. This is a CI
prefilter only. Production activation still requires a separately sandboxed
extension host with OS-enforced limits.
## Non-Negotiable Rules
- No GUI access.
- No direct WeChat send.
- No chat switching or group enumeration.
- No raw OCR frame or screenshot access.
- No direct ledger or SQLite access.
- No environment or secret reads.
- No shell, subprocess, SSH, or Docker access.
- No arbitrary network access.
- No background thread or process ownership.
- All behavior requires deterministic tests.
+119
View File
@@ -0,0 +1,119 @@
# Project Rules
These rules describe the mandatory workflow for JWeBot changes. They are not
advice; future changes must either follow them or document an explicit user
exception.
## Release Discipline
- Version format is `alpha X.Y.Z.MMDD`.
- Patch changes increment `Z`; minor version changes require explicit user
approval.
- A release change must update:
- `VERSION`;
- root `pyproject.toml`;
- every package `pyproject.toml`;
- every package `__version__`;
- `README.md`;
- `CHANGELOG.md`.
- Do not mark production complete merely because code was committed. Production
is complete only after staging, cutover, acceptance, observation, and backup
restore checks pass.
## Mandatory Pre-Production Gates
Before touching production runtime files on production host:
1. Local working tree must be understood and intentionally staged.
2. Local gate must pass:
```sh
PATH="$PWD/.venv/bin:$PATH" ./tools/check-local.sh
```
3. Upload a clean staging snapshot using Git-tracked files only.
4. Verify staging file hashes against the local tree.
5. Run the complete production host gate with:
```sh
PATH=/opt/wechat-local-ocr/venv/bin:$PATH ./tools/check-local.sh
```
6. Verify WeChat is logged in and the ready main window is at least `700x500`.
7. Verify OCR queues are empty.
8. Verify Edge SQLite `PRAGMA quick_check` returns `ok`.
## Production Cutover
- Use only `deploy/scripts/deploy-jwebot-production.sh` for normal production
cutover.
- The deployer must record both root and user systemd topology, runtime files,
configuration, and an online SQLite backup.
- The deployer must disable legacy system OCR services and keep the active
production topology to:
```text
jwebot-edge.service
wechat-ocr-adaptive-recorder.service
```
- Never skip Edge health, adaptive restart stability, queue, WeChat ready, or
SQLite gates.
- If any gate fails, confirm rollback state before retrying.
## Backup And Restore
- Pre-deploy and post-deploy baselines must include runtime files, systemd
units, environment/configuration, service states, and an online SQLite backup.
- Local and router Restic snapshots must be restored into isolated directories.
- Restore validation requires:
- `sha256sum -c SHA256SUMS`;
- SQLite `PRAGMA quick_check`;
- no restore into live paths during validation.
## GUI Delivery
- The guarded sender must validate the selected group before drafting and after
drafting.
- The sender may click a scaled send-button coordinate first, but must retain a
fallback path and visual confirmation.
- Any GUI send optimization must keep:
- GUI lock acquisition;
- tokenized draft/send protocol;
- group fingerprint revalidation;
- uncertain-delivery quarantine;
- privacy-safe per-step telemetry.
## Telemetry And Reports
- Latency work must preserve or improve per-step telemetry.
- The following stages must be visible when relevant:
- OCR capture and scan;
- queue wait and process;
- Edge routing;
- fast ACK preparation, GUI send, confirmation, and end-to-end latency;
- provider generation;
- final GUI delivery;
- `gui.send.step`, `gui.bridge.step`, and `gui.send.total`.
- Reports must separate p50/p95 for DeepSeek and OpenClaw when both are used.
- Reports must describe fixed waits, polling intervals, screenshot captures,
VNC input actions, and fallback paths in plain Chinese.
- Telemetry must not log message body, sender name, raw group id, reply text,
secrets, environment values, or provider payloads.
## Plugin Rules
- Registered slash commands must be declared in the ownership inventory.
- Prefer reviewed plugin descriptors plus generated adapters for new commands.
- Run `tools/sync-local-extensions.py --check` after plugin inventory changes.
- Plugin code must not access GUI, raw SQLite/Ledger, shell, SSH, Docker,
environment secrets, or unrestricted network.
- Runtime command ownership freezes at startup. Do not mutate command ownership
during request handling.
## Access Rules
- External production access uses an operator-provided SSH host alias.
- Root access is obtained by logging in as `jwebot` and then running `su -`.
- Do not enable direct root SSH or loosen the jump key `no-port-forwarding`
restrictions without explicit user approval.
+31
View File
@@ -0,0 +1,31 @@
# Prompt For AI Extension Authors
Use this prompt with an AI coding assistant:
```text
Create or update a JWeBot extension using only the repository's
jwebot_sdk public API and docs/plugin-contract.md.
Non-negotiable constraints:
- One preselected group only.
- No GUI, VNC, WeChat send, or chat switching.
- No raw OCR frames or screenshots.
- No direct SQLite, ledger, filesystem, or environment access.
- No subprocess, shell, SSH, Docker, or arbitrary sockets.
- No API keys or secrets in source, tests, manifest, logs, or docs.
- Network access only through a declared approved network profile.
- Return ExtensionResult with ReplyPlan and bounded Facts.
- Register only commands declared in plugin.toml.
- Preserve the reviewed author field in plugin.toml.
- Add a concise description and command-oriented help entries in plugin.toml.
- Add deterministic unit tests for success and failure behavior.
- Keep api_version = "1".
Start from a skeleton generated by:
./tools/create-plugin.py <plugin-id> /<command> --author <author> \
--description "<visible description>" --help-entry "/<command> [arguments]"
Before finishing, run:
./tools/validate-plugin.py <extension-dir>/plugin.toml
./tools/check-local.sh
```
+182
View File
@@ -0,0 +1,182 @@
# Extension Release Artifacts
Status: digest verification, OpenSSH publisher signing, and local atomic
activation implemented; production host provisioning and activation are not yet
implemented.
## Build
```sh
./tools/package-plugin.py examples/hello-command --output dist
```
The builder:
1. validates `plugin.toml`;
2. applies the static source policy;
3. rejects symlinks and unsupported runtime files;
4. bounds file count and expanded size;
5. hashes canonical relative paths and bytes;
6. creates a deterministic gzip/tar artifact;
7. writes a canonical release record.
Output names are content-addressed:
```text
<plugin-id>-<version>-<source-digest-prefix>.tar.gz
<plugin-id>-<version>-<source-digest-prefix>.release.json
```
## Verify
```sh
./tools/verify-release.py \
dist/<artifact>.tar.gz \
dist/<artifact>.release.json
```
Verification does not extract the archive. It checks:
- artifact name, size, and SHA-256;
- regular-file-only tar members;
- no absolute paths or `..` traversal;
- fixed `plugin/` archive root;
- bounded expanded size and file count;
- exact file list;
- canonical source digest.
## Determinism
File modification times, owners, groups, and host paths are not included in the
artifact. Identical reviewed inputs produce identical artifact bytes and
digests.
If a content-addressed destination already exists with different bytes, the
builder fails instead of replacing it.
## Release Record
The JSON record includes:
- schema version `2`;
- plugin ID, manifest author, and plugin version;
- SDK API version;
- source digest;
- artifact file name;
- artifact SHA-256 and size;
- exact runtime file list.
## Publisher Signing
Signing uses the OpenSSH `sshsig` format with namespace
`jwebot-release-v1`. Its domain-separated payload binds the publisher
principal, release filename, and exact canonical release JSON. The release JSON
already binds the artifact digest, size, source digest, author, and file list.
Create an Ed25519 publisher key outside the repository:
```sh
ssh-keygen -t ed25519 -f ~/.config/jwebot/publisher -C jwebot-publisher
```
Create an OpenSSH allowed-signers file containing only reviewed public keys:
```text
example-publisher ssh-ed25519 AAAA... jwebot-publisher
```
The verifier intentionally accepts only simple entries with explicit
lowercase publisher principals and Ed25519 keys. Wildcards, signer options, and
other key algorithms are rejected.
Build and sign in one step:
```sh
./tools/package-plugin.py examples/hello-command \
--output dist \
--signing-key ~/.config/jwebot/publisher \
--publisher example-publisher
```
An existing canonical release record can be signed separately:
```sh
./tools/sign-release.py dist/<release>.release.json \
--private-key ~/.config/jwebot/publisher \
--publisher example-publisher
```
The signing operation first verifies the referenced artifact's name, digest,
size, archive structure, file list, and source digest. It refuses to sign a
record whose sibling artifact is missing or inconsistent.
The private key must never be committed, copied into an extension artifact, or
installed on production host. The target receives only the reviewed public-key
allowed-signers file.
## Security Boundary
Digest verification proves content integrity relative to the release record.
Publisher verification proves that the exact canonical record was signed by a
key authorized for the declared publisher principal. The manifest `author`
identifies who wrote the plugin; signature `publisher` identifies who approved
and released those exact bytes.
Before production activation, a later phase must still add:
- reviewed public-key provisioning on production host;
- isolated extension-host health checks;
- target Debian `systemd-analyze verify` and socket protocol checks.
production host must never execute a mutable branch checkout or ordinary `git pull`.
## Local Activation Simulation
Activation requires an explicit state root:
```sh
./tools/activate-release.py \
dist/<artifact>.tar.gz \
dist/<artifact>.release.json \
--signature dist/<release>.release.sig.json \
--allowed-signers ~/.config/jwebot/allowed_signers \
--publisher example-publisher \
--state-root /tmp/jwebot-extension-state
```
The activator:
- verifies the artifact before writing release data;
- verifies the publisher signature against the explicit trust file;
- installs under `releases/<full-source-digest>/`;
- preserves the signature record beside the installed release record;
- writes files with private permissions;
- re-verifies an already installed release before reuse;
- starts the isolated Host and requires a matching health response before
switching `current`;
- switches a relative `current` symlink atomically;
- preserves the prior verified target as `previous`;
- serializes changes with a file lock.
Rollback:
```sh
./tools/rollback-release.py \
--state-root /tmp/jwebot-extension-state \
--allowed-signers ~/.config/jwebot/allowed_signers \
--publisher example-publisher
```
Rollback re-verifies the destination content and publisher signature, then
atomically swaps `current` and `previous`. It does not execute plugin code.
Unsigned activation is rejected by default. Tests may opt into the explicit
`--allow-unsigned-local` flag; that flag cannot be combined with signature
arguments and must never be used for production state.
Local packaging tests that cannot launch the installed Host may use
`--skip-health-check-local`. This is also an explicit non-production override;
signed production activation fails closed when no health check is supplied.
These commands have no default production path. A caller must provide
`--state-root`. The repository does not invoke them against production host.
+95
View File
@@ -0,0 +1,95 @@
# Repository Layout
JWeBot is a monorepo with explicit boundaries between public contracts,
privileged runtime code, reviewed plugins, deployment tooling, and local-only
configuration.
```text
JWeBot/
config/
templates/
schemas/
deploy/
defaults/
scripts/
systemd/
docs/
runbooks/
examples/
extensions/
official/
jwebot/
friends/
packages/
jwebot-config/
jwebot-sdk/
jwebot-kernel/
jwebot-extension-host/
jwebot-official-extensions/
tests/
tools/
jwebot_edge.py
jwebot_edge_delivery.py
wechat_local_ocr.py
wechat_adaptive_recorder.py
wechat_adaptive_gui_bridge.py
wechat_health_watch.py
wechat-gui-bridge
```
## Package Responsibilities
`jwebot-config`
- validates profile packages;
- compiles generated runtime files atomically;
- imports and redacts profile archives;
- migrates private context and diagnostic material out of old checkouts.
`jwebot-sdk`
- contains stable data contracts exposed to plugins;
- has no process, filesystem, raw socket, GUI, or environment capability.
`jwebot-kernel`
- owns routing, command ownership, capability enforcement, ledger access,
release verification, provider adapters, and delivery policy.
`jwebot-extension-host`
- executes one verified extension release per isolated invocation;
- runs behind an AF_UNIX socket and systemd sandbox.
`jwebot-official-extensions`
- contains the reviewed local command engine;
- provides generated adapters that return structured SDK plans;
- owns the fixed-host Steam service profile, not raw plugin network access.
## Root Entrypoints
Root Python and shell files remain compatibility entrypoints for systemd and
existing operators. Business logic belongs in packages. Compatibility modules
must stay thin and tested.
## Extension Collections
- `extensions/official/`: platform-owned service adapters.
- `extensions/jwebot/`: built-in reviewed commands.
- `extensions/friends/`: contributor-owned plugins.
The ownership inventory is authoritative. `tools/sync-local-extensions.py`
generates manifests, adapters, package tests, and README files from it.
## Configuration Boundary
The repository contains only templates and schemas. Real values live under
`~/.config/jwebot/`; runtime/diagnostic data lives under
`~/.local/state/jwebot/`. See `config/README.md`.
## Documentation Boundary
Committed documentation describes architecture, contracts, generic operations,
and sanitized examples. Machine-specific incident reports and debug logs are
local profile state, not documentation source.
+134
View File
@@ -0,0 +1,134 @@
# Edge Runbook
JWeBot Edge is the local source of truth for accepted events, command state,
provider work, acknowledgements, and final delivery.
## Routes
| Input after mention removal | Route | Execution |
|---|---|---|
| `/...` | Local | Kernel command or reviewed plugin |
| ordinary text | Chat | Direct DeepSeek client |
| `#...` | Cloud | Optional configured cloud agent |
The bot mention is read from the active profile. The public default is
`@JWeBot`.
## Steam
Commands:
```text
/steam
/steam在线
/steam游戏
/steam全部
/steam刷新
/steam时长 游戏名
/steam时长游戏名
/steam时长游戏名、玩家名
/steam时长游戏名人=玩家名
```
The configured Steam account supplies the owner ID and API key. At runtime the
provider:
1. uses private per-person SteamID mappings when configured, otherwise fetches
the public friend list;
2. batches public player summaries in groups of 100;
3. labels mapped accounts with their group display IDs and accepts their
private Steam query aliases;
4. fuzzy matches the requested game against the owner's public library;
5. falls back to scoped public libraries if the owner has no match;
6. requests the selected AppID for every scoped account with bounded
concurrency;
7. sorts public playtime descending and summarizes private/unavailable data.
For a player-specific query, `、`, `,`, `,`, `@`, or `人=` explicitly separates
the game from the player. The provider matches the live persona name and only
requests that account's game library; ambiguous matches are rejected instead
of guessed.
Steam access is fixed to `api.steampowered.com` through the configured loopback
Mihomo HTTP proxy. There is no direct-network fallback.
## Config
Inspect active profile metadata without printing values:
```sh
./tools/jwebot-config.py status
```
Validate and atomically regenerate:
```sh
./tools/jwebot-config.py activate <profile>
```
Services read:
```text
~/.config/jwebot/current/generated/runtime.env
```
Private context, debug state, incidents, and root evidence live outside Git as
documented in `config/README.md`.
## Health
```sh
runtime_python=${JWEBOT_RUNTIME_PYTHON:-/opt/jwebot/venv/bin/python}
printf '%s\n' '{"op":"health"}' |
"$runtime_python" ~/.local/lib/wechat-health-watch/jwebot_edge.py respond
"$runtime_python" ~/.local/lib/wechat-health-watch/jwebot_edge.py status
```
Expected:
- Edge and adaptive recorder active;
- `NRestarts=0` after the settle window;
- OCR queues empty;
- direct/cloud outboxes empty or actively progressing;
- SQLite `PRAGMA quick_check` returns `ok`;
- legacy OCR services disabled and inactive.
## Delivery Contract
- Fast ACK is persisted before GUI delivery.
- Final work starts independently from final GUI delivery.
- A final reply is not delivered until its ACK is visibly confirmed.
- Steam uses a dedicated progress ACK.
- ACK/local work may pass a queued final that has not started.
- Active GUI work is non-preemptive.
- The GUI sender remains strictly serial.
- Every final send rechecks the selected group fingerprint.
## Failure Handling
Provider failure:
- keep credentials and provider payloads out of logs;
- record only bounded error classes;
- retry through durable queue policy;
- do not bypass the configured proxy.
GUI uncertainty:
- never blindly replay a send after `send_started`;
- preserve the uncertain artifact in private incident state;
- require visual/operator confirmation before retry.
Group mismatch:
- skip delivery;
- keep the event ledgered;
- do not select or navigate to another group automatically.
## Telemetry
Telemetry may include step name, status, elapsed time, queue state, retry count,
and hashed event/group IDs. It must never include message bodies, sender names,
reply text, environment values, credentials, SteamIDs, or provider payloads.
+115
View File
@@ -0,0 +1,115 @@
# Production Deployment
Use this runbook only after a target host has passed the zero-to-running
bootstrap and staging gate.
## Inputs
- clean repository snapshot;
- active validated profile for the target runtime user;
- configured production Python;
- operator-provided SSH alias and local privilege elevation;
- ready WeChat main window on display `:1`.
Do not place hostnames, addresses, usernames, passwords, private keys, or
profile values in the repository or command history.
## Local Gate
```sh
PATH="$PWD/.venv/bin:$PATH" ./tools/check-local.sh
./tools/check-secrets.py --history
```
## Staging
Transfer a clean snapshot without `.git`, local profiles, virtualenvs, debug
state, captures, databases, or archives. On the target:
```sh
JWEBOT_RUNTIME_PYTHON=/opt/jwebot/venv/bin/python \
PATH="/opt/jwebot/venv/bin:$PATH" \
./tools/check-local.sh
```
Verify the active profile:
```sh
/opt/jwebot/venv/bin/python -m jwebot_config status
/opt/jwebot/venv/bin/python -m jwebot_config validate <profile>
```
## Pre-Cutover
Confirm:
1. WeChat main window is visible and meets minimum geometry.
2. Edge and adaptive recorder are active with stable restart counts.
3. Adaptive and legacy OCR queues are empty.
4. Edge direct/cloud outboxes have no failed backlog.
5. Edge SQLite `quick_check` is `ok`.
6. Legacy OCR services are disabled and inactive.
7. A restorable baseline exists.
## Coordinated Cutover
Elevate locally on the target and run:
```sh
JWEBOT_RUNTIME_PYTHON=/opt/jwebot/venv/bin/python \
./deploy/scripts/deploy-jwebot-production.sh \
/path/to/staged/JWeBot \
jwebot
```
The deployer prints an eight-stage progress bar and:
- records root and user service topology;
- copies root and user runtime files;
- backs up generated configuration;
- creates a SQLite online backup and runs `quick_check`;
- refuses non-empty queues or an unready WeChat window;
- installs the runtime;
- leaves legacy OCR disabled;
- verifies Edge health and adaptive restart stability;
- writes and verifies backup SHA-256 checksums;
- rolls back files, state, and unit topology on failure.
## Post-Cutover
```sh
systemctl --user is-active jwebot-edge.service
systemctl --user is-active wechat-ocr-adaptive-recorder.service
systemctl --user show wechat-ocr-adaptive-recorder.service \
-p NRestarts --value
```
```sh
printf '%s\n' '{"op":"health"}' |
/opt/jwebot/venv/bin/python \
~/.local/lib/wechat-health-watch/jwebot_edge.py respond
```
```sh
/opt/jwebot/venv/bin/python - <<'PY'
import sqlite3
from pathlib import Path
path = Path.home() / ".local/state/jwebot-edge/edge.sqlite3"
print(sqlite3.connect(f"file:{path}?mode=ro", uri=True)
.execute("pragma quick_check").fetchone()[0])
PY
```
Player-visible acceptance must use deliberate synthetic messages in the
configured group and confirm exactly one ACK and one final response.
## Rollback
If any gate fails:
1. stop further cutover attempts;
2. verify the coordinated deployer's rollback result;
3. confirm unit topology and SQLite integrity;
4. archive privacy-safe evidence under the active profile;
5. fix and rerun staging before another production attempt.
+138
View File
@@ -0,0 +1,138 @@
# Zero To Running
This runbook prepares a new apt-based Linux host without embedding any private
host, account, group, or provider value in the repository.
JWeBot does not install desktop WeChat itself. Install a compatible client,
create an X11/VNC desktop on display `:1`, sign in manually, and keep the main
window visible before deployment.
## 1. Obtain A Clean Snapshot
Clone or unpack the public repository. Before using a fork:
```sh
./tools/check-secrets.py --history
```
## 2. Prepare The Host
Run as root:
```sh
./deploy/scripts/bootstrap-jwebot.sh \
prepare \
/path/to/JWeBot \
jwebot \
default
```
The six-stage prepare flow:
1. installs OS packages for Python, X11 interaction, screenshots, clipboard,
and OCR fallback;
2. creates the runtime account when absent;
3. creates `/opt/jwebot/venv`;
4. installs runtime Python dependencies and JWeBot packages;
5. generates a private config profile;
6. creates private state directories.
## 3. Configure
Edit:
```text
/home/jwebot/.config/jwebot/profiles/default/
```
At minimum:
- set the bot mention and provider URLs in component TOML;
- put API keys only in `secrets/runtime.env`;
- configure Steam owner ID and loopback proxy when using `/steam`;
- put person aliases only in `context/people.json`;
- configure a notification argv adapter or leave it disabled;
- verify deployment paths.
Activate as the runtime user:
```sh
sudo -u jwebot -H /opt/jwebot/venv/bin/python \
-m jwebot_config activate default
```
## 4. Prepare The Desktop
The runtime expects:
- an X11 desktop at `DISPLAY=:1`;
- the runtime user's `.Xauthority`;
- a visible WeChat main window named `Weixin`;
- a maximized window large enough for the adaptive layout gate;
- loopback VNC on port `5901` for guarded input;
- no second process controlling the same GUI.
VNC/noVNC must remain loopback-only or be reached through an authenticated SSH
tunnel. Never expose it directly.
## 5. Verify
```sh
./deploy/scripts/bootstrap-jwebot.sh \
verify \
/path/to/JWeBot \
jwebot \
default
```
Verify checks runtime imports, required executables, profile validation,
repository config/privacy gates, and the visible WeChat window.
## 6. Deploy
```sh
./deploy/scripts/bootstrap-jwebot.sh \
deploy \
/path/to/JWeBot \
jwebot \
default
```
This invokes the coordinated production deployer. It does not bypass queue,
ready-window, health, SQLite, or restart-stability gates.
## 7. Acceptance
```sh
systemctl --user is-active jwebot-edge.service
systemctl --user is-active wechat-ocr-adaptive-recorder.service
```
```sh
printf '%s\n' '{"op":"health"}' |
/opt/jwebot/venv/bin/python \
~/.local/lib/wechat-health-watch/jwebot_edge.py respond
```
Send synthetic messages in the configured group:
```text
@JWeBot /version
@JWeBot /steam
@JWeBot /steam时长Dota2
```
Confirm one ACK, one final reply, no duplicate delivery, empty queues, stable
restart counts, and SQLite `quick_check=ok`.
## Non-Apt Hosts
Use `prepare` as a dependency manifest:
- install equivalent X11, OCR, Python, and clipboard packages;
- create the same runtime user and virtualenv;
- run `jwebot_config init/activate`;
- run the verify and coordinated deployment gates manually.
Do not translate package names into an unreviewed installer and run it directly
on production.
+129
View File
@@ -0,0 +1,129 @@
# Runtime Telemetry And ACK Semantics
Status: local instrumentation contract for `alpha 0.6.x`.
## What ACK Means
ACK is the short acknowledgement sent immediately after Edge accepts a request.
It is not the final answer and it does not mean the expensive work has
completed.
The ordered path is:
1. OCR/responder submits one normalized event to Edge.
2. Edge durably records the event and queues the requested work.
3. Edge returns the short ACK (`喵......` or the cloud acknowledgement).
4. The recorder sends that ACK through the guarded GUI path.
5. The recorder calls Edge with `fast_ack_sent` or `fast_ack_failed`.
6. Expensive work may run concurrently, but its final reply cannot be delivered
until ACK delivery is confirmed.
This separates perceived response latency from completion latency while
preserving message order. A failed ACK prevents the later final reply from
appearing without its expected acknowledgement.
## Privacy Boundary
Telemetry never records message bodies, sender names, raw group identifiers,
reply text, secrets, environment values, or service payloads.
Event and group identifiers are represented only by 12-character SHA-256
prefixes. Plugin IDs, route names, queue states, operation names, counts,
bounded error class names, and durations are allowed.
Every structured log uses schema version `1`:
```json
{
"schema_version": 1,
"name": "edge.ack.confirmed",
"status": "ok",
"component": "edge",
"event_hash": "0123456789ab",
"elapsed_ms": 83.417,
"ack_kind": "chat",
"delivered": true
}
```
## Instrumented Stages
| Stage | Events |
|---|---|
| Capture and OCR | `ocr.warmup`, `ocr.capture`, `ocr.frame.wait`, `ocr.scan`, `ocr.pipeline` |
| OCR queue | `ocr.queue.wait`, `ocr.queue.process` |
| Recorder to Edge | `recorder.edge.request` |
| ACK GUI and confirmation | `recorder.ack.gui`, `recorder.ack.end_to_end`, `recorder.ack.confirm`, `edge.ack.prepared`, `edge.ack.confirmed` |
| Fast ACK dedicated view | `recorder.fast_ack.gui`, `recorder.fast_ack.end_to_end`, `recorder.fast_ack.confirm`, `edge.fast_ack.prepared`, `edge.fast_ack.confirmed` |
| Edge routing | `edge.route.classified`, `edge.local.complete`, `edge.alias.complete` |
| Extension startup and execution | `edge.extensions.startup`, `edge.extension.execute` |
| Host process and IPC | `host.process.spawn`, `host.runtime.load`, `host.request.received`, `host.service.request`, `host.service.wait`, `host.service.complete`, `host.invoke`, `host.process.complete` |
| Kernel broker | `edge.service.call` |
| Model and cloud providers | `edge.provider.complete` |
| Durable queues | `edge.queue.wait`, `edge.queue.claim`, `edge.queue.work` |
| Final GUI delivery | `edge.delivery.process`, `edge.delivery.complete`, `edge.delivery.end_to_end`, `delivery.gui.send` |
| Guarded GUI send internals | `gui.send.step`, `gui.bridge.step`, `gui.send.total` |
`gui.send.step` is emitted by the parent OCR process around safety boundaries:
GUI lock acquisition, pre-draft screenshot, pre-draft group check, bridge
process calls, post-draft screenshot, post-draft group check, callbacks, and
total completion. `gui.bridge.step` is emitted from the child bridge timing
metadata and covers VNC focus clicks, fixed settle waits, screenshot polling,
draft confirmation, send-button confirmation, and Enter fallback.
The Kernel also retains process-local low-cardinality counts and
average/maximum latency summaries. They are exposed through Edge health output
for diagnosis and reset when the process restarts.
`edge.route.classified` records one bounded `execution_path` value:
`local-command`, `deepseek`, or `openclaw`. OCR scan events record cache hits,
misses, and periodic refresh without recording image or message content.
Direct-request queue events use `queue=direct_generation` for parallel provider
or Steam work and `queue=direct_delivery` for the single ordered GUI worker.
Their bounded `worker` field distinguishes `generation-0`, `generation-1`, and
`delivery-0` without exposing event content.
## Error Rules
- Record only the exception class, never exception text from plugin or user
data boundaries.
- Distinguish controlled errors, retries, uncertain delivery, startup fallback,
timeouts, and final failures.
- Do not replay an active Host invocation after a service call may have
committed state.
- Keep existing durable queue state as the source of truth; telemetry is
observational and must never control delivery.
- Telemetry observer and logger failures are swallowed so instrumentation
cannot break the reply path.
## Performance Analysis
The current points support separate measurement of:
- screen capture delay;
- OCR recognition cost and bubble count;
- stable-frame/debounce delay;
- recorder-to-Edge socket latency;
- ACK GUI latency and ACK confirmation latency;
- Host process startup and plugin load cost;
- service broker round trips;
- model/provider execution;
- queue waiting and retry time;
- final guarded GUI delivery.
Future optimization can then trade compute for latency deliberately, for
example:
- narrower OCR regions or separate sender/body recognizers;
- more frequent capture with bounded coalescing;
- selective high-cost OCR only for likely incoming bubbles;
- warm or persistent Extension Hosts;
- concurrent ACK delivery and expensive computation;
- queue prioritization by route and age;
- preloaded models and cached layout/title fingerprints.
No such optimization should be accepted without comparing end-to-end ACK and
final-delivery percentiles and checking duplicate/ordering regressions.
Run `tools/performance-baseline.py` for repository-only microbenchmarks or pass
one or more private `--telemetry` logs to calculate stage distributions.
+26
View File
@@ -0,0 +1,26 @@
# Versioning
JWeBot uses an alpha version string:
```text
alpha X.Y.Z.MMDD
```
Rules:
- `alpha` means the public API and deployment process may still change.
- `X.Y.Z` is the project version.
- `MMDD` is the delivery date.
- Normal small updates increment only the patch version, for example
`alpha 0.0.1.1014` to `alpha 0.0.2.1014`.
- Minor versions such as `0.1.x` require explicit user review and milestone
approval.
- Local repository migration does not imply a production deployment.
- Production services must not be changed unless the operator explicitly asks
for deployment or runtime verification.
The exact current version is stored in:
```text
VERSION
```