c10c84b6
refactor: muxa asks Opts for the session name, not a second field
a73x 2026-08-27 07:17
Commit message
src/cli/muxa.zig
| Old | New | ||
|---|---|---|---|
| @@ -59,20 +59,21 @@ const Opts = struct { | |||
| 59 | vt: bool = false, | 59 | vt: bool = false, |
| 60 | /// Optional so that only a name that was TYPED is validated: `""` is | 60 | /// Optional so that only a name that was TYPED is validated: `""` is |
| 61 | /// the wire's own default spelling and would fail a check written for | 61 | /// the wire's own default spelling and would fail a check written for |
| 62 | /// a name. `_session` below is what the frames actually carry. | 62 | /// a name. `sessionName` is what the frames actually carry. |
| 63 | session: ?[]const u8 = null, | 63 | session: ?[]const u8 = null, |
| 64 | /// Which session every verb this invocation makes asks about — the | ||
| 65 | /// attach it opens with AND every ask that follows carry the same | ||
| 66 | /// name, which is what keeps the daemon's attached-tail equality rule | ||
| 67 | /// (server.zig) from ever seeing a mismatch out of this binary. Empty | ||
| 68 | /// is the wire's own default spelling, so a bare `muxa status` builds | ||
| 69 | /// byte-identical frames to before this flag existed. | ||
| 70 | _session: []const u8 = "", | ||
| 71 | /// Null until `parseArgs` reads argv[1]; a parse that returned an Opts | 64 | /// Null until `parseArgs` reads argv[1]; a parse that returned an Opts |
| 72 | /// has one. | 65 | /// has one. |
| 73 | _verb: ?Verb = null, | 66 | _verb: ?Verb = null, |
| 74 | _arg: ?[]const u8 = null, | 67 | _arg: ?[]const u8 = null, |
| 75 | 68 | ||
| 69 | pub fn sessionName(o: Opts) []const u8 { | ||
| 70 | // ONE name for the attach AND every ask after it, so the daemon's | ||
| 71 | // attached-tail equality rule (server.zig) never sees a mismatch | ||
| 72 | // out of this binary. Empty is the wire's own default spelling, so | ||
| 73 | // a bare `muxa status` builds the frames it always did. | ||
| 74 | return o.session orelse ""; | ||
| 75 | } | ||
| 76 | |||
| 76 | /// The verb's argument. A second is a mistake: no verb here takes two. | 77 | /// The verb's argument. A second is a mistake: no verb here takes two. |
| 77 | pub fn positional(self: *Opts, word: []const u8) bool { | 78 | pub fn positional(self: *Opts, word: []const u8) bool { |
| 78 | if (self._arg != null) return false; | 79 | if (self._arg != null) return false; |
| @@ -113,8 +114,6 @@ fn parseArgs(args: []const [:0]const u8) ParseError!Opts { | |||
| 113 | if (o.session) |name| { | 114 | if (o.session) |name| { |
| 114 | if (!proto.validSessionName(name)) return error.Usage; | 115 | if (!proto.validSessionName(name)) return error.Usage; |
| 115 | } | 116 | } |
| 116 | o._session = o.session orelse ""; | ||
| 117 | |||
| 118 | // Name ONE transport. A `--sock` silently ignored beside a `--quic` | 117 | // Name ONE transport. A `--sock` silently ignored beside a `--quic` |
| 119 | // would send an agent's frames somewhere other than the socket it | 118 | // would send an agent's frames somewhere other than the socket it |
| 120 | // named, and the two answers differ — this is the mistake `mux` | 119 | // named, and the two answers differ — this is the mistake `mux` |
| @@ -248,11 +247,11 @@ test "muxa: --help and --version are answered wherever they can be typed" { | |||
| 248 | test "muxa: --session rides every verb; a bad name is usage, not wire bytes" { | 247 | test "muxa: --session rides every verb; a bad name is usage, not wire bytes" { |
| 249 | const a = [_][:0]const u8{ "muxa", "status", "--session", "b" }; | 248 | const a = [_][:0]const u8{ "muxa", "status", "--session", "b" }; |
| 250 | const o = try parseArgs(&a); | 249 | const o = try parseArgs(&a); |
| 251 | try std.testing.expectEqualStrings("b", o._session); | 250 | try std.testing.expectEqualStrings("b", o.sessionName()); |
| 252 | 251 | ||
| 253 | // No --session named: the wire's own default spelling, empty. | 252 | // No --session named: the wire's own default spelling, empty. |
| 254 | const bare = [_][:0]const u8{ "muxa", "status" }; | 253 | const bare = [_][:0]const u8{ "muxa", "status" }; |
| 255 | try std.testing.expectEqualStrings("", (try parseArgs(&bare))._session); | 254 | try std.testing.expectEqualStrings("", (try parseArgs(&bare)).sessionName()); |
| 256 | 255 | ||
| 257 | // A name no tool could ever address is refused at parse (the usage | 256 | // A name no tool could ever address is refused at parse (the usage |
| 258 | // exit, 2) rather than reaching a daemon as a payload nothing can | 257 | // exit, 2) rather than reaching a daemon as a payload nothing can |
| @@ -1266,9 +1265,9 @@ pub fn main() !u8 { | |||
| 1266 | /// else that distinguishes them, which is the property `--quic` is selling. | 1265 | /// else that distinguishes them, which is the property `--quic` is selling. |
| 1267 | fn dispatch(alloc: std.mem.Allocator, conn: *Conn, o: Opts, deadline: i64) !u8 { | 1266 | fn dispatch(alloc: std.mem.Allocator, conn: *Conn, o: Opts, deadline: i64) !u8 { |
| 1268 | return switch (o._verb.?) { | 1267 | return switch (o._verb.?) { |
| 1269 | .status => verbStatus(alloc, conn, o._session, deadline), | 1268 | .status => verbStatus(alloc, conn, o.sessionName(), deadline), |
| 1270 | .capture => verbCapture(alloc, conn, o.vt, o._session, deadline), | 1269 | .capture => verbCapture(alloc, conn, o.vt, o.sessionName(), deadline), |
| 1271 | .send => verbSend(alloc, conn, o._arg, o._session, deadline), | 1270 | .send => verbSend(alloc, conn, o._arg, o.sessionName(), deadline), |
| 1272 | // The one thing `run` needs that `await` does not, checked here so | 1271 | // The one thing `run` needs that `await` does not, checked here so |
| 1273 | // the shared pipeline below can read `cmdline == null` as "this is | 1272 | // the shared pipeline below can read `cmdline == null` as "this is |
| 1274 | // an await" rather than as "a run that was spelled wrong". | 1273 | // an await" rather than as "a run that was spelled wrong". |
| @@ -1562,7 +1561,7 @@ fn doAwait( | |||
| 1562 | .since_seq = since_seq, | 1561 | .since_seq = since_seq, |
| 1563 | .settle_ms = o.settle, | 1562 | .settle_ms = o.settle, |
| 1564 | .timeout_ms = o.timeout, | 1563 | .timeout_ms = o.timeout, |
| 1565 | }, o._session); | 1564 | }, o.sessionName()); |
| 1566 | try conn.sendFrame(.await_req, payload, deadline); | 1565 | try conn.sendFrame(.await_req, payload, deadline); |
| 1567 | const frame = try conn.awaitFrame(.await_reply, deadline); | 1566 | const frame = try conn.awaitFrame(.await_reply, deadline); |
| 1568 | defer frame.deinit(alloc); | 1567 | defer frame.deinit(alloc); |
| @@ -1597,7 +1596,7 @@ fn awaitReissuing( | |||
| 1597 | // daemon that lost our connection lost the client slot with | 1596 | // daemon that lost our connection lost the client slot with |
| 1598 | // it, so an await_req arriving unattached asks about nothing. | 1597 | // it, so an await_req arriving unattached asks about nothing. |
| 1599 | // A failure here is still the reconnect failing. | 1598 | // A failure here is still the reconnect failing. |
| 1600 | attachZero(conn, o._session, deadline) catch |reattach| { | 1599 | attachZero(conn, o.sessionName(), deadline) catch |reattach| { |
| 1601 | // A refused re-attach is the daemon's answer, not the tear | 1600 | // A refused re-attach is the daemon's answer, not the tear |
| 1602 | // that got us here: `ConnectionLost` would send an agent | 1601 | // that got us here: `ConnectionLost` would send an agent |
| 1603 | // to check the network for a session that is gone. | 1602 | // to check the network for a session that is gone. |
| @@ -1816,16 +1815,16 @@ fn awaitVerb( | |||
| 1816 | const who = if (cmdline == null) "await" else "run"; | 1815 | const who = if (cmdline == null) "await" else "run"; |
| 1817 | const started = std.time.milliTimestamp(); | 1816 | const started = std.time.milliTimestamp(); |
| 1818 | 1817 | ||
| 1819 | attachZero(conn, o._session, deadline) catch |e| | 1818 | attachZero(conn, o.sessionName(), deadline) catch |e| |
| 1820 | return failSend(e, o._session, .attach, who, "attach failed"); | 1819 | return failSend(e, o.sessionName(), .attach, who, "attach failed"); |
| 1821 | 1820 | ||
| 1822 | // BEFORE the input, not after: the watermark has to be the one this | 1821 | // BEFORE the input, not after: the watermark has to be the one this |
| 1823 | // command must beat. Read afterwards, a command fast enough to return | 1822 | // command must beat. Read afterwards, a command fast enough to return |
| 1824 | // between the two would have already moved the seq past a value we | 1823 | // between the two would have already moved the seq past a value we |
| 1825 | // never recorded, and the await would sit waiting for a return that | 1824 | // never recorded, and the await would sit waiting for a return that |
| 1826 | // had happened. | 1825 | // had happened. |
| 1827 | const since = currentSeq(alloc, conn, o._session, deadline) catch |e| switch (e) { | 1826 | const since = currentSeq(alloc, conn, o.sessionName(), deadline) catch |e| switch (e) { |
| 1828 | error.AttachRefused => return failAttachRefused(o._session, .attach), | 1827 | error.AttachRefused => return failAttachRefused(o.sessionName(), .attach), |
| 1829 | error.SessionExited => return reportSessionEnded(alloc, conn.session_exit, elapsed(started)), | 1828 | error.SessionExited => return reportSessionEnded(alloc, conn.session_exit, elapsed(started)), |
| 1830 | else => return failAs(who, "status failed", @errorName(e)), | 1829 | else => return failAs(who, "status failed", @errorName(e)), |
| 1831 | }; | 1830 | }; |
| @@ -1840,11 +1839,11 @@ fn awaitVerb( | |||
| 1840 | return failAs(who, "cannot build the command line", @errorName(e)); | 1839 | return failAs(who, "cannot build the command line", @errorName(e)); |
| 1841 | defer alloc.free(line); | 1840 | defer alloc.free(line); |
| 1842 | conn.sendFrame(.input, line, deadline) catch |e| | 1841 | conn.sendFrame(.input, line, deadline) catch |e| |
| 1843 | return failSend(e, o._session, .attach, who, "input failed"); | 1842 | return failSend(e, o.sessionName(), .attach, who, "input failed"); |
| 1844 | } | 1843 | } |
| 1845 | 1844 | ||
| 1846 | const r = awaitReissuing(alloc, conn, o, since, awaitDeadline(o, conn)) catch |e| switch (e) { | 1845 | const r = awaitReissuing(alloc, conn, o, since, awaitDeadline(o, conn)) catch |e| switch (e) { |
| 1847 | error.AttachRefused => return failAttachRefused(o._session, .attach), | 1846 | error.AttachRefused => return failAttachRefused(o.sessionName(), .attach), |
| 1848 | error.SessionExited => return reportSessionEnded(alloc, conn.session_exit, elapsed(started)), | 1847 | error.SessionExited => return reportSessionEnded(alloc, conn.session_exit, elapsed(started)), |
| 1849 | else => { | 1848 | else => { |
| 1850 | var detail: [128]u8 = undefined; | 1849 | var detail: [128]u8 = undefined; |