Optional replyId on send (API + MCP) — protocol-level threaded replies (#31)
CI / build + unit (push) Successful in 33s

This commit was merged in pull request #31.
This commit is contained in:
2026-09-25 13:53:47 +02:00
parent 0909fbc6e2
commit 15d93ec2a4
11 changed files with 137 additions and 15 deletions
+25 -1
View File
@@ -1,5 +1,7 @@
import { fromBinary, toBinary } from '@bufbuild/protobuf';
import * as P from '@meshtastic/protobufs';
import { describe, expect, it } from 'vitest';
import { extractFrames, frame } from './codec.js';
import { buildTextToRadio, extractFrames, frame } from './codec.js';
const F = (payload: number[]) => [
0x94,
@@ -78,3 +80,25 @@ describe('frame', () => {
expect(rest.length).toBe(0);
});
});
// re-serialize just the nested Data message so its wire bytes are inspectable without the random packet id interfering
function dataBytes(framed: Uint8Array): Uint8Array {
const { packets } = extractFrames(framed); // buildTextToRadio returns framed bytes
expect(packets).toHaveLength(1);
const tr = fromBinary(P.Mesh.ToRadioSchema, packets[0]);
if (tr.payloadVariant?.case !== 'packet') throw new Error('not a packet');
const pv = tr.payloadVariant.value.payloadVariant;
if (pv?.case !== 'decoded') throw new Error('not decoded');
return toBinary(P.Mesh.DataSchema, pv.value);
}
describe('buildTextToRadio', () => {
it('writes wire tag 0x3d (Data.reply_id, field 7) iff replyId is given', async () => {
const withReply = await buildTextToRadio(42, 'hi', 3, 42);
expect(Array.from(dataBytes(withReply.bytes))).toContain(0x3d);
// omitting it must leave the field off the wire (proto3 default 0) — snake_case reply_id would be silently dropped here
const plain = await buildTextToRadio(42, 'hi', 3);
expect(Array.from(dataBytes(plain.bytes))).not.toContain(0x3d);
});
});