Add validated input buffering, shared JSON helpers, the tnt.module.v1 protocol helpers, and an opt-in external-process module runtime behind TNT_MODULE_PATHS. Closes #52
4.3 KiB
TNT Module Protocol
This document defines the compatibility contract for external TNT modules. The first implementation target is external-process modules that exchange JSON Lines with TNT over stdin/stdout. Keeping modules out of the server address space makes the community extension surface easier to audit, restart, rate-limit, and disable.
The protocol is intentionally separate from messages.log v1. TNT 1.x keeps
the persisted public history format stable. Module-generated content must
always provide a plain-text fallback that can be stored and rendered by older
or less capable clients.
TNT core should stay conservative: text-first, terminal-compatible, and easy to deploy over plain SSH. Modules are the extension surface for personalized workflow features, rich rendering, terminal-specific visuals, and other experience experiments. Integrating a module with TNT must not make plain terminal users lose the basic chat path.
Compatibility
- Protocol version:
tnt.module.v1 - Transport: UTF-8 JSON Lines
- Framing: one complete JSON object per line
- Direction: TNT sends events to module stdin; modules write responses to stdout
- Error stream: modules should write diagnostics to stderr
- License: module protocol examples and official community modules should use the same license as TNT unless a module states stricter terms
Modules are disabled unless TNT_MODULE_PATHS is set. The value is a
colon-separated list of module directories, each containing tnt-module.json
and the declared executable entrypoint.
TNT may add optional fields to existing messages. Modules must ignore unknown fields. TNT must ignore unknown response fields unless the response type explicitly requires them.
Manifest
Each module directory should include tnt-module.json:
{
"protocol": "tnt.module.v1",
"name": "echo",
"version": "0.1.0",
"description": "Echoes public messages for testing",
"entrypoint": "./echo-module.sh",
"permissions": ["message:read", "message:create"],
"events": ["message.created"]
}
Required fields:
protocol: protocol compatibility stringname: stable module id, lowercase ASCII,a-z,0-9, and-version: module versionentrypoint: executable path relative to the manifest directorypermissions: explicit capabilities requested by the moduleevents: event names the module wants to receive
Handshake
TNT starts a module process and writes a handshake event:
{"type":"handshake","protocol":"tnt.module.v1","server":{"name":"tnt","version":"1.0.1"}}
The module should answer:
{"type":"handshake.ok","protocol":"tnt.module.v1","module":{"name":"echo","version":"0.1.0"}}
If the module cannot run, it should answer:
{"type":"error","code":"unsupported_protocol","message":"requires tnt.module.v2"}
Events
Message-created event:
{
"type": "message.created",
"message": {
"id": "local-00000001",
"timestamp": "2026-06-04T12:00:00Z",
"sender": "alice",
"kind": "text",
"plain_text": "hello",
"metadata": {}
}
}
The plain_text field is mandatory for every user-visible message. Future
rich content, images, and terminal-specific render hints must be represented
as optional metadata or attachment records with a plain-text fallback.
Responses
Create a public message:
{"type":"message.create","plain_text":"echo: hello"}
No-op acknowledgement:
{"type":"event.ok"}
Module error:
{"type":"error","code":"bad_request","message":"missing plain_text"}
Security Rules
- Modules are untrusted external processes.
- TNT should enforce per-module permissions before delivering events or accepting responses.
- TNT should cap stdout line length, startup time, event handling time, and total queued output.
- TNT should disable a module after repeated invalid JSON, protocol errors, or timeout failures.
- Modules must never receive private messages unless they request and are granted an explicit private-message permission.
Rendering Rules
Every module-created message must be renderable as plain text. Terminal image protocols such as Kitty graphics or Sixel are optional renderer capabilities, not message requirements. A module may provide attachment metadata later, but TNT must be able to fall back to a link, filename, digest, or short label.