Skip to content

Commit 13bd379

Browse files
andrewgazelkacodex
andauthored
room: add multiplayer team room service (#145)
## Summary Adds a generic `room` package and NixOS service for multiplayer team presence around agent work. The first UI is tuned for Codex collaboration without baking Codex into the package or service name. ## Details - serves a Svelte UI from a Rust/Axum `room` binary - syncs participant presence, current focus, draft text, and agent task/status over WebSockets - records incoming room events in a Loro document on the server - exposes `packages.<system>.room` and `services.room` with host, port, package, and firewall options ## Validation - `nix build .#room` - `nix run .#lint` - `npm ci && npm run check` in `packages/room/site` Co-authored-by: Codex <codex@openai.com>
1 parent 8bf6cec commit 13bd379

19 files changed

Lines changed: 2322 additions & 0 deletions

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
members = [
33
"modules/services/resource-monitor/stats-writer",
44
"packages/dag-runner",
5+
"packages/room",
56
"packages/ix-dev-diagnose",
67
"packages/loop",
78
"packages/mcp",

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
syncManaged = ./packages/minecraft/sync-managed;
8282
};
8383
dagRunner = ./packages/dag-runner;
84+
room = ./packages/room;
8485
drgn = ./packages/drgn;
8586
llmClippy = ./packages/llm-clippy;
8687
loop = ./packages/loop;

lib/default.nix

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,32 @@ let
622622
inherit pkgs;
623623
ix = ixForPackages;
624624
};
625+
room =
626+
let
627+
roomSiteSrc = lib.fileset.toSource {
628+
root = paths.packages.room + "/site";
629+
fileset = lib.fileset.intersection (lib.fileset.gitTracked (paths.packages.room + "/site")) (
630+
lib.fileset.unions [
631+
(paths.packages.room + "/site/package.json")
632+
(paths.packages.room + "/site/package-lock.json")
633+
(paths.packages.room + "/site/index.html")
634+
(paths.packages.room + "/site/svelte.config.js")
635+
(paths.packages.room + "/site/tsconfig.json")
636+
(paths.packages.room + "/site/vite.config.ts")
637+
(paths.packages.room + "/site/src")
638+
]
639+
);
640+
};
641+
site = buildNpmSite pkgs {
642+
pname = "room-site";
643+
version = "0.1.0";
644+
src = roomSiteSrc;
645+
};
646+
in
647+
pkgs.callPackage paths.packages.room {
648+
inherit pkgs site;
649+
ix = ixForPackages;
650+
};
625651
drgn = pkgs.callPackage paths.packages.drgn { };
626652
ix-fleet = pkgs.callPackage paths.packages.ixFleet {
627653
ix = ixForPackages;
@@ -716,6 +742,7 @@ let
716742
(root + "/Cargo.toml")
717743
(root + "/Cargo.lock")
718744
(paths.modules + "/services/resource-monitor/stats-writer")
745+
paths.packages.room
719746
paths.packages.dagRunner
720747
paths.packages.ixDevDiagnose
721748
paths.packages.loop

lib/per-system.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ in
329329
mcp
330330
;
331331
minestom-hello-server-jar = repoPackages.minestom.helloServerJar;
332+
inherit (repoPackages) room;
332333
}
333334
// examplePackages
334335
// healthChecks.lifecyclePackages

modules/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
minestom = ./services/minestom.nix;
1717
minecraft-bedrock = ./services/minecraft-bedrock.nix;
1818
postgresql = ./services/postgresql.nix;
19+
room = ./services/room;
1920
observability = ./services/observability;
2021
remote-desktop = ./services/remote-desktop.nix;
2122
resource-monitor = ./services/resource-monitor;

