@intelligent-tank/kanban-plugin
v0.1.60
Published
Loop Board for OpenCode tasks, workflow revisions, routine loops, feedback, verification, recovery, and session awareness
Readme
OpenCode Kanban Board
Loop Board is a local control plane for OpenCode tasks, workflows, session routing, verification, routine loops, and self-iteration.
Quick Start
New to the board? Start with $ask-board in a coding agent. It routes you to
the smallest safe next step and never guesses a board URL, execution identity,
Workspace, project, or Session.
Install and start the board host:
npm install -g @intelligent-tank/[email protected] kanban-board --directory /path/to/workspaceOn an agent machine, create or reuse one execution identity, owned Workspace, and normal coding Session. Interactive setup asks for the board URL, six-digit user ID, name, and an optional Session title before changing anything:
kanban-board-cli setupSynchronize the bundled skills, then restart the target agent:
kanban-board-cli skills sync --agents codex,claude-codeTurn a confirmed Spec into an artifact-backed Workflow with
$to-cards. Review its explicit Session and dependency graph in the board, approve it, then start it. A Workflow never silently switches to a different Session.For routine operation use
kanban-board-cli statusandkanban-board-cli doctor; resolve permission and question waits before retrying a card. Card details, execution audit, and Recovery Center retain the loop history.
The concise step-by-step version is also available as
docs/QUICKSTART.md. The remaining sections are the
operator reference for deployment, storage migration, APIs, and IM bridging.
Install
npm installnpm install builds the TypeScript output and copies UI assets into dist/.
Start
Independent processes (recommended)
Run the board as the only owner of port 8765. OpenCode TUI must not load
kanban-plugin/index.ts; the board talks to OpenCode Web on port 4096 through
its API, so either process can restart without terminating the other.
npx kanban-board --directory /path/to/workspace --web-port 4096
cd /path/to/workspace && opencodekanban-plugin/index.ts is now an optional, serverless bridge that only exposes
the board address inside OpenCode. It never owns port 8765; omitting it leaves
the TUI completely unaware of the board.
Use an existing OpenCode Web service on port 4096:
npm start -- --directory /path/to/workspaceStart OpenCode Web and the Kanban board together:
npm run start:all -- --directory /path/to/workspaceDefaults:
- Kanban board:
http://127.0.0.1:8765 - OpenCode Web:
http://127.0.0.1:4096
Useful overrides:
npm start -- --directory /path/to/workspace --kanban-port 8765 --web-port 4096
npm run start:all -- --directory /path/to/workspace --opencode-bin opencodeThe connection settings modal persists the public Kanban and OpenCode Web
base URLs in the board's persistent store, so other browsers and users use the
same links. Environment variables remain valid startup defaults:
KANBAN_PUBLIC_BASE_URL and OPENCODE_WEB_PUBLIC_BASE_URL.
The packaged browser UI can connect to a remote board before it loads API
data: open kanban-plugin/ui/index.html?baseUrl=https://kanban.example.com
or use ?kanbanBaseUrl=...; use ?opencodeBaseUrl=... when OpenCode Web is
on another host. The same values may be injected before app.js as
window.KANBAN_BASE_URL and window.OPENCODE_WEB_BASE_URL.
Storage and compatibility
Node 22.5+ uses SQLite by default at .kanban/store.sqlite. The board keeps
the existing store.json format as an import and rollback source: on the first
SQLite startup it is copied to .kanban/store.json.bak before import, then the
records are committed in one SQLite transaction. The original JSON is never
deleted. If the transaction fails, startup fails closed and the JSON remains
available for a retry.
Startup migration is automatic. It is also available as an explicit, idempotent upgrade command:
npx kanban-board-migrate --directory /path/to/workspace
npx kanban-board-upgrade --directory /path/to/workspacekanban-board-migrate only imports .kanban/store.json; kanban-board-upgrade
is the operator-facing wrapper that reports the installed package version and
the migration result. Both preserve store.json and store.json.bak, and both
refuse to overwrite a non-empty SQLite database. The JSON result is useful for
deployment logs:
npx kanban-board-upgrade --directory /path/to/workspace --jsonExisting JSON-only environments remain supported with:
KANBAN_STORAGE=json npx kanban-board --directory /path/to/workspaceOn Node 20, the board automatically falls back to JSON because node:sqlite
is not available. The active backend and migration state are exposed in
/api/config.storage. To migrate, run the command after upgrading to Node
22.5+; do not set KANBAN_STORAGE=json for that command.
Trace bodies and uploaded assets remain file-backed under .kanban/traces and
.kanban/artifacts; SQLite stores their metadata and task/workflow records.
Remote Board Clients
The command that starts a board (kanban-board) is local to the machine that
hosts it. Commands that operate an existing board (kanban-board-cli,
kanban-board-publish, kanban-board-delivery, and kanban-board-verify)
can run from any agent machine. Point them at the remote board with one shared
environment variable or an explicit override:
export KANBAN_BASE_URL=https://kanban.example.com
kanban-board-cli status
kanban-board-cli doctor --json
kanban-board-publish --type task --file task.json --session ses_xxx
kanban-board-delivery /absolute/path/to/SPEC.md --base-ref main --project core --workspace workspace-id --assignee agent --session ses_xxx
kanban-board-verify --base-url https://kanban.example.comFor first-time execution setup, run the CLI once with the human-provided
six-digit user ID. The board host creates the default /root/<userId>
directory, registers it as an owned workspace, and creates one reusable idle
coding Session. This works against remote boards too, so the directory is
never accidentally created on the agent's local machine:
kanban-board-cli --base-url https://kanban.example.com \
setup --id 323017 --name 'Chen Zhongbing'The first non-interactive setup requires --base-url; interactive setup asks
for the board URL, user ID, name, and optional Session title, then requires a
final confirmation. It does not invent those values. On success, it persists
KANBAN_BASE_URL to
~/.config/loop-board/env (or $XDG_CONFIG_HOME/loop-board/env) for later
kanban-board-cli calls. Source that file when other local tools need the
same environment variable.
Use --workspace-dir /absolute/path only when the board host does not use
/root. Repeating the command reuses the same bootstrap session after an SDK
validation.
For a board protected by trusted-proxy identity, also set KANBAN_AUTH_USER
and KANBAN_AUTH_ROLE; all four client commands forward them using the
configured header names. KANBAN_PUBLIC_BASE_URL is different: it belongs on
the board host and controls links rendered for browsers.
Verify
After changing the board, run the static and unit checks:
npm run typecheck
npm test -- --runInBand
npm run buildWith Kanban on 8765 and OpenCode Web on 4096, verify the live self-iteration prerequisites:
npm run supervisor:dry-run
npm run verify:self-iteration -- --mutateverify:self-iteration checks runtime config, model list, scheduler status, OpenCode Web health, self-iteration readiness, UI shell/tabs/columns, task/session/workflow API readability, active task session binding, and with --mutate creates then deletes one planned smoke task with verification criteria.
On installed npm packages, use:
npx kanban-board-verify
npx kanban-board-verify --autoinject-smoke --autoinject-timeout-ms 60000--autoinject-smoke creates one real pending task and waits for it to be injected into OpenCode.
Package
Release notes and operational upgrade steps are packaged with the module:
Create a distributable tarball:
npm run pack:localThe generated .tgz is written to dist/.
Agent Skill
For a concise install, setup, delivery, and recovery guide, read
docs/QUICKSTART.md.
The distributable includes the newcomer router ask-board, board-integrated
kanban-board-agent, interactive kanban-board-cli, and
to-cards skills plus the current Matt-style engineering skill set. See
skills/MATT_SKILLS_MANIFEST.md for the bundled list and install order.
The helper script can publish JSON payloads:
node skills/kanban-board-agent/scripts/publish-kanban.mjs --type task --file task.json --session ses_xxx
node skills/kanban-board-agent/scripts/publish-kanban.mjs --type workflow --file workflow.json --session ses_xxxFor a Spec produced by to-cards, use the artifact-backed delivery bridge:
npx kanban-board-delivery /absolute/path/to/SPEC.md \
--base-ref <commit> --project <project-id> --workspace <workspace-id> --assignee <user-id> \
--session ses_xxx
npx kanban-board-delivery /absolute/path/to/SPEC.md \
--base-ref <commit> --project <project-id> --workspace <workspace-id> --assignee <user-id> --dry-runChanged artifacts require --confirm-revision on a later publication. Runtime
inspection and recovery use the unified CLI:
npx kanban-board-cli status
npx kanban-board-cli doctor --json
npx kanban-board-cli progress <task-id>
npx kanban-board-cli permissions
npx kanban-board-cli questions
npx kanban-board-cli reply-question <question-id> --answers '[["Safe"]]'
npx kanban-board-cli logs --n 100 --module engineThe complete named-command/API matrix is in
docs/KANBAN_CLI_REFERENCE.md. Every public board API has a named CLI command;
kanban-board-cli api <METHOD> <PATH> is available for forward compatibility.
Feishu bridge
The optional Feishu bridge accepts Feishu event callbacks at
POST /webhook/feishu. URL verification is supported, and incoming text that
contains a task verb such as 创建, 实现, or 开发 becomes a board card.
Task creation is deliberately explicit: set all of
FEISHU_DEFAULT_SESSION_ID, FEISHU_DEFAULT_WORKSPACE_ID, and
FEISHU_DEFAULT_PROJECT_ID. The assignee defaults to the workspace owner, or
can be set with FEISHU_DEFAULT_ASSIGNEE_ID; it must still own the workspace.
This prevents a chat message from silently selecting an unrelated Session.
Set FEISHU_VERIFICATION_TOKEN when registering the event URL. For targeted
status and detail messages, configure the message service's direct sender:
export FEISHU_MESSAGE_QUEUE_URL=http://127.0.0.1:18080
export FEISHU_MESSAGE_SEND_URL="$FEISHU_MESSAGE_QUEUE_URL/zhaohu/send-message-userid" # optional when the base URL is set
export FEISHU_QUEUE_TOKEN="$MSG_QUEUE_TOKEN" # only when the service requires it
export FEISHU_QUEUE_REPLY_ID_TYPE=user_idThe board POSTs { userId, receiveIdType, message } to
/zhaohu/send-message-userid. FEISHU_WEBHOOK_URL is optional and only
supports the legacy group-bot fallback; it is not required for user-targeted
Card or Workflow status notifications. Secrets are never returned by the board
API. Every board-originated send request also carries the fixed caller header
userId: 323017; the recipient remains in the JSON body. Inspect readiness with:
kanban-board-cli feishu-status
curl -X POST "$KANBAN_BASE_URL/webhook/feishu" \
-H 'content-type: application/json' \
-d '{"type":"url_verification","challenge":"test","token":"..."}'For the conversation-aware sender, set the full new gateway URI instead. The
receive/consume bridge preserves the inbound fromId and toId, then replies
with them reversed, as { fromId: inbound.toId, toId: inbound.fromId, content }:
export FEISHU_MESSAGE_SEND_URL='https://<BASEURL>/zhaohu/sendTextMessage/client/<CLIENT_ID>'
export FEISHU_QUEUE_TOKEN="$MSG_QUEUE_TOKEN"When using the bundled feishu_server.py as a local proxy, configure the
board URL to its equivalent local path and set MESSAGE_GATEWAY_BASE_URL on
that server to the external <BASEURL>. The proxy validates MsgQueueToken
and forwards the opaque IDs unchanged.
The same facts are exposed as /api/integrations/feishu and under
/api/config.feishu.
The standalone feishu_server.py can also be used as the inbound queue
adapter. Configure FEISHU_VERIFICATION_TOKEN and register this URL in the
Feishu event subscription:
https://<message-service-host>/zhaohu/message-queue/receive/<zhaohuId>The endpoint accepts Feishu im.message.receive_v1 payloads, handles
url_verification, normalizes text messages into Redis, and deduplicates
retries by message_id. Consumers then call:
curl -X POST "$MESSAGE_SERVICE_URL/zhaohu/message-queue/consume/$ZHAOHU_ID"Internal normalized producers can continue posting to the same receive
endpoint with MsgQueueToken when MSG_QUEUE_TOKEN is configured.
To let the board own the local receive -> consume -> OpenCode session -> reply loop, configure the board process with:
export FEISHU_MESSAGE_QUEUE_URL=http://127.0.0.1:18080
export FEISHU_MESSAGE_QUEUE_ID=<zhaohuId>
export FEISHU_MESSAGE_QUEUE_CONSUME_URL='http://127.0.0.1:18080/zhaohu/message-queue/consume/{queueId}'
export FEISHU_QUEUE_TOKEN="$MSG_QUEUE_TOKEN" # only when configured
export FEISHU_QUEUE_POLL_INTERVAL_MS=2000For request-level diagnostics on the board, enable the opt-in HTTP trace in the
same shell that starts kanban-board:
export FEISHU_QUEUE_DEBUG_HTTP=1
export FEISHU_QUEUE_HTTP_LOG_MAX_CHARS=12000 # optional body/response limitThis writes http.request, http.response, and http.error records to the
board .kanban/engine.log. It includes the resolved URL, method, request body,
response status, duration, and headers with MsgQueueToken/Authorization
redacted. The standalone feishu_server.py has the equivalent
MSG_QUEUE_DEBUG_HTTP=1 switch and prints the inbound queue request plus the
outbound provider request/response. Keep the switches off after debugging;
message text and recipients are still operational data even though credentials
are masked.
The bridge starts when the queue URL and queue ID are present. It routes each
message by userId to a persistent source=im OpenCode Session, creating one
through the SDK when that user has no existing IM Session. The old
FEISHU_QUEUE_SESSION_ID is retained only as a compatibility fallback for
older deployments. readNotify messages and receipt-shaped JSON containing
toId, versionId, and fromId are ignored. It checks the selected Session
through the board's SDK-first runtime probe before consuming/injecting.
busy, permission, question, retry, and unknown messages are retained
in the board's local retry queue; the consume contract exposes no external
requeue endpoint. Only an observed idle Session receives the prompt. The
assistant response is then sent back to userId as
the configured receiveIdType (default user_id). Use
FEISHU_QUEUE_REPLY_ID and FEISHU_QUEUE_REPLY_ID_TYPE only as a fallback for
internal messages without a sender.
Each complete assistant message created in that session turn is sent back as
its own reply, in order, rather than waiting to merge the whole turn. Reply
monitoring runs independently of consumption, so /stop or
/intercept <new input> can abort an active turn immediately; the latter
locally queues the new input on the same IM Session. Long
individual replies are sent as ordered chunks so their tail is not silently
dropped; set FEISHU_MESSAGE_MAX_CHARS to override the default 3,500-character
chunk size.
Outbound messages are labeled with their source: [Kanban][IM Session],
[Kanban][Card], or [Kanban][Workflow]. An IM request receives a separate
start marker before the first assistant reply. FEISHU_QUEUE_TOKEN is sent as
the MsgQueueToken header on consume and reply requests.
The consume URL can also be edited from 系统 -> 连接配置 in the board. It is
persisted as a non-secret board setting and applied without restarting the
bridge; the token remains a server-only environment variable.
The same 系统 -> 连接配置 modal contains the board-wide IM notification
policy: 任务状态通知 controls Card/Workflow start, complete, failed, and
paused messages (and the IM-session start marker). It is enabled by default and
is the only switch for those status messages. An IM Session always returns each
complete assistant message in order. Card/Workflow incremental output is the
per-record imNotify property, shown and editable in the Card/Workflow UI;
Card detail messages are labeled [Kanban][Card][Detail] and are deduplicated
by task and execution phase.
Status routing comes from the assignee's User routing (imPlatform and
imChatId, falling back to imUserId and then the board userId) and is independent of the
coding Session. Keep coding Sessions and conversational IM Sessions separate.
The CLI/API rejects source=im execution Sessions by default; use
--allow-im-session or allowImSession: true only for an explicitly
conversational delivery.
New Cards and Workflows published through the API, kanban-board-cli,
kanban-board-publish, or to-cards default to imNotify: false, even when
bound to an IM Session. Use --im-notify or an explicit imNotify: true only
after the owner has requested intermediate Card or Workflow detail pushes.
--im-detail-notify and imDetailNotify remain input-only compatibility
aliases for older publishers; new records do not write that property. Existing
stored records are preserved.
The standalone sender accepts receiveIdType (user_id, open_id,
union_id, email, or chat_id) and the queue adapter also exposes:
When the queue's userId belongs to another user system (for example
323017) rather than Feishu's receive_id, configure the mapping only in the
local message service:
export FEISHU_USER_ID_MAP='{"323017":"<feishu-receive-id>"}'