Repository navigation
feat: M1 daemon base (store, RPC, crews and bots, workspaces) - #1
Merged
Merged
Conversation
Section 19 now lists what the official documentation confirms and which real test (M2/M3) still has to run. Two corrections came out of it: - Absolute paths in permission rules need the `//` prefix and the POSIX form Claude Code uses on Windows (`//c/Users/...`). A single leading slash is relative to the settings file, so the old wording would have left the secrets folder readable. - The inbox message line format is not documented; the courier must not depend on it before the M3 real test.
- Prefixed ULID ids (crw_, bot_, msg_, dlv_, tsk_) that validate their prefix on parse and deserialize - Slugs and handles from display names: transliterated, max 32 chars, Windows device names avoided, numeric suffix for collisions - Avatar palette and #RRGGBB validation (spec 15.3) - Field validation for names, roles and instructions - M1 protocol types (hello, status, crews and bots) exported to app/src/lib/protocol.gen.ts by the export_bindings test; ts-rs is a dev-dependency only, and CI fails if the generated file is stale
- WAL, foreign keys and a 5 s busy timeout on every connection (spec 12) - Numbered migrations tracked in PRAGMA user_version; a database from a newer build is refused instead of being modified - 0001_init: crews, bots and settings. Handles are unique among a crew's active bots (partial index); slugs stay unique forever so an archived bot's folder is never reused. Bots get a color column for the avatar (spec 15.3) - Crew archiving cascades to its bots in one transaction
- config.toml with the spec 6 defaults; unknown keys are rejected - BOTLOFT_HOME / %LOCALAPPDATA%\Botloft and %USERPROFILE%\Botloft resolution, plus the //c/... form Claude Code expects in permission rules - Owner token: 32 random bytes in secrets\owner.token, only its SHA-256 kept in memory, compared in constant time - platform: protected DACL with a single user SID on Windows (tested against the real ACL), 0700/0600 elsewhere; Windows console close/logoff/shutdown signals - Single instance through an exclusive lock file per data folder instead of a fixed-name mutex, so a dev daemon can run next to an installed one; spec 14 updated
Writes the files of spec 5.1 into <root>\<crew>\<bot>: - .claude/settings.json: exec-form hooks calling botloftd.exe, crossSessionInbound=accept and a deny rule for the secrets folder in the //c/... form Claude Code matches on Windows - .botloft/mcp.json: the local MCP endpoint with the bot token expanded from the environment, so it never touches disk - .claude/rules/botloft.md: identity, role, instructions, shared folder and how to use the crew tools; regenerated on every change - CLAUDE.md: created once and then left to the bot Every write goes through a temp file and a rename.
- /rpc: JSON-RPC 2.0 over WebSocket. session.hello with the owner token must come first (10 s); anything else, a wrong token or another protocol version closes the connection. Browser origins other than the app get 403; clients without Origin still need the token - /health for the app's daemon check - crews.* and bots.* (spec 11.2) run under the store lock, write the workspace before the database and broadcast crew.changed and bot.changed to every connection; a client that falls 1024 events behind is dropped and reloads on reconnect - Handles follow the bot's name and must be unique among active bots of the crew; folders keep their slug. Bots get a palette color unless one is given - Integration tests run a real daemon on an ephemeral port with a WebSocket client: the M1 acceptance flow (create a crew and a bot via RPC and get a ready workspace), updates, archiving, auth and errors Spec: color in bots.create/update and the bots table, slug and handle rules, Origin and hello details, standard JSON-RPC codes.
`botloftd serve [--config <file>]` resolves the data folder, loads the config, starts daily-rotated logs (14 kept), takes the instance lock, prepares the owner token and database and listens on 127.0.0.1 until Ctrl+C or a Windows close/logoff/shutdown signal. log_level applies to Botloft's crates only; dependencies stay at warn because the WebSocket stack logs frames at debug and trace.
11 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Milestone M1 — daemon base (spec section 17): config, store and migrations,
/health,/rpcwithsession.hello, crews and bots CRUD, and workspace generation.Done when (spec): an integration test creates a crew and a bot via RPC. That is
crates/botloftd/tests/rpc_crews_bots.rs, which runs a real daemon on an ephemeral port and talks to it over a WebSocket.What's in it
crw_,bot_,msg_,dlv_,tsk_), slug and handle rules, avatar palette, field validation, and the M1 protocol types. The types are exported toapp/src/lib/protocol.gen.tsbycargo test -p botloft-core export_bindings; CI fails if that file is stale.PRAGMA user_version(a database from a newer build is refused); crews and bots repositories.botloftd serve [--config]: data folder (BOTLOFT_HOMEor%LOCALAPPDATA%\Botloft), daily logs kept 14 days, single-instance lock, owner token, database, listens on127.0.0.1only.secrets\owner.tokenwith a protected DACL for the current user only; the daemon keeps just its SHA-256 and compares in constant time./rpc: JSON-RPC 2.0 over WebSocket.session.hellofirst (10 s), otherwise-32001and close; foreign browserOrigingets 403.crews.*andbots.*from spec 11.2, withcrew.changed/bot.changednotifications to every connection..claude/settings.json(exec-form hooks,crossSessionInbound: accept, deny rule for the secrets folder),.botloft/mcp.json,.claude/rules/botloft.md, and aCLAUDE.mdcreated once and then left to the bot.Spec changes
//c/Users/.... A single leading slash is relative to the settings file, so the previous wording would have left the secrets folder readable.coloron bots (mascot avatars, 15.3), slug and handle rules,Originand hello details, standard JSON-RPC error codes, log level scope.Not in this PR
platform/are compiled but not tested.Test plan
cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace(64 tests)pnpm checkinapp/icacls)botloftd servewithBOTLOFT_HOME=.dev\home: health check, second instance refused, WebSocket session from an external client, workspace files on disk, no token or message text in the logsNeeds real Claude Code (M2)
M1 does not start Claude Code, but it writes files whose effect can only be confirmed with a real bot (Claude Code >= 2.1.234):
claudein a generated workspace and confirm.claude/rules/botloft.mdis loaded (ask it who it is).botloftd.exe hook <event>without a shell.owner.tokenin the secrets folder and confirm the deny rule blocks it.botloftMCP server gets theAuthorizationheader with${BOTLOFT_BOT_TOKEN}expanded.