diff --git a/patches/meshcore/0001-Add-companion-radio-firmware-bot-command-parity.patch b/patches/meshcore/0001-Add-companion-radio-firmware-bot-command-parity.patch index ccfd855..6d7ff21 100644 --- a/patches/meshcore/0001-Add-companion-radio-firmware-bot-command-parity.patch +++ b/patches/meshcore/0001-Add-companion-radio-firmware-bot-command-parity.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Fri, 15 May 2026 11:44:21 -0600 -Subject: [PATCH 01/11] Add companion radio firmware bot command parity +Subject: [PATCH 01/12] Add companion radio firmware bot command parity --- .../companion_radio/BotCommandRegistry.cpp | 121 ++ diff --git a/patches/meshcore/0002-Harden-firmware-bot-response-coordination.patch b/patches/meshcore/0002-Harden-firmware-bot-response-coordination.patch index 08770bc..52ecfda 100644 --- a/patches/meshcore/0002-Harden-firmware-bot-response-coordination.patch +++ b/patches/meshcore/0002-Harden-firmware-bot-response-coordination.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Fri, 15 May 2026 19:58:42 -0600 -Subject: [PATCH 02/11] Harden firmware bot response coordination +Subject: [PATCH 02/12] Harden firmware bot response coordination --- examples/companion_radio/BotCommands.cpp | 2 +- diff --git a/patches/meshcore/0003-Allow-prefixless-firmware-bot-DMs.patch b/patches/meshcore/0003-Allow-prefixless-firmware-bot-DMs.patch index 328a4e8..a45eca3 100644 --- a/patches/meshcore/0003-Allow-prefixless-firmware-bot-DMs.patch +++ b/patches/meshcore/0003-Allow-prefixless-firmware-bot-DMs.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Fri, 15 May 2026 20:09:58 -0600 -Subject: [PATCH 03/11] Allow prefixless firmware bot DMs +Subject: [PATCH 03/12] Allow prefixless firmware bot DMs --- examples/companion_radio/BotPolicy.cpp | 2 +- diff --git a/patches/meshcore/0004-Align-firmware-bot-commands-with-upstream-behavior.patch b/patches/meshcore/0004-Align-firmware-bot-commands-with-upstream-behavior.patch index cb330e2..bdccf7c 100644 --- a/patches/meshcore/0004-Align-firmware-bot-commands-with-upstream-behavior.patch +++ b/patches/meshcore/0004-Align-firmware-bot-commands-with-upstream-behavior.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 04:12:02 +0000 -Subject: [PATCH 04/11] Align firmware bot commands with upstream behavior +Subject: [PATCH 04/12] Align firmware bot commands with upstream behavior --- .../companion_radio/BotCommandRegistry.cpp | 25 +- diff --git a/patches/meshcore/0005-Add-hop-aware-bot-coordination-utility-commands-and-.patch b/patches/meshcore/0005-Add-hop-aware-bot-coordination-utility-commands-and-.patch index ed2a6dd..4a55064 100644 --- a/patches/meshcore/0005-Add-hop-aware-bot-coordination-utility-commands-and-.patch +++ b/patches/meshcore/0005-Add-hop-aware-bot-coordination-utility-commands-and-.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 12:38:13 -0600 -Subject: [PATCH 05/11] Add hop-aware bot coordination, utility commands, and +Subject: [PATCH 05/12] Add hop-aware bot coordination, utility commands, and bounded delays Adds new utility commands (TIME, LORA, ID, NEIGHBORS), neighbor tracking, diff --git a/patches/meshcore/0006-Mark-bot-adverts-speed-up-initial-advert-default-con.patch b/patches/meshcore/0006-Mark-bot-adverts-speed-up-initial-advert-default-con.patch index 917c8d3..3962ce6 100644 --- a/patches/meshcore/0006-Mark-bot-adverts-speed-up-initial-advert-default-con.patch +++ b/patches/meshcore/0006-Mark-bot-adverts-speed-up-initial-advert-default-con.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 18:58:28 -0600 -Subject: [PATCH 06/11] Mark bot adverts, speed up initial advert, default +Subject: [PATCH 06/12] Mark bot adverts, speed up initial advert, default contacts auto-overwrite Three coordination/discoverability fixes: diff --git a/patches/meshcore/0007-Prefix-bot-responses-with-request-token-for-inter-bo.patch b/patches/meshcore/0007-Prefix-bot-responses-with-request-token-for-inter-bo.patch index b8e38a1..8a17b7d 100644 --- a/patches/meshcore/0007-Prefix-bot-responses-with-request-token-for-inter-bo.patch +++ b/patches/meshcore/0007-Prefix-bot-responses-with-request-token-for-inter-bo.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 19:33:04 -0600 -Subject: [PATCH 07/11] Prefix bot responses with request token for inter-bot +Subject: [PATCH 07/12] Prefix bot responses with request token for inter-bot suppression Adds a 4-hex request token (low 16 bits of the request fingerprint) as a diff --git a/patches/meshcore/0008-Drop-MCBOT-advert-suffix-and-auto-learn-known-bot-pa.patch b/patches/meshcore/0008-Drop-MCBOT-advert-suffix-and-auto-learn-known-bot-pa.patch index c7a00aa..59644d7 100644 --- a/patches/meshcore/0008-Drop-MCBOT-advert-suffix-and-auto-learn-known-bot-pa.patch +++ b/patches/meshcore/0008-Drop-MCBOT-advert-suffix-and-auto-learn-known-bot-pa.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 19:55:36 -0600 -Subject: [PATCH 08/11] Drop [MCBOT] advert suffix and auto-learn known-bot +Subject: [PATCH 08/12] Drop [MCBOT] advert suffix and auto-learn known-bot path Removes BOT_ADVERT_MARKER / writeBotAdvertName / isBotAdvertName from diff --git a/patches/meshcore/0009-Widen-hop-step-drop-ack-prefix-from-test-response.patch b/patches/meshcore/0009-Widen-hop-step-drop-ack-prefix-from-test-response.patch index c84174c..60af5a8 100644 --- a/patches/meshcore/0009-Widen-hop-step-drop-ack-prefix-from-test-response.patch +++ b/patches/meshcore/0009-Widen-hop-step-drop-ack-prefix-from-test-response.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 20:05:19 -0600 -Subject: [PATCH 09/11] Widen hop-step + drop "ack " prefix from test response +Subject: [PATCH 09/12] Widen hop-step + drop "ack " prefix from test response Two changes: diff --git a/patches/meshcore/0010-Widen-coordinator-delays-a-tiny-bit-more.patch b/patches/meshcore/0010-Widen-coordinator-delays-a-tiny-bit-more.patch index a2ac1bc..dbfbef0 100644 --- a/patches/meshcore/0010-Widen-coordinator-delays-a-tiny-bit-more.patch +++ b/patches/meshcore/0010-Widen-coordinator-delays-a-tiny-bit-more.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 20:31:12 -0600 -Subject: [PATCH 10/11] Widen coordinator delays a tiny bit more +Subject: [PATCH 10/12] Widen coordinator delays a tiny bit more ~25% bump across the main coordination knobs: - BOT_RESPONSE_DELAY_BASE_MILLIS 1200 -> 1500 diff --git a/patches/meshcore/0011-Auto-advert-on-startup-initial-flood-at-5s-instead-o.patch b/patches/meshcore/0011-Auto-advert-on-startup-initial-flood-at-5s-instead-o.patch index a07fcdc..2bc399b 100644 --- a/patches/meshcore/0011-Auto-advert-on-startup-initial-flood-at-5s-instead-o.patch +++ b/patches/meshcore/0011-Auto-advert-on-startup-initial-flood-at-5s-instead-o.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 20:49:51 -0600 -Subject: [PATCH 11/11] Auto-advert on startup (initial flood at 5s instead of +Subject: [PATCH 11/12] Auto-advert on startup (initial flood at 5s instead of 120s) BOT_PREFS_INITIAL_FLOOD_ADVERT_MILLIS 120000 -> 5000. The bot now sends diff --git a/patches/meshcore/0012-Spell-out-hops-byte-in-test-path-drop-channel-name-f.patch b/patches/meshcore/0012-Spell-out-hops-byte-in-test-path-drop-channel-name-f.patch new file mode 100644 index 0000000..40fd0a1 --- /dev/null +++ b/patches/meshcore/0012-Spell-out-hops-byte-in-test-path-drop-channel-name-f.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: cj-vana +Date: Sat, 16 May 2026 21:32:04 -0600 +Subject: [PATCH 12/12] Spell out hops/byte in test+path; drop channel name + from test ack + +Test response (writeAckResponse): +- before: @[user] #bot | 2h@2B SNR -1.25 | recv 21:25:45 +- after : @[user] | 2 hops, 2-byte hashes, SNR -1.25 | recv 21:25:45 + +DMs keep the 'direct' indicator; channel responses now omit the channel +name (redundant in-channel) for readability. + +Path response (executePathLike + executePathArg): +- before: Path @[user] 6h@2B SNR -8.50 | +- after : Path @[user] 6 hops, 2-byte hashes, SNR -8.50 | +- before: Path direct zero-hop SNR 0.00 +- after : Path direct zero-hop, SNR 0.00 + +Trace and other responses unchanged for now. +--- + examples/companion_radio/BotCommands.cpp | 10 +++++----- + examples/companion_radio/FirmwareBot.cpp | 14 ++++++++------ + 2 files changed, 13 insertions(+), 11 deletions(-) + +diff --git a/examples/companion_radio/BotCommands.cpp b/examples/companion_radio/BotCommands.cpp +index 1aa4406d..198dc32f 100644 +--- a/examples/companion_radio/BotCommands.cpp ++++ b/examples/companion_radio/BotCommands.cpp +@@ -381,15 +381,15 @@ BotCommandResult executePathLike(const BotCommandContext& context, char* output, + formatQuarters(context.path_snr_quarters, snr, sizeof(snr)); + const char* target = context.response_target[0] ? context.response_target : NULL; + if (context.path_hash_count == 0 || context.path_len == 0) { +- if (target) return writeFormatted(output, output_len, "%s @[%s] direct zero-hop SNR %s", label, target, snr); +- return writeFormatted(output, output_len, "%s direct zero-hop SNR %s", label, snr); ++ if (target) return writeFormatted(output, output_len, "%s @[%s] direct zero-hop, SNR %s", label, target, snr); ++ return writeFormatted(output, output_len, "%s direct zero-hop, SNR %s", label, snr); + } + if (!context.path) return writeFormatted(output, output_len, "%s unavailable", label); + + int written = target +- ? snprintf(output, output_len, "%s @[%s] %uh@%uB SNR %s | ", label, target, ++ ? snprintf(output, output_len, "%s @[%s] %u hops, %u-byte hashes, SNR %s | ", label, target, + (unsigned)context.path_hash_count, (unsigned)context.path_hash_size, snr) +- : snprintf(output, output_len, "%s %uh@%uB SNR %s | ", label, ++ : snprintf(output, output_len, "%s %u hops, %u-byte hashes, SNR %s | ", label, + (unsigned)context.path_hash_count, (unsigned)context.path_hash_size, snr); + if (written < 0) return makeResult(BOT_COMMAND_RESULT_NO_SPACE, 0); + size_t pos = (size_t)written; +@@ -402,7 +402,7 @@ BotCommandResult executePathArg(const BotCommand& command, const BotCommandConte + ParsedPathArg path; + if (!parsePathArgument(command.args, command.args_len, context.path_hash_size, &path)) return writeText(output, output_len, "Usage: path [path]"); + if (!output || output_len == 0) return makeResult(BOT_COMMAND_RESULT_NO_SPACE, 0); +- int written = snprintf(output, output_len, "Path %uh@%uB | ", (unsigned)path.hash_count, (unsigned)path.hash_size); ++ int written = snprintf(output, output_len, "Path %u hops, %u-byte hashes | ", (unsigned)path.hash_count, (unsigned)path.hash_size); + if (written < 0) return makeResult(BOT_COMMAND_RESULT_NO_SPACE, 0); + size_t pos = (size_t)written; + appendPathHops(output, output_len, &pos, path.bytes, path.hash_size, path.hash_count); +diff --git a/examples/companion_radio/FirmwareBot.cpp b/examples/companion_radio/FirmwareBot.cpp +index e228b7c8..afd44d41 100644 +--- a/examples/companion_radio/FirmwareBot.cpp ++++ b/examples/companion_radio/FirmwareBot.cpp +@@ -319,10 +319,12 @@ BotWriteResult writeAckResponse(const BotMessage& message, const BotCommand& com + if (!output || output_len == 0) return BOT_WRITE_NO_SPACE; + + const char* sender = message.sender_name[0] ? message.sender_name : "unknown"; +- const char* connection = message.channel_kind == BOT_CHANNEL_DM +- ? "direct" +- : (message.channel_name[0] ? message.channel_name : "channel"); +- int n = snprintf(output, output_len, "@[%s] %s", sender, connection); ++ int n; ++ if (message.channel_kind == BOT_CHANNEL_DM) { ++ n = snprintf(output, output_len, "@[%s] direct", sender); ++ } else { ++ n = snprintf(output, output_len, "@[%s]", sender); ++ } + if (n < 0) { + output[0] = 0; + return BOT_WRITE_NO_SPACE; +@@ -337,9 +339,9 @@ BotWriteResult writeAckResponse(const BotMessage& message, const BotCommand& com + char path_block[40]; + int path_n; + if (message.path_hash_count == 0 || message.path_hash_size == 0) { +- path_n = snprintf(path_block, sizeof(path_block), " | 0h SNR %s%d.%02d", sign, value / 4, (value % 4) * 25); ++ path_n = snprintf(path_block, sizeof(path_block), " | 0 hops, SNR %s%d.%02d", sign, value / 4, (value % 4) * 25); + } else { +- path_n = snprintf(path_block, sizeof(path_block), " | %uh@%uB SNR %s%d.%02d", ++ path_n = snprintf(path_block, sizeof(path_block), " | %u hops, %u-byte hashes, SNR %s%d.%02d", + (unsigned)message.path_hash_count, (unsigned)message.path_hash_size, sign, value / 4, + (value % 4) * 25); + } diff --git a/tests/firmware_bot/test_firmware_bot.cpp b/tests/firmware_bot/test_firmware_bot.cpp index 2d59b76..eea66c5 100644 --- a/tests/firmware_bot/test_firmware_bot.cpp +++ b/tests/firmware_bot/test_firmware_bot.cpp @@ -539,7 +539,7 @@ static void test_ack_response_format() { char out[BOT_MAX_RESPONSE_LEN + 1]; size_t written = 0; assert(FirmwareBot::writeAckResponse(message, command, out, sizeof(out), &written) == BOT_WRITE_OK); - assert(strcmp(out, "@[alice] #bot | 0h SNR 0.00 | recv 21:25:45 | hello") == 0); + assert(strcmp(out, "@[alice] | 0 hops, SNR 0.00 | recv 21:25:45 | hello") == 0); assert(written == strlen(out)); message.channel_kind = BOT_CHANNEL_DM; @@ -547,7 +547,7 @@ static void test_ack_response_format() { message.channel_name[0] = 0; assert(FirmwareBot::parseCommand("!t", 2, &command)); assert(FirmwareBot::writeAckResponse(message, command, out, sizeof(out), &written) == BOT_WRITE_OK); - assert(strcmp(out, "@[unknown] direct | 0h SNR 0.00 | recv 21:25:45") == 0); + assert(strcmp(out, "@[unknown] direct | 0 hops, SNR 0.00 | recv 21:25:45") == 0); uint8_t path[] = { 0x12, 0x34, 0xab, 0xcd }; message.channel_kind = BOT_CHANNEL_BOT; @@ -558,7 +558,7 @@ static void test_ack_response_format() { message.path_hash_count = 2; message.packet_snr_quarters = -5; assert(FirmwareBot::writeAckResponse(message, command, out, sizeof(out), &written) == BOT_WRITE_OK); - assert(strcmp(out, "@[bob] #bot | 2h@2B SNR -1.25 | recv 21:25:45") == 0); + assert(strcmp(out, "@[bob] | 2 hops, 2-byte hashes, SNR -1.25 | recv 21:25:45") == 0); assert(strstr(out, "Bot test OK") == NULL); char small[16]; @@ -799,23 +799,23 @@ static void test_path_command() { context.path_snr_quarters = 23; BotCommandResult result = BotCommands::executeCommand(command, context, out, sizeof(out)); assert(result.code == BOT_COMMAND_RESULT_OK); - assert(strcmp(out, "Path 3h@2B SNR 5.75 | 1234 -> abcd -> 0001") == 0); + assert(strcmp(out, "Path 3 hops, 2-byte hashes, SNR 5.75 | 1234 -> abcd -> 0001") == 0); strncpy(context.response_target, "alice", sizeof(context.response_target) - 1); result = BotCommands::executeCommand(command, context, out, sizeof(out)); assert(result.code == BOT_COMMAND_RESULT_OK); - assert(strcmp(out, "Path @[alice] 3h@2B SNR 5.75 | 1234 -> abcd -> 0001") == 0); + assert(strcmp(out, "Path @[alice] 3 hops, 2-byte hashes, SNR 5.75 | 1234 -> abcd -> 0001") == 0); context.response_target[0] = 0; assert(FirmwareBot::parseCommand("!path 1234,abcd,0001", 20, &command)); result = BotCommands::executeCommand(command, context, out, sizeof(out)); assert(result.code == BOT_COMMAND_RESULT_OK); - assert(strcmp(out, "Path 3h@2B | 1234 -> abcd -> 0001") == 0); + assert(strcmp(out, "Path 3 hops, 2-byte hashes | 1234 -> abcd -> 0001") == 0); assert(FirmwareBot::parseCommand("!path 1234abcd0001", 18, &command)); result = BotCommands::executeCommand(command, context, out, sizeof(out)); assert(result.code == BOT_COMMAND_RESULT_OK); - assert(strcmp(out, "Path 3h@2B | 1234 -> abcd -> 0001") == 0); + assert(strcmp(out, "Path 3 hops, 2-byte hashes | 1234 -> abcd -> 0001") == 0); assert(FirmwareBot::parseCommand("!path 12,34", 11, &command)); result = BotCommands::executeCommand(command, context, out, sizeof(out)); @@ -830,7 +830,7 @@ static void test_path_command() { context.path_hash_size = 0; result = BotCommands::executeCommand(command, context, out, sizeof(out)); assert(result.code == BOT_COMMAND_RESULT_OK); - assert(strcmp(out, "Path 2h@4B | 12345678 -> abcdef01") == 0); + assert(strcmp(out, "Path 2 hops, 4-byte hashes | 12345678 -> abcdef01") == 0); context.path_hash_size = 2; assert(FirmwareBot::parseCommand("!path", 5, &command)); @@ -841,17 +841,17 @@ static void test_path_command() { context.path_snr_quarters = 0; result = BotCommands::executeCommand(command, context, out, sizeof(out)); assert(result.code == BOT_COMMAND_RESULT_OK); - assert(strcmp(out, "Path direct zero-hop SNR 0.00") == 0); + assert(strcmp(out, "Path direct zero-hop, SNR 0.00") == 0); context.path = NULL; result = BotCommands::executeCommand(command, context, out, sizeof(out)); assert(result.code == BOT_COMMAND_RESULT_OK); - assert(strcmp(out, "Path direct zero-hop SNR 0.00") == 0); + assert(strcmp(out, "Path direct zero-hop, SNR 0.00") == 0); strncpy(context.response_target, "alice", sizeof(context.response_target) - 1); result = BotCommands::executeCommand(command, context, out, sizeof(out)); assert(result.code == BOT_COMMAND_RESULT_OK); - assert(strcmp(out, "Path @[alice] direct zero-hop SNR 0.00") == 0); + assert(strcmp(out, "Path @[alice] direct zero-hop, SNR 0.00") == 0); context.response_target[0] = 0; assert(FirmwareBot::parseCommand("!tracer", 7, &command));