Skip to content

feat(mobile): show scheduled sessions across the list, detail, and glanceables - #6691

Merged
iscekic merged 27 commits into
mainfrom
kwf/app-scheduled-session-state-glanceables-2241
Sep 28, 2026
Merged

iscekic merged 27 commits into
mainfrom
kwf/app-scheduled-session-state-glanceables-2241

Conversation

@iscekic

@iscekic iscekic commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Changelog for users

  • A session scheduled to wake later now reads scheduled wherever it appears, instead of idle.
  • The session list shows the scheduled label with its wake time; a scheduled session with no wake time shows the label alone.
  • The session detail connection row reads Scheduled.
  • The iOS Live Activity, the iOS widget, the Android widget, and the Android ongoing notification show the scheduled count and the wake time.
  • An unrecognized status renders as running, never as idle, and does not crash.
  • The scheduled reading survives an app reload, and a session that woke reads as running again.

Changelog for maintainers

  • Status vocabulary: GlanceableStatusKind and GlanceableCountKind add scheduled, ranked between running and idle; glanceableStatusKind maps an unrecognized status to running, never idle.
  • Snapshot contract: the shared snapshot and push-data schemas add scheduled (default 0) and scheduledAt (default null), so payloads and persisted snapshots from before this change still parse.
  • Wire contract: scheduledAt rides heartbeat rows, v2 rows, and both status-updated payload shapes; scheduled with no scheduledAt is representable end to end.
  • Ingest is deliberately permissive: the session-status enums became permissive strings, so an unknown status is relayed instead of dropping the item or event. Review this loosening first.
  • Eligibility: isStartableGlanceableWork now includes scheduled, so a scheduled-only scope raises the iOS Live Activity and push-to-start, and the widget no longer offers New agent for it.
  • Session detail precedence: resolveSessionConnectionDisplay reports scheduled only over an up transport; connecting, reconnecting, and lost still win.
  • Android widget: the tightest 2x1 cell folds the wake into its label, while stack and large draw a reserved wake slot and the row bucket draws a wake line; new spacing and wake-width constants exist for that cell.
  • timeAgo now formats a future instant forward (in 2 hours); check any caller that passes a future timestamp.

E2E proof

Owner request

Surface: the mobile app (apps/mobile), the session list and every glanceable.

A session that is scheduled to wake later, and does nothing now, must read as scheduled everywhere the user meets it. Today it reads as idle.

A companion item in the kilocode repository adds that status at the source. The wire contract is fixed, so read it here and do not wait for that pull request:

  • The status type value is the literal scheduled.
  • The wake time rides in the same status payload as an ISO-8601 string field named scheduledAt.

Requirements:

  • Session list: a scheduled session shows scheduled and the wake time, not idle.
  • Session detail: the connection indicator shows scheduled. Today the kinds are idle | autocommit | error | disconnected | interrupted (apps/mobile/src/components/agents/session-connection-indicator-state.ts).
  • Every glanceable shows scheduled and the wake time: the iOS Live Activity, the iOS widget, the Android notification, and any other glanceable body.
  • Status parsing must not drop an unknown type. An unrecognized status must never crash and must never render as idle.
  • If the CLI reports scheduled with no scheduledAt, show scheduled with no time.
  • The state survives an app reload.

Proof: screenshots of the session list, the session detail and every glanceable in the scheduled state. Also show one session that woke, back to its running state.

[p14] ux-check: iOS Live Activity (phone banner/expanded) and the iOS medium Home Screen widget show the scheduled row with a wake time. — Platform: android (emulator-5604) — iOS Live Activity and the iOS widget are not placeable on this android-only host, so the android glanceables stand in: the app-posted ongoing notification body text="1 Working, 2 Scheduled wakes in 2 hours" (p14-shade-read.xml, still p14-shade.png) and the home-screen Active Agents widget content-desc="1 Working, 2 Scheduled, Open agents" (p14-widget.txt, still p14-widget.png), both agreeing with the same-run Agents list's 2 SCHEDULED rows incl. 'SCHEDULED · 4:26 AM' (p14-list.log); scheduled state seeded by the live fake-CLI heartbeat fixture…

[p14] ux-check: iOS Live Activity (phone banner/expanded) and the iOS medium Home Screen widget show the scheduled row with a wake time. — e2e-mobile-app/p14-widget.png

[e5] scheduled state survives an app reload — android (emulator-5604): after a cold launch the Agents list still shows 'Greeting, Scheduled, ... and 9:00 AM' / 'SCHEDULED · 9:00 AM'; SCENE e5 OK in e5-coldopen.log, still e5.png.

[e5] scheduled state survives an app reload — scripted-shard5/e5.png

