Repository navigation
Conversation
The User DO stores the one subscription id; the proxy only signs and forwards. One UserNotification type and one deliver replace the per-kind methods. Callback turns notify only when they need permission, and a rejected action counts as completed.
…tration Open task uses the router instead of a page reload. The native registration request runs once per app mount, and not at all when an id was injected.
isSystemPackage replaces the per-script lists, and preview configs keep the backend's committed NOTIFICATION_DELIVERY binding.
The proxy only isolated the install signing key, and the backend already holds secrets of the same weight (CF_AI_GATEWAY_API_TOKEN). The User DO now signs its two requests directly from four optional env values the deploy service injects; without them push is unavailable. This drops the system worker kind, its manifest, preview and dev-server plumbing, the shared delivery contract and the NOTIFICATION_DELIVERY binding. Docs describe backend signing and the injected values.
Callback and spawned-agent turns both start with a gadget as the initiator and finish unattended, so only user-started turns announce completion. Permission requests still notify for every turn.
Preview:
|
| // Only a person waits on their own turn: callbacks and spawned agents finish unattended. | ||
| if (meta && (awaitingPermission || initiator.type === "user")) { |
There was a problem hiding this comment.
🟡 Resumed turns send false completion alerts
When an approval resumes an agent, initiator.type is user, so notification announces another completed task. #resumeSuspendedAgent starts that continuation with the approver's profile, not a new user request.
Learn more
Agent turns can begin from a new human request or from a permission decision on an existing task. The notification gate uses the author type alone to decide whether to announce a completed task. #resumeSuspendedAgent starts permission-decision continuations with the approving user's profile, so they pass that gate. A completion alert then appears for a continuation the recipient did not initiate as a fresh task.
Example: Alice requests a connection, then Bob accepts it. Bob's acceptance resumes the agent with Bob as its initiator; when that turn finishes, Bob receives a completed-task alert even though his action was only an approval.
Recommended fix: Carry a separate user-request-versus-continuation signal through startAgent and its persistent ActiveAgentRecord, and gate completion notifications on that signal. Preserve permission alerts for unattended continuations.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
This also breaks the PR’s stated recipient ownership in a collaborative workspace: the original requester gets the permission notification, but if another collaborator approves, the continuation is attributed to the approver and its completion goes there instead. The persisted state should distinguish a fresh user request from a continuation and retain the task’s notification recipient across the suspension.
|
Review: 1 findings. Posted 1 actionable inline comment. |
The User DO keeps one subscription per device, keyed by the install-scoped device key the notification service returns with it, so a device that registers again replaces its own entry. Unacknowledged notifications go to every device; a subscription is dropped only when the service reports it device_gone or invalid_subscription. Requires MR !416 to return deviceKey from POST /v1/subscriptions.
|
Review: 1 findings. Posted 1 additional actionable inline comment. |
|
Two lower-priority hardening/documentation follow-ups from my review:
|
The turn's outcome was latched while its last step ran, so a rejection made while that step was persisting still notified "needs permission". Read the current turn's pending connection requests and awaited actions in the turn's finally instead, which also notifies completion for a turn a person's approval resumed.
"Open task" now navigates with ?showChat, and the editor switches a single-pane layout from the gadget pane to the chat, then drops the parameter.
|
Review: 0 findings. No additional actionable findings beyond the existing review threads. The current changes address the earlier browser-navigation and pending-decision race findings. |
Eval resultsVerdict: ⚪ Unchanged. No task moved beyond what 10 runs can tell apart from noise.
Failed checks
|
🔬 Eval runs reviewPerformancePass-rate changes stayed within noise: change-calendar went 90% → 100%, while chess and incident-desk went 100% → 90%; worker-logs passed every completed run but had one candidate infrastructure error, preventing comparison. Mean costs were approximately $0.0280 → $0.0286, $0.0552 → $0.0475 and $0.0288 → $0.0217 respectively, but run costs overlap and neither cache hits nor cache breaks changed significantly. Chess remained the largest step consumer at 59.4 → 51.1 model steps; untested generated code cost the candidate two runs, while malformed edits and repeated ambiguous replacements wasted steps on both sides. 🟡 VERDICT: INCONCLUSIVEThe scored failures are unrelated generated-code mistakes, and the diff leaves agent prompts, tools and evals unchanged, but the candidate-only verification disconnect lacks enough diagnostic evidence to establish whether the new turn-end notification path contributed.
TriageFailure modes
Tool errors
What to do
|
When an agent turn finishes, or stops to ask for permission, the person who started it now hears about it: a toast in their open Workshop tabs, or a push to each of their devices through the Cloudflare OS app if no tab picks it up within 3 seconds. Replaces #650 and #676; the first six commits are @deregtd's from #650, and the rest rework them based on review there.
How it works:
UserNotification(completed / needs permission) to the initiator's User DO.docs/notifications.mdhas the full flows and what crosses the boundary.Design decisions:
CF_AI_GATEWAY_API_TOKEN, and gadget code never sees backend env.device_gone,invalid_subscription). Any other failure keeps it, so a signing misconfiguration can't wipe everyone's devices.Push needs the internal deploy-service and notification-service change (MR !416): the deploy service injects
NOTIFICATION_SERVICE_URL,CFOS_INSTALL_ID,CFOS_INSTALL_KEY_IDand the secretCFOS_INSTALL_PRIVATE_KEYinto the backend, andPOST /v1/subscriptionshas to returndeviceKey(64 lowercase hex, stable per installation and device) next tosubscriptionId. Neither side has shipped, so no migration; they land together.Open with !416: the service caps registration at 10 devices per Cloudflare user (
MAX_DEVICES_PER_OWNER); an 11th device's app registration fails with "Device limit reached" before it reaches this install. That's central policy and nothing here changes it, but it conflicts with "any number of devices", so it needs a decision there.Follow-ups (deferred): retry native registration after a transient failure, unregister on sign-out, and shared contract fixtures with the service.
Known limitation: in a shared workspace, when a collaborator approves an action, the completion goes to the approver, not the person who started the task.