yuzu

interaction

What it doesDesktop user interaction — notifications, message boxes, input dialogs, surveys, DND
Version0.3.0
KindAction · mutating · gathered (device.interaction.notify, device.interaction.message_box, device.interaction.input, device.interaction.survey, device.interaction.set_dnd)
PlatformsWindows ✅ · macOS ✅ · Linux ✅
Actionsinput (definition device.interaction.input) · message_box (definition device.interaction.message_box) · notify (definition device.interaction.notify) · set_dnd (definition device.interaction.set_dnd) · survey (definition device.interaction.survey)
Securitysecurable Infrastructure · operation Write · risk Medium · dispatch Mutating · approval gate AdminOrApproval
Rolesexecute: notify: endpoint-admin, endpoint-operator; message_box: endpoint-admin; input: endpoint-admin; survey: endpoint-admin; set_dnd: endpoint-admin, endpoint-operator · author: content-author

How it works

Four of the five actions dispatch a platform-native dialog or notification mechanism and report what happened; the fifth, set_dnd, is a pure local KV-store flag with no OS call. notify and survey first check whether Do Not Disturb is active and, if so, short-circuit to status|suppressed without ever spawning anything. Every dialog action validates its required parameters in the OS-agnostic dispatch layer before reaching a platform leg — a missing title never spawns a subprocess, on any OS. Text fields are then sanitized (metacharacters and quotes stripped) before being interpolated into an osascript/PowerShell script fragment; every spawn itself goes through the bounded argv runner with no shell involved end to end. The plugin never fabricates a dialog outcome: a non-zero exit, a runner-level failure, or an unreachable GUI session is always reported as an honest status|unavailable/status|not_reachable/status|error line rather than a guessed button or cancellation. It is deliberately not a general scripting surface — the only mechanisms it invokes are ShellNotifyIcon/MessageBoxW/PowerShell (Windows), notify-send/zenity (Linux), and osascript (macOS).

flowchart LR
  OP["Operator / workflow"] --> SRV["Server - authz: Infrastructure.Write"]
  SRV -->|"gRPC mTLS"| HOST["Agent plugin host"] --> EX["interaction.execute"]
  EX --> DND{"DND active? notify and survey only"}
  DND -->|"yes"| SUP["status suppressed"]
  DND -->|"no / n-a"| VAL{"required params present?"}
  VAL -->|"no"| ERR["status error"]
  VAL -->|"yes"| WIN["Windows leg - Shell_NotifyIconW / MessageBoxW / PowerShell InputBox / WinForms"]
  VAL -->|"yes"| MAC["macOS leg - osascript display notification / dialog"]
  VAL -->|"yes"| LIN["Linux leg - notify-send / zenity"]
  SUP & ERR & WIN & MAC & LIN --> ROWS["key|value rows"] --> RS[("ResponseStore")] --> API["REST /api/responses"]

OS capability

ActionWindowsmacOSLinux
input✅ supported · rung 3 · powershell_inputbox🟡 constrained · rung 3 · osascript✅ supported · rung 2 · zenity
message_box✅ supported · rung 1 · messageboxw🟡 constrained · rung 3 · osascript✅ supported · rung 2 · zenity
notify✅ supported · rung 1 · shell_notifyicon🟡 constrained · rung 3 · osascript✅ supported · rung 2 · notify_send
set_dnd✅ supported · rung 1 · local_kv_store✅ supported · rung 1 · local_kv_store✅ supported · rung 1 · local_kv_store
survey✅ supported · rung 3 · powershell_winforms🟡 constrained · rung 3 · osascript✅ supported · rung 2 · zenity

Declared limits per leg (descriptor fallback text, verbatim):

Privileges and prerequisites

