Verification record
Date: 2026-08-12
Environment: Windows 11 host, WSL workspace, Windows .NET SDK 10.0.302 at /mnt/d/dev/dotnet/dotnet.exe, x64 publish target.
This record distinguishes automated/process verification from visual checks. No result from the inherited /mnt/d/dev/AgentNotify documentation was accepted without rerunning it.
Pre-release security audit, and the repository gates rerun on macOS (2026-09-19)
The pre-release review of the dev tree is published as audit_2026-09-19.md:
what the product is trusted with, what leaves the machine, findings H1/M1-M3/L1-L5 with code
references, the controls that hold up, and a suggested fix order. Its open findings are tracked in
TODO.md and it is linked from SECURITY.md.
What was actually run, on the owner's Intel Mac against the merged dev tree, using the native SDK at
~/.dotnet/dotnet (10.0.401) through the repository scripts:
AGENTNOTIFY_DOTNET_EXE="$HOME/.dotnet/dotnet" ./scripts/build.sh -p:EnableWindowsTargeting=true
AGENTNOTIFY_DOTNET_EXE="$HOME/.dotnet/dotnet" ./scripts/test.sh
EnableWindowsTargeting=true dotnet package list --vulnerable --include-transitive
node /tmp/an-render/layout.mjs "http://127.0.0.1:49002/ui/#/overview" # headless Chrome over CDP
npm run typecheck --prefix site && npm exec --yes --package=node@24.21.0 -- \
node node_modules/next/dist/bin/next build --webpack
- Release build: succeeded, 0 warnings, 0 errors.
./scripts/test.sh: 1,263 passed, 0 failed, 0 skipped.- Dependency scan, direct and transitive against nuget.org: no known vulnerable packages in any of the 11 projects.
- WebUI:
/ui/#/overview,#/insights,#/quota,#/usage,#/router, and#/attentionrendered with live data and no uncaught exception, console error, or browser log error. The same CDP probe reported no horizontal overflow and no clipped element on any of the six, and the overview's four metric cards share one row above a 2x2 panel grid that links to the Attention, Usage, Quota, and Router pages. Screenshots:/tmp/an-render/p-*.png. - Documentation site: typecheck and static export succeeded, 31 prerendered pages including
/insights/,/router/, and/docs/install-with-agent/, with no/docs/index route — the topbar Documentation link points straight at the install guide.
Not verified: how any of it looks. The screenshots were captured but the tools available here cannot
open images, so spacing, contrast, density, and the rendered Pages output remain unchecked, as does
the Windows-side rendering. scripts/package.sh needs PowerShell on Windows, so packaging was not
run; no installer payload, embedded resource, or publish setting changed in this work. The gates were
run with a native SDK and EnableWindowsTargeting=true because the default scripts/*.sh path expects
a Windows dotnet.exe. No penetration test, fuzzing, or static analysis was performed, and the audit's
keychain finding (H1) was read from the code and the existing reproduction note rather than re-triggered.
Overview dashboard rendered in a real browser (2026-09-19)
The Overview page now summarizes notifications, 30-day usage and cost, the lowest live quota window, and the router instead of only the notification counts. Verified by running the freshly built broker on a scratch config directory and driving headless Chrome over the DevTools protocol:
dotnet exec agentnotifyd.dll --config-dir /tmp/an-render/config --port 48999 --no-desktop
# then, over CDP: navigate to /ui/#/overview, /ui/#/insights, /ui/#/quota and read document.body.innerText
All three pages rendered with live data (16.7 KB, 22.0 KB, and 28.5 KB of DOM), and the CDP session
reported no uncaught exception, console error, or browser log error on any of them. /ui/,
/ui/api/overview, /ui/api/usage?days=30, /ui/api/quota, /ui/api/router, and
/ui/api/router/summary?days=1 all answered 200 from that instance. The first headless run caught a
real defect — a missing } in the router panel's busy-target line, which stopped every page from
rendering — so the pass above is the post-fix run.
Not verified: how the page looks. No human or model inspected the screenshots (screenshot output at
/tmp/an-render/*.png was not viewed), so spacing, contrast, and density on a real display remain
unchecked, as does the Windows-side rendering. The macOS broker that was already running on the
owner's machine (PID 65275, port 47821, installed build) was left untouched; this used a separate
config directory and port.
macOS dev build installed and running (2026-09-19)
Built and installed the unreleased dev tree (v0.2.0-alpha.2 + 11 commits: native quota menu bar,
router switching) on the owner's Intel Mac — macOS 26.6.2, x86_64, Apple Swift 6.3.3 CLT. The host
does have a native .NET SDK at ~/.dotnet/dotnet (10.0.401); it is merely absent from PATH, which
corrects the "no .NET SDK" note in the section below. Command run:
AGENTNOTIFY_DOTNET_EXE="$HOME/.dotnet/dotnet" ./scripts/publish-cross.sh osx-x64
This produced artifacts/cross/agentnotify-osx-x64.tar.gz (CLI, broker, agentnotify-menubar,
SHA-256 checksums). The CLI and broker projects — and with them the Contracts/Core/API assemblies
carrying the router-switching change — have now been compiled (Release, osx-x64), though the
tests below still have not run. The three binaries were installed into ~/.local/bin
(adhoc re-signed), the existing but previously unloaded dev.agentnotify.broker LaunchAgent was
bootstrapped, and after 3 s: agentnotify health returned status: ok, and the broker had spawned
agentnotify-menubar --port 47821 as a live child process.
Not verified: the menu-bar status item's on-screen rendering and quota numbers (the process runs; the screen was not inspected), the full test suite on this host or on Windows, and a GitHub Actions release run producing the osx-x64 archive.
Router and Insights split out of Core; oversized files split (2026-09-19)
AgentNotify.Router (router, translation, connectors) and AgentNotify.Insights (usage, quota,
billing) are now separate assemblies that reference AgentNotify.Core; Core has no compile-time
dependency on either. Alongside that, the largest source files were split into partial-class files
by responsibility (WebUiEndpoints.*, RouterEndpoints.*, Program.*, HarnessInstaller.*,
LocalUsageService.*, RouterProxy.*, RouterConfigService.*, ProviderFormBuilder.*,
ChannelSettingsPanel.*). No behavior was intended to change; the route strings and method bodies
were moved verbatim.
Commands run on the owner's macOS host (Intel, macOS 26.6.2) with the native SDK at
~/.dotnet/dotnet (10.0.401):
dotnet build AgentNotify.slnx -c Release -p:EnableWindowsTargeting=true # 0 warnings, 0 errors
dotnet test tests/AgentNotify.Tests/AgentNotify.Tests.csproj -c Release # 1262 passed, 0 failed, 0 skipped
Not run: ./scripts/build.sh, ./scripts/test.sh, and ./scripts/package.sh (they invoke the
Windows SDK at /mnt/d/dev/dotnet/dotnet.exe, absent on this host), any Windows launch, any
browser session, and any visual check of the Windows UI or the macOS menu bar. The router/insights
split is therefore compiled and test-covered, but unexercised at runtime.
Router switching and effort mapping: unverified (2026-09-19)
Ordered/sticky/round-robin switching, one-way native-Claude fallback, and editable per-target effort
mappings/defaults were merged to dev without the required gates. This work was done on a macOS
host with no .NET SDK: ./scripts/build.sh, ./scripts/test.sh, and ./scripts/package.sh all stop
at the configured Windows SDK /mnt/d/dev/dotnet/dotnet.exe, which does not exist there. Nothing in
this change has been compiled, tested, packaged, or seen in a browser.
What was actually checked: a line-by-line source audit, node --check on every changed web module,
and git diff --check. The audit found and fixed five defects before merge — a
System.Text.Json.Nodes reassignment that would have thrown on any same-wire effort rewrite, a
cooldown that would have made native Anthropic errors stop passing through when no fallback is
configured, a failover gate that would have dropped cooldowns for single-target upstreams, Claude's
five-step map being applied to OpenAI-wire clients (silently downgrading Codex's own effort), and a
nullable-reference warning in target ordering.
Required before this is trusted: run all three scripts on the Windows/WSL toolchain, exercise the
Settings and Effort mapping pages in a browser, and drive a real Claude Code session through a
scripted exhaustion to confirm the silent switch and the mapped effort vocabulary. New automated
coverage exists but has never executed: native-Claude fallback ordering (RouteResolverTests),
round-robin start rotation and native exhaustion with effort mapping (RouterProxyTests), and
output_config.effort decoding plus family inference (RouterTranslationTests).
Verified
Release build
Command:
./scripts/build.sh --no-restore
Result:
Build succeeded.
0 Warning(s)
0 Error(s)
All seven projects built: Contracts, Core, API, WPF App, CLI, Setup, and Tests.
Automated tests
Command:
./scripts/test.sh --no-restore
Result:
Passed: 597
Failed: 0
Skipped: 0
Coverage includes validation, lifecycle transitions, config recovery/round-trip, custom type normalization/definitions/default priority, managed sound import/sanitization/deduplication and sound profile resolution, SQLite notification and delivery migrations/CRUD/outbox/attempts, atomic concurrent outbox claiming, DPAPI/AES-GCM secret round-trip and redaction, provider input bounds, credential-preserving/merging/removal updates, validated route service updates, idempotent route materialization, dispatcher success/timeout/retry/dead-letter/recovery, sanitized diagnostics and bounded test-send, webhook templating/headers/HMAC/idempotency/status policy/private-address controls, SMTP message projection/authentication/TLS modes/recipient normalization/injection and address policy/status propagation, Telegram encrypted destination projection/plain-text limits/fixed endpoint JSON/status policy, Discord encrypted URL validation/mention suppression/Markdown escaping/content limits/thread/status policy, Slack/GovSlack URL validation/control-sequence suppression/threading/bounded response/status policy, Teams/Zoho Cliq/Google Chat URL and payload hardening, Mattermost self-hosted destination/acknowledgement/mention controls, Matrix/ntfy/Gotify/Pushover/Pushbullet/Twilio SMS/direct WhatsApp/Twilio WhatsApp/MQTT encrypted destination and acknowledgement contracts, routing/retry policy, authentication, health, create/list/get/patch/dismiss, malformed/null JSON, callback isolation including outbound queue failure, keyed deduplication including concurrent calls, CLI connection failure, bare --unresolved, hyphenated and custom notification type parsing, invalid identifier rejection, and version output.
Delivery security foundation
Windows tests successfully round-trip a dpapi-user:v1 envelope with current-user scope. Repository tests persist a provider secret, confirm the stored value is encrypted and the public profile omits both plaintext and ciphertext, decrypt only through the delivery-only service, preserve credentials on non-secret edits, reject malformed profile shapes and future schema versions, atomically claim a single item across concurrent workers, record schema migrations v1/v2, and exercise the route/outbox/attempt lifecycle. Network adapters are deliberately not active in this milestone.
Durable dispatcher
Schema migration v2 adds a unique notification/route key so repeated coordination cannot duplicate delivery. Automated tests prove route filtering, atomic/idempotent enqueue, successful delivery, secret availability only at the adapter boundary, timeout-to-retry, retry exhaustion to dead-letter, interrupted-claim recovery, exact redacted diagnostics, bounded test-send, and isolation of outbox persistence failure from the already-committed local API response. Adapters perform no work without an explicitly enabled provider and matching route.
Generic HTTPS webhook
Contract tests use an in-memory HTTP handler and verify JSON template expansion, encrypted endpoint/secret-header use, HMAC-SHA-256 calculation, timestamp and idempotency headers, status retry classification, and rejection before send for HTTP, URI credentials/fragments, missing secrets, newline injection, loopback, link-local metadata, multicast, private, benchmarking, and documentation addresses. The production handler disables redirects, cookies, ambient proxies, connection reuse, and response-body reads; its connect callback resolves and validates all destination IPs immediately before opening the socket. No external endpoint was contacted during automated verification.
Channel management UI
The native Channels tab compiles with provider, route, and diagnostics sub-tabs. Service tests prove secret edits merge without plaintext UI readback, explicit optional-secret removal, route filter normalization, creation-time preservation, and missing-provider rejection. The UI starts new profiles/routes disabled, keeps stored password fields blank, requires explicit private-network and message-content consent, confirms cascade deletion, and calls the bounded dispatcher test path. Visual layout, keyboard flow, screen-reader behavior, and a real external webhook test remain human verification items.
SMTP email
Contract tests with an injected sender verify strict STARTTLS and TLS-on-connect selection, encrypted username/password projection, recipient normalization/deduplication, invalid-address and CRLF-injection rejection, private/link-local destination rules, subject/body construction, route message redaction, and sanitized transport-result propagation. MailKit 4.17.0 compiles into the self-contained Windows target. No SMTP credentials were assumed and no external mail server was contacted; a real-server interoperability test remains explicitly unverified.
Telegram Bot
Contract tests verify encrypted bot-token/chat projection, strict token/chat/topic validation, default content protection, silent/topic options, route message redaction, 4096-character surrogate-safe truncation, plain JSON without a markup mode, fixed official endpoint construction, token omission from the JSON body, link-preview suppression, malformed-response handling, and retry/permanent HTTP classification. The production handler pins connections to public DNS results for api.telegram.org and disables redirects, cookies, and ambient proxies. No Telegram token or destination was assumed and no external message was sent; real-bot interoperability remains explicitly unverified.
Discord incoming webhook
Contract tests verify encrypted webhook use, strict official host/port/path/id/token/query validation, versioned API paths, optional thread routing, wait=true, display-name projection, route message redaction, Markdown escaping, complete mention suppression, 2000-character surrogate-safe truncation, token omission from JSON, network sanitization, and retry/permanent HTTP classification. The production handler pins connections to public DNS results for discord.com and disables redirects, cookies, proxies, decompression, and response-body reads. No webhook credential was assumed and no external Discord message was sent; real-webhook interoperability remains explicitly unverified.
Slack incoming webhook
Contract tests verify encrypted webhook use, strict Slack/GovSlack host/port/path/token/query validation, optional thread timestamp, mrkdwn and automatic link-name suppression, control-sequence encoding, route message redaction, 4000-character surrogate-safe truncation, token omission from JSON, bounded ok acknowledgement handling, oversized/malformed success handling, network sanitization, and retry/permanent HTTP classification. The production handler pins connections to public DNS results for the selected official hook host and disables redirects, cookies, proxies, and decompression. No webhook credential was assumed and no external Slack message was sent; real-webhook interoperability remains explicitly unverified.
Microsoft Teams Workflows
Contract tests verify encrypted signed-URL use, current global Power Platform host/path/query validation, rejection of HTTP/foreign/unsigned/retired shapes, Adaptive Card 1.2 envelope construction, route message redaction, Markdown and mention-tag escaping, bounded surrogate-safe text, token omission from JSON, network sanitization, and retry/permanent HTTP classification. The production handler validates every public DNS result before connecting and disables redirects, cookies, proxies, decompression, and response-body reads. No workflow credential was assumed and no external Teams card was sent; real-workflow and sovereign-cloud interoperability remain explicitly unverified.
Zoho Cliq
Contract tests cover all nine documented regional hosts, encrypted URL use, channel-name/channel-ID/bot path validation, strict token/query rules, route redaction, Markdown escaping, 5000-character surrogate-safe truncation, token omission from JSON, and retry/permanent HTTP classification. The production handler validates every public DNS result before connecting and disables redirects, cookies, proxies, decompression, and response-body reads. No Cliq credential was assumed and no external message was sent; real-webhook interoperability remains explicitly unverified.
Google Chat
Contract tests verify encrypted webhook use, strict official host/port/path/key/token validation, optional thread creation/reuse with both documented reply policies, route message redaction, formatting and user/everyone mention neutralization, serialized UTF-8 payload enforcement below 32,000 bytes, cancellation-aware write spacing, credential omission from JSON, network-error sanitization, and retry/permanent HTTP classification. The production handler validates every public DNS result for chat.googleapis.com immediately before connecting and disables redirects, cookies, proxies, decompression, and response-body reads. No Google Chat webhook credential was assumed and no external message was sent; real-webhook and human Settings interoperability remain explicitly unverified.
Mattermost
Contract tests verify encrypted URL use, HTTPS and terminal hook-path validation, self-hosted subpaths and custom ports, rejection of query/fragment/user-info and malformed tokens, explicit private-network consent while always rejecting link-local metadata, optional silent delivery, route redaction, Markdown and Mattermost/Slack-compatible mention neutralization, 16,383-character surrogate-safe truncation, bounded ok acknowledgement handling, oversized/malformed success handling, network sanitization, and retry/permanent HTTP classification. The production handler validates every resolved address immediately before connecting and preserves platform TLS hostname/certificate validation. No Mattermost server or credential was assumed and no external message was sent; real-server and human Settings interoperability remain explicitly unverified.
Matrix
Contract tests verify HTTPS homeserver and private-network policy, encrypted access-token/room-ID use, bearer-header authentication, percent-encoded room targeting, stable idempotent transaction paths, plain m.text content, explicit empty mention metadata and legacy mention neutralization, route redaction, 48 KiB serialized UTF-8 bounds, bounded event-ID acknowledgement parsing, and retry/permanent status policy. The production handler validates every resolved address before connecting and preserves platform TLS validation. End-to-end encrypted rooms are deliberately unsupported. No real homeserver or credential was assumed; real-server and human Settings smoke remain unverified.
ntfy
Contract tests verify hosted/self-hosted HTTPS policy, private-network consent, encrypted topic and tk_ token handling, bearer authentication, exclusion of credentials and topic from the URL, explicit anonymous-topic consent, priority/tag mapping, route redaction, stable sequence IDs, 4096-byte UTF-8 truncation, bounded message acknowledgement parsing, and retry/permanent status classification. The production handler validates every resolved address before connecting and preserves platform TLS validation. No ntfy server, topic, or credential was contacted; real-server and human Settings smoke remain unverified.
Gotify
Contract tests verify self-hosted HTTPS policy, reverse-proxy subpaths/custom ports, private-network consent, encrypted application-token projection through X-Gotify-Key, token exclusion from URLs, title/message/priority mapping, route redaction, forced plain-text display extras, omission of remote/action extras, bounded message-ID acknowledgement parsing, and retry/permanent status classification. The production handler validates every resolved address before connecting and preserves platform TLS validation. No Gotify server or credential was contacted; real-server and human Settings smoke remain unverified.
Pushover
Contract tests verify the exact official HTTPS message endpoint, URL-encoded encrypted application/user keys, optional encrypted device restriction, built-in/custom sound projection, route redaction, plain-text-only fields, low/normal/high/critical mapping, explicit emergency retry/expiry fields and mandatory receipt acknowledgement, 1024/250 Unicode-scalar message/title limits, bounded response parsing, provider rejection, permanent 4xx/quota policy, retryable 5xx/network failures, and secret omission from URLs. The production handler permits at most two pooled connections and validates every public DNS result for api.pushover.net before connecting. No Pushover account or credential was contacted; real-account, emergency acknowledgement, receipt polling, and human Settings smoke remain unverified.
Pushbullet
Contract tests verify the exact official HTTPS Pushes endpoint, encrypted Access-Token header, token omission from URLs and JSON, all-devices/device/channel/email targeting with exactly one optional target, explicit quota consent, stable per-outbox guid, route redaction, plain note-only projection, 32 KiB serialized request bound, bounded active-note acknowledgement, rate-limit/server retry classes, permanent credential/target classes, and network-error sanitization. The production handler permits at most two pooled connections and validates every public DNS result for api.pushbullet.com before connecting. No Pushbullet account, quota, token, or destination was contacted; real-account and human Settings smoke remain unverified.
Twilio SMS
Contract tests verify the exact Account-scoped Messages endpoint; Basic authentication with an encrypted API Key SID/secret or Account SID/Auth Token; encrypted one-recipient and sender allowlists; E.164, AC/SK/MG/SM SID validation; provider-mode transitions; paid-send consent; critical-by-default priority blocking with explicit test-send bypass; 6–36,000-second validity; route redaction; GSM-7 extension-septet and UCS-2 surrogate-safe one-segment bounds; smart encoding; content discard/address obfuscation; omission of media/callback/risk-disable fields; bounded queued/accepted acknowledgement; immediate rejection handling; and best-effort no-replay 408/cancellation/network/5xx/malformed-response policy with only 429 retries. The production handler allows one connection and validates every public DNS result for api.twilio.com. Process-termination replay is not eliminated. No Twilio credential, phone number, Messaging Service, or paid send was used; real-account, delivery-status, pricing, compliance, and human Settings smoke remain unverified.
WhatsApp Cloud API
Contract tests verify the exact versioned graph.facebook.com/{version}/{phone-number-id}/messages endpoint, Bearer-token placement, encrypted one-recipient/phone-ID/token projection, E.164 and identifier validation, explicit opt-in/template-approval/paid-send gates, critical-by-default priority blocking with deliberate test-send bypass, approved-template name/language constraints, zero-to-five ordered allowlisted body variables, duplicate/unknown mapping rejection, route-redaction arity preservation, Unicode-scalar bounds, 32 KiB request/response limits, strict single-wamid acknowledgement, redirect/client-error classification, and best-effort no-replay 408/cancellation/network/5xx/malformed-response policy with only 429 retries. The production handler allows one connection and validates every public DNS result for graph.facebook.com. Process-termination replay is not eliminated. No Meta credential, business account, phone number, recipient, template, or paid send was used; real-account, template interoperability, delivery status, billing, compliance, and human Settings smoke remain unverified.
Twilio WhatsApp
Contract tests verify the exact Account-scoped Messages endpoint; Basic authentication with an encrypted API Key SID/secret or Account SID/Auth Token; encrypted one-recipient, Messaging Service, and Content Template allowlists; E.164 and AC/SK/MG/HX/SM SID validation; whatsapp: recipient projection; explicit opt-in/template/text-only/paid acknowledgements; critical-by-default priority blocking with deliberate test-send bypass; queue validity; zero-to-five ordered numbered Content variables; duplicate/unknown/null mapping rejection; route-redaction arity preservation; Unicode-scalar bounds; omission of free-form body/sender/media/callback fields; content discard/address obfuscation; bounded accepted/queued/read acknowledgement; immediate rejection handling; and best-effort no-replay 408/cancellation/network/5xx/malformed-response policy with only 429 retries. The production handler allows one connection and validates every public DNS result for api.twilio.com. Process-termination replay is not eliminated. No Twilio/Meta credential, account, recipient, Messaging Service, Content template, or paid send was used; real-account, delivery status, billing, compliance, and human Settings smoke remain unverified.
MQTT 5
Contract tests verify fixed encrypted topic and username/password/certificate-thumbprint projection; username/password, mTLS, combined, and separately acknowledged anonymous modes; strict ASCII broker host, port, stable client ID, topic, payload, expiry, and opaque identifier bounds; wildcard/system/empty-level topic rejection; route-redacted JSON preservation; QoS 0 terminal ambiguity; QoS 1/2 duplicate-risk acknowledgement and retry semantics; sanitized publisher outcomes; explicit private-network propagation; and rejection of mixed public/private DNS before any connection. Production-options tests prove the transport uses a validated IPEndPoint while retaining the configured broker host as the TLS target, and prove TLS/trust/chain/revocation bypass flags remain disabled. Source and compile review verify non-retained JSON, MQTT 5 content/expiry fields, stable user properties, clean sessions, and provider reason-code classification. No broker, credential, topic, certificate, or external network endpoint was used; real-broker interoperability, mTLS selection, broker ACLs, QoS behavior under connection loss, and human Settings smoke remain unverified.
Skill
distribution/agentnotify was initialized using the official skill initializer. quick_validate.py reports:
Skill is valid!
The installed SKILL.md was also checked for its expected name: agentnotify metadata.
Packaging
Command:
./scripts/package.sh
Result:
artifacts/AgentNotifySetup.exe: approximately 193 MB, one self-contained file.- Embedded tray payload: approximately 89 MB.
- Embedded CLI payload: approximately 41 MB.
- Installer payload/resource validation passed.
Latest locally packaged artifact SHA-256:
2000b536dc8eac4b72821d0ac6df7b79cb258f4ce7b2f0bfb7456a4df3d7e78b AgentNotifySetup.exe
Regenerate the checksum after any rebuild because it necessarily changes with the binary.
Published GitHub prerelease
The first hosted prerelease was published from tag v0.0.1-alpha.1 after the release workflow verified the exact version match, SemVer-style metadata, Windows Release build, 597-test suite, packaging, and skill validity. GitHub Actions run 31566620009 completed successfully. The GitHub prerelease contains AgentNotifySetup.exe, SHA256SUMS.txt, and SKILL.md.
The prerelease is intentionally not the mature v1.0.0 milestone. The release workflow marks any tag containing a hyphen as a prerelease; stable promotion to v1.0.0 additionally requires a tested main release commit, signing, human checks, and release review.
The portable scripts/package.ps1 implementation and WSL wrapper generated a matching artifacts/SHA256SUMS.txt. The static Pages source built locally into _site, and the hosted CI, Pages, and release workflows have now executed successfully on GitHub.
Custom notification type smoke
The packaged tray and CLI accepted --type deployment-waiting, normalized and persisted deployment_waiting, returned the same identifier through get, displayed a fallback toast, and resolved the row. The Settings definition editor and configured accent/label rendering are compiled and covered by configuration tests but still require a human visual pass.
Sound verification boundary
Automated tests prove WAV/MP3 extension and size validation, safe content-addressed import, duplicate reuse, managed-path resolution, configuration sanitization, and per-type fallback. WPF media playback compiles and is isolated from the API path, but audible playback/preview remains a human check because no user-selected audio file was assumed or modified during automation.
Windows version-resource inspection confirmed for setup, tray, and CLI:
Product: AgentNotify
Company: Kabani Tech Private Limited
File version: 0.0.1.0
Authenticode status is NotSigned, as documented.
Published tray/API/CLI smoke
The actual packaged AgentNotify.Tray.exe and agentnotify.exe were copied to a temporary directory on the Windows D: filesystem and executed. Verified:
- tray/API process started;
- authenticated
healthreturned version0.0.1-alpha.1, APIv1, and the running PID; input-requiredCLI spelling was accepted and serialized asinput_required;- create returned an active notification ID;
- a second create with the same key returned the same ID and updated content;
list --unresolved --agent smoke-testreturned the row;- unauthenticated
/v1/healthreturned HTTP401; - malformed JSON returned HTTP
400; - resolving the ID removed it from the unresolved query; and
- launching the tray binary again left exactly one
AgentNotify.Tray.exeprocess.
The smoke notification was resolved, the process was stopped, and the temporary binaries were removed.
Direct Linux curl in this WSL configuration could not reach the Windows loopback listener; Windows curl.exe and the Windows CLI succeeded. That is a WSL networking environment detail, not an API bind failure.
Installer window smoke
The packaged installer was launched from the Windows D: filesystem. Process inspection confirmed:
Main window title: Install AgentNotify
Main window handle: nonzero
Responding: true
The process was then stopped without installing, and the temporary copy was removed.
Silent install/uninstall smoke
The packaged installer was run with explicit license acceptance into a temporary D: directory:
--silent --accept-license --install-dir <temporary>\AgentNotify --no-startup
Verified installed outputs:
AgentNotify.Tray.exeagentnotify.exe- generated
GettingStarted.htmlwith no unreplaced skill placeholder - valid
SKILL.md LICENSE.txtTHIRD_PARTY_NOTICES.txtuninstall.ps1
The installed CLI returned agentnotify 0.0.1-alpha.1. The Windows uninstall registry entry reported AgentNotify, version 0.0.1-alpha.1, and publisher Kabani Tech Private Limited. Running the registered uninstall script removed the known files and uninstall registration. The temporary test directory was then removed.
Verified by implementation and compilation, not visually inspected
- Tray menu includes Notification Center, Getting Started, Copy/Download skill, Pause, Start with Windows, logs, and Exit.
- The supplied multi-resolution
assets/branding/an.icois compiled into app/setup resources and executable icon metadata. - Toast HWND receives
WS_EX_NOACTIVATE; XAML also setsShowActivated=false,Focusable=false, andTopmost=true. - Toast manager queues overflow and uses foreground-monitor work area with DPI conversion.
- Dashboard status changes feed back into the toast manager.
- Active attention rows are queried and restored at startup.
- Normal setup finish launches the tray app and offline guide when the default checkboxes remain enabled.
- Native Settings window opens from the tray and validates port, retention, stack size, placement, lifetimes, pause/DND, and the initial sound toggle.
These were code-reviewed and built, but the automation did not capture the user’s desktop because doing so could expose unrelated private screen content.
Remaining human checks
- Inspect toast/dashboard/installer visual layout at 100%, 150%, and 200% scaling.
- Verify toast position with taskbars on each edge and mixed-DPI multi-monitor arrangements.
- Type continuously in an editor while toasts arrive and confirm focus/caret never moves.
- Click every tray/menu/toast/dashboard action in a clean installed profile.
- Complete a normal interactive install, confirm the browser guide opens on Finish, sign out/in to test startup, then uninstall through Windows Settings.
- Verify screen-reader naming, keyboard navigation, contrast, and reduced-motion behavior.
- Repeat install/uninstall under a second clean Windows user.
Release blocker for public distribution
The app is functional but unsigned. Obtain an Authenticode certificate for Kabani Tech Private Limited, sign the two payload binaries before embedding them, sign the final installer last, timestamp all signatures, and publish a fresh SHA-256 checksum.
Documentation audit — 2026-08-12
This documentation-only branch reconciled the README, roadmap, feature backlog, development handoff, outbound-channel index, and GitHub Pages source with the merged MQTT milestone (e20e1c4). It corrected the stale 87-test baseline, recorded the 597-test state and current local installer checksum, documented all 18 implemented outbound adapters, and marked AWS SNS as paused/not implemented. No application source, project file, generated artifact, or distributable skill content was changed by this audit.
Documentation gates for this branch:
git diff --check— required before commit../scripts/build.sh— full Windows Release build, required even for documentation branches by repository workflow../scripts/test.sh— full automated suite, required before merge../scripts/build-site.sh— static documentation site build.quick_validate.py distribution/agentnotify— skill remains valid; the skill was not modified.
Settings readability and window chrome — 2026-08-12
The settings surfaces previously relied on WPF's default control templates, which paint system-black text. Against the dark settings background this made labels, checkbox captions, tab headers, and list contents effectively unreadable, and the empty provider/route lists rendered as blank white boxes.
src/AgentNotify.App/Theme.xaml now restates every control the settings
surfaces use. It is merged into SettingsWindow only, so toast and
notification-center visuals are deliberately untouched.
Automated and mechanical checks performed:
./scripts/build.sh— full Windows Release build, 0 warnings, 0 errors../scripts/test.sh— 597 passed, 0 failed.- Theme load smoke test: a throwaway WPF harness merged
Theme.xamlthrough the samepack://application:,,,/Theme.xamlURI used bySettingsWindow, then instantiated and laid out one of every themed control type. The dictionary resolved with 28 top-level resources and every implicit template applied without error. This proves the dictionary resolves and the templates are structurally valid; it does not evaluate visual appearance.
Not yet performed — still requires a human at a Windows desktop:
- Visual confirmation that every settings tab is legible, including the Channels tab and the provider-type dropdown in its opened state.
- Confirmation that the Notification Center header no longer shows a second close affordance next to the native title-bar buttons.
- Confirmation that the new empty-state hints appear on the Providers and Routes lists before any profile exists, and disappear once one is saved.
- Contrast measurement against WCAG AA for the muted
#9CA3AFand#7C8699text on the#111827panel background.
Built-in notification tones — 2026-08-12
Four original tones (chime.wav, ping.wav, alert.wav, knock.wav) were
synthesised for this project and released under CC0, so the app ships with
usable sound out of the box and nothing third-party is redistributed. They are
embedded in AgentNotify.Tray and seeded into the managed sound directory on
first construction of NotificationSoundService; seeding is idempotent and
never overwrites an existing file of the same name. Users can still import
their own WAV/MP3 files, which is unchanged.
Automated and mechanical checks performed:
./scripts/build.sh— 0 warnings, 0 errors../scripts/test.sh— 601 passed, 0 failed (597 previous plus 4 newManagedSoundStore/BuiltInTonestests)../scripts/package.sh— installer rebuilt; embedded resources changed, so packaging was required. SHA-256bd7c8ac93cea2369438ec857566dd6d5e5c3d3825228ac1f03c56dc6056c446e.- Embedded-resource audit: the built
AgentNotify.Tray.dllexposes exactlyAgentNotify.Resources.Tones.{chime,ping,alert,knock}.wav, matching the namesNotificationSoundServicelooks up. Each was extracted from the assembly through the reflection path the app itself uses and confirmed to carry a validRIFF/WAVEheader and to be byte-identical (SHA-256) to the source file inassets/tones/. RefreshSoundTypesalways forces a selection, so the per-type tone picker cannot be driven with a null type.
Not yet performed — still requires a human at a Windows desktop:
- Listening to each of the four tones through Preview to judge loudness balance and whether they are pleasant at the default 0.8 volume.
- Confirming the tones are seeded into the sound directory on a genuinely clean per-user profile after a real install.
- Confirming the built-in tone dropdowns stay in sync with the file boxes when switching between built-in, custom, and cleared selections.
Cross-platform Phase 1 and 2 (feature/cross-platform-core)
What was actually run
WSL is a real Linux x64 userland, so a linux-x64 self-contained publish of agentnotifyd and
agentnotify runs natively here. The Linux broker was therefore exercised for real, not only
compiled.
./scripts/build.sh— 0 warnings, 0 errors, including the two new projects../scripts/test.sh— 619 passed, 0 failed (601 previous plus 18 new cross-platform and desktop notifier tests).- Cross-compilation of
AgentNotify.Cliforlinux-x64,linux-arm64,osx-x64, andosx-arm64succeeded, as didAgentNotify.Hostforlinux-x64. - The published Linux
agentnotifybinary is an ELF executable, runs in WSL, reports its version, extracts and loads the nativelibe_sqlite3.so, and fails with a clean message and exit code 1 when no broker is listening. agentnotifydwas started in WSL against an isolatedHOME. It reported the key-file protection and console notifier, created its data directory, and served the API.- End-to-end through the Linux CLI against the Linux broker:
healthreturnedok;sendcreated aninput_requirednotification and printed the DTO; a secondsendwith the same--keyupdated the existing row in place rather than creating a duplicate;listshowed one row;resolvemoved it toresolved. The console notifier printed the attention line. - File permissions on real Linux files: the data directory is
drwx------, andconfig.json,agentnotify.db, andsecret.keyare-rw-------. - Single instance: a second
agentnotifydagainst the same data directory refused to start with a clear message and exit code 1, and started successfully once the first had exited. SIGTERMhandling: the broker printedStopping…, loggedagentnotifyd stopped, and exited within one second with a normal exit status.
Three defects that only running it exposed
- Local state could land in the working directory. On Unix
Environment.GetFolderPath(LocalApplicationData)returns an empty string when the base directory does not exist yet, which is the normal state of a fresh account. The empty string combined to the relative pathAgentNotify, so the first run wroteconfig.json— containing the local bearer token — plussecret.keyand the history database into whatever directory the broker was started from. In this repository that was the checkout itself.DefaultConfigDirnow resolves throughSpecialFolderOption.Create, thenXDG_DATA_HOME, then$HOME, and always returns an absolute path. Covered by a regression test. SIGTERMwas ignored entirely. ThePosixSignalRegistrationobjects were created and discarded. Once finalized the handler is unhooked, so the broker neither shut down nor died and could only be stopped withSIGKILL— it would have been unmanageable under systemd or launchd. The registrations are now held for the lifetime of the process.- Shutdown could hang indefinitely.
DeliveryDispatcher.StopAsyncis unbounded; the WPF app had always bounded it with a two-secondWait. The host now bounds the dispatcher stop, bounds overall shutdown at fifteen seconds, and exits immediately on a second signal.
Not verified
- No
notify-sendbinary and no graphical session exist here, so the Linux desktop notifier has never displayed a notification. Only the console fallback was observed. - No macOS machine, so neither macOS notifier path nor the Keychain key store has ever run.
- No
secret-tool, so the Linux Secret Service key store has never run. Only the key-file fallback was exercised. - No ARM64 hardware, so the
linux-arm64andosx-arm64binaries are compiled but never executed. - The Windows tray application was rebuilt and its tests pass, but no WPF visual check was
performed after the adapter-factory and secret-protector-factory refactor. On Windows the
factory returns the same
DpapiSecretProtectorthe app constructed directly before, and the adapter list and disposal set are unchanged, but that is reasoning, not observation.
Cross-platform Phase 3 (feature/cross-platform-release)
./scripts/publish-cross.sh linux-x64producedagentnotify-linux-x64.tar.gzand aSHA256SUMS.txt. The archive was extracted and both binaries inside it ran and reported version0.0.1-alpha.1, so the released artefact — not just a build output — is known to work on Linux../scripts/publish-cross.sh linux-x64 osx-arm64produced both archives.- Both new shell scripts pass
sh -n/bash -n.shellcheckis not installed here, so no lint beyond a syntax check was run. - All four workflow files parse as YAML.
Not verified:
- No GitHub Actions run has happened. The Linux and macOS CI jobs and the release upload job are written but have never executed; the repository has not been pushed. Until a run is green, "builds and tests on macOS" is an expectation, not a result.
install.shhas never been run end to end, because it downloads from a GitHub release that does not contain these archives yet. Its platform detection, checksum verification, and failure paths are unexercised.- The
win-x64,linux-arm64,osx-x64, andosx-arm64archives have not been produced in a full run of the script, only the two above.
Portable file-name sanitizing (fix/portable-filename-sanitizing)
The first Linux and macOS CI run failed on ConfigTests.SoundProfiles_AreSanitizedAndResolvePerTypeOverride,
identically on both runners. This was a real defect rather than a test artefact.
Path.GetFileName is platform-dependent. Windows treats \, /, and the volume separator as
boundaries; on Linux and macOS a backslash is an ordinary file-name character. A configured sound
of C:\outside\global.MP3 therefore passed through sanitizing unchanged on Unix, so the invariant
that a configured sound is a bare file name inside the managed sounds directory did not hold there.
Configuration is portable between machines, so the same config.json must normalize identically on
every platform. SafeFileName.Last now strips both separators and any volume prefix using plain
string operations, and the three call sites in AgentNotifyConfig and ManagedSoundStore use it.
Verified:
- 628 tests pass on Windows (619 previous plus 9 covering platform-independent normalization).
- On real Linux: a
config.jsonhand-written with"defaultSoundFile": "C:\\outside\\global.MP3"and"input_required": "..\\attention.wav"was loaded by the Linuxagentnotifyd, which rewrote the file withglobal.MP3andattention.wav. This exercises the fix through the running broker rather than through the test host.
Not verified until the next CI run: that the previously failing test now passes on the macOS runner. No Mac is available here.
Test-harness timeout (test/stabilize-concurrency-timeout)
The Windows CI job failed once on DedupTests.ConcurrentSameKey_CreatesOneActiveNotification with
an HttpClient.Timeout of five seconds elapsing. The identical commit content passed on the main
push and failed on the dev push minutes later, so this was timing on a shared runner rather than
a defect: the test issues twelve concurrent same-key posts, keyed creation is deliberately
serialized inside the broker, and a slow two-core runner can hold the last request in that queue
past five seconds.
The fixture's request timeout is now thirty seconds. It exists to stop a hung request from wedging the suite, not to assert throughput, and thirty seconds still catches a genuine hang. No assertion was weakened and the concurrency of the test is unchanged, because twelve simultaneous same-key requests are exactly the race worth exercising.
First green Linux and macOS CI (fix/ci-smoke-keyring-assumption)
The second CI run passed the full test suite on both ubuntu-latest and macos-latest, confirming the file-name sanitizing fix. Remaining results from that run:
- ubuntu-latest passed the broker smoke test outright: the headless broker started,
/v1/healthreturnedok, a notification was created through the API, a second post with the same key returned the same id,config.jsonandagentnotify.dbwere mode600, and the broker stopped cleanly onSIGTERM. This is independent confirmation, on a machine that is not WSL, of the behaviour recorded earlier. - macos-latest reported
secrets: macOS login keychain. The Keychain key store therefore runs correctly on a real Mac, which was previously listed here as unverifiable. - The macOS smoke step failed on a defect in the workflow, not in the product: it required
secret.keyto exist, but that file is only written when no platform keyring is available. macOS found its keychain and correctly wrote no key file. The step now checkssecret.keyonly when present and asserts the broker reported whichever protection it chose.
Still unverified after this run:
- Neither desktop notifier has displayed a notification. The CI smoke test runs the broker with
--no-desktop, and the runners have no graphical session, so the console backend is used by design. - The Linux Secret Service store has still never run;
secret-toolis absent from the runners, so Linux exercised the key-file fallback. - ARM64 binaries have still never executed.
Confirmed green run
Run 31607106390 passed both jobs. The broker smoke test completed end to end on each runner:
| Runner | Secret protection chosen | Smoke test |
|---|---|---|
| ubuntu-latest | key file (no secret-tool installed) |
health, create, keyed dedup, 0600 state, clean SIGTERM |
| macos-latest | macOS login keychain, no key file written | health, create, keyed dedup, 0600 state, clean SIGTERM |
Both platform branches of the secret-protection selection are therefore exercised by CI, and the
headless broker is confirmed to start, serve /v1, deduplicate by key, protect its local state,
and stop on SIGTERM on real Linux and real macOS.
Release 0.0.2-alpha.1 preparation (chore/release-0.0.2-alpha.1)
- Version bumped to
0.0.2-alpha.1inDirectory.Build.props, with numeric assembly and file metadata at0.0.2.0. The version is centralized there; no other file hard-codes it. ./scripts/build.sh0 warnings/0 errors,./scripts/test.sh628 passed,./scripts/package.shrebuilt the installer. Installer SHA-256:74877688e5bd6e26c0146b8e68b750ebe695e21ae51b01e57dee8eec7a2d208c.scripts/release-notes.shwas run locally forv0.0.2-alpha.1. It produced the changelog section, prerelease banner, install instructions, checksum guidance, documentation links, and the compare range, and it exits non-zero for a version with no changelog section.- Fixed a defect in the release workflow before it could ship: the Windows job and the portable job
both uploaded an asset named
SHA256SUMS.txt, so the second would have replaced the first rather than sitting beside it. The portable file is nowSHA256SUMS-portable.txt. - The release checkout now uses
fetch-depth: 0; the default shallow checkout has no earlier tags, so the compare link would have been omitted.
Not verified until the tag is pushed: the release workflow has never run with these changes. The
cross-platform-assets job in particular has never executed at all, so the portable archives have
never been produced by CI or attached to a release.
Release v0.0.2-alpha.1 outcome
The tagged run (31609644846) published the release with the written description, the Windows
installer, its checksum, and SKILL.md. The cross-platform-assets job built all five archives
successfully but failed at the upload step with no matches found for 'checksums'.
The cause was a shell quoting defect in the workflow, not in the archives: gh release upload
takes file#Display label arguments, and the label was written unquoted, so the shell split
SHA256SUMS-portable.txt#SHA-256 checksums for the portable archives into six words and gh looked
for files named checksums, for, and the. Every argument is now quoted and each archive has a
descriptive label. The fix was checked by reproducing the argument construction in bash and
confirming it yields two arguments rather than eight.
Because the tag and its release already existed, the archives for this release were built locally
with ./scripts/publish-cross.sh and uploaded to the existing release rather than retagging. The
next tagged release will exercise the corrected workflow path, which has still never run to
completion.
Publishing the v0.0.2-alpha.1 archives
scripts/publish-cross.sh hard-depended on zip for the Windows archive, which is absent from
many minimal Linux and WSL installs, including this one. The CI runners have it, which is why the
job built all five archives while the same script failed locally. The script now falls back to
Python when zip is missing and checks its tools before starting a multi-runtime publish.
Because the local gh CLI was authenticated as an account with read-only access to the repository,
GitHub answered the asset upload with a 404 that masked a permissions failure. After the owner
authenticated as Akash97p, the archives were uploaded to the existing release rather than
retagging.
Verified against the published release, not the local build: agentnotify-linux-x64.tar.gz was
downloaded back from GitHub, its SHA-256 checked against the published SHA256SUMS-portable.txt,
and both binaries extracted from it ran and reported 0.0.2-alpha.1.
Still unverified: the cross-platform-assets job has never completed. Its archives were built and
attached locally for this release, so the corrected upload step remains unproven until the next tag.
Settings window crash on a saved provider (fix/settings-json-null-crash)
Reported by the owner while configuring a real Telegram bot: the test send delivered a message to Telegram successfully but the Settings window then showed an error mentioning null, and clicking the saved Telegram provider in the list closed the application every time.
Both symptoms had one cause. JsonElement.TryGetInt32 does not behave like a Try method: it
returns false only for a number that will not fit, and throws InvalidOperationException for every
other value kind, including JSON null. The Settings window read optional integers as
root.TryGetProperty(name, out var e) && e.TryGetInt32(out var v), and optional settings are
serialized as null when left blank. A Telegram provider saved without a topic ID stores
"messageThreadId": null, so loading it threw.
- Clicking the provider runs
Provider_Selected, which is not insideRunAsync, so the exception reached the WPF dispatcher unhandled and terminated the tray process — taking the broker and its API down with it. - Test send runs inside
RunAsync, which catches everything and showsexception.Message. It saves, sends, then reloads and reselects the provider, which threw after the message had already been delivered. That is why a successful send appeared to fail.
The same unsafe pattern was present at eight call sites: SMTP port, Telegram topic ID, Pushover
emergency retry and expiry, Twilio SMS validity, Twilio WhatsApp validity, and MQTT port, QoS, and
message expiry. All eight now use JsonConfigReader, which treats absent, null, and wrong-typed
values alike as "not set". Provider_Selected additionally catches anything a stored profile can
throw and reports it in the status line, so no saved row can terminate the process again.
Verified:
- The stored configuration was read directly from the user's SQLite database and confirmed to
contain
"messageThreadId": null. Encrypted secret columns were not read. - A test asserts that
JsonElement.TryGetInt32really does throw on a JSON null, so the diagnosis is demonstrated rather than assumed. - 644 tests pass, including 16 new ones covering null, missing, wrong-typed, oversized, and fractional values, and the exact Telegram document that caused the crash.
- Release build 0 warnings/0 errors; installer repackaged, SHA-256
ae12982bff7fe2ca7fadba696772ce51eb4e5bb5fad2a866bb00746fc8b3863a.
Not verified: no WPF interaction was performed. Whether clicking the Telegram provider now loads its fields without closing the application must be confirmed by the owner on Windows.
About section (feature/about-section)
Adds an About tab to the Settings window and an "About AgentNotify" tray menu entry that opens it.
The tab shows the app icon, name, version read from assembly metadata, a prerelease warning shown
only when the version carries a suffix, a description of the product, a local-first summary, links
to the repository/documentation/releases/issues, the per-user data directory, a warning that
config.json holds the local bearer token, and publisher/licence/unsigned notes.
The tray item is placed immediately above Exit rather than literally last, because Exit last is the established convention and an entry below it reads as a mistake. Moving it is a one-line change.
Hyperlinks hand only https URIs to ShellExecute. The URIs are compiled into the XAML today, so
this cannot currently matter, but routing an arbitrary scheme through the shell is what turns a link
into a command launcher if the source ever becomes dynamic. Browser launch failures are swallowed so
a misconfigured default browser cannot close the Settings window.
Verified: Release build 0 warnings/0 errors, 644 tests pass, installer repackaged with SHA-256
4cf2cb98416352f488083d20fc666a2994388ad57ccd9426d03bc9e5ba3778ad.
Not verified: no WPF interaction was performed. Whether the icon renders from the packed
Resources/an.ico URI, whether the tab lays out correctly, and whether the tray entry opens the
Settings window on the About tab all need a human on Windows.
Owner confirmation on Windows (0.0.3-alpha.1)
Two items previously recorded here as unverified were confirmed by the owner on Windows:
- The Settings crash fix: selecting the saved Telegram provider now loads its fields instead of closing the application, and a real Telegram bot delivered a test message with no error afterwards.
- The About section: the tray entry opens Settings on the About tab and the tab renders as intended.
A spread of eight notifications across every built-in type was also sent through the installed
build. The four high and critical ones routed to Telegram and were recorded as Delivered on the
first attempt; the four normal-priority ones correctly stayed local, exercising the route's
minimum-priority filter.
Documentation was swept for machine-specific paths and version drift: personal filesystem paths were
replaced with /path/to/agent-notify, and every current-version reference and test count now matches
the released version. Historical version references in RELEASING.md and CHANGELOG.md are left as
written, since they record what was published at the time.
Provider and route editor reset (fix/provider-editor-reset)
Reworked the left-hand list columns of Channels → Providers and Channels → Routes so a saved
selection can always be left: adaptive Grid layout that keeps the buttons visible, + New provider
/ + New route above the list, Delete selected below it, an editor heading naming the profile
being edited, an explanation on the disabled provider-type dropdown, Escape to deselect, and a
form reset on every path that clears a selection. The cause is analysed in
BUG.md.
Verified: Release build 0 warnings/0 errors, 644 tests pass. The XAML compiles, which is what proves the new named elements and handlers resolve.
Not verified: no WPF interaction was performed. The clipping analysis is a reading of the layout,
not a measurement of a rendered window. A human on Windows needs to confirm that both buttons are
visible at the window's minimum height, that + New provider after selecting a saved profile clears
the form and re-enables the type dropdown, that Escape does the same, and that saving afterwards
creates a second profile rather than overwriting the first.
Initial event profile and agent skill installer
Local verification on 2026-08-26:
./scripts/build.sh -p:EnableWindowsTargeting=truecompleted the full solution cross-build with 0 warnings and 0 errors under the Linux .NET 10 SDK. This compiles the WPF and setup projects but is not a native Windows execution test../scripts/test.sh --no-restorepassed 659 tests with 0 failures and 0 skips.- Event-profile coverage included envelope validation, authentication, type defaults, extension projection, unknown-extension policy, immutable replay after resolution, explicit condition-key updates, and one-time outbound enqueue behavior.
- CLI coverage installs the embedded skill for Codex and Claude Code, exercises both command forms, protects changed files unless forced, and verifies dry-run behavior.
- The skill-creator
quick_validate.pycheck reportedSkill is valid!fordistribution/agentnotify. ./scripts/build-site.sh, JSON Schema parsing, and the generated event/schema/skill page checks completed successfully.
Hosted verification on topic commit d265ac0:
- Windows Actions run
32892500440passed the native solution build, all 659 tests, andscripts/package.ps1, including the installer and embedded resources. - Portable Actions run
32892500455passed on both Ubuntu and macOS. Each runner published self-contained single-file CLI and broker executables, installed the embedded Codex skill with that CLI, started the published broker executable, and exercised API persistence, keyed deduplication, owner-only files, and signal-driven shutdown.
Documentation Actions run
32893126444 deployed the site
successfully. Direct HTTPS checks returned 200 for the then-current event documentation, JSON
Schema, and agent skill guide.
No WPF visual behavior changed, and no new visual check is claimed.
ARC 0.1 (feature/arc-contract)
Local verification on 2026-08-26:
./scripts/build.sh -p:EnableWindowsTargeting=truecompleted the full solution cross-build under the Linux .NET 10 SDK with 0 warnings and 0 errors. This compiled the WPF and setup projects but did not execute either Windows UI../scripts/test.sh --no-restorepassed 666 tests with 0 failures and 0 skips. The 17 focused ARC cases cover authentication, all request-kind defaults, strict field handling, vendor extensions, immutable replay after resolution, one-time outbound enqueueing, active-only updates, idempotent resolution, missing conditions, and AgentNotify presentation projection../scripts/build-site.shgenerated the complete documentation set, including ARC, andpython3 -m json.tool src/AgentNotify.Protocol/Schemas/arc-0.1.schema.jsonparsed the published schema successfully.git diff --checkpassed, and a case-insensitive repository/site scan found no superseded protocol name or field references in the current tree.- Packaging was not rerun because this branch changes no installer payload, embedded resource, publish setting, or release automation. The protocol schema is published documentation and NuGet package content; the Windows installer payload remains the tray and CLI executables.
No WPF visual behavior changed, and no visual or real external-provider check is claimed.
Next.js and shadcn/ui documentation site (feature/site-shadcn)
Local verification on 2026-08-26:
./scripts/build-site.shcompleted from a cleannpm ci, passed TypeScript checking, compiled the optimized Next.js 16 application, and generated all 21 static routes. The wrapper copied the export to_site, published the ARC schema and favicon set, and created compatibility aliases for the former.htmldocumentation URLs.- A generated-output checker inspected 39 HTML entry points and found no missing internal links,
scripts, stylesheets, fonts, images, schema files, or documentation targets. The schema published
at
_site/schemas/arc-0.1.schema.jsonis byte-identical to the protocol project source. npm audit --prefix site --audit-level=highreported zero known vulnerabilities. A semantic scan of the current source and generated site found no reference to the superseded protocol name.- The generated site was served from its real
/agent-notify/base path and inspected in headless Google Chrome. Landing-page renders at 1440x1200 and 390x844 preserve hierarchy and do not overflow; ARC renders at 1440x1100 and 390x844 show the desktop navigation/TOC and responsive documentation menu respectively. This is an actual browser render check, not an inference from generated HTML. ./scripts/build.sh -p:EnableWindowsTargeting=truecompleted the full solution cross-build with 0 warnings and 0 errors../scripts/test.sh --no-restorepassed all 666 tests with 0 failures and 0 skips.git diff --checkpassed. Packaging was not rerun because the site branch changes no installer payload, embedded application resource, publish setting, or release workflow. The Pages workflow itself has not run remotely yet, so hosted deployment of this new implementation is not claimed.
No WPF source or behavior changed, and no WPF visual or external-provider check was performed.
Monochrome brand assets (feature/monochrome-brand)
Local verification on 2026-08-26:
- The owner-supplied white-on-black and black-on-white sources were copied byte-for-byte into
assets/branding/as PNG and AVIF. The canonical 512-pixel PNG was regenerated from the dark source. - The application ICO contains seven images at 16, 24, 32, 48, 64, 128, and 256 pixels; the web ICO contains 16, 32, and 48-pixel images. The Android, Apple touch, and standalone favicon PNGs have the exact dimensions declared by their filenames and manifest.
./scripts/build-site.shpassed and generated all 21 routes. The export was served under the real/agent-notify/base path and inspected in Google Chrome at 1440x900 and 390x844. The new mark is sharp, aligned with the header text, readable at navigation size, and consistent with the black/white UI../scripts/build.sh -p:EnableWindowsTargeting=truecompleted with 0 warnings and 0 errors, including the WPF tray, CLI, setup project, and their icon resource metadata../scripts/test.sh --no-restorepassed all 666 tests with 0 failures and 0 skips../scripts/package.shcould not run locally because the configured Windows .NET SDK path is absent and no other Windowsdotnet.exeis installed. A Linux SDK can cross-compile but cannot drive the repository's PowerShell/Windows packaging boundary.
Hosted verification on topic commit 12fc681:
- Windows Actions run
32953223592passed the native solution build, all 666 tests,scripts/package.ps1, and installer/embedded-resource validation. - Portable Actions run
32953223540passed on both Ubuntu and macOS, including native CLI/broker publish and smoke tests.
No Windows executable or WPF window was launched locally, so taskbar/tray rendering and the packed About/setup image remain pending native Windows or hosted executable verification.
Monochrome Windows UI (feature/windows-monochrome-ui)
Local verification on 2026-08-26:
Theme.xamlnow defines the neutral desktop tokens and control templates used by Settings and channel management. A source sweep found none of the former blue/purple surface, border, focus, or action tokens in the App or Setup XAML/C# files. Semantic success, warning, error, and notification-type accents were deliberately retained where color conveys state rather than decoration.- Settings, channel management, Notification Center, toasts, and Setup retain every behavior-bearing
x:Name, selection handler, click handler, and binding referenced by their code-behind. The redesign is confined to presentation plus minimize/maximize helpers for the new custom title bars. - Targeted Release builds of
AgentNotify.AppandAgentNotify.Setuppassed with 0 warnings and 0 errors, proving the XAML resources, control templates, WindowChrome declarations, packed icon URIs, and event handlers compile. The subsequent full solution Release build also passed with 0 warnings and 0 errors. ./scripts/test.sh --no-restorepassed all 666 tests with 0 failures and 0 skips.- Local packaging remains unavailable because this WSL environment has no Windows
dotnet.exe. The topic branch therefore used the hosted Windows installer/resource job.
Hosted verification on topic commit 807778c:
- Windows Actions run
32955922259passed the native solution build, all 666 tests,scripts/package.ps1, and installer/embedded-resource packaging. - Portable Actions run
32955922411passed on both Ubuntu and macOS, including native CLI/broker publish and smoke tests.
Not verified: no WPF surface was rendered or interacted with. A Windows human still needs to inspect Settings at minimum/default/maximized sizes, provider and route editors, Notification Center active/recent lists, stacked toasts, installer scrolling, 100/125/150/200% DPI, keyboard focus/order, high contrast, and screen-reader labels. Compilation is not a substitute for those checks.
Relay browser/device-grant connection (feature/relay-connect)
Local verification on 2026-08-31:
./scripts/build.sh --no-restorecompleted the full Release solution build, including WPF XAML, with 0 warnings and 0 errors../scripts/test.sh --no-restorepassed all 706 tests with 0 failures and 0 skips. The 13 newRelayPairingTestscases cover sender metadata, authorization-header placement, pending-to-approved, denial/expiry/consumption, RFC-styleslow_down, four tolerated transient failures and failure five, cross-origin browser URL rejection, HTTP/localhost policy, exception credential redaction, cancellation before a poll, discovery, and installation verification../scripts/package.shrebuilt the self-contained tray, CLI, and setup payload and createdartifacts/AgentNotifySetup.exewith SHA-256b9ff26b2b800ce58b331a27c57482361c75f134dc88b155e78936de47f1f0b9e. Packaging succeeded but emitted the existing IL3000 single-file warning atSettingsWindow.xaml.csforAssembly.Location; this branch did not introduce that line.python3 /home/akash/.codex/skills/.system/skill-creator/scripts/quick_validate.py distribution/agentnotifyreportedSkill is valid!.- The packaged Windows CLI was launched through Windows PowerShell from the WSL workspace.
agentnotify.exe relay --helpprinted the new command reference and exited0; a no-network validation smoke withrelay pair --url http://relay.example.comrejected non-local HTTP and exited1. git diff --checkpassed. The default Relay UI source contains no credential value, and fake-handler tests prove synthetic poll/installation credentials are absent from URLs and thrown exception text.
Not verified: no live Relay server was available. The browser was not opened for a real approval,
no real one-time credential was persisted, no test envelope reached a Relay console/device, and no
post-pairing runtime log directory was available for the required inst_/pol_ value scan. No WPF
window was rendered or exercised. A Windows human must still test Connect → browser approval → verified
identity → Save provider → Send test, plus Cancel, window close, provider switch, browser-launch failure,
expiry, rejection, DPI, keyboard, and screen-reader behavior.
Relay no-device handling (fix/relay-no-devices)
Local verification on 2026-08-31:
- A user verified the sender approval flow against a Relay running on
http://localhost:4000; the connection succeeded and the old test-send path reproducedrelay_400. Source inspection confirmed the adapter had sentrelay-placeholder-devicebecause sender pairing had not created a phone. - Focused
RelayChannelTestsandRelayPairingTestspassed all 44 tests. New regression coverage proves an empty or revoked-only device list reports zero active phones,no_devices_pairedis permanent and issues no envelope POST, an unknown pinned device issues no POST, and the paired installation identity is retained in parsed delivery configuration. ./scripts/build.shcompleted the full Release solution build, including WPF XAML, with 0 warnings and 0 errors../scripts/test.sh --no-restorepassed all 710 tests with 0 failures and 0 skips../scripts/package.shrebuilt the Windows application and installer. The resultingartifacts/AgentNotifySetup.exeSHA-256 is7d617019d08abc3383bcaff9b4fdbee15ef9454efd26473f3a171be3860a8689. Publish emitted only the existingSettingsWindow.xaml.csIL3000 warning aboutAssembly.Locationin a single-file app.git diff --checkpassed. The distributable skill was unchanged, so skill validation was not required.
Not verified: no phone client is present in this workspace, no receiving device was enrolled, and no successful envelope/device delivery was claimed. The updated WPF message was compiled but not visually inspected. After installing this build, the reported no-phone state should be retested against the live localhost Relay; a successful test delivery still requires an enrolled phone.
Relay and Android mobile live status (owner report)
Manual report on 2026-09-03:
- The owner reports that the current Relay and Relay mobile builds were tested working, including the live Relay/mobile path.
- This supersedes the earlier statement that no phone client existed. The Android receiver now exists.
This AgentNotify documentation branch did not repeat the device test, inspect the phone, or capture a step-by-step pairing/delivery/decryption/acknowledgement log. Treat the result as an owner-performed integration confirmation, not as an independently reproduced security or compatibility audit.
Bidirectional communication research (docs/bidirectional-agent-communication)
Local verification on 2026-09-03:
- With local Node 22 selected,
./scripts/build-site.shtype-checked the site, compiled it, and generated all 23 static pages, including the new bidirectional research page. The first attempt with the shell's Node 18.20.4 stopped before compilation because Next.js 16 requires Node 20.9 or newer; no product defect was involved. ./scripts/build.shcompleted the full Release solution build with 0 warnings and 0 errors../scripts/test.shpassed all 720 tests with 0 failures and 0 skips.git diff --checkpassed. Packaging was not rerun because no installer payload, embedded resource, publish setting, or release automation changed. The distributable skill was not modified.
No coding-agent adapter, response API, SQLite interaction migration, desktop response UI, Relay reverse channel, or mobile response control was implemented or integration-tested. The new document is a researched plan, and every feasibility rating remains a future adapter claim to prove against a pinned host version.
First real-Mac hardware verification (docs/mac-hardware-verification)
Verified on 2026-09-04 on owner hardware: Intel i5-10400H, macOS 26.6.2, running the
extracted agentnotify-osx-x64 archive (CLI and broker both report 0.0.4-alpha.1).
This is the first time the macOS build has run outside CI, and the first time the
osascript notifier path has displayed a notification anywhere.
This was the published 0.0.4-alpha.1 release archive from GitHub, not a build from
dev. Nothing merged after that tag was present — in particular the AgentNotify Relay
channel is newer, so its behaviour on macOS remains entirely unobserved. What follows is
a statement about the released binary and nothing else.
Observed:
- Both downloaded binaries carried
com.apple.quarantineand both are adhoc-signed (spctl -arejects them, as expected for an unsigned prerelease). Onexattr -dr com.apple.quarantine <dir>cleared execution with nosudorequired; the executable bits were already set. No elevation was needed at any point. agentnotify --versionand--helpwork immediately after the quarantine clear.- The broker starts and reports
secrets: macOS login keychainandnotifications: osascript(terminal-notifieris not installed on this machine). - A login-keychain entry (service
AgentNotify, accountprovider-secrets) is created and nosecret.keyfallback file is written, matching the CI-runner behaviour on a real login session. - End-to-end broker behaviour confirmed against an isolated
--config-diron port 47899 and against the default data directory on port 47821:healthreturnsok,sendcreates, a secondsendwith the same--keyreturns the same id (keyed dedup),get/list/resolveround-trip, a second broker instance refuses withAgentNotify is already running for this user, andSIGTERMshuts the broker down cleanly (agentnotifyd stoppedin the log). - The listening socket is loopback-only (
TCP 127.0.0.1:47899 (LISTEN)). - Owner-only state confirmed in both directories: data dir
0700,config.jsonandagentnotify.db0600,logs/0700. - A direct
osascript display notificationbanner was shown on screen and confirmed by the owner, so the macOS notifier backend is proven end to end for the first time. Sticky attention types still degrade to ordinary banners underosascript, as documented. - Default-path token discovery confirmed by the owner:
agentnotify healthandsendwork with no--tokenflag against the default~/Library/Application Support/AgentNotifydata directory.
Not verified: the osx-arm64 binary has never executed (no ARM64 hardware); the
terminal-notifier backend path has never run (not installed here); the launchd agent unit
and install.sh have never been exercised end to end; the AgentNotify Relay channel
postdates the tested archive and has never run on macOS; and the binaries remain
adhoc-signed, so Gatekeeper quarantine clearing is still required on every fresh download.
macOS installer, launch agent, and agent skills (fix/macos-unix-installer)
Verified on 2026-09-10 on the same owner Intel Mac running macOS 26.6.2:
- The first real
install.shrun exposed three release-path defects: macOS/bin/shtreated a Unicode ellipsis after each of two unbraced variables as part of the parameter name underset -u; the script requested the Windows-onlySHA256SUMS.txtinstead ofSHA256SUMS-portable.txt; and GitHub's/releases/latestdownload route returned 404 because all AgentNotify releases are prereleases. - After correcting those defects,
/bin/sh -n scripts/install.shpassed. An unpinned./scripts/install.shresolvedv0.0.4-alpha.2, downloadedagentnotify-osx-x64.tar.gz, matched it against the published portable checksum, and installed both executables to~/.local/bin.agentnotify --versionandagentnotifyd --versionboth reported0.0.4-alpha.2. - A valid
~/Library/LaunchAgents/dev.agentnotify.broker.plistwas bootstrapped in the user's GUI domain. launchd reported the service running,lsofshowed only127.0.0.1:47821, andagentnotify healthreturnedstatus: ok, APIv1, and version0.0.4-alpha.2. The broker log reported theosascriptnotifier and AES-GCM protection under the macOS login keychain. agentnotify install-skill codex,claude, andopencodeinstalled the bundled skill at~/.agents/skills/agentnotify,~/.claude/skills/agentnotify, and~/.config/opencode/skill/agentnotify. Each installedSKILL.mdmatched the distribution copy; Codex also receivedagents/openai.yaml.- Relay configuration was attempted only through read-only discovery. The supplied hostname
an.relay.dev.kabnitech.comreturned DNSNXDOMAINfrom the system resolver and public resolvers1.1.1.1and8.8.8.8. Pairing was therefore not started, andagentnotify relay status --jsonremainednot_configured. /bin/sh -n scripts/install.sh tests/install-script-test.sh, the offline mocked-release installer check, andgit diff --checkpassed locally../scripts/build.shcould not start on this Mac because the repository gate intentionally requires a Windows .NET 10 SDK path from WSL; no local .NET build was claimed.- Hosted Windows Actions run
34482095178passed restore, the full Release build, tests, and installer/embedded-resource packaging. Portable run34482095194passed the build, tests, installer regression check, and native CLI/broker smoke test on both Ubuntu and macOS.
First live macOS Relay pairing
The owner corrected the Relay hostname to https://an.relay.dev.kabanitech.com during the same
2026-09-10 session. Public Cloudflare and Google DNS both resolved it to 212.227.243.171, and its
/.well-known/agentnotify-relay response identified AgentNotify Relay 0.1.0, API v1, envelope
version 1, and the experimental opaque-transport capability.
agentnotify relay pair completed a real browser approval, verified the returned installation
credential, and saved provider Hosted relay without printing the credential. A subsequent
agentnotify relay status --json reported status: connected, identity Akashs-MacBook-Pro, and
enabled: false. A boolean scan of broker logs found no inst_ or pol_ credential prefix.
The pairing command deliberately saved the new provider disabled. The owner then explicitly asked to
enable it and route all priorities to the connected phone. All notifications to mobile was
created enabled with minimum priority Low, no type/project/agent filters, and
include_message: true. The provider was enabled without changing its encrypted credential.
A success test at priority Low with key agent-notify-mac-relay-test exercised the lowest route
threshold. Its durable outbox row changed from Processing to Delivered; attempt 1 succeeded with
HTTP 201 and no error code. This proves the live macOS broker matched the route, sealed the payload,
and had the hosted Relay accept the envelope. The owner then confirmed that the Mac Relay test
notification appeared on the connected mobile app, completing the first verified macOS-to-mobile
Relay path.
Still unverified: Apple Silicon execution, terminal-notifier, and signed/notarized installation.
The launchd registration, provider, route, and skill files are local user configuration, not
repository artifacts.
Release packaging for v0.0.4-alpha.2
Built on 2026-09-04 from chore/release-0.0.4-alpha.2: Release build 0 warnings / 0 errors,
738 tests passed, scripts/package.sh produced AgentNotifySetup.exe with SHA-256
a4c5c68138a11113469b97c74b292d768020d74f10aaa8b84fe1ad93dc522ac4.
This is the local packaging run. The tag workflow builds, tests and packages independently on a hosted Windows runner and publishes its own artifacts; the two checksums are not expected to match, because the installer embeds build-time paths.
Notify-only harnesses (feature/harness-opencode-codex-claude, 2026-09-11)
Automated gates (macOS, user-local .NET SDK 10.0.401, EnableWindowsTargeting=true):
- Full-solution Release build: 0 warnings, 0 errors.
- Full suite: 763 tests passed (738 baseline + 25 new harness tests), 0 failed.
node --check distribution/harness/opencode/agentnotify.jspassed.python3 -m py_compile distribution/harness/shared/agentnotify_hook.pypassed, and the script exits0on a sample permission payload with no broker running.- Both
hooks.example.json(Codex) andsettings.example.json(Claude Code) parse as JSON. git diff --checkpassed. No WPF surface changed or rendered. Packaging was not rerun: two new files are now embedded in the CLI (an installer-payload change), butscripts/package.shrequires the Windows .NET SDK and PowerShell, which this Mac does not have. The next hosted Windows run must confirm installer/resource packaging.
Not verified: no real OpenCode, Codex, or Claude Code session has loaded these harnesses,
and no harness-sent desktop notification has been observed. The owner manual checklist in
docs/HARNESS.md is the acceptance test. Record the outcome here when run.
Relay interaction sync (feature/relay-interaction-sync, 2026-09-12)
Automated gates (macOS, user-local .NET SDK 10.0.401, EnableWindowsTargeting=true):
- Full-solution Release build: 0 warnings, 0 errors.
- Full suite: 835 tests passed (821 + 14 new publisher/sync/cursor/API/CLI tests), 0 failed.
git diff --checkclean. Site typecheck + static export passed with the newinteractionsandrelay-interactionspages.- No WPF surface changed or rendered. Packaging was not rerun (Windows-only script); the next hosted Windows run must confirm installer/resource packaging.
Not verified: no Relay server implements POST/GET /v1/interaction-responses
yet, so no live phone-to-host answer has flowed; the mobile UI does not exist.
The contract they will be built against is docs/RELAY_INTERACTIONS.md.
Continuous answer polling (fix/interaction-answer-delivery, 2026-09-12)
Automated gates (macOS, user-local .NET SDK 10.0.401, EnableWindowsTargeting=true):
- Full-solution Release build: 0 warnings, 0 errors.
- Full suite: 838 tests passed (835 + 3 new sync-validation/transient-failure tests), 0 failed.
git diff --checkclean. Three stale sync fixtures with short digests and missing nonces were corrected to the strict wire shape the Relay now enforces; two other suites already covered the nonce requirement.
Not verified here: no WPF surface was rendered, no hosted Windows packaging ran, and no live Relay was available on this machine for a phone-answer round trip. The end-to-end proof remains: answer from a real phone against a real Relay with the desktop broker running, and observe the waiting host receive the decision.
Interaction wait lifecycle and ask fallback (fix/interaction-wait-lifecycle, 2026-09-12)
Automated gates (macOS, user-local .NET SDK 10.0.401, EnableWindowsTargeting=true):
- Full-solution Release build: 0 warnings, 0 errors.
- Full suite: 842 tests passed (839 + 3 new), 0 failed.
- Each new test was run against the pre-fix code first and confirmed to fail, so the three defects are pinned by tests that can actually detect them.
python3 -m py_compilepasses on the edited harness hook.
Not verified here: no WPF surface was rendered, no hosted Windows packaging ran, and the hook's new cancel/resolve path was not executed against a running broker — it is a subprocess call made only on a fallback that needs a live ask session to reach.
Release v0.1.0-alpha.2 (2026-09-12)
Hosted Windows release workflow succeeded on tag v0.1.0-alpha.2: build, full suite,
packaging, and publication. Published assets: AgentNotifySetup.exe (191.77 MB),
agentnotify-win-x64.zip, the four linux/osx × x64/arm64 portable archives,
SHA256SUMS.txt, SHA256SUMS-portable.txt, and SKILL.md. The desktop repository's
CI, Linux/macOS CI, and documentation-site workflows all passed on main for the same
commit.
Not verified: the installer has not been run on Windows from this release, and the binaries remain unsigned.
Bidirectional loop, end to end on real hardware (2026-09-13)
The round trip this project exists for was observed for the first time, on the owner's Intel Mac against the owner's Relay and a real Android handset.
Performed: the installed ask-permission hook was invoked with a Claude Code
PermissionRequest payload. It opened a broker interaction, published it through the
Relay, and blocked. The phone rendered the question, the owner tapped a choice, and the
desktop poller ingested the answer and settled the interaction. The hook printed the
host-native decision JSON and exited 0.
Observed on the settled interaction: status: answered, source: relay,
device_id: 29f6bad9…, and a client-generated UUID response_id — so the answer came
off the Relay from the phone, not from the CLI or the local API. Both allow and deny
were exercised; deny produced
{"decision":{"behavior":"deny","message":"Denied via AgentNotify."}} and allow
produced {"decision":{"behavior":"allow"}}.
The fallback path was verified in the same session and is the half that had never been
executed: a question nobody answered left the hook printing nothing — so the host falls
through to its own prompt — and left the interaction cancelled rather than pending.
That is the fix/interaction-wait-lifecycle change, confirmed against a live broker
rather than only by unit test.
Environment: broker 0.1.0-alpha.2 on macOS x86_64 under the per-user launchd agent.
The published osx-x64 binaries are SIGKILLed by macOS as shipped — they are
cross-published from a Linux runner and their adhoc signature is not accepted here.
codesign --force --sign - on both binaries fixes it. Worth treating as a packaging
defect rather than a local quirk.
All three interaction kinds, answered from the phone (2026-09-13)
Following the permission round trip above, one of each kind the broker supports was
raised and answered from the paired handset. Every answer arrived with source: relay
and device_id: 29f6bad9…, so all three came off the Relay rather than the CLI or the
local API.
| Kind | Prompt | Answer received |
|---|---|---|
permission |
delete the build cache at /tmp/build |
allow — "Allow once" |
single_choice |
which database for the new service (four options, three with detail lines) | pg — "PostgreSQL" |
text |
name the next release | "Next release name should be bla bli blu" |
The text answer is the one worth noting: it round-tripped free-form content with spaces and punctuation intact through the sealed envelope and the broker's bounds check, so the return path carries typed content and not only a choice id. The three were answered out of order — text first, permission last — and each settled independently, which is what the per-interaction expiry and first-answer-wins are meant to allow.
What this does not cover: multi-select (no such kind exists — single_choice means
exactly one, and the broker rejects an answer carrying more) and free-form messaging to
an agent, which is deliberately outside this contract.
One gap this exercise exposed, not a defect in the loop itself: the distributable
SKILL.md teaches send, resolve and list only. It never mentions interactions,
so no agent currently knows it can ask a question on its own initiative, even though
every layer beneath it works. Tracked in TODO.md.
Web interface (feature/web-ui, 2026-09-13)
Environment: Intel MacBook Pro (x86_64), macOS, .NET SDK 10.0.401, headless Google Chrome.
dotnet build AgentNotify.slnx -c Release -p:EnableWindowsTargeting=true: succeeded, WPF app and installer included (cross-targeted from macOS; not a Windows run).- Full suite: 887 tests passed (842 + 45 new), 0 failed. New: 32 provider-form tests (every
registered adapter has an editor; builder and reader agree for all nineteen kinds; secret
keep/clear semantics; Twilio and MQTT mode switches drop unused credentials; Relay pairing;
a real Telegram adapter delivering from a built configuration) and 13 web interface tests
(launch codes need the bearer token and work once; cookie is
HttpOnly/SameSite=Strict; foreignHostgets 421; missing header or cross-siteOrigingets 403 and changes nothing; sign-in throttles after ten failures; invalid settings change nothing; stored secrets never appear in responses; a browser answer binds to the displayed digest; CSP and asset serving; every imported module is served; sound upload sanitizes the name and refuses traversal). - Two defects were caught by those tests before anything shipped:
GET /ui/redirected to itself (routing treats/uiand/ui/alike), and expression-bodied(HttpContext) => await …handlers bound toRequestDelegate, silently discarding their result — provider and route saves answered 200 with an empty body, so validation errors never reached the page. - Visual check, by driving headless Chrome over the DevTools protocol against a scratch broker
with seeded data: every page at 1440 px, light and dark, and 390 px with the navigation drawer.
No console errors or exceptions on any page. Found and fixed: a stray
nullrendered by nativeappend, step cards underlined, a label misaligned in two-column grids, harness commands truncated, and long working-directory paths widening the layout at phone width. - Interaction from the page: a text answer and a single-choice answer were entered and sent in
the browser;
GET /v1/interactions?status=answeredshowed both withsource: web. - Deployed to this Mac's launchd broker (
osx-x64publish, ad-hoc re-signed, previous0.1.0-alpha.2binaries kept in~/.local/bin/.agentnotify-backup-0.1.0-alpha.2)./ui/answered 200,/ui/api/overviewwithout a session 401, andagentnotify ui --printsigned a headless browser in; overview, channels (the existing Relay profile, credential shown as stored, not revealed) and agents rendered against real data with no console errors. Nothing was saved from the page on the real broker. scripts/publish-cross.shfailed its checksum step on macOS (GNUfind -printf,xargs -r,sha256sum); fixed to fall back toshasum -a 256.
Not verified: the web interface served by the Windows tray app, the tray menu's web-interface entry (Open in browser… at the time; Web interface… since), sound preview of built-in tones (seeded only by the Windows app), and a Relay pairing started from the page against a live Relay. (A person has since used it on macOS; see the next section.)
Web interface without sign-in (fix/web-ui-no-sign-in, 2026-09-13)
The first cut required a one-time launch link or the pasted token before the page would load. The
owner rejected that: the Windows app has no password, and a sign-in step on a loopback-only page is
friction without a matching threat. Removed the launch codes, sessions, cookie, sign-in page, and
POST /v1/ui/launch; agentnotify ui now opens the address directly. The host-name allowlist,
the required header and same-origin Origin on changes, the CSP, and write-only secrets remain.
dotnet build AgentNotify.slnx -c Release -p:EnableWindowsTargeting=true: succeeded, 0 warnings.- Full suite: 884 passed (887 − 4 sign-in tests + 1 test that the page needs no token while
/v1still does), 0 failed. - Deployed to this Mac's launchd broker:
/ui/and/ui/api/overviewanswered 200 with no token, a foreignHostgot 421, and/v1/notificationswithout the token still got 401. - Owner check (2026-09-13): the repository owner opened
http://127.0.0.1:47821/ui/on this Mac and confirmed it opens straight to the dashboard and works. This is the first time a person has used the web interface; the Windows tray app serving it is still unverified.
Local Usage WebUI (feature/local-usage-webui, 2026-09-13)
Automated checks on the owner's Intel Mac with .NET SDK 10.0.401:
dotnet build AgentNotify.slnx -c Release -p:EnableWindowsTargeting=true --no-restoresucceeded with 0 warnings and 0 errors, including the Windows-targeted projects.- The full test suite passed: 888 passed, 0 failed, 0 skipped. The new fixture tests cover malformed Claude lines, duplicate Claude assistant responses, cumulative and unchanged Codex samples, mirrored subagent exclusion, counter normalization, changed-file invalidation, and file deletion. A WebUI integration test confirms the aggregate route omits prompt text and log paths.
node --checkpassed for the Usage page and application shell;git diff --checkpassed.- The required WSL
scripts/build.sh,scripts/test.sh, andscripts/package.shwere attempted but could not start on macOS because/mnt/d/dev/dotnet/dotnet.exeis absent. Native .NET build and tests ran instead. Windows installer packaging remains unverified for this branch.
Live local checks:
- A scratch broker on
127.0.0.1:47877read 16 Claude Code/Codex log files with no skipped files and returned nonempty 30-day source, model, and daily aggregates. A headless Chrome screenshot at 1440 px showed the Usage page and its tables/charts rendering with real data. This was a browser rendering check, not a human visual review. - A self-contained
osx-x64broker was published and ad-hoc signed;codesign --verifyandagentnotifyd --versionpassed. The prior installed broker was backed up to~/.local/bin/.agentnotifyd-backup-local-usage-20260913, and launchd restarted the updated broker. On the usual127.0.0.1:47821listener,/ui/api/usage?days=30returned HTTP 200 with 16 files and no skips,/ui/api/overviewreturned HTTP 200, and unauthenticated/v1/notificationsstill returned HTTP 401.
Not verified: a Windows tray-hosted Usage page, Windows installer payload, a human visual check of this new page, or exact agreement with provider invoices. The in-memory file cache is rebuilt on broker restart; advanced fork/replay attribution, OpenCode, and pricing remain work.
API-equivalent cost and project usage (feature/usage-api-cost-projects, 2026-09-13)
- The native macOS .NET SDK 10.0.401 full Release build, including Windows cross-targeted projects, succeeded with 0 warnings and 0 errors. The full suite passed: 889 tests, 0 failed, 0 skipped. Fixture checks cover exact-rate arithmetic, separate Claude 1-hour cache writes, unknown-model cost coverage, two same-named but distinct working-directory projects, opaque project IDs, and omission of full paths and prompt text from the WebUI response.
node --checkpassed for the edited Usage module. A self-containedosx-x64broker publish succeeded. The required WSLscripts/build.sh,scripts/test.sh, andscripts/package.shcould not start on this Mac because the Windows SDK path/mnt/d/dev/dotnet/dotnet.exeis absent; Windows installer packaging is unverified.- A scratch broker on
127.0.0.1:47878read 16 local Claude Code/Codex logs, returned 30 project groups with no unpriced events in the 30-day view, and did not return working-directory paths. A headless Chrome screenshot at 1440 px showed the new cost tile, source costs, dated pricing basis, and project rows. This was a browser rendering check, not a human visual review.
Not verified: exact agreement with either provider's bill or subscription allowance, historical rate changes, long-context/priority/server-tool modifiers, Windows tray hosting, or installer payload. The price catalog is an explicitly dated counterfactual standard-API estimate.
OpenCode local Usage adapter (feature/usage-opencode, 2026-09-13)
- The native macOS .NET SDK 10.0.401 full Release build, including Windows cross-targeted projects, succeeded with 0 warnings and 0 errors. The full suite passed: 891 tests, 0 failed, 0 skipped. New fixtures cover read-only OpenCode SQLite assistant-message extraction, malformed and user row exclusion, date filtering, separate reasoning normalization, provider/model price selection, project path and message-text omission, live DB refresh, and the WebUI endpoint.
node --checkpassed for the edited Usage module andgit diff --checkpassed. A self-containedosx-x64broker publish succeeded withIncludeNativeLibrariesForSelfExtract=true. The first publish omitted that flag and failed when installed without its sibling SQLite dylib; the corrected standalone binary was tested from~/.local/binbefore installation. The required WSLscripts/build.sh,scripts/test.sh, andscripts/package.shwere attempted but cannot start on this Mac because their configured/mnt/d/dev/dotnet/dotnet.exeis absent. Windows installer packaging remains unverified.- A scratch published broker on
127.0.0.1:47878read 17 local usage stores with 0 skipped, including the owner's live OpenCode database. Its 30-day response included Claude Code, Codex, and OpenCode, 34 working-directory project groups, and explicitly unpriced OpenCode records. The response omitted full home paths;/ui/api/overviewreturned 200 and the bearer-protected/v1/notificationsreturned 401 without a token. Headless Chrome screenshots at 1440 and 500 CSS pixels showed the Usage view rendering on desktop and narrow layouts. A 390-pixel Chrome screenshot was clipped by headless Chrome's 500-CSS-pixel minimum viewport, so it was not used as a layout verdict. These are browser rendering checks, not human visual review. - The corrected broker was ad-hoc signed and installed on the owner's usual launchd service,
with the previous binary backed up at
~/.local/bin/.agentnotifyd-backup-usage-opencode-20260913. On127.0.0.1:47821, the 30-day Usage API returned all three sources and 34 project groups; overview returned 200 and the agent API returned 401 without a bearer token. The response omitted full home paths.
Not verified: reconciliation with OpenCode's account charges or quota, prices for models without an exact published rate, historical prices, a physical-phone viewport, Windows tray hosting, or the Windows installer payload. OpenCode Go estimates use published quota-equivalent token rates, not additional subscription spend.
Usage unpriced-banner removal (fix/usage-unpriced-banner, 2026-09-13)
The top-of-page unpriced-model warning was removed. Source, project, and model cost labels still
show + unpriced, and the total cost tile still says it covers priced records only. Native macOS
.NET 10 Release solution build succeeded with 0 warnings and 0 errors; all 891 tests passed.
node --check and git diff --check passed. A self-contained osx-x64 broker publish with
embedded native libraries succeeded. The required WSL build/test/package scripts were attempted
but could not start because /mnt/d/dev/dotnet/dotnet.exe is absent on this Mac. Windows installer
packaging and a human browser visual check for this edit remain unverified.
Live quota WebUI (feature/live-quota-webui, 2026-09-13)
- Native macOS .NET SDK 10.0.401 Release solution build, including Windows cross-targeted projects, succeeded with 0 warnings and 0 errors. The full suite passed: 896 tests, 0 failed, 0 skipped. New fixtures cover Codex multi-bucket windows and credits, Claude missing/scoped windows, five-minute cache and 30-second manual-refresh gate, stale-on-failure behavior, credential-scope invalidation, fixed-endpoint Claude request and token omission, and the WebUI quota route/refresh header guard.
node --checkpassed for the app shell and Quota view;git diff --checkpassed. The required WSLscripts/build.sh,scripts/test.sh, andscripts/package.shwere attempted but could not start on this Mac because/mnt/d/dev/dotnet/dotnet.exeis absent. Windows installer packaging and Windows tray-hosted quota probing remain unverified.- A self-contained
osx-x64broker with native SQLite embedded was published. An isolated scratch broker on127.0.0.1:47878returned current Codex app-server five-hour/seven-day windows and Claude Code account five-hour/seven-day windows, plus an explicit unavailable OpenCode state. No access token or bearer header appeared in the JSON response. Its embedded Usage JavaScript no longer contained the unpriced-model banner. Headless Chrome screenshots at 1440 and 500 CSS pixels showed the Quota page and three provider cards rendering. These are browser rendering checks, not a human visual review. - The initial installed binary exposed a launchd-only Codex failure: the npm Codex launcher uses
/usr/bin/env node, while launchd supplied a minimalPATH. Reproducing that minimalPATHagainst a scratch broker returned Codex unavailable; after adding the launcher's bin directory to the child process environment, the same restricted-path scratch check returned both Codex and Claude windows. The corrected signed standalone binary was installed with the prior broker backed up at~/.local/bin/.agentnotifyd-backup-live-quota-20260913. On the normal127.0.0.1:47821listener,/ui/api/quotareturned Codex and Claudeokwith two windows each and OpenCodeunavailable; the Usage script lacked the removed banner, overview returned 200, and unauthenticated/v1/notificationsreturned 401.
Not verified: exact agreement with Codex or Claude account dashboards after subsequent activity, stability of Anthropic's undocumented first-party OAuth usage endpoint, Windows CLI discovery, Windows installer payload, or a human visual check from the owner's Windows browser.
Multiple quota accounts and OpenCode Go estimate (feature/multi-account-quota, 2026-09-14)
- Native macOS .NET SDK 10.0.401 Release solution build with
-p:EnableWindowsTargeting=truesucceeded with 0 warnings and 0 errors. The full suite passed: 902 tests, 0 failed, 0 skipped. New checks cover named-account cache isolation and removal, profile validation and startup normalization, persisted WebUI add/remove operations, per-model OpenCode Go cap arithmetic, and unknown estimates when a published rate is incomplete. node --checkpassed for the Quota view andgit diff --checkpassed. The documentation site's TypeScript check and 27-page static export passed locally. Next.js 16.3.3 intermittently lost request context during cold static export (upstream issue 98200), so the site is pinned to Next.js 16.2.12 with its supported TypeScript CLI mode and one static worker. Cold builds also failed on this Mac's Node 22; a cold Node 24.21.0 export passed, and the Pages workflow runs Node 24. The local build script uses that Node version when its host Node is older.- A self-contained
osx-x64broker with embedded native libraries was published, ad-hoc signed, and installed into the owner's launchd service. On127.0.0.1:47821, the quota response used contract version 2, returned two live windows each for the current Codex and Claude Code profiles, and returned local five-hour, seven-day, and rolling 30-day estimates for the owner's two observed OpenCode Go models. The response contained no access token, bearer header, or credential-file content. A 1440-pixel headless Chrome screenshot showed the account editor, named profile cards, and Go estimate. This was a browser rendering check, not a human visual review.
The required WSL scripts/build.sh, scripts/test.sh, and scripts/package.sh cannot start on
this Mac because /mnt/d/dev/dotnet/dotnet.exe is absent; Windows installer packaging is therefore
unverified. Also unverified are actual second-profile probes using the owner's credentials,
Windows tray hosting, exact provider-dashboard agreement, and Claude profiles whose macOS login is
stored only in Keychain rather than the selected profile's .credentials.json. OpenCode Go values
are local token-based estimates against published per-model caps, not provider-reported remaining
quota; the 30-day view is rolling rather than the provider's billing cycle.
WebUI over an SSH forward with a different local port (fix/webui-forwarded-port, 2026-09-13)
- Reproduced the owner's
421withHost: 127.0.0.1:47822against the Mac broker listening on127.0.0.1:47821. The original guard required theHostport to equal the listener port, which cannot hold when an SSH local forward uses a different browser-side port. A separate SSH command targeting Mac port 47822 would fail to connect because the broker does not listen there. - The guard now accepts only loopback host names with an explicit port and compares state-changing
requests'
Originto that exactHost, including its forwarded port. Foreign host names still return 421. A WebUI integration test covers page and API reads on the forwarded host, a successful same-origin write, and refusal of a write from the broker-port origin. - Native macOS .NET 10 Release solution build passed with 0 warnings/errors; the full suite passed
897 tests, 0 failed, 0 skipped. The repository's WSL
scripts/build.shandscripts/test.shwere attempted but cannot run on this Mac without/mnt/d/dev/dotnet/dotnet.exe. Windows installer packaging was not needed for this API guard change and was not run. - A self-contained
osx-x64broker publish succeeded, was ad-hoc signed and verified, and replaced the installed launchd broker. Its predecessor is backed up at~/.local/bin/.agentnotifyd-backup-forwarded-port-20260913. After restart, requests to the live127.0.0.1:47821listener carryingHost: 127.0.0.1:47822returned 200 for/ui/,/ui/api/quota, and a same-origin quota-refresh POST. A wrong-portOriginreturned 403 and a foreignHostreturned 421. The quota response reported Codex and Claude Codeokand OpenCodeunavailable. These checks simulate SSH's forwardedHostandOriginheaders; the owner's Windows browser and end-to-end SSH tunnel remain for owner verification.
Usage recent-session view (feature/usage-session-view, 2026-09-14)
- Native macOS .NET SDK 10.0.401 Release solution build with
-p:EnableWindowsTargeting=truesucceeded with 0 warnings and 0 errors. After that build, the full suite passed without rebuilding: 903 tests, 0 failed, 0 skipped. The new fixture verifies session grouping after deduplication, chronological aggregation, opaque IDs, and omission of raw provider session IDs and full project paths.node --checkandgit diff --checkpassed. - The documentation site's TypeScript check and 27-page static export passed. The required WSL
build, test, and package scripts were attempted but cannot start on this Mac because
/mnt/d/dev/dotnet/dotnet.exeis absent. GitHub Actions then completed the Windows Release build, all 903 tests, and installer/embedded-resource packaging successfully on commite74107d. The previously failing Windows fixture had placed an unescapedC:\\...path in hand-built JSON; serializing that fixture properly fixed the test without changing production parsing. One local test run overlapped the Release build and collided on a generated runtime-config file; the clean sequential full-suite run above is the reported local result. - A self-contained, ad-hoc-signed
osx-x64broker was installed into the owner's launchd service. The live 30-day Usage response on127.0.0.1:47821reported contract version 2, 6,505 usage records, 58 attributable sessions, and the newest 50 session summaries, with no home-directory path in the response. The SSH-forwarded host header still returned the UI and quota API, and the quota report still returned both current provider accounts plus the OpenCode Go estimate. A 1440-pixel headless Chrome rendering showed the updated Usage view; this was a browser rendering check, not a human visual review.
Not verified: Windows tray hosting, a human visual review, sessions missing from source records, or fork/replay parentage. Session cost remains the same current-rate estimate used elsewhere in Usage.
Compact Insights dashboard (feature/insights-dashboard, 2026-09-14)
- Native macOS .NET SDK 10.0.401 built the Release solution with
-p:EnableWindowsTargeting=true: 0 warnings and 0 errors. The focused WebUI/quota/config run passed 36 tests, and the sequential full suite passed 903 tests with 0 failed and 0 skipped. The account-management fixture renames both a built-in profile and an additional profile, reloads their persisted labels, and rejects an empty label. Every embedded JavaScript module passednode --check, andgit diff --checkpassed. scripts/build-site.shcompleted its TypeScript check and generated all 27 static pages. The required WSL build/test/package entry points cannot run on this Mac because their configured Windows SDK path,/mnt/d/dev/dotnet/dotnet.exe, is absent; Windows packaging remains a CI gate.- A self-contained, ad-hoc-signed
osx-x64broker containing the new embedded assets was installed into the owner's launchd service, with the previous executable saved as~/.local/bin/.agentnotifyd-backup-insights-dashboard-20260914.agentnotify healthreturnedokon127.0.0.1:47821. The live Dashboard composed four healthy Codex/Claude profiles, OpenCode Go estimates, 30-day local usage, project rankings, and delivery health. The Live quota view showed remaining-balance bars whose fill matched the reported remaining percentage. A final owner-directed color pass assigns the neutral bar at 40–100%, yellow at 20–39%, and red at 0–19%. - Headless Chrome rendered the Dashboard, Live quota, and simplified Usage pages with real data at 1440 CSS pixels. A narrow render confirmed the single-column dashboard breakpoint. The account management panel was present with all four local profiles and remains collapsed by default. These were browser rendering checks, not an owner visual sign-off. Reduced motion is covered by the existing CSS media rule; assistive-technology behavior was not manually tested.
- GitHub Actions on commit
301ace7passed the Windows Release build, all 903 tests, Windows packaging, and the Linux/macOS matrix. The preceding color-only merge had one unexplained Windows test-step failure after the same dashboard revision had passed; its anonymous run exposed no TRX detail. CI now writes the TRX to an explicit runner-temporary directory and emits a clear annotation even whendotnet testexits before producing that file. The clean rerun required no product-code change.
Not verified: Windows tray-hosted rendering, installer payload execution, manually renaming the owner's real profiles, or a human visual review of the new layouts.
Expanded OpenCode Go catalog (feature/opencode-go-pricing, 2026-09-14)
- Checked the official OpenCode Go table on 2026-09-14 for exact model IDs, per-million-token rates, published cache-write prices where present, and monthly dollar caps. The catalog now contains 20 exact fixed-rate IDs. Context-tiered and peak/off-peak entries remain unpriced because the local ledger cannot select their rate.
- The focused usage tests passed 7/7. A native macOS .NET 10 Release solution build passed with
0 warnings and 0 errors. After updating the catalog date assertion, the full suite passed
903 tests, 0 failed, 0 skipped. The fixture covers a newly priced model, a published cache-write
rate in both Usage and Go quota estimates, and an excluded tiered model.
git diff --checkpassed. The WebUI module passednode --check. - The documentation site's TypeScript check and all 27 static pages passed. The repository's
WSL build/test/package scripts were attempted but cannot run on this Mac without
/mnt/d/dev/dotnet/dotnet.exe. GitHub Actions forf52a5b3passed the Windows Release build, all 903 tests, installer/embedded-resource packaging, and the Linux/macOS matrix. - A first manual
osx-x64single-file publish omittedIncludeNativeLibrariesForSelfExtract=true. The executable worked alongside its SQLite dylib in the publish directory but failed under launchd when copied alone; the prior broker was immediately restored and confirmed healthy. The corrected publish used the repository release flags for native-library embedding and compression, was ad-hoc signed, and started from a directory containing only that executable. After installation and launchd restart,agentnotify healthreturnedokon127.0.0.1:47821. The previous executable is backed up at~/.local/bin/.agentnotifyd-backup-opencode-go-pricing-20260914. - The live 30-day Usage API reported
pricing_as_of: 2026-09-14; Live quota reported three windows each for the two locally observed Go models. The embedded quota module served the new 20-model coverage text, and the page returned HTTP 200 with a forwarded browser-sideHostport of 47822. A 1440-pixel headless Chrome screenshot rendered the compact quota page and its 20-model coverage line. This was a browser rendering check, not a human visual review.
Not yet verified: Windows tray-hosted Go estimates or provider-reported Go quota. Local Go usage on this Mac currently includes only Muse Spark 1.3 Contributor and GLM-5.3, so newly priced models need future local usage before they appear as model cards.
GitHub Pages and documentation status refresh (docs/webui-landing-page, 2026-09-14)
- Updated the landing page and published guide descriptions for the shipped WebUI, three local usage sources, published-rate cost estimates, named Codex/Claude Code quota profiles, the separately labeled OpenCode Go local estimate, and the Insights dashboard. Reconciled the bidirectional, interaction, harness, Relay, architecture, roadmap, backlog, cross-platform, installation, troubleshooting, and release docs with current implementation status.
./scripts/build-site.shpassed TypeScript checking and exported all 27 pages. A read-only check confirmed that internal links from the exported landing and guide pages resolve and that the landing, WebUI, and bidirectional guide contain the updated claims.git diff --checkpassed.- A native macOS .NET 10 Release solution build passed with 0 warnings and 0 errors. The full
AgentNotify.Testssuite passed 903 tests, 0 failed, 0 skipped. The repository's WSL./scripts/build.shand./scripts/test.shcould not run on this Mac because their configured Windows SDK path,/mnt/d/dev/dotnet/dotnet.exe, is absent. Installer packaging was not run; this branch changes documentation and the separate GitHub Pages site, not installer payloads. - Headless Chrome rendered the exported landing page at desktop width and at an emulated 390-pixel
mobile viewport. The mobile document's
scrollWidthequaled its 390-pixel viewport width after constraining the grid columns. These are browser rendering checks, not a human visual review.
Not verified locally: Windows tray-hosted behavior, provider interoperability, or a human review of the updated Pages layout. Those product behaviors are unchanged by this documentation branch.
Hosted-only Relay, tray label, and ARC 0.2 (2026-09-16)
Run on the owner's Mac with the local .NET 10 SDK at ~/.dotnet rather than scripts/*.sh,
which resolve a Windows SDK path that does not exist on this machine. The whole solution,
including the WPF app and the installer, compiles there with -p:EnableWindowsTargeting=true.
Performed:
dotnet build AgentNotify.slnx -c Release -p:EnableWindowsTargeting=true— succeeded, 0 warnings, 0 errors, coveringAgentNotify.AppandAgentNotify.Setup. This is a compile of the WPF project including XAML markup compilation, so the removedRelayDeploymentBoxandRelayBaseUrlBoxcontrols leave no danglingx:Namereference in the code-behind.dotnet test tests/AgentNotify.Tests/AgentNotify.Tests.csproj -c Release— 915 passed, 0 failed, up from 906 before this work.npm run buildinsite/— the GitHub Pages export generated all 27 routes, and the built output contains no link to any Relay repository.- The ARC 0.2 JSON Schema was checked with
jsonschema4.26 (Draft 2020-12): the schema itself is valid, all fourdocs/ARC.mdexamples validate against it, 14 malformed envelopes are rejected (0.1 version, a response on a created event, an outcome on a created event, a text kind carrying choices, a choice kind without them, a single choice, an answer with both or neither answer field, an answer without a key, an answer with no response object, a short digest, a resolution carrying a message, an unknown outcome, an unknown core field), and three further valid shapes are accepted.
Not verified:
- No WPF surface was rendered or exercised. The Relay panel with its deployment selector and server-address field removed, and the renamed Web interface… tray entry, compile but have not been looked at by a person. Layout, spacing, and the read-only hosted-endpoint line are unconfirmed. A cross-compile is not a running Windows app; run it before trusting the appearance.
- No live Relay was contacted. Pairing against the hosted endpoint, and a real phone answering an ARC 0.2 answerable request end to end, remain unobserved.
- Packaging was not run.
Release v0.1.0-alpha.3 (2026-09-16)
Tag v0.1.0-alpha.3 on main at a8c27e2. The hosted Windows release workflow
(35105119307) succeeded in
under six minutes: tag/version match, SemVer-style metadata check, Release build, the full suite,
package.ps1, and prerelease publication. The Linux job then attached the portable archives. Every
other workflow on the same commits also passed — CI and CI (Linux and macOS) on both dev and
main, and the documentation site deploy.
Published assets and checksums:
5ddd439eec47b39e8167d7693b5c5214e5f81b0c37f680bd428604fdd80253ce AgentNotifySetup.exe
c62552fd67aa8c3920fe3799fd2f5f64c6d2106bd0b6e89efef4511253ceaf51 agentnotify-linux-arm64.tar.gz
e9bceab3e2de27373229a946b532937c2c68a39e76beee8a6b89c9b16f913bf5 agentnotify-linux-x64.tar.gz
1b58e63904dee6ca5aac85624a751ece50e567604d286ee14c547a388112ac60 agentnotify-osx-arm64.tar.gz
bf334dd04609c460bfee053508976f8afac01bef9f1de91841f866f9707260dc agentnotify-osx-x64.tar.gz
890a137070b7f1d0347a666b049df838c94e36f7b4664a95f49855490d6168bb agentnotify-win-x64.zip
Not verified: nobody has installed or run this build. The installer was produced and checksummed by CI, not executed. Everything listed as unverified in the section above still stands — no WPF surface has been looked at, no live Relay has been contacted with the hosted-only client, and no phone has answered an ARC 0.2 request end to end. The binaries remain unsigned.
WSL agent discovery (feature/wsl-agent-discovery, 2026-09-16)
Environment: macOS (Darwin 25.6), .NET SDK 10.0.401 at ~/.dotnet. There is no Windows machine or
WSL in this session, so the repository scripts were not used and WslDiscovery itself returns
nothing here.
Ran:
dotnet test tests/AgentNotify.Tests/AgentNotify.Tests.csproj -c Release— 933 passed, 0 failed, 0 skipped, up from 915 ondevon the same machine. New coverage: share-path parsing, UTF-8 and UTF-16wsl --listoutput,/etc/passwdhome lookup, usage from a fake WSL home including a Linux project path and a distribution that stops, discovered/hand-added/renamed WSL quota accounts, label normalization, the skills-root home override, the Codex-in-WSL start arguments andWSLENV, and web interface skill installs by distribution name (including a stopped one).- Release builds of
AgentNotify.Tests,AgentNotify.Host,AgentNotify.Cli, andAgentNotify.App(-p:EnableWindowsTargeting=true) — 0 warnings, 0 errors. node --checkon the four edited web interface modules andbash -n scripts/agentnotify.
Not verified — none of this has run on Windows or WSL:
- Registry enumeration,
wsl.exe --list --runningon a real machine, and reading/etc/passwdand agent logs through\\wsl.localhost. - That a stopped distribution is left stopped when the pages load.
- The Codex app server started through
wsl.exeand a login, interactive shell (including nvm), and Claude quota read through the share. - SQLite reading OpenCode's database over the WSL share.
- The Agents page, Live quota account list, Usage "Includes WSL" line, and the WPF Install tab's WSL rows were not rendered.
install-skill --wsland the wrapper'sWSL_DISTRO_NAMEforwarding.
In-place installer update (feature/installer-in-place-update, 2026-09-16)
Environment: macOS (Darwin 25.6), .NET SDK 10.0.401 at ~/.dotnet. There is no Windows machine or
WSL in this session, so the repository scripts were not used.
Ran:
dotnet build src/AgentNotify.Setup/AgentNotify.Setup.csproj -c Release -p:EnableWindowsTargeting=true— 0 warnings, 0 errors, including XAML markup compilation of the renamed/newx:Nameelements.dotnet build src/AgentNotify.App/AgentNotify.App.csproj -c Release -p:EnableWindowsTargeting=true— 0 warnings, 0 errors.
Not verified — none of this has run on Windows:
- Update detection from the uninstall registration, the update-mode window layout (collapsed licence panel, read-only folder, version line), and the finish/relaunch actions.
- That the tray receives
Local\AgentNotify.Exit.v1and exits cleanly, and the kill fallback against an alpha.3 tray that has no such event. - Renaming an in-use
agentnotify.exeduring an update, and deleting the.oldleftover on the next install and inuninstall.ps1. - Silent update, relaunch, and
--no-launch. - Packaging was not run, so no installer containing this change exists yet.
WSL discovery on a real Windows machine (fix/wsl-default-user, 2026-09-17)
Environment: Windows 11 host with WSL 2.7.11 running Ubuntu-20.04, WSL workspace, Windows .NET SDK
at /mnt/d/dev/dotnet/dotnet.exe. The distribution sets its user through /etc/wsl.conf
([user] default=akash); its Lxss registry entry has DefaultUid 0.
At a19304e (dev) the repository scripts ran: scripts/build.sh 0 warnings, 0 errors,
scripts/test.sh 933 passed, 0 failed, 0 skipped, and scripts/package.sh produced
artifacts/AgentNotifySetup.exe. The owner installed that build and reported:
- Adding
\\wsl.localhost\Ubuntu-20.04\home\akash\.codexand...\.claudeby hand on Live quota worked; both cards showed Live with real 5-hour and 7-day balances, so Codex's app server ran inside the distribution and the Claude credentials were read through the share. - No WSL account was discovered automatically, and the dashboard showed no usage (0 tokens, 0 sessions) although agents run in that distribution.
Cause, confirmed on this machine: wsl.exe --list --running --quiet does list Ubuntu-20.04 (as
UTF-16LE without a byte-order mark, which the parser handles), but discovery took the home of
DefaultUid 0, /root. The installed agentnotify.exe install-skill claude --wsl Ubuntu-20.04 --dry-run reported \\wsl.localhost\Ubuntu-20.04\root\.claude\skills\agentnotify.
After the fix (default user read from /etc/wsl.conf, falling back to DefaultUid) the same
dry run from the fixed CLI build reported
\\wsl.localhost\Ubuntu-20.04\home\akash\.claude\skills\agentnotify. WslTests pass, including
new wsl.conf parsing and passwd lookup by name.
Not verified yet: discovered quota cards and WSL usage in an installed build with this fix, and a
distribution without wsl.conf. Linux and macOS are unaffected (discovery is Windows-only).
Editable and removable Live quota accounts (feature/editable-quota-accounts, 2026-09-17)
Environment: the same Windows 11 host and WSL workspace, repository scripts.
Ran:
scripts/build.sh— 0 warnings, 0 errors.scripts/test.sh— 945 passed, 0 failed, 0 skipped. New coverage: removing, restoring, renaming, and moving built-in and discovered WSL accounts through the web API (including refusing a directory another account already monitors and a relative path), removed IDs filtered from the live quota report, config normalization ofremovedQuotaAccounts, and Usage reading a hand-added profile's ledger while counting a directory that is both added and discovered once.node --checkonquota.js.
Not verified: the Manage accounts layout (desktop and narrow widths) has not been looked at in a
browser, and the installed build has not yet shown discovered WSL cards or WSL usage. Packaging
produced a new artifacts/AgentNotifySetup.exe for the owner to test.
OpenCode usage over the WSL share (fix/opencode-usage-over-wsl, 2026-09-17)
Environment: the same Windows 11 host; Ubuntu-20.04 holds ~1 GB of Codex JSONL (331 files, the
largest 256 MB), 72 MB of Claude Code JSONL, and a 261 MB OpenCode database with a 10 MB WAL.
The owner reported that every Insights tab loaded for minutes after the WSL discovery fix. Measured
against the installed build with curl.exe: the first /ui/api/usage?days=30 took 123 s, and
every later /usage and /quota call still took ~30 s, while /overview and /quota/accounts
answered in milliseconds. Reading the whole database sequentially through \\wsl.localhost took
1.8 s and listing the JSONL files 0.6 s, so the time was SQLite's page-level reads over the share,
repeated on every request (JSONL results were already cached). That installed build also reported
files_skipped: 1 and no OpenCode source at all: the query was failing.
The fixed agentnotifyd was run from the build output on port 47899 with a throwaway
--config-dir, next to the installed tray:
/usage?days=30: 62 s cold (JSONL parsing), then 0.45 s and 0.40 s./quota: 2.2 s (live account probes), then 0.008 s.- Claude Code and Codex totals were identical to the installed broker's. OpenCode now appeared with
1,866 events and 228,606,112 tokens, exactly what a native Python read of the same database
in WSL counted for the last 30 days.
files_skippedwas 0, and no snapshot directory remained in%TEMP%. scripts/build.sh0 warnings, 0 errors. Onescripts/test.shrun failed a single test whose name was not captured; four reruns passed. Two same-sized writes within one clock tick would leave size and modification time unchanged, so the cache stamp now also includes the SQLite header's change counter and the WAL header. After that,scripts/test.shpassed 945 of 945 in five consecutive runs, and the broker was measured again: 60 s cold, then 0.48 s and 0.43 s, 7,571 events, nothing skipped.
Not verified: a cold load is still about a minute after each broker start, because parsed JSONL is held only in memory; the installed tray with this build; and a snapshot taken during an OpenCode checkpoint.
Model pricing, Muse Code / Kilo CLI / Gemini CLI usage (feature/model-pricing, feature/more-agent-usage, 2026-09-17)
Environment: the same Windows 11 host and Ubuntu-20.04. Rates were read on 2026-09-17 from the
official pages linked in docs/WEB_UI.md (OpenAI pricing and model pages, Meta Model API, Gemini API
pricing and Gemini 3 guide, Z.ai, Xiaomi MiMo pay-as-you-go, OpenCode Zen and Go). Gemini 3 Pro
Preview and Poolside Laguna have no official rate page and stay unpriced.
Ran:
scripts/build.sh0 warnings, 0 errors;scripts/test.sh978 passed, 0 failed, 0 skipped (new: per-provider rates, free models, long-context and fast tiers, Go peak hours, Muse sessions with a subagent, Gemini chats withprojects.json, a Kilo database, and the nativefindlisting parser).- Local data found on this machine: Muse Code (5,673 session files, 5,639 of them subagents), Kilo CLI
(
kilo.db, 279 MB), Gemini CLI (22 chat files in WSL, 6 on Windows). Antigravity and Windsurf conversation stores are opaque binary; Cursor, Kiro, Copilot, and the Kilo VS Code extension hold no per-request token counts; Cline's CLI data held no tasks. - An independent Python recount inside WSL matched the fixed broker exactly for Muse Code (9,571 calls, 1,037,211,966 tokens) and Gemini CLI (325 messages, 17,524,462 tokens); Kilo differed by 143,839 tokens from a recount taken minutes earlier while Kilo was in use.
agentnotifydfrom the build output on port 47899,/ui/api/usage?days=all: before the listing and buffer changes, 401 s cold and 9.9 s warm (walking 6,699 Muse files through the share alone took 10.4 s). After them, 132 s cold, then 1.06 s and 0.82 s, with identical totals: 48,660 events, 7,064 files, nothing skipped, $2,535.94 API-equivalent (was $1,441.76 with the old catalog), 56.9M of 4.8B tokens unpriced (Codex records before a model is known, Gemini 3 Pro Preview, Poolside Laguna, a custom OpenCode provider).- A 30-day Codex check found 4 turns in fast mode (
service_tier: priority) and no GPT-5.5/5.4 request above 272K input tokens.
Not verified: the installed tray with these builds; macOS and Linux locations for Muse Code (only the
XDG default is read); Gemini CLI projects whose folder is a hash not listed in projects.json; a cold
scan is still over two minutes on this machine because parsed history is kept only in memory.
CrossPlatformTests.ConfigStore_WritesTheTokenFileOwnerOnlyOnUnix failed once in a full run on the
profiles worktree and passed alone and in two further full runs; it is unrelated to these changes.
OpenCode Go billing cycle (feature/opencode-go-renewal, 2026-09-17)
Environment: the same Windows 11 host and WSL workspace. scripts/build.sh 0 warnings, 0 errors;
scripts/test.sh 1,019 passed, 0 failed, 0 skipped. New coverage: the cycle calculation around the
renewal day, month ends, leap years, and a year boundary in a fixed +05:30 zone; config normalization;
the monthly window counting only the current cycle with starts_at/resets_at; and the renewal-day
API including validation and the cross-origin refusal. node --check passed for quota.js and
insights.js.
Not verified: the renewal control has not been looked at in a browser, and no real OpenCode Go console figure has been compared with the estimate. OpenCode's documentation does not state whether its 5-hour and weekly limits roll or reset at fixed times, or the exact renewal time of day.
API accounts (feature/api-billing-accounts, 2026-09-17)
Automated verification only:
./scripts/build.sh— 0 warnings, 0 errors../scripts/test.sh(worker run) — 1035 passed, 0 failed, 0 skipped, including new per-provider parsing (DeepSeek, Moonshot, SiliconFlow, OpenRouter, OpenAI/Anthropic pagination and cents conversion), error mapping (401, 404, 429 withRetry-After, 500, oversize body, invalid JSON, timeout), redirect handling, key-absence in endpoint JSON/snapshots/database bytes, acknowledgement, validation, rename/key-replacement, delete, cache/refresh throttling, and WebUI endpoint coverage.- No live provider was called: every provider response in tests comes from a fake
HttpMessageHandler.
Not verified: a human visual check of the API accounts cards and the Manage form in a browser, including a narrow window; a real key against any provider; and whether Anthropic's cost report amounts are cents as its guide states. Endpoint shapes come from the providers' official documentation (DeepSeek, Moonshot/Kimi, SiliconFlow, OpenRouter, OpenAI Costs API, Anthropic Usage and Cost API) read on 2026-09-17; OpenAI's pagination fields were not shown there and are treated as optional.
The implementation was written by a delegated worker and reviewed before merging. Review fixes:
negative balances (Moonshot documents a non-positive available balance) were rejected as unreadable
and are now reported; a stored key that cannot be decrypted produced "could not be reached" and now
asks for the key to be replaced, without any request being sent; OpenAI and Anthropic date ranges
used the system clock instead of the injected one; the default service silently fell back to a
throwaway in-memory encryption key when the secret protector could not be created, which would have
stored keys no later start could decrypt, and now fails instead; and the API accounts section
blocked the whole Live quota page until every provider answered, and now loads on its own. After
the fixes scripts/test.sh passed 1,037 of 1,037 on the branch.
v0.1.0-alpha.4 release (2026-09-17)
Prepared on dev (chore: prepare v0.1.0-alpha.4) after scripts/build.sh (0 warnings, 0 errors),
scripts/test.sh (1,050 passed), and scripts/package.sh on the Windows host; the local CLI reported
agentnotify 0.1.0-alpha.4 and the local installer SHA-256 was
d8b94ac5c1f1d98c4dd2cc8dfca138ff33ae508df3d3e61c9943823bfe89df46. scripts/release-notes.sh v0.1.0-alpha.4 v0.1.0-alpha.3 produced the notes. The skill validator was not run (its script path
is not configured on this machine); the distributable skill did not change in this release.
Released as merge 3887844 (merge: release v0.1.0-alpha.4 to main), tagged v0.1.0-alpha.4, and
pushed with dev; main was then fast-forwarded to dev at 8d405bd. GitHub Actions:
- Release run 35237072846 —
package-and-releaseandcross-platform-assetssucceeded, publishing the prerelease at 2026-09-17T15:01:18Z withAgentNotifySetup.exe,SHA256SUMS.txt,SKILL.md,agentnotify-win-x64.zip,agentnotify-{linux,osx}-{x64,arm64}.tar.gz, andSHA256SUMS-portable.txt. - CI and CI (Linux and macOS) succeeded on
devandmainat8d405bdand onmainat3887844; the documentation site workflow succeeded on both branches.
Not verified: the published installer has not been downloaded and installed, and the hosted build's checksum was not compared with the local one (hosted and local builds are not byte-identical).
Provider router (feature/provider-router, 2026-09-17)
Automated: the full suite passes on this MacBook (macOS 26.6.2, .NET SDK 10.0.401, run through
scripts/test.sh with AGENTNOTIFY_DOTNET_EXE pointing at ~/.dotnet/dotnet) — 1,185 passed, 0
failed, 0 skipped, up from 1,050 before the branch. The new tests cover the repository and
configuration service, route resolution precedence, base-URL destination rules, all three request
decoders and encoders, the three SSE parsers fed byte-by-byte and whole, the three stream writers
round-tripped through their own parsers, non-streaming translation for every wire, failover,
cooldowns, Retry-After, mid-stream failures, and the loopback/auth/CSRF guards on /router/v1.
Live end-to-end on this machine, with agentnotifyd on port 47822 and a scripted fake
chat-completions upstream:
- A Codex-shaped streaming
POST /router/v1/responsesagainstcombo/coding, whose first target was a port with nothing listening. The router recordedconnection_errorfor attempt 0, failed over to attempt 1, and returned a well-formed Responses stream:response.created,in_progress, a message item with twooutput_text.deltas, then afunction_callitem with streamed arguments, thenresponse.completed. The upstream received translated Chat Completions:systemfrominstructions, the priorfunction_call/function_call_outputpair as an assistanttool_callsmessage plus atoolmessage, the Codexcustomapply_patchtool rewritten as a function with a singleinputstring property, theweb_searchbuilt-in dropped,stream_options.include_usageset, andAuthorization: Bearercarrying the stored upstream key. - A Claude-Code-shaped streaming
POST /router/v1/messagesover the same chat upstream producedmessage_start, two content blocks (text, then tool use),message_deltawithstop_reason: tool_useandusage.output_tokens, andmessage_stop. - A non-streaming Anthropic request returned a
messagewithusage.input_tokensconverted back to Anthropic's exclusive convention. POST /router/v1/messages/count_tokensagainst a chat upstream returned404 not_supported.- Guards: no key
401, wrong key401, a browserOriginheader403, a foreignHostheader421, an unknown model404 unknown_model. The management API'sGET /ui/api/routerresponse contained neither the router key nor the stored upstream key. - The ledger row for the failover request recorded
combo→coding, final upstreamfake, status 200,ok, 42 input / 8 cached / 7 output tokens,reported, with both attempts and their durations.
That live run found a real defect: OpenAI-compatible providers send the finish_reason chunk
before the final usage-only chunk, and the stream writers were emitting their terminal frame on the
finish event, so response.completed went out with no usage at all — Codex would have shown no
token counts, even though the ledger had them. Terminal frames are now emitted from a separate
Complete() call after the parser drains, and four tests pin the late-usage ordering for all three
wires. The captured response.completed after the fix carries
{input_tokens: 42, input_tokens_details.cached_tokens: 8, output_tokens: 7, total_tokens: 49}.
Not verified:
- No request has been sent to a real provider. Every upstream in these runs was a local script. DeepSeek, OpenRouter, Kimi, Z.ai, Groq, Ollama, and the OpenAI/Anthropic passthrough paths are unproven against the real services, as are their rate-limit and error shapes.
- No real agent has been pointed at the router. Codex and Claude Code were imitated by scripted requests; neither has run a session through it.
- The Router web page has not been seen by anyone. Its module and the whole front end parse, the
asset is served, the page calls only the endpoints that exist, and it uses no
innerHTML— but the browser extension was unavailable in this session, so nothing was rendered or clicked. - The Windows tray build has not been compiled with the router.
App.xaml.cswas edited but WPF cannot be built on macOS; only the portable host and CLI were built here. - Cancellation, the 300-second idle timeout, and the 32 MiB body limit were exercised only by unit tests, not against a real slow provider.
Routed models in the agents' own pickers (feature/router-agent-connect, 2026-09-18)
How Codex and Claude Code expose a model list was established against the installed binaries,
not assumed. Codex 0.154.0 validates its model_catalog_json file at config load, so feeding
codex app-server candidate files and reading its parse errors gave the exact required entry fields
(slug, display_name, supported_reasoning_levels, shell_type, visibility, supported_in_api,
priority, support_verbosity, truncation_policy as an object, experimental_supported_tools,
model_messages, and base_instructions), the accepted shell_type values (default, local,
shell_command, unified_exec, disabled), that combo/… and alias slugs are accepted, and that
default_subagent_model, default_subagent_reasoning_effort, review_model, and
model_reasoning_effort load. Codex's provider block accepts experimental_bearer_token. Claude Code
2.1.275 reads ANTHROPIC_DEFAULT_OPUS/SONNET/HAIKU_MODEL and ANTHROPIC_SMALL_FAST_MODEL, and its
settings schema defines modelPicker: {options: [{model, label, description, behavesAs}], replaceBuiltInOptions}.
Automated: 1,203 passed, 0 failed on this MacBook, including 17 connector tests covering managed regions, duplicate-key avoidance, restoring the owner's values, JSON merging, the picker rows, refusal of invalid JSON and unknown selectors, the catalogue refresh after router changes, and restoring kept copies.
Live, with the real agents — agentnotifyd on port 47823 with AGENTNOTIFY_AGENT_HOME pointed at
a throwaway home holding a Codex config.toml (own model, effort, approval policy, a project table)
and a Claude Code settings.json (own model and env), and a scripted chat-completions upstream:
- Connecting both through
POST /ui/api/router/agents/{id}/connectwrote the managed regions, the catalogue, the subagent/review/effort keys, the ClaudemodelPickerrows andenvblock, and kept a copy of each file first. codex doctorwith thatCODEX_HOMEreportedconfig.toml parse okandmodel coding · agentnotify.codex execran a whole agent turn through the router:model: coding,provider: agentnotify. The routed model's streamed text reached Codex, its tool call came back through translation, and Codex executed it (/bin/zsh -lc 'echo routed-through-agentnotify'— succeeded), sent the result back through the router, and finished with the model's final text.claude -p … --model fake/fake-model-awith thatCLAUDE_CONFIG_DIRran through the router with no login: three upstream requests, and one of them used the model mapped to the background slot, which shows the slot mapping takes effect. No unknown-model warning appeared oncebehavesAswas set.- The ledger showed both wires,
openai_responsesfrom Codex andanthropic_messagesfrom Claude Code, all200 ok. - Disconnecting both restored Codex's own
model,model_reasoning_effort, and provider (codex doctor:gpt-5.6-luna · openai, parse ok) and removed every managed key from Claude Code's settings while keeping the owner's ownenventry.
That live run found four defects, all fixed with tests: the owner's own model = … was left
beside the managed one, which TOML rejects, so Codex would not have started; the web API dropped the
subagent/effort options in both directions; unmanaged keys the owner set were being commented out;
and a Responses turn's text and tool call reached Chat providers as two consecutive assistant
messages, which some providers reject. Claude Code also printed an unknown-model warning for routed
selectors, which is why the modelPicker rows now carry behavesAs.
Not verified: the Agents page itself has not been seen in a browser (the extension was unavailable),
no real provider was involved, only macOS was used, and the Windows tray build was not compiled.
Claude Code rewrote its own model setting to opus[1m] during the run — its doing, not AgentNotify's,
but a reminder that the host edits these files too.
v0.2.0-alpha.1 release (2026-09-18)
Tagged v0.2.0-alpha.1 on main at 9400931 (merge: release v0.2.0-alpha.1 to main) and pushed
with dev. The minor version moved from the 0.1 alpha series to 0.2 because the provider router is a
new capability. Before tagging, the full suite passed on macOS (1,203 passed, 0 failed) and the CLI
reported agentnotify 0.2.0-alpha.1; local Windows packaging was not run on this Mac, so the hosted
workflow is the only build of the installer.
- Release run 35306674488
succeeded and published the
prerelease at
2026-09-18T04:25:49Z with
AgentNotifySetup.exe,SHA256SUMS.txt,SKILL.md,agentnotify-win-x64.zip,agentnotify-{linux,osx}-{x64,arm64}.tar.gz, andSHA256SUMS-portable.txt. - Installed on the owner's Intel MacBook from the published release, replacing
0.1.0-alpha.2: the broker's launchd job was stopped,scripts/install.shwithAGENTNOTIFY_VERSION=v0.2.0-alpha.1downloadedagentnotify-osx-x64.tar.gzand verified its checksum, and the job was started again.agentnotify --version,agentnotifyd --version, and/v1/healthall report0.2.0-alpha.1; the 139 existing active notifications were still there; the router answers404 router_disableduntil it is turned on; the four Model router pages are served; and both Codex and Claude Code are detected, waiting only for an upstream to be added.
Not verified: the Windows installer from this release has not been installed, and nothing has been routed through the installed broker yet.
One-step providers and subscriptions (feature/router-easy-setup, 2026-09-18)
On the owner's Intel MacBook, macOS SDK ~/.dotnet 10.0.401 through the repository scripts
(AGENTNOTIFY_DOTNET_EXE=~/.dotnet/dotnet, -p:EnableWindowsTargeting=true for the WPF projects):
./scripts/build.sh: 0 warnings, 0 errors../scripts/test.sh: 1,219 passed, 0 failed (16 new: presets and per-model wires, the column migration on an older database, discovery shapes and refusals, Codex's model cache, OpenCode key reuse, the ChatGPT-plan request shape and headers, token renewal written back to Codex's file, a missing sign-in, Muse key minting and caching, the OpenCode session header, Codex catalogue reuse of native entries, and the create/fetch endpoints)../scripts/publish-cross.shbuilt all five portable archives with the new pages embedded../scripts/package.shneeds Windows (PowerShell under WSL) and was not run, so the installer was not built.- Live, against the real services, through a throwaway host that serves the real web interface
and router over a scratch database with an in-memory secret protector (the installed broker's
keychain item cannot be read by a development binary without a macOS prompt, so the dev broker
itself was not used):
- Model fetch: OpenCode Go 38 models with MiniMax on
anthropic_messages, OpenCode Zen 64 with Gemini left out, OpenRouter 445 (both using the keys OpenCode holds), and the ChatGPT plan's 5 models from Codex's cache. DeepSeek with the key OpenCode holds was refused by DeepSeek (that key is stale; the owner's own DeepSeek upstream uses a different one). Meta without a key was refused, as expected. - ChatGPT plan: one streamed Responses request (Codex's shape, passthrough) and one streamed
Anthropic Messages request (Claude Code's shape, translated) to
gpt-5.6-lunaeach answeredok; the ledger showsopenai_responses200 for both. The access token was valid, so renewal ran only in tests. - OpenCode Go:
minimax-m3(Messages) andglm-5.3-flash(Chat Completions) through one upstream each answeredok. Before the session header was added both failed withMissingSessionID, which is how that requirement was found. - Codex connected in a scratch home with Codex's
models_cache.jsoncopied in:codex debug models(Codex 0.155.0) parsed the generated catalogue, and the ChatGPT-plan entries carried Codex's own instructions template and 272k context window. Claude Code connected in the same home withreplaceBuiltInOptions: false.
- Model fetch: OpenCode Go 38 models with MiniMax on
- Seen in a browser (headless Chrome screenshots, driven over the DevTools protocol): the Providers gallery and list, the OpenRouter editor with 445 fetched models, the ChatGPT-plan editor (unofficial and signed-in notices, 2 of 5 ticked), the Muse Code editor (not signed in), Routing with a fallback chain, and Agents with the Codex note and the "Show only routed models" switch off. The old provider list's badge overlap was reproduced on the installed 0.2.0-alpha.1 page first.
Not verified: the Muse Code plan end to end (Muse Code is not installed on this Mac, so neither its sign-in file nor key minting has met the real service); ChatGPT-plan token renewal against OpenAI; a real Codex or Claude Code session through the new providers; narrow-window layout; the Windows tray build with these pages.
One account list across pages (feature/agent-accounts, 2026-09-18)
macOS, same toolchain as above. ./scripts/build.sh (with -p:EnableWindowsTargeting=true): 0
warnings, 0 errors. ./scripts/test.sh: 1,224 passed, 0 failed (5 new: profiles from the account
list, a second Codex account connected into its own files, an API-account key reference used and then
missing, the one-credential-source rule, and a ChatGPT-plan upstream on a second Codex account with the
model-list fallback).
Live, through the throwaway host over a scratch home holding .codex, .codex-second, .claude, and
.claude-second (the Codex auth.json files copied in; no ChatGPT request was sent from it):
- An OpenRouter key added as an API account was offered on the OpenRouter tile; models were fetched
through the
api_account:reference (445), a provider saved with it stored no key, and one request tonex-agi/nex-n2.5-mini:freethrough it answered. - The ChatGPT plan offered both Codex accounts, defaulting to the one not yet added, with the second account's model list taken from the default account's cache.
- Model router → Agents listed four accounts grouped by host; connecting
codex:home:secondwrote only.codex-second/config.tomlandcodex-model-catalog-codex-home-second.json, and the built-in account's file was unchanged. - Configuration → Agents listed the four accounts with skill and harness state and
--pathcommands for the second profiles; the navigation shows Configuration before Model router. Seen in headless Chrome screenshots.
Not verified: installing a skill or harness into a real second profile from the page; the Windows tray build; a second Claude Code account actually running through the router.
Claude Code's own models through the router (fix/router-claude-code-native, 2026-09-18)
macOS, same toolchain as above. ./scripts/build.sh -p:EnableWindowsTargeting=true: 0 warnings, 0
errors. ./scripts/test.sh -p:EnableWindowsTargeting=true: 1,237 passed, 0 failed (13 new or changed:
native resolution and its precedence, native forwarding through the endpoint with the client's own
credential and headers, the router key as a bearer never going native, a wrong key header refused,
native errors returned uncooled with retry-after, provider error codes in the message but not their
text, 402 failover, 429 + retry-after when every target is cooling, mid-conversation system
messages, dropped unknown blocks, the owner's own ANTHROPIC_CUSTOM_HEADERS kept and restored, and
reconnect-then-disconnect leaving nothing behind).
Diagnosed against the owner's real setup (Claude Code 2.1.276, profile ~/.claude-second):
- A capture server in place of the router showed Claude Code, pointed at a custom base URL, still
sends its own Claude sign-in (
Authorization: Bearer sk-ant-oat…), addsANTHROPIC_CUSTOM_HEADERS, and calls onlyPOST /v1/messages?beta=true. Its requests carry asystem-role message, which the old Anthropic decoder rejected (Invalid role system): the ledger'sclaude-opus-5 … 400 invalid_requestrows. - In a running session, an
envblock added tosettings.jsontook effect on the next request; one removed did not (requests kept going to the old base URL with the old header) until restart. - The ChatGPT-plan
429s at 16:37 were not reproduced: the same translated request, and one padded to about 48k tokens, answered200through both the installed broker and directly, on both Codex accounts. Bothchatgptandchatgpt-2upstreams reference~/.codex, so they share one account's limit. The Meta402came from the per-token Meta Model API key, not the Muse Code plan.
End to end with the dev build (a broker on port 47831 over a copy of the owner's database, outbound
providers disabled in the copy; stored API keys unreadable there because the sandboxed shell cannot
reach the login keychain) and a real interactive claude2 session in tmux:
- Connecting through Model router → Agents while the session was open: the next prompt on built-in
Opus answered, recorded as
native · anthropic · 200. /model chatgpt/gpt-5.6-lunain the same session answered through the ChatGPT plan (200).- Disconnecting while the session stayed open, then
/model opus: answered,native · anthropic · 200. settings.jsonheld noANTHROPIC_AUTH_TOKENwhile connected and was restored afterwards.
One full-suite run failed InteractionRelayPublisherTests.Publish_SkipsBodylessNonRelayAndFilteredRoutes
once; it passed on the rerun, three times alone, and on dev without this change, so it is a timing
flake under load, not this change. ./scripts/package.sh was not run: it needs WSL and PowerShell,
and the only change to embedded resources is one snippet string in router-shared.js.
Not verified: a routed model on a stored API key (DeepSeek, OpenCode Go) through the dev build, for the
keychain reason above; count_tokens on the native path; an Anthropic API-key (x-api-key) Claude
Code login; the Windows tray build.
Smart routing and every Codex account (feature/smart-routing, 2026-09-18) — v0.2.0-alpha.2
macOS, same toolchain as above. ./scripts/build.sh -p:EnableWindowsTargeting=true: 0 warnings, 0
errors. ./scripts/test.sh -p:EnableWindowsTargeting=true: 1,249 passed, 0 failed (12 new: smart
routing's order after a picked model, a bare model across providers, vendor-path and case matching, a
chain keeping its order; smart failover through a 429 and a 401 to the same model elsewhere; the
setting surviving default-route changes; Codex accounts not added before the plan is, added per
signed-in account, a duplicate moved to the unused account keeping its slug, a signed-out account left
out; the page's switch and model groups; a non-streaming client on the ChatGPT plan).
./scripts/package.sh was not run (WSL and PowerShell only); the release tag's workflow builds and
packages on Windows.
Live, with the dev build over a copy of the owner's database (port 47832, outbound providers disabled in the copy, stored API keys unreadable there because the sandboxed shell cannot reach the keychain):
- Loading the router page repaired the owner's real
chatgpt-2from~/.codexto~/.codex-secondand relabelled it; the page listed six models smart routing can switch, GPT models orderedchatgpt→chatgpt-2, and Muse Sparkopencode-go-2→meta(OpenCode Go before pay-per-token). - With smart routing on and
chatgptpointed at a missing account,chatgpt/gpt-5.6-lunafailed over (subscription_signed_out) tochatgpt-2and answered, both streaming and non-streaming. The non-streaming case first answered400: the ChatGPT backend refusesstream: false, which is now fixed by streaming to it and collecting the reply. - Routing page screenshots (headless Chrome, 1400 px): a four-model fallback chain wraps one model per line inside its card; before the fix it ran over the form beside it.
Tag v0.2.0-alpha.2: the Release workflow succeeded and published the prerelease with the Windows
installer, portable archives for Windows, Linux, and macOS, checksums, and the skill; CI (Linux and
macOS) and the documentation site passed. The Windows CI run on the same dev commit failed two of
the new Codex-account tests: they used /home/me/.codex, which is not a fully qualified path on
Windows, so the account-directory check rejected it. The product was unaffected (real account
directories are absolute); the tests now build their paths from the temp directory.
Not verified: smart failover to a provider on a stored API key (the keychain reason above); a real
429 from a real provider triggering it; the Providers page's ChatGPT form seen after the account
picker was removed; the Windows tray build.
Agent install prompt (docs/agent-install-prompt, 2026-09-18)
docs/INSTALL_WITH_AGENT.md was followed by a real Claude Code (claude -p, 2.1.276) on the owner's
Intel Mac, where v0.2.0-alpha.2 was already installed from the published release (install script,
checksum verified, re-signed, launchd agent restarted). It found the install current, skipped
reinstalling, ran every check, and ended with the checklist: broker, CLI, test notification, web
interface, skill, four agent accounts, usage, live quota (one Claude account needing sign-in shown as
❌), router, and Relay. It then listed next steps that were already done, so the prompt now asks for
only the ones not yet set up. Not run: a fresh install on a clean macOS, Linux, or Windows machine.
Documentation and GitHub Pages audit (docs/documentation-audit, 2026-09-19)
Audited the maintained documentation against the current API routes, configuration normalization,
interaction DTOs, six local usage sources, nineteen outbound adapters, provider-router behavior,
release version, and Pages source. Corrected stale release/test references; documented every
/v1/interactions route (including manual Relay publishing), current rate-limit scope, and nonce
requirements; corrected the built-in CLI interaction help; updated the cross-platform security
boundary and router/API-account risks; and exposed the install-with-agent, provider-router, and
security guides on GitHub Pages. The Pages build now
copies and byte-compares both ARC 0.1 and ARC 0.2 schemas instead of silently dropping the schema the
homepage links to.
Verified on the owner's Intel MacBook with macOS 26.6.2, Node 22.18.0/npm 10.9.3, and .NET SDK 10.0.401:
git diff --check: passed.- Local Markdown target audit: 47 maintained Markdown files checked; every relative file target
exists. The previous broken tracked link to the ignored
artifacts/AgentNotifySetup.exewas replaced with the tagged prerelease page while retaining the local packaging path as code text. ./scripts/build-site.sh: passed (npm ci, Next type generation,tsc --noEmit, and static export; 30 static pages generated)._site/docs/{install-with-agent,router,security}/index.htmlexist.- Both generated schema files exist and are byte-identical to
src/AgentNotify.Protocol/Schemas/arc-{0.1,0.2}.schema.json. - Generated-site crawl: 4,585 local links/assets across 57 HTML files; every target exists.
AGENTNOTIFY_DOTNET_EXE="$HOME/.dotnet/dotnet" ./scripts/build.sh -p:EnableWindowsTargeting=true: succeeded, 0 warnings and 0 errors.AGENTNOTIFY_DOTNET_EXE="$HOME/.dotnet/dotnet" ./scripts/test.sh -p:EnableWindowsTargeting=true: 1,254 passed, 0 failed, 0 skipped.
Not verified: a human visual review of the changed homepage/navigation at desktop and narrow widths;
a Windows runtime/WPF check; or ./scripts/package.sh, which requires WSL/PowerShell and was not
required because this branch changes documentation, site sources, and CLI help text rather than
installer payload, embedded resources, publish settings, or release automation. No application
behavior or database schema changed.
Native macOS quota menu bar (feature/macos-quota-menu-bar, 2026-09-19)
Implemented a native AppKit status item for macOS that shows the lowest selected Codex or Claude five-hour remaining balance as a whole percentage, keeps every configured Codex and Claude account and every returned quota window in its menu, and marks stale or unavailable data instead of hiding it. The child process uses only the loopback broker API and receives only the broker port; it does not read the bearer token, configuration file, account profile paths, credentials, or raw provider responses. Live Quota in the WebUI now controls enablement, a 5/10/15/30/60-minute refresh interval, and which accounts participate in the headline. Disabling the status item also restores on-demand quota polling. Packaging, installation, CI, release automation, maintained documentation, and the GitHub Pages sources were updated with the same behavior and platform boundaries.
Verified on the owner's Intel MacBook:
- Targeted configuration, quota projection, and WebUI tests: 44 passed, 0 failed.
AGENTNOTIFY_DOTNET_EXE="$HOME/.dotnet/dotnet" ./scripts/build.sh -p:EnableWindowsTargeting=true: succeeded, 0 warnings and 0 errors.AGENTNOTIFY_DOTNET_EXE="$HOME/.dotnet/dotnet" ./scripts/test.sh -p:EnableWindowsTargeting=true: 1,257 passed, 0 failed, 0 skipped.tests/install-script-test.sh: passed, including installation of the native menu executable.node --checkpassed for the changed Live Quota JavaScript; shell syntax checks passed for the changed build, publish, install, and installer-test scripts.scripts/build-macos-menu-bar.shbuilt and ad-hoc signed bothx86_64andarm64Mach-O executables;codesign --verify --strictaccepted both.- A synthetic loopback runtime smoke test kept the native client alive for an enabled projection
with a
73%headline and made it exit cleanly when the projection became disabled. No real account credentials, bearer token, or provider quota probe was used. ./scripts/build-site.sh: passed (npm ci, Next type generation,tsc --noEmit, and static export; 30 static pages generated). A generated-site crawl checked 4,585 local links/assets across 57 HTML files, and every target existed.- A Markdown target audit checked 150 relative targets and found no missing target.
AGENTNOTIFY_DOTNET_EXE="$HOME/.dotnet/dotnet" ./scripts/publish-cross.sh: produced all five portable archives (win-x64,linux-x64,linux-arm64,osx-x64, andosx-arm64) plusSHA256SUMS.txt; every checksum verified. Both macOS archives containagentnotify-menubar, the extracted binaries report the intended architecture, and strict code-signature verification passed.git diff --check: passed before the final commit.
Not verified: human visual inspection of either the AppKit menu or the new WebUI configuration card;
execution on Apple Silicon; Windows runtime/WPF behavior; or the hosted GitHub Pages and release
workflows, because this branch was not pushed. ./scripts/package.sh was not run because it requires
Windows/WSL/PowerShell; the cross-platform release archives and Unix installer regression were run
instead. The status item was not tested against real Codex or Claude quota credentials, deliberately
avoiding provider access during the implementation run.
Owner verification still outstanding
These need the repository owner and a real machine; nothing in CI can close them.
- The human WPF checks listed earlier in this file, for the settings theme and the built-in tones. No visual surface has been confirmed by a person.
- A human visual check of the new Usage page, including a narrow browser window.
- A human visual check of Live quota's Manage accounts, API accounts, and the OpenCode Go renewal control on Windows, including a narrow window. The owner has used these on the tray-hosted interface and reported them working.
- API accounts against real OpenAI and Anthropic Admin keys, Kimi, SiliconFlow, and OpenRouter; only DeepSeek has been checked with a real key.
- Apple Silicon and
terminal-notifieron macOS remain unobserved. - The provider router against a real provider, driven by a real Codex or Claude Code session, and its web page seen in a browser; also a Windows build of the tray app including it.
- Redistribution rights for the four personal MP3s in the ignored
notification-tone/folder. If they are clear, add them underassets/tones/, extendBuiltInTones.All, and record their provenance inTHIRD_PARTY_NOTICES.md.
Effort mapping families, Routing page merge, menu-bar provider marks (2026-09-19)
Three dev branches, each test-gated before a --no-ff merge:
feature/effort-family-mapping— effort mapping grouped by model family with per-model overrides under a Per model tab, family overrides stored inrouter_effort_family_overrides, and the five standard effort levels mapped on the OpenAI wire as they already were on Claude Code's. Automatic tables are identity wherever the target can spell a level, so no client's effort is silently renamed;minimaland provider-specific words pass through verbatim when supported.feature/router-settings-into-routing— the one-card Settings page folded into Routing (five Model router pages).feature/menubar-provider-logos— the Claude starburst and the OpenAI knot (Codex) as template images in the macOS quota status item and per-account rows, parsed in-process from embedded SVG path data; sourcing recorded in THIRD_PARTY_NOTICES.md.
Gates actually run on this Mac (macOS 26.6.2, x86_64, native .NET SDK 10.0.401 at
~/.dotnet/dotnet, Swift 6.3.3 CLT): the full test suite via
AGENTNOTIFY_DOTNET_EXE=… ./scripts/test.sh — 1262 passed, 0 failed — which also compiles the
Core/Api/Cli/Host assemblies. This is the first time the router-switching and effort-mapping tests
have executed anywhere. scripts/build.sh and scripts/package.sh remain Windows/WSL-only and were
not run; the Windows tray installer was not rebuilt. publish-cross.sh osx-x64 produced the archive,
and the three binaries were installed into ~/.local/bin (adhoc re-signed; the previous binaries
were kept as .agentnotify*-backup-effort-logos-*). The menu-bar glyphs were additionally smoke-tested
headlessly: both paths parse and render with pixel coverage matching their source viewBoxes.
Verified live against the owner's configuration: agentnotify health reports ok after a
launchctl kickstart -k dev.agentnotify.broker, the new agentnotify-menubar child is running, and
/ui/api/router/effort-mappings returns 7 family groups (deepseek, glm, kimi, muse, openai, qwen,
unknown) covering 28 routed models with the owner's 3 per-model overrides counted. The served
app.js no longer registers router-settings, and the served router-effort.js is the
Families/Per model build.
Not verified: the menu-bar glyphs and the Effort mapping page as actually rendered on screen (this session had no screenshot capability), the Windows/WSL build/test/package gates, a real provider request flowing through the new effort mapping, and a GitHub Actions release run.
macOS one item per selected quota account (2026-09-19)
feature/menubar-all-accounts replaced the single lowest-balance status item with one native
NSStatusItem per selected Codex/Claude account. Each item has that account's provider mark and
five-hour percentage (or --% without a current five-hour window), and each owns an independent copy
of the complete account menu. Live quota's account picker now directly controls which items appear.
Verification actually run: build-macos-menu-bar.sh compiled and adhoc-signed the Swift executable;
node --check passed for the updated Live quota module; the complete .NET suite passed 1262/1262;
and publish-cross.sh osx-x64 produced the archive. All three binaries were installed into
~/.local/bin, re-signed, and the LaunchAgent was restarted. agentnotify health returned ok, the
new agentnotify-menubar child was running, and the live projection contained 4 selected accounts,
all 4 with five-hour values, so the native client creates 4 status items. The user had already
visually confirmed the provider marks before this change, then confirmed the installed four-item
rendering works as intended.
v0.2.0-alpha.3 release preparation (2026-09-19)
The owner visually confirmed the installed macOS menu bar shows all four selected accounts correctly.
Prepared v0.2.0-alpha.3 with a dated changelog section and matching Version /
InformationalVersion; numeric assembly/file metadata remains 0.2.0.0 as required by Windows.
release-notes.sh v0.2.0-alpha.3 v0.2.0-alpha.2 produced the complete prerelease notes and compare
link.
Local release gates on the Intel Mac: the full .NET suite passed 1262/1262; the Next.js
documentation type-check and 30-page static export passed; and publish-cross.sh built all five
portable archives (win-x64, linux-x64, linux-arm64, osx-x64, osx-arm64). Every checksum in
artifacts/cross/SHA256SUMS.txt verified. Both macOS archives contain agentnotify-menubar, both CLI
and broker binaries in the Intel archive report 0.2.0-alpha.3, and the menu-bar binary passes strict
codesign verification. The Windows WPF build, 1262-test run, and installer packaging were deferred
to the tag-triggered Release workflow because they cannot run through the repository's Windows/WSL
scripts on this macOS host.
The tag-triggered Release run 12
subsequently passed both hosted jobs. Windows validated the tag/version match, built and tested the
solution, packaged the installer, generated release notes, and published the prerelease. The macOS
job then built and attached all five portable archives plus their checksum manifest. The published
v0.2.0-alpha.3 prerelease
contains the Windows installer, five portable archives, both checksum files, and the distributable
skill. The parallel Windows CI, Linux/macOS CI, and documentation deployment also passed for the
tagged commit 08d1d27.
Three-system navigation, rebuilt overview, audit follow-up (2026-09-19)
The navigation was collapsed to the three systems that actually exist — Notifications (Activity,
Delivery, Configuration), Model router, Insights — with Overview and About standing outside them.
/overview was then rewritten as one band per system over live projections.
What ran. The full .NET suite passed 1270/1270 through
AGENTNOTIFY_DOTNET_EXE="$HOME/.dotnet/dotnet" ./scripts/test.sh. The new
scripts/check-webui.sh parses every wwwroot/*.js as an ES module and passes.
What was actually looked at. The rebuilt overview was rendered in headless Chrome against the
running installed broker (port 47821) through a read-only GET-only proxy that serves the working-tree
wwwroot, and the resulting screenshots were opened and read — not merely captured. Confirmed on
screen, against live data: four metric cards; the Notifications band's 14-day column chart, busiest
projects, health grid and dead-letter warning; the Model router band's success ring and ranked
targets; the Insights band's sparkline, agent mix and tightest balances. The proxy could not mutate
anything, and the owner's installation, database, and keychain were untouched.
A bug the test suite could not see. The first render showed only "Loading AgentNotify…". The
cause was a single missing ); node --check had reported the file clean, because without a
package.json declaring "type": "module" it parses a .js file as a classic script, and script
parsing accepts nesting that module parsing rejects. The .NET tests fetch the script's text and so
could never catch it either. scripts/check-webui.sh closes that gap and was proved to fail on the
exact defect before being kept. Quota rows were also found ambiguous on screen — two read
"Claude Code · 7-day" with different values — and now carry provider, window, and account.
Gates that did not run, and why. ./scripts/build.sh cannot complete on this macOS host:
AgentNotify.App and AgentNotify.Setup are WPF and fail with NETSDK1100. ./scripts/package.sh
needs Windows PowerShell. Neither ran here, so the WPF desktop app is unbuilt and unexercised by
this change; nothing in this change touches WPF code. No Windows runtime check and no manual check
of the installed desktop UI were performed.
Audit follow-up. H1 and M3 were fixed in c83ce18 and are covered by the suite. They are
not proven on a running installation: the broker on this Mac was installed at 22:44, before
c83ce18, and no secret-protection marker exists. An earlier version of this entry cited that
broker's "secret_protection":"macOS login keychain" as evidence; that field predates the fix and
shows nothing about it. Likewise the overview render above served the working-tree wwwroot
through the proxy — the installed broker still serves the pre-change navigation and overview. M2 was decided rather than patched:
no sign-in for /ui, with the trust boundary now stated in the README. M1 and L1–L5 remain open.
Web UI restructure and control-plane site (2026-09-20)
Web UI. Notifications folded into Inbox / Delivery / Settings / Agents with tabs in an always-shown
top bar; broker status and theme moved there; router Agents and Effort mapping turned into expandable
lists; every nav icon made distinct. Full suite 1271/1271; check-webui.sh passes. Rendered in
headless Chrome against the live broker through the read-only proxy and the screenshots read: Inbox
with its tabs and counts, Effort mapping collapsed and with a row opened, router Agents collapsed and
with a row opened, and Delivery and Effort mapping at phone width. Headless Chrome lays out at no less
than 500px, so "phone width" here means 500px, not 390px. Not checked: the light theme, and clicking
through the tabs by hand — rows were opened by a preview-only script, not a click.
Site. npm run typecheck and the static export (npm run build, 32 pages) pass. The export was
served under /agent-notify/ like GitHub Pages: /docs/ now redirects to
/docs/install-with-agent/ (followed in headless Chrome), and the rebuilt landing page was
screenshotted and read. Not deployed: the Pages workflow runs on push, and nothing was pushed. npm audit reports three advisories in next, its bundled postcss, and sharp; they predate this change
and are not in the static output's runtime.
Hosted demo, feature pages, and live router diagram (2026-09-20)
Demo. /demo/ is the broker's own wwwroot, staged by scripts/build-site.sh, with two files
replaced: js/api.js becomes a shim that answers from js/demo-data.js instead of fetching, and
index.html gains a "this is invented data" ribbon. Nothing in src/ changed, so the .NET suite was
not re-run; check-webui.sh was run against both the original tree and the staged copy and passes,
and the build now asserts that the staged js/app.js is byte-identical to the shipped one and that
the staged api.js contains no fetch(.
All 17 routes were loaded in headless Chrome from the staged copy and again from the built _site
output served under /agent-notify/: every one renders, none shows a notice-danger, and none is
left on a skeleton. Overview, router Agents, and Live quota were screenshotted and read. Not
verified: clicking through the demo by hand, the light theme, and the write paths — the mutations
in demo/api.js (resolving a notification, saving a route, connecting an agent) were exercised only
by reading the code, not by driving the UI. Sound preview, file upload, phone pairing, and storing a
provider key deliberately refuse with an explanatory message rather than pretending to work.
Fixture data. Every account, project, path, model, number, and message under site/demo/data.js
is invented. No value was copied or redacted from this machine's broker, and the live API was not
read to produce it.
Site. npm run typecheck and the static export pass (35 pages, up from 32). New pages /arc/,
/channels/, and /relay/ and the rebuilt /router/ were served under /agent-notify/ and
screenshotted; the live diagrams (RouterFlow, ArcLifecycle, RelayPath) were checked to be in
step with their own pills. Step text is rendered directly rather than faded in: under headless
capture the first implementation left cards blank because no animation frame had run, which would
also have hidden the text for any reader whose animation failed to start. Not deployed at the time
of writing — the Pages workflow runs on push.
Responses item-identifier sanitising (2026-09-20)
What was diagnosed. A Codex conversation failed with HTTP 400 from the upstream naming
input[8].id and then input[10].id, the value being
rs_<24 hex>:rs_<32 hex>. Evidence gathered: the router mints no rs_ identifier anywhere
(grep over src/AgentNotify.Router), joins nothing with a colon, and on a same-wire hop
PassthroughBody.ReplaceModel rewrote only model — so the identifier came from an upstream and
was forwarded verbatim. The ledger rows confirm the failing attempts went to the chatgpt upstream
(openai_responses, codex_chatgpt, chatgpt.com/backend-api/codex), and that a 400 is recorded
as client_error, which correctly does not fail over.
What changed. A Responses passthrough now drops item identifiers outside the contract's
[A-Za-z0-9_-], removing the whole item when it is a reasoning item, and logs a warning naming
the count. Acceptable identifiers are untouched, and no other wire is affected.
Verified. Full suite 1281/1281, including six new cases covering the composite identifier, id-only stripping on a non-reasoning item, acceptable input left byte-identical, sanitising staying off unless asked, several offenders in one pass, and the acceptability predicate.
Not verified. The fix was not reproduced against the ChatGPT backend — the failing request
body is not recorded anywhere (provider bodies are deliberately never stored), so the diagnosis is
reasoned from the upstream's own error text rather than from a captured request. It is not proven
that dropping the reasoning item makes that conversation succeed: a Responses upstream can also
reject a function_call whose preceding reasoning item is absent, which would surface as a
different 400. Keeping the identifier, however, fails every time, so this cannot be worse.
Installed on this Mac (2026-09-20 12:19 local). osx-x64 published from 645bff8, archive
checksum verified, the three running binaries backed up to
~/.local/bin/.*-backup-responses-id-fix-20260920-1219, replaced, and re-signed (codesign -v
passes on all three). LaunchAgent reloaded: agentnotify health returns ok, pid 15810, API on
127.0.0.1:47821, menu bar pid 15869. The secret-protection marker still reads macos-keychain
and this start logged "protected with AES-GCM under the macOS login keychain", so the upgrade did
not repeat the store misselection of the previous night.
The installed binaries could not be confirmed by string search — a compressed single-file bundle
hides managed literals — so the evidence is that each is byte-identical to the published artifact
apart from the signature codesign rewrote, and that both differ from the backup taken minutes
earlier. The fix itself is still unexercised against a real upstream. The next Codex turn is
the proof; if one occurs, the broker log will now say how many replayed items were dropped, which
also distinguishes this fault from a different 400.
Still open, unexplained. A separate 400 at 03:07:39 UTC went to deepseek
(openai_chat, a translated hop, zero bytes streamed). A translated request carries no input[]
identifiers, so it is a different fault and is not addressed here.
Failing over on an upstream 400 (2026-09-20)
Why. A combo of four targets stopped at its first 400 and handed the agent
Upstream returned HTTP 400 (invalid_request_error) with three targets untried. Treating any 4xx
as the request's fault is right for a single-provider client and wrong here: each target is sent its
own rendering — different wire, translation, identifier rules, accepted fields — so one provider's
refusal does not predict the next one's.
What changed. 400 joins the fail-over statuses, as upstream_rejected, and is the one that
does not cool the target down, since it describes the request rather than the provider's health.
A translation failure for one target now ends that attempt instead of the whole request, so a target
on a wire that can carry the request still gets its turn. A request the router itself cannot read is
still rejected before any target is tried, and a failure after the first byte has reached the client
is still never retried.
Verified. Full suite 1283/1283. The pre-existing Upstream400_NotRetried pinned the old
behaviour and was rewritten as Upstream400_FailsOverToTheNextTarget; two cases were added for a
400 on the last target still being reported, and for the target staying warm afterwards.
Not verified. Not exercised against a real provider chain — the tests use a fake handler.
The deepseek 400 that prompted this is still undiagnosed: it is a translated openai_chat
hop, adjacent assistant messages are already coalesced by MergeAdjacentAssistants, and reasoning
items are dropped during decoding, so neither of the two obvious causes fits. The provider's error
message is deliberately never stored, so the body that offended it was not recoverable. Failover now
routes around it rather than explaining it.