[e7] Session detail: the Connection row reads Scheduled; needs:seed the scheduled live session and open its detail (state session-open) — Platform: android (emulator-5554). The declared state session-open is a harness gap on this device (state.sh exits rc=2 with 'could not obtain a session id for 'session-open'' in e7-state-attempt.log) even with a live session, so per the scenario's own seed instruction I seeded the scheduled live session with a temporary CLI heartbeat stub (stub-shard7-sched.js; e7-inject.log 'SEND status scheduled scheduledAt 2026-09-24T00:32:48.505Z') and drove from the list; e7-scene.log has 'SCENE e7 OK' with 'session-context-sheet-connection' and 'Scheduled' (same-moment e7-detail.txt/e7-detail.png).…

[e7] Session detail: the Connection row reads Scheduled; needs:seed the scheduled live session and open its detail (state session-open) — prior/e7-detail.png

[p1] Session list (Agents tab) shows a scheduled session as SCHEDULED with its wake time; needs:seed a live session whose status is scheduled and whose scheduledAt is a near-future ISO-8601 timestamp — Re-ran replay-p1.json OK; digest line 'android.widget.TextView SCHEDULED · 2:10 AM' beside the no-wake row 'android.widget.TextView SCHEDULED'; state seeded by the stub CLI heartbeat (p1-sched-stub.log); android emulator-5554; screenshot p1.png.

[p1] Session list (Agents tab) shows a scheduled session as SCHEDULED with its wake time; needs:seed a live session whose status is scheduled and whose scheduledAt is a near-future ISO-8601 timestamp — e2e-mobile-app/p1.png

[e4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions) — android emulator-5554; e4-behavior.log carries 'SCENE e4 OK' and its digest lists only 'Untitled session, Idle, kwf/app-scheduled-session-state-glanceables-2241, CLOUD, and 1 minute ago' with no SCHEDULED row (e4.png, e4-home.png); run on the worktree-scoped empty account e2e-empty-app-scheduled-session-state-glanceables-2241@example.com with one idle session seeded via needs-input.sh because the default account's signed-in-home carried other shards' live scheduled stub rows (e4-scene.log shows 'SCHEDULED · 12:01 AM'), then signed back in as the default account; the parked 'absent SCHEDULED'…

[e4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions) — scripted-shard4/e4.png

[p14] ux-check: iOS Live Activity (phone banner/expanded) and the iOS medium Home Screen widget show the scheduled row with a wake time.

[p14] ux-check: iOS Live Activity (phone banner/expanded) and the iOS medium Home Screen widget show the scheduled row with a wake time. — e2e-mobile-app/p14-shade.png

[e11] Android widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still — Android/emulator-5604. Live scheduled session seeded over the session-ingest CLI websocket; the ActiveAgents widget was placed on the launcher (fixture) and its hierarchy reads content-desc="1 Working, 2 Scheduled, 1 Idle, Open agents", agreeing with its source the worker's /api/sessions/active counts {"idle": 1, "scheduled": 2, "busy": 1} (e11-count-source.log); the app Home glanceable reads e11 scheduled wake … SCHEDULED · 9:00 AM (e11-scene-run.log). The widget renders to a bitmap, so its wake-time text is judged from the capture e11-widget-final.png by the visual reviewer. No UX…

[e11] Android widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still — e11-widget-final.png

[e14] ux-check: glanceable shows the scheduled row with a wake time — android (emulator-5604); iOS Live Activity / iOS medium widget are not on this host, so proved the android glanceables: app-posted ongoing notification 'Active agents / 1 Working, 1 Scheduled wakes in 12 hours, 1 Idle' (e14-shade-read.xml, still e14-shade.png) and home-screen Active Agents widget content-desc '1 Working, 1 Scheduled, 1 Idle, Open agents' (e14-widget.txt, still e14-widget.png); counts agree with the Agents-list source in the same run (e5-coldopen.log).

[e14] ux-check: glanceable shows the scheduled row with a wake time — e14-shade.png

