feat(push): carry a notification's workspace server to the phone (#26516)

A desktop relays alerts for agents on its paired servers, but the push named
only the server's worktree, so a tap opened it on the desktop and bounced.
The gateway accepts an optional runtime execution host and forwards it.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
Jinwoo Hong
2026-10-11 02:09:36 -04:00
committed by GitHub
parent 92fb883cf2
commit 43645e5274
3 changed files with 46 additions and 1 deletions
@@ -6,6 +6,7 @@ export type PushOrcaData = {
hostFingerprint: string
worktreeId?: string
paneKey?: string
executionHost?: string
notificationId?: string
notificationSeq: number
notificationEpoch: string
@@ -54,6 +55,9 @@ export function buildPushDelivery(input: {
hostFingerprint,
...(notification.paneKey === undefined ? {} : { paneKey: notification.paneKey }),
...(notification.worktreeId === undefined ? {} : { worktreeId: notification.worktreeId }),
...(notification.executionHost === undefined
? {}
: { executionHost: notification.executionHost }),
...(notification.notificationId === undefined
? {}
: { notificationId: notification.notificationId }),
@@ -0,0 +1,35 @@
import { expect, it } from 'vitest'
import { PushNotificationSchema } from '@orca-cloud/push-contract'
import { buildPushDelivery, orcaDataStrings } from './push-delivery-message.js'
const base = {
notificationSeq: 1,
notificationEpoch: 'epoch',
source: 'agent-task-complete',
agentState: 'finished',
title: 'Done',
body: '',
worktreeId: 'repo-1::/srv/work'
}
it('carries the workspace server to APNs and FCM, and accepts older messages without one', () => {
const executionHost = 'runtime:env%201'
for (const extra of [{}, { executionHost }]) {
const delivery = buildPushDelivery({
notification: PushNotificationSchema.parse({ ...base, ...extra }),
hostFingerprint: 'host',
registrationId: 'phone',
expiresAt: Date.now() + 300000
})
const expected = 'executionHost' in extra ? executionHost : undefined
expect(delivery.orca.executionHost).toBe(expected)
expect(orcaDataStrings(delivery.orca).executionHost).toBe(expected)
}
})
it('accepts only runtime execution hosts', () => {
const invalid = ['local', 'ssh:box', 'runtime:', 'runtime:a|b', `runtime:${'x'.repeat(2048)}`]
for (const executionHost of invalid) {
expect(PushNotificationSchema.safeParse({ ...base, executionHost }).success).toBe(false)
}
})
@@ -27,7 +27,13 @@ export const PushNotificationSchema = z
title: z.string().min(1).max(PUSH_LIMITS.titleMaxChars),
body: z.string().max(PUSH_LIMITS.bodyMaxChars),
worktreeId: z.string().min(1).max(2048).optional(),
paneKey: z.string().min(1).max(2048).optional()
paneKey: z.string().min(1).max(2048).optional(),
// The workspace's server; mirrors the app's runtime host id (cannot import src/shared).
executionHost: z
.string()
.max(2048)
.regex(/^runtime:[^|\s]+$/)
.optional()
})
.strict()
.refine(