OSRuns asExtra grant neededMeasuredIf the read is refused
Windowsagent service account; registers and runs as LocalSystem today (#1442, not yet the intended virtual service account)None — Shell_NotifyIconW/MessageBoxW are unprivileged, and PowerShell needs no elevation to run InputBox/WinForms2026-09-07, bare-metal, as SYSTEMPowerShell dialogs (input/survey) report status|unavailable|PowerShell dialog exited with an error or ...timed out; native notify/message_box have no refusal path — MessageBoxW always returns a button
macOSroot (shipped LaunchDaemon has no UserName key)None — but no LaunchDaemon has a reachable Aqua/GUI session by design2026-09-07, bare-metal, as euid 501 (jsmith) — an interactive logged-in user, not the production root-daemon posturestatus|unavailable|no reachable GUI session (notify) or status|not_reachable (message_box/input/survey)
Linuxagent unprivileged account (yuzu)None — the plugin checks DISPLAY/WAYLAND_DISPLAY before ever spawning2026-09-06, container, as euid 0status|unavailable|no reachable GUI session, checked upfront before spawning zenity/notify-send

Binaries/subprocesses: PowerShell (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe, Windows input/survey) · osascript (/usr/bin/osascript, macOS all four dialog actions) · zenity and notify-send (/usr/bin/zenity, /usr/local/bin/zenity, /usr/bin/notify-send, /usr/local/bin/notify-send, Linux). No network access. set_dnd and Windows notify/message_box spawn nothing.

Data contract

Inputs

DefinitionParameterTypeRequiredDefaultConstraintsDescription
device.interaction.inputtitlestringyes--Dialog title text
device.interaction.inputpromptstringyes--Prompt text shown above the input field
device.interaction.inputdefault_valuestringno-Pre-filled default text in the input field
device.interaction.message_boxtitlestringyes--Dialog title text
device.interaction.message_boxmessagestringyes--Dialog body text
device.interaction.message_boxbuttonsstringnook-Button configuration: ok, okcancel, or yesno
device.interaction.notifytitlestringyes--Notification title text
device.interaction.notifymessagestringyes--Notification body text
device.interaction.notifytypestringnoinfo-Notification severity: info, warning, or error
device.interaction.set_dndenabledstringyes--Set to ‘true’ to enable DND, ‘false’ to disable
device.interaction.set_dndduration_minutesint32no0-Optional duration in minutes. If set and enabled=true, DND will automatically expire after this many minutes. If 0 or omitted, DND remains active indefinitely until explicitly disabled.
device.interaction.surveytitlestringyes--Survey window title
device.interaction.surveyquestionsstringyes--JSON array of question objects. Each object has: prompt (string), type (text|yesno|choice), and choices (array of strings, required when type=choice). Example: [{“prompt”:“Your name?”,“type”:“text”}, {“prompt”:“Agree?”,“type”:“yesno”}, {“prompt”:“Department”,“type”:“choice”,“choices”:[“IT”,“HR”,“Eng”]}]

Outputs

Each action writes one or more key|value lines via write_output(). The dashboard/REST layer treats interaction as a key/value plugin and splits each line into exactly a key and the remainder of the line as its value — so a value can itself contain literal | characters, e.g. status|error|missing required parameter: title has key status and value error|missing required parameter: title. Not every declared column is written on every invocation: response/cancelled are mutually exclusive per action, and status appears only on a validation failure, a suppressed DND, or an undeliverable/failed dialog — a clean button press or clean survey completion never emits a status line at all.

device.interaction.input — response|cancelled|status

FieldTypeValuesAvailableExampleDescription
responsestring-Windows, Linux, macOS-The text the operator/user entered and confirmed. Absent when the dialog was cancelled or could not be shown. Values: free text.
cancelledbool-Windows, Linux, macOStruetrue when the user dismissed the dialog without entering text (Escape, Cancel, or AppleScript error -128); omitted otherwise.
statusstring-Windows, Linux, macOSerror|missing required parameter: titleSet instead of response/cancelled when the dialog never ran: a validation failure (every platform), or an unavailable/failed dialog on Windows, macOS, or Linux. Values: error|, unavailable|.

device.interaction.message_box — response|status

FieldTypeValuesAvailableExampleDescription
responsestring-Windows, Linux, macOSokThe button the operator/user clicked on the dialog. Absent when the dialog could not be shown — see status. Values: ok, cancel, yes, no.
statusstring-Windows, Linux, macOSerror|missing required parameter: titleSet when no button could be returned: a validation failure (missing title/message or a bad buttons value — every platform), the macOS not_reachable sentinel (no GUI session), or a Linux zenity delivery failure. Empty on a normal button-press response. Values: not_reachable, error|, unavailable|.

device.interaction.notify — status

FieldTypeValuesAvailableExampleDescription
statusstring-Windows, Linux, macOSerror|missing required parameter: titleOutcome of the notification attempt: ok (shown, or handed to the OS successfully), suppressed| (Do Not Disturb was active), error| (missing/invalid parameter, or an unsupported platform), or unavailable| (macOS/Linux: no reachable GUI session). Values: ok, suppressed|, error|, unavailable| (reason is free text).

device.interaction.set_dnd — dnd_enabled|dnd_duration_minutes|dnd_expires_at|status

FieldTypeValuesAvailableExampleDescription
dnd_enabledbool-Windows, Linux, macOStrueThe DND state after this call — true when DND is now active, false when cleared. Values: true, false.
dnd_duration_minutesint32-Windows, Linux, macOS-The duration parameter echoed back, in minutes. Only emitted when DND was enabled with a positive duration_minutes. Values: integer.
dnd_expires_atint64-Windows, Linux, macOS-Unix epoch seconds when DND auto-clears. Only emitted alongside dnd_duration_minutes (enabled with a positive duration); 0 or omitted means indefinite. Values: epoch seconds, or omitted.
statusstring-Windows, Linux, macOSokAlways ok — set_dnd is a local KV-store write with no external dependency and no failure path once dispatched.

device.interaction.survey — cancelled|question_count|answer_0|answer_1|answer_2|answer_3|answer_4|status

FieldTypeValuesAvailableExampleDescription
cancelledbool-Windows, Linux, macOStruetrue when the user dismissed the survey (Cancel, Escape, or AppleScript error -128) before answering every question.
question_countint32-Windows, Linux, macOS-The number of questions in the submitted survey. Only emitted when the survey completed without cancellation or failure. Values: integer.
answer_0string-Windows, Linux, macOS-The answer to question 0 (0-indexed): free text for a text question, yes/no for a yesno question, or the chosen item’s text for a choice question. Omitted if the survey was cancelled or could not be shown. Values: free text, or yes/no for a yesno question.
answer_1string-Windows, Linux, macOS-The answer to question 1 (0-indexed), same convention as answer_0. Omitted past question_count-1. Values: free text, or yes/no for a yesno question.
answer_2string-Windows, Linux, macOS-The answer to question 2 (0-indexed), same convention as answer_0. Omitted past question_count-1. Values: free text, or yes/no for a yesno question.
answer_3string-Windows, Linux, macOS-The answer to question 3 (0-indexed), same convention as answer_0. Omitted past question_count-1. Values: free text, or yes/no for a yesno question.
answer_4string-Windows, Linux, macOS-The answer to question 4 (0-indexed), same convention as answer_0. Omitted past question_count-1. Values: free text, or yes/no for a yesno question.
statusstring-Windows, Linux, macOSerror|missing required parameter: titleSet instead of cancelled/answers when the survey never completed: a validation failure (every platform, e.g. missing title or unparseable questions), a suppressed Do Not Disturb, or an undeliverable/unrecognized-response dialog. Values: suppressed|, error|, unavailable|.

Result status

StatusCompletenessProvenanceWhen
UNDECLARED (agent-recorded default)——Every clean dialog outcome — a button press, survey answers, a DND-suppressed run, or a validation failure — matches every sample’s [result_status] UNDECLARED / UNKNOWN / line. forward_runner_failure is a no-op unless the runner itself failed.
UNAVAILABLEPARTIALsubprocess_runner:spawn_errorThe dialog child process (osascript/zenity/notify-send/PowerShell) could not be spawned at all.
CONSTRAINEDPARTIALsubprocess_runner:deadlineThe runner’s deadline elapsed and the still-running dialog process was killed.
CONSTRAINEDPARTIALsubprocess_runner:cancelledThe run was cancelled before it finished.
CONSTRAINEDPARTIALsubprocess_runner:signaledThe child was killed by a signal, not a clean exit.
OKPARTIALsubprocess_runner:line_limitA deliberate bounded stop: the runner capped output at its line limit and killed a still-producing child — not a failure, but incomplete.

Where the data goes

Sample output

Windows — captured: windows Microsoft Windows NT 10.0.26200.0 x64 · bare-metal · 2026-09-07 · SYSTEM · leg-hash dc6c8e5f72d5

== action=notify
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=message_box
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=input
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=survey
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=set_dnd
dnd_enabled|true
status|ok
[result_status] UNDECLARED / UNKNOWN

macOS — captured: macos macOS 26.6.2 arm64 · bare-metal · 2026-09-07 · euid 501 (jsmith) · leg-hash dc6c8e5f72d5

== action=notify
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=message_box
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=input
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=survey
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=set_dnd
dnd_enabled|true
status|ok
[result_status] UNDECLARED / UNKNOWN

Linux — captured: linux Debian GNU/Linux 13 (trixie) aarch64 · container · 2026-09-06 · euid 0 · leg-hash dc6c8e5f72d5

== action=notify
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=message_box
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=input
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=survey
status|error|missing required parameter: title
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=set_dnd
dnd_enabled|true
status|ok
[result_status] UNDECLARED / UNKNOWN

Caveats and known gaps

  1. No sample shows a real dialog. All three captures ran with zero parameters, so every dialog action failed validation before reaching a platform leg. response, cancelled, answer_0–answer_4, question_count, dnd_duration_minutes, and dnd_expires_at have no observed real value anywhere in this repo — see the code paths in Data contract → Outputs for what would populate them.
  2. The macOS capture ran as an interactive user, not the production posture. It was taken at euid 501 (a logged-in human), not as the root LaunchDaemon the plugin actually ships under, so it cannot demonstrate the constrained “no reachable GUI session” path even though the descriptor declares macOS CONSTRAINED for all four dialog actions.
  3. Honest-status is a hard invariant, not a bug to “fix.” Every dialog leg treats a non-zero exit, a runner failure, or an unreachable session as status|unavailable/status|not_reachable, never a fabricated button. A pre-0.3.0 version wrongly reported response|ok for an undelivered macOS dialog; do not reintroduce a catch-all “unrecognized output → ok” branch on any platform.
  4. message_box’s status fires on every OS, not only macOS. It is set on a validation failure (missing title/message or a bad buttons value — all three OSes, confirmed by every sample), the macOS not_reachable sentinel, or a Linux zenity delivery failure — never only the macOS undeliverable path.
  5. executeRoles differs per action. notify/set_dnd allow endpoint-operator; message_box/input/survey are endpoint-admin-only. Do not assume one roles line covers every action of this plugin.

Source and tests