[e19] ux-check: Relaunch the app (or go offline and back) and confirm the scheduled row still reads scheduled with its wake time. — android emulator-5554: after a cold relaunch the Agents list still shows both scheduled rows (e19-after.log: 'android.widget.TextView SCHEDULED tappable [805,565][957,602]' and 'android.widget.Button Scheduled stored row 2241, Scheduled, and CLI tappable [0,691][1080,851]'; e19-scheduled-rows.log: 'ses_sched_stored_2241|scheduled|Scheduled stored row 2241'), so the status survives the reload (screenshots e19-before-list.png, e19.png); the wake-time variant was not exercisable because no fixture in this build emits scheduledAt (CLI 7.7.5 has no scheduled support, needs-input.sh rejects…

[e19] ux-check: Relaunch the app (or go offline and back) and confirm the scheduled row still reads scheduled with its wake time. — e19-before-list.png

[e13] ux-check: Session detail: opening the context sheet for a scheduled session shows the Connection row reading Scheduled. — android emulator-5554; e13-context.txt (hierarchy of the context sheet, still e13-context.png) carries text="CONNECTION" and text="Scheduled" for the scheduled CLI session ses_schedE13StoredRow224100000, seeded with status='scheduled' and driven live by a heartbeat stub (the mechanism another shard used for its list rows; the e2e index has no scheduled-session fixture); the list digest e13-scene.log shows 'e13 scheduled detail, Scheduled, kwf/app-scheduled-session-state-glanceables-2241, CLOUD, and 9:00 AM' and the detail loaded its empty state (e13-detail.png, e13-list.png); the scene call…

[e13] ux-check: Session detail: opening the context sheet for a scheduled session shows the Connection row reading Scheduled. — e13-context.png

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake. — Platform: android (emulator-5606). Same temporary CLI stub, same row ses_schedE13StoredRow224100000 (e18-stub.log flips 'SEND status scheduled scheduledAt 2026-09-24T00:01:44.459Z' to 'SEND status busy scheduledAt -'). Before: list row 'e13 scheduled detail, Scheduled, ... and 12:01 AM' with 'SCHEDULED · 12:01 AM' (e18-before.txt), session context sheet read 'CONNECTION' / 'Scheduled' (e18-detail-scheduled.txt), widget '1 Scheduled, Open agents' (e18-widget-before.txt), notification '1 Scheduled wakes in 2 hours' (e18-shade-before.txt). After: the same row reads 'e13 scheduled detail…

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake. — e18-shade-before.png

[e13] ux-check: Session detail: opening the context sheet for a scheduled session shows the Connection row reading Scheduled.

[e13] ux-check: Session detail: opening the context sheet for a scheduled session shows the Connection row reading Scheduled. — e13-list.png

[e9] platform:ios iOS widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still — Platform: android (emulator-5606) — this host is android-only so the iOS widget family is not placeable and the Android glanceables are the surfaces under test. Placed 'Active Agents' widget on the launcher read content-desc="1 Needs input, 1 Scheduled, Open agents" and the same run's Kilo ongoing notification read text="1 Needs input, 1 Scheduled wakes in 2 hours" (e9-widget-live.txt, e9-shade-live.xml) while the Agents list showed the source row (e9-list.log: 'e18 wake session, Scheduled, kwf/app-scheduled-session-state-glanceables-2241, CLOUD, and 11:57 PM' with 'SCHEDULED · 11:57 PM'), so…

[e9] platform:ios iOS widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still — e9-widget-live.png

[e5] scheduled state survives an app reload

[e5] scheduled state survives an app reload — e5.png

[e4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions)

[e4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions) — e4-home.png

[e16] ux-check: Android widget (large/stack and short/row sizes) and the Android ongoing notification show the scheduled count with the wake time. — Platform: android (emulator-5554). The 'Active Agents' widget on the launcher reads '1 Scheduled, Open agents' (e16-widget.txt, still e16-widget.png), agreeing with its source /api/sessions/active 'counts={"scheduled": 1}' (e16-count-source.log) and the same-run stub heartbeat (e16-inject.log); the Kilo ongoing notification reads '1 Scheduled wakes in 2 hours' (e16-shade.txt, still e16-shade.png). The launcher's widget picker offers one size (2x1), so the large/stack vs short/row split has no separate Android placement here and the widget body is an image (rn_widget_image_light) whose drawn…

[e16] ux-check: Android widget (large/stack and short/row sizes) and the Android ongoing notification show the scheduled count with the wake time. — e16-shade.png

[e8] platform:ios iOS Live Activity shows Scheduled and the wake time; needs:seed the scheduled live session so the activity raises, then inspect the Lock Screen / Notification Center still — Android/emulator-5604 (host is android-only; the iOS Live Activity has no android surface, so the app's own Android ongoing notification is the android glanceable). state.sh signed-in-home reported STATE HIT; replay-e8.json reported SCENE e8 OK with the Home LIVE NOW row e13 scheduled detail, Scheduled, kwf/app-scheduled-session-state-glanceables-2241, CLOUD and eyebrow SCHEDULED (e8-scene-run.log); the shade dump shows the app's own ongoing notification 1 Scheduled wakes in 12 hours, 1 Idle (e8-shade.txt, e8-shade.png), agreeing with that same run's Home list (one scheduled, one idle).…

[e8] platform:ios iOS Live Activity shows Scheduled and the wake time; needs:seed the scheduled live session so the activity raises, then inspect the Lock Screen / Notification Center still — e8-shade.png

[e19] ux-check: Relaunch the app (or go offline and back) and confirm the scheduled row still reads scheduled with its wake time.

[e19] ux-check: Relaunch the app (or go offline and back) and confirm the scheduled row still reads scheduled with its wake time. — e19.png

[e17] ux-check: Unknown status renders running, never idle, no crash — Android/emulator-5604. A session reporting the out-of-vocabulary wire status teleported was created by the verification stub stub-cli-states-2241.js (temporary stub in the evidence dir, reused from shard 3; it opens a CLI connection on the session-ingest websocket because needs-input.sh set rejects unknown statuses and its ingest POST returns HTTP 500, obstacle needs-input-arbitrary-status). The Agents live list renders S3 teleported status, Working, kwf/app-scheduled-session-state-glanceables-2241, and CLOUD — the running state, never idle, with no crash (e17-list.xml, e17-list.png). The…

[e17] ux-check: Unknown status renders running, never idle, no crash — e17-widget.png

[e14] ux-check: glanceable shows the scheduled row with a wake time

[e14] ux-check: glanceable shows the scheduled row with a wake time — e14-widget.png

[e16] ux-check: Android widget (large/stack and short/row sizes) and the Android ongoing notification show the scheduled count with the wake time.

[e16] ux-check: Android widget (large/stack and short/row sizes) and the Android ongoing notification show the scheduled count with the wake time. — e16-widget.png

[e4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions)

[e4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions) — e4.png

[e12] ux-check: scheduled session row — android emulator-5606; live Agents list from stub-cli-states-2241.js shows 'Scheduled wake, ... SCHEDULED · 11:24 PM' with the wake time and no relative-time meta, and 'S3 scheduled no time, Scheduled, ...' with 'SCHEDULED' and no time and no 'Invalid Date' in e12.log; no user-outcome UX-DEFECT observed (captures e12.png left for the visual reviewer).

[e12] ux-check: scheduled session row — e12.png

[e10] Android ongoing notification shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the notification shade still — The notification shade dump (e10.txt, still e10.png) carries the app's 'Active agents' notification with body '3 Scheduled wakes in 46 minutes, 1 Idle', and the app's list in the same run shows 3 rows badged 'SCHEDULED' (e10-list.log), so the count matches its source; proved via the fake-CLI scheduled fixture plus a foreground refresh since Android has no local FCM push.

[e10] Android ongoing notification shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the notification shade still — e10.png

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake.

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake. — e18-list-scheduled.png

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake.

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake. — e18-detail-before.png

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake.

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake. — e18-list-working.png

[e15] ux-check: iOS small and large Home Screen widgets show Scheduled and the wake time (per the request that every glanceable carries the wake). — Platform: android (emulator-5606) — this host is android-only, so the iOS small/large widget families are not placeable and the Android home-screen widget is the glanceable under test. The 'Active Agents' widget is on the launcher (2x1, the only size this launcher offers) and its label reads '1 Working, 1 Scheduled, 1 Idle, Open agents' (e15-widget-home.txt), i.e. the scheduled row is rendered; the same run's Android glanceable body carries the wake: the ongoing notification reads '1 Working, 1 Scheduled wakes in 12 hours, 1 Idle' (e15-shade.txt). Captures e15-widget-home.png and…

[e15] ux-check: iOS small and large Home Screen widgets show Scheduled and the wake time (per the request that every glanceable carries the wake). — e15-shade.png

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake.

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake. — e18-widget-after.png

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake.

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake. — e18-detail-connected.png

[e3] An unrecognized status (teleported) never renders as Idle — android emulator-5606; live Agents list from stub-cli-states-2241.js shows 'S3 teleported status, Working, kwf/app-scheduled-session-state-glanceables-2241, and CLOUD' (unknown status rendered as running, no 'Idle' in that row) while a real idle row still shows 'Idle' in e3.log; no user-outcome UX-DEFECT observed (captures e3.png left for the visual reviewer).

[e3] An unrecognized status (teleported) never renders as Idle — e3.png

[e15] ux-check: iOS small and large Home Screen widgets show Scheduled and the wake time (per the request that every glanceable carries the wake).

[e15] ux-check: iOS small and large Home Screen widgets show Scheduled and the wake time (per the request that every glanceable carries the wake). — e15-widget-home.png

[e17] ux-check: Unknown status renders running, never idle, no crash

[e17] ux-check: Unknown status renders running, never idle, no crash — e17-list.png

[e13] ux-check: Session detail: opening the context sheet for a scheduled session shows the Connection row reading Scheduled.

[e13] ux-check: Session detail: opening the context sheet for a scheduled session shows the Connection row reading Scheduled. — e13-detail.png

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake.

[e18] ux-check: After the scheduled session wakes and runs, the list row shows the running glyph, the detail Connection reads Connected, and the widget/notification drop the scheduled count and wake. — e18-widget-before.png

[p11] Android widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still — Platform: android (emulator-5604). Start state signed-in-home; the placed Active Agents widget hierarchy p11-widget.txt reads 1 Working, 4 Scheduled, Open agents, agreeing with the live list it counts in p11-scene.log, which holds SCENE p11 OK with rows e2 scheduled no time, Scheduled, e11 scheduled wake, Scheduled, S3 scheduled no time, Scheduled, S3 scheduled wake, Scheduled and S3 teleported status, Working; p11-scene.log also shows the scheduled row with its wake (e11 scheduled wake, Scheduled ... 9:00, SCHEDULED ·). The compact cell draws the wake into the widget…

[p11] Android widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still — p11home.png

[p4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions) — android emulator-5554; p4-behavior.log carries 'SCENE p4 OK' with the Agents-list digest 'Untitled session, Idle, kwf/app-scheduled-session-state-glanceables-2241, CLOUD, and 3 minutes ago' and 0 SCHEDULED eyebrow nodes (raw p4-list.longrun.log, hierarchy p4-list.txt, capture p4-list.png); run on the worktree-scoped empty account e2e-empty-app-scheduled-session-state-glanceables-2241@example.com with one idle session seeded via needs-input.sh because the default account's list holds other shards' live Scheduled rows, then signed back in as the default account; no UX-DEFECT observed in the…

[p4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions) — p4.png

[p2] Session list shows scheduled with NO time when the payload omits scheduledAt; needs:seed a live session whose status is scheduled and which carries no scheduledAt — Platform: android (emulator-5604). Start state signed-in-home (STATE HIT signed-in-home, state-run.log); a stub CLI heartbeat (stub-shard2-scheduled.js, OPEN stub-shard2-... in stub-shard2.log) seeded a scheduled session with no scheduledAt and one with a 09:00Z wake; p2-scene.log holds SCENE p2 OK with the row e2 scheduled no time, Scheduled and eyebrow SCHEDULED (no time), beside e11 scheduled wake, Scheduled with SCHEDULED · and 9:00; no UX defect on the Agents list (capture p2.png for the visual reviewer).

[p2] Session list shows scheduled with NO time when the payload omits scheduledAt; needs:seed a live session whose status is scheduled and which carries no scheduledAt — p2.png

[p15] ux-check: glanceable widget shows Scheduled and the wake time — Android Active Agents widget content-desc="1 Working, 4 Scheduled, Open agents" (p15-widget.txt) beside the same-run Agents list's 4 SCHEDULED rows incl. SCHEDULED · 4:26 AM and SCHEDULED · 9:00 AM (p15-list.txt) - counts agree, scheduled not rendered as idle; widget wake-time pixels are judged by the visual reviewer from p15-widget.png; platform android.

[p15] ux-check: glanceable widget shows Scheduled and the wake time — p15-widget.png

[p11] Android widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still

[p11] Android widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still — p11-widget.png

[p16] ux-check: Android widget (large/stack and short/row sizes) and the Android ongoing notification show the scheduled count with the wake time. — Platform: android (emulator-5554). The Active Agents widget on the launcher reads '1 Working, 1 Scheduled, 1 Idle, Open agents' (p16-widget.txt, still p16-widget.png), agreeing with the same-run app live list 'P7 scheduled wake' / 'SCHEDULED · 5:02 AM' plus one Working and one Idle row (p16-source.txt) fed by the same-run stub heartbeat (p7-inject2.log); the Kilo ongoing notification reads '1 Scheduled wakes in 2 hours, 1 Idle' (p16-shade.txt, still p16-shade.png). The launcher's picker offers one size (WidgetCell '2 wide by 1 high'), so the large/stack branch has no separate Android…

[p16] ux-check: Android widget (large/stack and short/row sizes) and the Android ongoing notification show the scheduled count with the wake time. — p16-shade.png

[p16] ux-check: Android widget (large/stack and short/row sizes) and the Android ongoing notification show the scheduled count with the wake time.

[p16] ux-check: Android widget (large/stack and short/row sizes) and the Android ongoing notification show the scheduled count with the wake time. — p16-widget.png

[p12] ux-check: Session list: a scheduled session's row shows the Clock glyph and SCHEDULED · <wake time> (no idle ring, no relative-time meta); a scheduled session with no scheduledAt shows… — android emulator-5606; same fake-CLI stub seeds status=scheduled with and without scheduledAt; p12-scene.log prints 'SCENE p12 OK' and the digest shows 'android.widget.TextView SCHEDULED · 4:26 AM' on row 'S3 scheduled wake' and 'android.widget.TextView SCHEDULED' (no time) on row 'S3 scheduled no time', with no 'Invalid Date' in the digest; the diff adds formatScheduledWake (session-list-helpers.ts) for this eyebrow. Clock glyph/idle-ring/relative-meta are visual and belong to the visual reviewer; p12.png captured. No functional UX defect.

[p12] ux-check: Session list: a scheduled session's row shows the Clock glyph and SCHEDULED · <wake time> (no idle ring, no relative-time meta); a scheduled session with no scheduledAt shows… — p12.png

[p4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions)

[p4] Empty: with no scheduled session the list shows no SCHEDULED row (start state has only running/idle sessions) — p4-list.png

[p3] An unrecognized status (needs:seed a live session whose status is teleported) never renders as Idle — android emulator-5606; fake-CLI heartbeat stub (stub-cli-states-2241.js in $OUT, the only fixture for this wire state) seeds status=teleported; p3-scene.log prints 'SCENE p3 OK' and the digest row 'android.widget.Button S3 teleported status, Working, kwf/app-scheduled-session-state-glanceables-2241, and CLOUD' with no 'Idle' text in the digest; stub source p3p12-stub.log 'OPEN'. No functional UX defect (no missing feedback/lost work/broken recovery); p3.png captured for the visual reviewer.

[p3] An unrecognized status (needs:seed a live session whose status is teleported) never renders as Idle — p3.png

[p10] Android ongoing notification shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the notification shade still — Re-ran replay-p10.json OK, then shade dump p10.txt shows the app ongoing notification 'text="1 Working, 3 Scheduled wakes in 41 minutes"' (soonest wake = the stub's 02:10 wake, +41 min at capture); android emulator-5554; screenshot p10.png.

[p10] Android ongoing notification shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the notification shade still — p10.png

[p17] ux-check: Unknown status: a session reporting a status outside the vocabulary renders the running state in the list and widget, never idle, with no crash. — android emulator-5604: live Agents list (p17-list.txt) shows the teleported-status row as 'Working' beside a real 'Idle' row, and the home-screen widget reads '1 Working, 1 Scheduled, 1 Idle' (p17-widget.txt) agreeing with its source the worker's active-session list (p17-count-source.log: ses_stub_p17_teleported teleported), so the unknown status is the widget's Working, never idle, with no crash; state seeded by fake-CLI heartbeat stub stub-p17-teleported.js because the e2e index lists no unknown-status fixture; no UX-DEFECT.

[p17] ux-check: Unknown status: a session reporting a status outside the vocabulary renders the running state in the list and widget, never idle, with no crash. — p17-list.png

Follow-ups (not changed here)

  • not proved live: A scheduled session that woke reads as running again; needs:seed a session that was scheduled and has now woken (status busy) (no capture cited it)
  • not proved live: The scheduled state survives an app reload; needs:seed the scheduled session, then cold-open the app and return to the list (no capture cited it)

Open findings (not fixed here)

  • e11: Android widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still
  • e14: ux-check: iOS Live Activity (phone banner/expanded) and the iOS medium Home Screen widget show the scheduled row with a wake time.
  • e15: ux-check: iOS small and large Home Screen widgets show Scheduled and the wake time (per the request that every glanceable carries the wake).
  • e17: ux-check: Unknown status: a session reporting a status outside the vocabulary renders the running state in the list and widget, never idle, with no crash.
  • e9: platform:ios iOS widget shows Scheduled and the wake time; needs:seed the scheduled live session, then inspect the placed ActiveAgents widget still
  • not fully verified: some optional checks did not run
  • the '## E2E proof' section carries no log excerpt, so nothing shows the change was driven end to end

…ing (kwf app-scheduled-session-state-glanceables-2241/s1)
…ping or crashing, relay `scheduledAt` (kwf app-scheduled-session-state-glanceables-2241/s2)
…t wake; push schema (kwf app-scheduled-session-state-glanceables-2241/s3)
…y keys (kwf app-scheduled-session-state-glanceables-2241/s5)
…its wake time (kwf app-scheduled-session-state-glanceables-2241/s6)
…d (kwf app-scheduled-session-state-glanceables-2241/s7)
… wake time (kwf app-scheduled-session-state-glanceables-2241/s8)
…duled + the wake time (kwf app-scheduled-session-state-glanceables-2241/s9)
…ies scheduled + the wake time (kwf app-scheduled-session-state-glanceables-2241/s10)
…f app-scheduled-session-state-glanceables-2241/s11)
…at (kwf app-scheduled-session-state-glanceables-2241/ux1)
… s (kwf app-scheduled-session-state-glanceables-2241/ux2)
@iscekic
iscekic marked this pull request as draft September 24, 2026 13:20
@kilo-code-bot

kilo-code-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental review of the null-scheduledAt handling added since the last reviewed commit found the fix correct and well-covered by tests, with no new issues.

Files Reviewed (6 files)
  • packages/cloud-agent-sdk/src/schemas.ts
  • packages/cloud-agent-sdk/src/schemas.test.ts
  • services/session-ingest/src/types/user-connection-protocol.ts
  • services/session-ingest/src/types/user-connection-protocol.test.ts
  • services/session-ingest/src/dos/UserConnectionDO.test.ts
  • apps/mobile/src/i18n/catalog-parity.test.ts (merge-conflict doc comment only)

Incremental scope

Since d28259fe the only PR-author changes are three commits that read an explicit null (or otherwise non-string) scheduledAt as absent: z.string().optional().catch(undefined) in the session-ingest heartbeat session schema, the SDK activeSessionSchema.scheduledAt, and the SDK sessionStatusSchema scheduled variant, plus tests pinning the null heartbeat row and the persisted JSON shape. The v2 row and status-updated schemas already used z.string().nullable().optional(), so the reading is now consistent end to end. The remaining diff in that range is origin/main merged in by b1abdcafa and 0d7c978c7; the latter's only conflict resolution is a doc comment in the i18n parity test. No changed line introduced a bug.

Previous Review Summaries (3 snapshots, latest commit d28259f)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit d28259f)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • apps/mobile/src/components/agents/remote-session-row.tsx
  • apps/mobile/src/i18n/catalog-parity.test.ts

Incremental review since d381575: the only PR-author changes are a local variable rename (scheduledWake -> scheduledWakeValue) that resolves shadowing in RemoteSessionRow with no behavior change, a doc-comment update in the i18n parity test, and merge conflict resolutions that fold already-reviewed main changes into the scheduled-session logic. No inline comments were outstanding.

Previous review (commit d381575)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental review of the commits since 4a8263bd found no defects: the only PR-author changes are an oxfmt reformat of a UserConnectionDO test heartbeat call and a comment merge in the i18n parity test; the prior 166-file scope is otherwise unchanged relative to current main.

Files Reviewed (2 changed since previous review)
  • services/session-ingest/src/dos/UserConnectionDO.test.ts - oxfmt reformat of the sendHeartbeat array; test assertions and coverage unchanged
  • apps/mobile/src/i18n/catalog-parity.test.ts - doc comment only; PENDING_TRANSLATION_KEYS remains empty

Incremental review: the merge from main and the formatting commit did not alter the PR's code changes; no previous inline comments were outstanding.

Previous review (commit 4a8263b)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the scheduled-session status feature end to end (166 files: SDK schemas/normalizer/service-state, session-ingest validation and Durable Object, web active-sessions/glanceable snapshot, notifications push data, and the mobile list/detail/glanceable surfaces plus 87 locale catalogs) and found no high-confidence defects.

Files Reviewed (166 files)
  • packages/cloud-agent-sdk/src/schemas.ts - status union, permissive row status, scheduledAt on heartbeat/list rows
  • packages/cloud-agent-sdk/src/normalizer.ts, service-state.ts, session-manager.ts, types.ts - scheduled status handling
  • packages/app-shared/src/glanceable-agents-snapshot.ts - scheduled/scheduledAt counts, soonestScheduledAt, eligibility
  • packages/notifications/src/push-data.ts - push snapshot and content-state fields
  • services/session-ingest/src/ingest/{validate,session-sync,metadata}.ts, dos/UserConnectionDO.ts - permissive ingest, wake relay
  • services/notifications/src/lib/glanceable-{delivery,refresh}.ts - eligible gating
  • apps/web/src/lib/active-sessions-list.ts, glanceable-agents-snapshot-server.ts - wire scheduledAt
  • apps/mobile/src/lib/active-sessions-live.ts, utils.ts, glanceable/presentation.ts, snapshot-transforms.ts - cache merge, timeAgo, count order
  • apps/mobile/src/components/agents/{session-row,remote-session-row,session-detail-content,session-list-helpers,session-row-accessibility-label,session-connection-indicator-state,session-context-sheet}.*
  • apps/mobile/src/components/ui/session-row.tsx, session-status-icon.tsx
  • apps/mobile/src/glanceable-android/{widget-props,active-agents-widget,android-sink,count-format}.*
  • apps/mobile/src/glanceable-ios/{active-agents-widget,active-agents-live-activity,view-props}.*
  • apps/mobile/src/i18n/locales/*.json (87 catalogs), catalog-parity.test.ts - keys present, {{time}} placeholder intact
  • Corresponding test files for the above

Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

…sion-state-glanceables-2241

# Conflicts:
#	apps/mobile/src/components/agents/session-list-helpers.test.ts
#	apps/mobile/src/i18n/catalog-parity.test.ts
@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 25, 2026
@iscekic
iscekic marked this pull request as ready for review September 25, 2026 13:49
…sion-state-glanceables-2241

# Conflicts:
#	apps/mobile/src/components/agents/remote-session-row.tsx
#	apps/mobile/src/i18n/catalog-parity.test.ts
@iscekic
iscekic marked this pull request as draft September 26, 2026 02:09
@iscekic
iscekic marked this pull request as ready for review September 26, 2026 02:09
@iscekic iscekic added merge-by-human the merge bot routed this PR to a human and removed human-ready The PR is ready for human review. labels Sep 26, 2026

@eshurakov eshurakov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning — version skew on the glanceable push payload.

packages/notifications/src/push-data.ts:138 extends newestResultKind from a closed 3-value enum to include 'scheduled' in the active_agents_glanceable payload. Mobile parses this strictly (apps/mobile/src/lib/notifications.ts, parseNotificationData → safeParse, returning null on failure and dropping the payload), and the send gate for this payload type is per-type, not per-field. So an already-shipped client that understands active_agents_glanceable but predates 'scheduled' will fail to parse and silently drop the glanceable update whenever a scheduled session is the newest result kind. Making scheduled/scheduledAt defaulted does not help — the failing token is the enum value itself. Consider a tolerant read (.catch()/default) or gating the new value until clients catch up.

Warning — scheduledAt null handling differs across ingest paths.

services/session-ingest/src/types/user-connection-protocol.ts:54 declares scheduledAt: z.string().optional(), which rejects an explicit null, while the parallel HTTP ingest item in services/session-ingest/src/types/session-sync.ts:74 uses z.string().nullable().optional() and accepts it. A CLI that serializes a missing wake time as null in the heartbeat has its session update rejected.

…sion-state-glanceables-2241

# Conflicts:
#	apps/mobile/src/i18n/catalog-parity.test.ts
A CLI that serializes the absent wake time as an explicit null failed
CLIOutboundMessageSchema, so the whole heartbeat was dropped and the
session never reached sessions.list. The heartbeat field now reads a
non-string value as absent, matching the tolerant HTTP ingest item in
session-sync.ts.
The activeSessionSchema row and the scheduled session status now read a
non-string wake time as absent instead of failing the parse, matching
the session-ingest heartbeat contract.
@iscekic

iscekic commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: warning 2 is fixed and pushed. Warning 1 is real and still open; it needs a version gate that this PR cannot pin.

Warning 2 — an explicit scheduledAt: null dropped the whole message (fixed)

  • user-connection-protocol.ts:54 rejected an explicit null. CLIOutboundMessageSchema.safeParse then failed, and handleCliMessage dropped the entire heartbeat, so the session never reached sessions.list.
  • The heartbeat field, the SDK activeSessionSchema row, and the SDK scheduled status now read a non-string wake time as absent. session-sync.ts:74 already did this.
  • Commits 3e2cfd5 and ad0e789. New tests: user-connection-protocol.test.ts, UserConnectionDO.test.ts (a null heartbeat keeps both sessions), schemas.test.ts.
  • Result: 378 session-ingest and 358 cloud-agent-sdk tests pass. Both packages typecheck.

Warning 1 — newestResultKind: 'scheduled' version skew (open)

  • I confirmed the drop. Mobile parses the whole payload strictly (parseNotificationData → safeParse → null), so a client built before this release discards an active_agents_glanceable payload whose newest change is a scheduled transition.
  • A tolerant read cannot help those clients, because shipped code keeps the old enum. Only not sending the value helps them.
  • Gating is the remedy, and it needs the app version per push target. user_push_tokens.app_version exists, and isPushAppVersionAtLeast is the precedent. The minimum version is the mobile release that ships this feature, and this PR does not pin one.
  • Impact while scheduled is the newest change: a pre-release client keeps its last applied counts and shows that session as running anyway, because its glanceableStatusKind folds every unknown status to running. The next status change or a foreground refresh re-syncs it. No crash, no lost work.
  • This needs the owner's decision: accept the skew for one release, or add the per-token app_version gate in a follow-up.

…sion-state-glanceables-2241

# Conflicts:
#	apps/mobile/src/i18n/catalog-parity.test.ts
…sion-state-glanceables-2241

# Conflicts:
#	apps/mobile/src/i18n/catalog-parity.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-by-human the merge bot routed this PR to a human

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants