Skip to content

Add Alert node for out-of-game notifications - #78

Open
Gaetarra wants to merge 3 commits into
soymods:mainfrom
Gaetarra:feature/alert-node
Open

Add Alert node for out-of-game notifications#78
Gaetarra wants to merge 3 commits into
soymods:mainfrom
Gaetarra:feature/alert-node

Conversation

@Gaetarra

@Gaetarra Gaetarra commented Aug 29, 2026

Copy link
Copy Markdown

What This Changes

Adds an ALERT node so a graph can reach someone who is not looking at the
screen. Automation runs while the player is tabbed out or AFK, and MESSAGE
only reaches chat, which nobody sees while a farm runs unattended.

Two modes:

  • Play Sound — any vanilla sound id plus a volume, played through the sound
    manager with SimpleSoundInstance.forUI so the alert is not attenuated by
    where the player is standing.
  • Send Webhook — POSTs the node's text to an https endpoint set in
    settings. This is the mode that reaches a phone.

Verification

Check Status
./gradlew :common:test -Pmc_version=1.21.11 Not run locally. Relying on CI.
verifyCompatibilityManifest verifyCompatibilityStructure verifyBuildGenerationRouting Not run locally. Relying on CI.
Compiles on 1.21 / 1.21.8 / 1.21.10 / 1.21.11 Not run locally. All new code is version-agnostic common/src/main, but that is an argument, not a check.
Compiles on 26.1 / 26.2 (-p mc26, both loaders) Fabric passes on both. NeoForge passes on 26.1.2. NeoForge on 26.2 fails with Could not find net.neoforged:neoforge:unsupported — the manifest lists neoforge=unsupported for 26.2 and the mc26 build reads it without the root build's takeUnless filter. Pre-existing, unrelated to this change.
Clicked through the affected UI in a dev client Yes — mode selector opens, Sound and Volume are editable, both modes render as English.

Notes For Review

Non-obvious decisions:

  • The node completes its future as soon as the notification is dispatched,
    rather than awaiting the HTTP response. An alert must never stall the graph it
    is reporting on, so webhook failures are logged instead of propagated.
  • Webhook sends are rate limited to one per three seconds. An Alert inside
    Forever would otherwise hammer the endpoint, and Discord kills webhooks that
    are hammered.
  • Payload shape is chosen by host — Discord needs a JSON body, ntfy takes
    raw text. A deliberate two-case heuristic rather than a third node mode; there
    is a comment marking it as the thing to revisit if a third shape shows up.
  • The URL is validated once, in SettingsManager.sanitizeWebhookUrl, and
    https is required.
    It is a user-supplied string driving an outbound request
    from the game client, so it is checked at the boundary rather than at each
    call site. NodeAlertTest covers the rejection cases.
  • Alert reuses the multi-line text fields that MESSAGE and CALCULATE
    already have, by joining NodeTextContent, so message text supports runtime
    variable interpolation with no new UI.
  • Toast was considered and left out. It only helps someone already watching
    the screen, which is not the case this node exists for.

Out of scope, deliberately:

  • The webhook URL has no settings-popup field yet, so it is set by editing
    pathmind/settings.json. That control lives in a Stonecutter source file with
    15 conditional regions; adding it here would have made this much harder to
    review. Happy to follow up.

Worth a look by hand: NodeCatalog gained NodeType.ALERT in the
RENDER_INLINE_PARAMETERS tag. Without it NodeGraph.rendersInlineParameters
is false, the parameter strip is never drawn, and the mode selector that lives
inside that strip is unreachable. That is a general trap — several existing
node types have modes with no way to select them — but this PR only fixes it
for ALERT.

Gaetarra and others added 3 commits August 29, 2026 14:08
Automation runs while the player is tabbed out or AFK, so a graph needs a way
to reach someone who is not looking at the screen. Send Message only reaches
chat, which nobody sees while a farm runs unattended.

Alert has two modes. Play Sound takes any vanilla sound id plus a volume, and
plays it through the sound manager with SimpleSoundInstance.forUI so the alert
is not attenuated by where the player happens to be standing. Send Webhook
POSTs the node's text to an endpoint configured in settings, which is what
reaches a phone when nobody is at the machine.

Non-obvious calls made here:

- The node completes its future as soon as the notification is dispatched
  rather than awaiting the response. An alert must never stall the graph it is
  reporting on, so webhook failures are logged rather than propagated.
- Webhook sends are rate limited to one per three seconds. An Alert placed
  inside Forever would otherwise hammer the endpoint, and Discord kills
  webhooks that are hammered.
- Payload shape is chosen by host: Discord needs a JSON body, ntfy takes the
  raw text. This is a deliberate two-case heuristic rather than another mode.
- The URL is validated once in SettingsManager.sanitizeWebhookUrl and https is
  required. It is a user-supplied string driving an outbound request from the
  game client, so it is checked at the boundary instead of at each call site.
- Alert reuses the multi-line text fields that Send Message and Calculate
  already have, by joining NodeTextContent, so message text supports runtime
  variable interpolation with no new UI.
- Toast was considered and left out: it only helps someone already watching
  the screen, which is not the case this node exists for.

Not yet wired: the webhook URL has no settings-popup field, so it is set by
editing pathmind/settings.json. That control lives in a Stonecutter source
file and is left for a follow-up so this change stays reviewable.

Verified: Fabric compiles on 26.2 and NeoForge on 26.1.2, and the generated
mc26 source is byte-identical to the authored file, so no string transform was
needed. The escaping and URL-validation logic was checked separately against
the same inputs as NodeAlertTest. Not verified locally: :common:test and the
1.21.x targets, which need a JDK 21 toolchain; CI covers both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NodeMode has getModesForNodeType, not getAvailableModesForNodeType, so the
assertion would not have compiled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alert shipped unusable: it rendered only its message field, so the Sound and
Volume parameters were invisible and there was no way to switch to Send
Webhook. Its mode names also displayed as raw keys.

Two causes, both missed because the editor has no automated coverage:

- NodeGraph.rendersInlineParameters decides whether a node draws its parameter
  strip, and the mode selector is drawn inside that strip. It is true only for
  parameter nodes and for types tagged RENDER_INLINE_PARAMETERS. Alert was
  neither, so the strip that would have held both never rendered.

- NodeMode.getDisplayName ignores the display strings in the enum constructor
  and builds "pathmind.node.mode.<name>" as a translation key. Without lang
  entries the modes rendered as pathmind.node.mode.alert_sound.

Verified in a dev client: the mode selector opens, Sound and Volume are
editable, and both modes read as English.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant