a commander can say something in its own words
asked whether the voice-memos commander could send a notification, the
answer was no, three times over. the platform skill gives it the prefix
/notify and not the route. the route wants a catalogue code, not a title
and a message. and of the six codes that exist, none means "a commander has
something to tell you" — the nearest renders "Test notification".
so: officer/core:commander, one entry rather than one per commander, with
the commander travelling as {id}. arming a new one needs no catalogue
change and no app resync.
and `title`/`body` on the notification itself, which override the
catalogue. this is a deliberate exception to the rule the whole design
rests on — content does not travel — and it is the owner's decision, made
2026-08-24. a commander's output is one sentence only it could write; there
is no entry to render it from, and "1 item" is not worth waking a phone
for. sendoff.landed already carries the same relaxation for the same kind
of reason.
the control sits at the PRODUCER, not the transport. the commander's
CLAUDE.md now says what may go in those two fields and what may never — and
that a notification is an interruption, so send few. this file cannot judge
what is sensitive and does not try.
`title` is the opt-in, not `body`. the existing smuggle test passes a stray
`body` and asserts it does not reach the wire; a body that travelled on its
own would have silently defeated it. pinned on both transports.
FCM gets a `notification` block only for producer text — data-only is what
lets the app compose from its catalogue, and text with no entry to compose
from would arrive and show nothing.
worth recording: there is NO Notification Service Extension, in this repo
or the mobile one. mutable-content: 1 is set for something never built, so
aps.alert is what the phone actually shows and this works with no app
release. i said the opposite an hour ago, from the comments rather than
from looking.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -123,6 +123,26 @@ const CORE_ENTRIES: Record<string, NotificationEntry> = {
|
||||
route: 'officer://jobs/{id}',
|
||||
thread: 'officer/core:job',
|
||||
},
|
||||
/*
|
||||
A commander with something to say.
|
||||
|
||||
ONE entry rather than one per commander, and the commander travels as `{id}` — so arming a new
|
||||
commander needs no catalogue change and no app resync. `{id}` is the commander's row id, which is what
|
||||
`officer://command-hq/{id}` opens.
|
||||
|
||||
This entry is what a commander sees when it sends nothing but a code and an id: "Voice Memos" would be
|
||||
a lie without knowing which one, so the generic is honest and the deeplink does the rest. In practice a
|
||||
commander supplies its own `title` and `body`, which override this — that is the exception described in
|
||||
`sidecar/notify/types.ts`, and the whole reason a commander can notify usefully at all. The entry still
|
||||
matters: it is the fallback when it doesn't, and it is what makes the thread group per commander rather
|
||||
than piling every commander into one stack.
|
||||
*/
|
||||
'officer/core:commander': {
|
||||
title: 'Commander',
|
||||
target: 'mobile',
|
||||
route: 'officer://command-hq/{id}',
|
||||
thread: 'commander:{id}',
|
||||
},
|
||||
};
|
||||
|
||||
/** FNV-1a, 32-bit. Small, stable across processes, and not a security boundary. */
|
||||
|
||||
Reference in New Issue
Block a user