Support '!' prefix in broker messages as inline interrupt#375
Conversation
Messages arriving via the broker (Telegram, webhooks, direct messages) that start with "!" are now treated as interrupt messages: the "!" is stripped and the message is delivered with urgent/interrupt semantics, equivalent to --interrupt on the CLI.
deliverToAgent mutates msg.Msg and msg.Urgent in place, but at the direct-subscriber call site the pointer comes from the event bus and is shared across all matching subscribers. Shallow-copy before mutating so the original bus message stays intact.
There was a problem hiding this comment.
Code Review
This pull request introduces an inline interrupt signal feature where messages prefixed with '!' are promoted to urgent and have the prefix stripped before delivery. Corresponding unit tests were added to verify the prefix stripping, persistence, and behavior when the prefix is absent. The review feedback highlights a potential issue where messages containing only '!' or trailing spaces after '!' would result in an empty message body, violating validation requirements; a code suggestion is provided to handle this edge case by trimming whitespace and using a fallback default message.
| // A leading "!" in the message body acts as an inline interrupt signal: | ||
| // strip the prefix and promote to urgent so the harness is interrupted | ||
| // before delivery — equivalent to --interrupt on the CLI. | ||
| // Shallow-copy to avoid mutating the event-bus pointer shared across subscribers. | ||
| if strings.HasPrefix(msg.Msg, "!") { | ||
| stripped := *msg | ||
| stripped.Msg = msg.Msg[1:] | ||
| stripped.Urgent = true | ||
| msg = &stripped | ||
| } |
There was a problem hiding this comment.
If the incoming message is exactly ! or contains only spaces after ! (e.g., ! ), stripping the prefix leaves an empty string "". According to pkg/messages/types.go (lines 127-129), the Msg field is strictly required to be non-empty (msg field is required). Any downstream component or validation check that runs on the message after retrieval or delivery will fail if Msg is empty.
Additionally, trimming leading and trailing whitespace makes the prefix detection more robust against accidental spaces (e.g., !restart or ! restart).
We can resolve this by trimming the message and falling back to a default non-empty string like "interrupt" if the stripped message would otherwise be empty.
// A leading "!" in the message body acts as an inline interrupt signal:
// strip the prefix and promote to urgent so the harness is interrupted
// before delivery — equivalent to --interrupt on the CLI.
// Shallow-copy to avoid mutating the event-bus pointer shared across subscribers.
if trimmed := strings.TrimSpace(msg.Msg); strings.HasPrefix(trimmed, "!") {
stripped := *msg
content := strings.TrimSpace(trimmed[1:])
if content == "" {
content = "interrupt"
}
stripped.Msg = content
stripped.Urgent = true
msg = &stripped
}TrimSpace the message before checking for the "!" prefix so leading whitespace (e.g. " !restart") is handled correctly. TrimSpace the content after stripping the prefix so "! restart" works too. Default to "interrupt" when the stripped content is empty (bare "!" or "! ") to satisfy the Msg-required invariant in StructuredMessage.Validate. Adds table-driven tests covering all edge cases.
- Use t.Cleanup with explicit error discard for eventbus Close() calls in new test functions to satisfy the errcheck linter. - Add //go:build !no_sqlite constraint to signing_key_shared_test.go so newTestStore is available when `go vet -tags no_sqlite` runs.
Summary
!are now treated as interrupt messages!prefix is stripped from the message body before deliveryurgent=true/ interrupt semantics, equivalent to--interrupton the CLIdeliverToAgentin the message broker proxy, before persistence and dispatch, so both the stored message and the delivered message reflect the stripped content and urgent flagTest plan
TestMessageBrokerProxy_InterruptPrefix— verifies!-prefixed message is stripped and dispatched with interrupt=trueTestMessageBrokerProxy_InterruptPrefixNotStrippedWithoutBang— verifies normal messages are unaffectedTestMessageBrokerProxy_InterruptPrefixPersistence— verifies the persisted message has stripped content and urgent=true