modules/services/room/default.nix

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
config,
3+
ix,
4+
lib,
5+
pkgs,
6+
...
7+
}:
8+
let
9+
inherit (lib)
10+
mkEnableOption
11+
mkIf
12+
mkOption
13+
types
14+
;
15+
16+
cfg = config.services.room;
17+
in
18+
{
19+
options.services.room = {
20+
enable = mkEnableOption "multiplayer team room";
21+
22+
package = mkOption {
23+
type = types.package;
24+
default = (ix.packageSetFor pkgs).room;
25+
defaultText = lib.literalExpression "(ix.packageSetFor pkgs).room";
26+
description = "Package that provides the room server.";
27+
};
28+
29+
host = mkOption {
30+
type = types.str;
31+
default = "0.0.0.0";
32+
description = "Address the room server binds.";
33+
};
34+
35+
port = mkOption {
36+
type = types.port;
37+
default = 8080;
38+
description = "TCP port served by the room server.";
39+
};
40+
41+
openFirewall = mkOption {
42+
type = types.bool;
43+
default = true;
44+
description = "Whether to open the room port in the in-guest firewall.";
45+
};
46+
};
47+
48+
config = mkIf cfg.enable {
49+
ix.networking.portClaims.room = {
50+
protocol = "tcp";
51+
inherit (cfg) port;
52+
address = cfg.host;
53+
description = "multiplayer team room";
54+
};
55+
56+
systemd.services.room = {
57+
description = "Multiplayer team room";
58+
wantedBy = [ "multi-user.target" ];
59+
serviceConfig = ix.systemdHardening // {
60+
Type = "simple";
61+
DynamicUser = true;
62+
ExecStart = lib.escapeShellArgs [
63+
(lib.getExe cfg.package)
64+
"--host"
65+
cfg.host
66+
"--port"
67+
(toString cfg.port)
68+
];
69+
Restart = "on-failure";
70+
};
71+
};
72+
73+
networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall [ cfg.port ];
74+
};
75+
}

packages/room/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "room"
3+
version.workspace = true
4+
edition.workspace = true
5+
publish.workspace = true
6+
7+
[dependencies]
8+
anyhow.workspace = true
9+
axum = { workspace = true, features = ["ws"] }
10+
clap = { workspace = true, features = ["derive", "env"] }
11+
futures.workspace = true
12+
loro.workspace = true
13+
serde = { workspace = true, features = ["derive"] }
14+
serde_json.workspace = true
15+
tokio = { workspace = true, features = ["macros", "net", "rt-multi-thread", "signal", "sync", "time"] }
16+
tower-http = { workspace = true, features = ["fs", "trace"] }

packages/room/default.nix

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
ix,
3+
lib,
4+
pkgs,
5+
site,
6+
}:
7+
8+
let
9+
binary = ix.cargoUnit.buildBinary {
10+
pname = "room";
11+
src = ix.rustWorkspace.src;
12+
workspaceRoot = ix.rustWorkspace.root;
13+
cargoLock = ix.rustWorkspace.cargoLock;
14+
cargoArgs = [
15+
"-p"
16+
"room"
17+
];
18+
binary = "room";
19+
policy = {
20+
denyUnusedCrateDependencies = false;
21+
cargoAudit.enable = false;
22+
cargoMachete.enable = false;
23+
clippy.enable = false;
24+
};
25+
};
26+
in
27+
pkgs.runCommand "room"
28+
{
29+
nativeBuildInputs = [ pkgs.makeWrapper ];
30+
passthru.unwrapped = binary;
31+
meta = {
32+
description = "Serve a multiplayer team room with shared presence and agent state";
33+
mainProgram = "room";
34+
license = lib.licenses.mit;
35+
};
36+
}
37+
''
38+
mkdir -p "$out/bin"
39+
makeWrapper "${binary}/bin/room" "$out/bin/room" \
40+
--set ROOM_SITE_DIR "${site}/share/room-site"
41+
''

packages/room/site/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Room</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="/src/main.ts"></script>
11+
</body>
12+
</html>

0 commit comments

Comments
 (0)