Skip to content

feat(mobile): create sessions on local runtimes#4572

Open
iscekic wants to merge 7 commits into
mainfrom
feature/mobile-local-cloud-agent-pr
Open

feat(mobile): create sessions on local runtimes#4572
iscekic wants to merge 7 commits into
mainfrom
feature/mobile-local-cloud-agent-pr

Conversation

@iscekic

@iscekic iscekic commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • discover authenticated kilo remote runtimes even when they have no sessions
  • select a runtime-provided model and agent from mobile
  • create and start a local session with one idempotent request, then reuse the existing remote-session detail flow
  • provide bounded readiness recovery and explicit retryable/non-retryable mobile states

Related

Verification

  • mobile: 148 files / 1049 tests, typecheck, lint, format, and unused-code checks
  • session-ingest: 563 passed / 5 skipped, typecheck and lint
  • web runtime/readiness: 152 focused tests, typecheck and lint
  • session-ingest contracts: typecheck and lint
  • final branch is six logical commits on current main, byte-identical to the reviewed implementation tree

E2E

  • PASS: empty local-runtime state, exact setup copy, Retry CTA, and stable Retry behavior on iOS simulator
  • PASS: repaired feature CLI binary reaches Remote connection enabled against local API/session-ingest/event-service
  • ENVIRONMENT BLOCKED: happy-path mobile interaction could not complete because Maestro MCP disconnected and local Maestro failed twice while attaching its iOS driver (xctrunner found nothing to terminate); shared services, simulator, login, Metro provenance, and CLI runtime were otherwise healthy

State coverage

  • retryable runtime/catalog/ambiguous/rate-limit/readiness states have automated CTA coverage
  • upgrade/malformed/access-loss states have automated no-CTA coverage
  • prompt-start partial recovery has automated navigation/cache coverage

@iscekic iscekic self-assigned this Jul 16, 2026
);

const openRuntimePicker = useCallback(() => {
if (viewModel.kind !== 'selecting-runtime') {

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.

CRITICAL: openRuntimePicker guard makes the "Runtime" row a dead button in every state it's actually wired to

openRuntimePicker early-returns unless viewModel.kind === 'selecting-runtime'. However, it's only ever passed as onPressRuntime when viewModel.kind !== 'selecting-runtime': the ready branch (line 254 here) and the catalog-loading / catalog-error-retryable / catalog-error-non-retryable branches in local-session-config-states.tsx. The selecting-runtime state itself renders its own inline runtime list via onSelectFence and never calls onPressRuntime. As a result the guard condition can never be true when it actually fires, so tapping the always-enabled "Runtime" row silently no-ops in every reachable case — users can never reselect/change the runtime from this screen.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

runtimeId: string,
connectionId: string
): boolean {
if (!bridge.selectionScope || !bridge.isSelectionCurrent(bridge.selectionScope)) {

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.

CRITICAL: Initial runtime selection is impossible when selectionScope is null (2+ capable runtimes)

commitRuntimePickerSelection returns false (without ever calling bridge.onSelect) whenever bridge.selectionScope is falsy, without consulting isSelectionCurrent at all — even though isSelectionCurrent's type explicitly accepts RuntimePickerSelectionScope | null. The only production caller (local-session-config-screen.tsx's openRuntimePicker) sets selectionScope: viewModel.currentFence, and per local-runtime-catalog-view-model.ts, currentFence is null precisely in the primary "selecting-runtime" state (i.e. whenever the user has 2+ capable local runtimes and hasn't picked one yet — the single-runtime case auto-selects and skips this screen). In that state every tap in RuntimePickerContent calls this with a null selectionScope, so it always returns false and never fires onSelect. Users with multiple local runtimes can never make their initial runtime selection via this picker.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

sessionId: string,
deps: CompleteHappyPathDeps
): Promise<void> {
await deps.invalidateCaches();

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.

CRITICAL: Unhandled invalidateCaches() rejection permanently strands the orchestrator in submitting

completeHappyPath awaits deps.invalidateCaches() with no try/catch, so a rejection (e.g. a transient network blip) throws out of the function before captureEvent/notificationHaptic/navigate run. This mirrors handlePromptPartial below, which deliberately wraps its invalidateCaches() call in try/catch specifically so this failure mode can't block the toast/navigate — but completeHappyPath has no equivalent protection. Downstream, use-local-session-create-orchestrator.ts's completeHappySafe only has a finally (no catch), so the rejection propagates all the way to use-local-session-create.ts's submit/retry/checkAgain, which await it without a try/catch. Net effect: a transient cache-invalidation failure on the happy path leaves "Start session" permanently disabled (isSubmitting: true) with no error shown and no recovery CTA — the user is stuck with no way to retry.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

});
}
});
const offReconnect = connection.onReconnect(() => {

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: Reconnect refetch closes over a stale query object after the first fence change

This effect deliberately excludes query/fence from its dependency array ([connection, queryClient]), so it effectively runs once on mount and its onReconnect handler permanently closes over the query object bound to whichever fence was active at that time (often the disabled placeholder query, since fence is frequently null on first mount). When the user later selects a runtime fence, query is re-created for the new fence via useQuery(queryOptions), but this effect never re-subscribes — so connection.onReconnect(() => void query.refetch()) keeps refetching the original (now-detached) query rather than the currently active fence's catalog query. "Refetch the catalog on kilo-remote reconnect" silently stops working correctly after the first fence is chosen.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

stored.fence.connectionId !== fence.connectionId;
if (fenceChanged) {
requestIdStoreRef.current.clearByFence(stored.fence);
orchestratorRef.current = null;

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: Discarding the orchestrator reference doesn't cancel its in-flight work

When the fence changes while a submit/readiness-poll is in flight, this only clears the hook's own reference to the orchestrator (orchestratorRef.current = null, setActiveOrchestrator(null)); it never signals the orchestrator instance itself to stop. There is no AbortController/cancellation token anywhere in use-local-session-create-orchestrator.ts, so the abandoned orchestrator keeps running to completion against the stale deps closure (old fence/catalog/model, old router/queryClient bindings). If that stale attempt later resolves as ready or session_not_ready, completeHappySafe/handlePromptPartialSafe still fire — including navigation (router.replace), success haptics, analytics, and cache invalidation — for a runtime/agent/model selection the user has since navigated away from.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

connectionId,
issues: error instanceof Error ? error.message : 'unknown',
});
return;

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: Runtime-presence rejection drops the entire heartbeat, not just the runtime update

Each of the four validation failures in this block (return; at this line and at the connectionId-mismatch, runtimeId-mutation, and cross-socket-collision checks below) exits handleHeartbeat entirely, skipping the session-ownership update logic and the heartbeat_ack send that follow. lastHeartbeatAt was already bumped above, so the CLI won't be evicted as stale, but its session list goes stale and any sessions started/stopped in that same heartbeat are silently dropped until a "clean" heartbeat arrives — even though the sessions payload in the same message may have been perfectly valid. Only the runtime-registry update (this.runtimes / the runtime event) should be skipped on these rejections, not the unrelated session-handling and ack logic below.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

);
}

if (this.pendingCommands.size >= UserConnectionDO.MAX_PENDING_COMMANDS) {

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: No duplicate-in-flight guard for relay-originated runtime commands, unlike the existing viewer-command path

dispatchRuntimeCommand (shared by getRuntimeCatalog and createAndRunLocalSession) only enforces the global MAX_PENDING_COMMANDS cap — it has no check for an already-pending command against the same fence. This is inconsistent with handleWebCommand above, which explicitly rejects a duplicate in-flight list_models request for the same ws+sessionId+connectionId with CATALOG_REQUEST_PENDING_ERROR. The contract even defines a COMMAND_ALREADY_PENDING error code — fully wired through routes/runtime-control.ts, local-runtime-control-router.ts, and the mobile error-classification layers — specifically for this purpose, but it's never thrown anywhere in this file. If createAndRunLocalSession's HTTP request is retried (e.g. after a client-side timeout while the first create_and_run is still in flight), the DO will dispatch a second create_and_run to the same runtime, which can create duplicate sessions/prompt-starts unless the CLI itself deduplicates by request id.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 7 Issues Found | Recommendation: Address before merge

Executive Summary

This incremental update (8ce53dc..51f13bb) is a pure formatting/whitespace change (style: format local runtime control) across apps/web/src/lib/local-runtime-control/client.ts, apps/web/src/routers/local-runtime-control-router.ts, services/session-ingest/src/dos/UserConnectionDO.ts, services/session-ingest/src/middleware/runtime-control-auth.ts, services/session-ingest/src/routes/runtime-control.ts, and their tests — no logic changed, so no new issues were found; all previously reported findings remain unresolved.

Overview

Severity Count
CRITICAL 3
WARNING 4
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
apps/mobile/src/components/agents/local-session-config-screen.tsx 112 openRuntimePicker's guard (kind === 'selecting-runtime') can never be true at any call site that actually wires it, so the "Runtime" row is a dead button in every reachable state.
apps/mobile/src/lib/picker-bridge-runtime.ts 66 commitRuntimePickerSelection always returns false when selectionScope is null, which is exactly the state used for a user's first runtime selection when 2+ runtimes are available — initial selection is impossible via this picker.
apps/mobile/src/lib/hooks/local-session-create-effects.ts 53 completeHappyPath has no try/catch around invalidateCaches(), unlike its sibling handlePromptPartial; a transient failure here leaves the orchestrator permanently stuck in submitting with no error/recovery UI.

WARNING

File Line Issue
apps/mobile/src/lib/hooks/use-local-runtime-catalog.ts 66 Reconnect refetch closes over a stale query object after the first fence change; catalog refetch-on-reconnect silently stops working correctly.
apps/mobile/src/lib/hooks/use-local-session-create.ts 224 Discarding the orchestrator ref on fence change doesn't cancel its in-flight work (no AbortController anywhere); a stale attempt can still navigate/toast/haptic for an abandoned selection.
services/session-ingest/src/dos/UserConnectionDO.ts 450 Runtime-presence validation failures early-return out of the entire handleHeartbeat, silently dropping session-ownership updates and the heartbeat_ack for that message, not just the runtime update.
services/session-ingest/src/dos/UserConnectionDO.ts 1283 dispatchRuntimeCommand has no duplicate-in-flight guard per fence (unlike the existing list_models dedup in handleWebCommand), despite a fully-wired but unused COMMAND_ALREADY_PENDING error code — a retried createAndRunLocalSession call can dispatch a second create_and_run to the same runtime.
Files Reviewed (10 files in this incremental update)
  • apps/web/src/lib/local-runtime-control/client.ts (formatting only)
  • apps/web/src/lib/local-runtime-control/client.test.ts (formatting only)
  • apps/web/src/routers/local-runtime-control-router.ts (formatting only)
  • apps/web/src/routers/local-runtime-control-router.test.ts (formatting only)
  • services/session-ingest/src/dos/UserConnectionDO.ts (formatting only)
  • services/session-ingest/src/dos/UserConnectionDO.test.ts (formatting only)
  • services/session-ingest/src/middleware/runtime-control-auth.ts (formatting only)
  • services/session-ingest/src/middleware/runtime-control-auth.test.ts (formatting only)
  • services/session-ingest/src/routes/runtime-control.ts (formatting only)
  • services/session-ingest/src/routes/runtime-control.test.ts (formatting only)

Fix these issues in Kilo Cloud

Previous Review Summary (commit 8ce53dc)

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

Previous review (commit 8ce53dc)

Status: 7 Issues Found | Recommendation: Address before merge

Executive Summary

The local-runtime picker/config screen has two CRITICAL dead-end interaction bugs (users with 2+ runtimes can't make an initial selection, and no one can change runtime from the config screen once one is picked), plus a happy-path cache-invalidation failure that can permanently strand the "Start session" button.

Overview

Severity Count
CRITICAL 3
WARNING 4
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
apps/mobile/src/components/agents/local-session-config-screen.tsx 112 openRuntimePicker's guard (kind === 'selecting-runtime') can never be true at any call site that actually wires it, so the "Runtime" row is a dead button in every reachable state.
apps/mobile/src/lib/picker-bridge-runtime.ts 66 commitRuntimePickerSelection always returns false when selectionScope is null, which is exactly the state used for a user's first runtime selection when 2+ runtimes are available — initial selection is impossible via this picker.
apps/mobile/src/lib/hooks/local-session-create-effects.ts 53 completeHappyPath has no try/catch around invalidateCaches(), unlike its sibling handlePromptPartial; a transient failure here leaves the orchestrator permanently stuck in submitting with no error/recovery UI.

WARNING

File Line Issue
apps/mobile/src/lib/hooks/use-local-runtime-catalog.ts 66 Reconnect refetch closes over a stale query object after the first fence change; catalog refetch-on-reconnect silently stops working correctly.
apps/mobile/src/lib/hooks/use-local-session-create.ts 224 Discarding the orchestrator ref on fence change doesn't cancel its in-flight work (no AbortController anywhere); a stale attempt can still navigate/toast/haptic for an abandoned selection.
services/session-ingest/src/dos/UserConnectionDO.ts 443 Runtime-presence validation failures early-return out of the entire handleHeartbeat, silently dropping session-ownership updates and the heartbeat_ack for that message, not just the runtime update.
services/session-ingest/src/dos/UserConnectionDO.ts 1278 dispatchRuntimeCommand has no duplicate-in-flight guard per fence (unlike the existing list_models dedup in handleWebCommand), despite a fully-wired but unused COMMAND_ALREADY_PENDING error code — a retried createAndRunLocalSession call can dispatch a second create_and_run to the same runtime.
Files Reviewed (104 files)
  • apps/mobile/src/app/(app)/_layout.tsx
  • apps/mobile/src/app/(app)/agent-chat/new.tsx
  • apps/mobile/src/app/(app)/agent-chat/new/cloud.tsx
  • apps/mobile/src/app/(app)/agent-chat/new/local.tsx
  • apps/mobile/src/app/(app)/agent-chat/runtime-catalog-agent-picker.tsx
  • apps/mobile/src/app/(app)/agent-chat/runtime-catalog-model-picker.tsx
  • apps/mobile/src/app/(app)/agent-chat/runtime-picker.tsx
  • apps/mobile/src/components/agents/cloud-session-create-screen.tsx
  • apps/mobile/src/components/agents/local-session-config-route.test.ts
  • apps/mobile/src/components/agents/local-session-config-rows.tsx
  • apps/mobile/src/components/agents/local-session-config-screen.tsx - 1 issue
  • apps/mobile/src/components/agents/local-session-config-screen.test.ts
  • apps/mobile/src/components/agents/local-session-config-states.tsx
  • apps/mobile/src/components/agents/local-session-create-prompt-input.tsx
  • apps/mobile/src/components/agents/local-session-create-recovery-panel.tsx
  • apps/mobile/src/components/agents/new-session-source-paths.ts
  • apps/mobile/src/components/agents/new-session-source-paths.test.ts
  • apps/mobile/src/components/agents/new-session-source-screen.tsx
  • apps/mobile/src/components/agents/runtime-catalog-agent-picker-content.tsx
  • apps/mobile/src/components/agents/runtime-catalog-model-picker-content.tsx
  • apps/mobile/src/components/agents/runtime-picker-content.tsx
  • apps/mobile/src/lib/agent-session-cache.ts
  • apps/mobile/src/lib/agent-session-cache.test.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-errors.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-errors.test.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-picker-scope.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-picker-scope.test.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-projection.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-projection.test.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-selection.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-selection.test.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-test-fixtures.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-types.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-view-model.ts
  • apps/mobile/src/lib/hooks/local-runtime-catalog-view-model.test.ts
  • apps/mobile/src/lib/hooks/local-session-config-selection.ts
  • apps/mobile/src/lib/hooks/local-session-config-selection.test.ts
  • apps/mobile/src/lib/hooks/local-session-config-view-model.ts
  • apps/mobile/src/lib/hooks/local-session-config-view-model.test.ts
  • apps/mobile/src/lib/hooks/local-session-create-effects.ts - 1 issue
  • apps/mobile/src/lib/hooks/local-session-create-effects.test.ts
  • apps/mobile/src/lib/hooks/local-session-create-enablement.ts
  • apps/mobile/src/lib/hooks/local-session-create-enablement.test.ts
  • apps/mobile/src/lib/hooks/local-session-create-errors.ts
  • apps/mobile/src/lib/hooks/local-session-create-errors.test.ts
  • apps/mobile/src/lib/hooks/local-session-create-orchestrator-shared.ts
  • apps/mobile/src/lib/hooks/local-session-create-polling.ts
  • apps/mobile/src/lib/hooks/local-session-create-polling.test.ts
  • apps/mobile/src/lib/hooks/local-session-create-prompt-actions.ts
  • apps/mobile/src/lib/hooks/local-session-create-prompt-actions.test.ts
  • apps/mobile/src/lib/hooks/local-session-create-recovery-actions.ts
  • apps/mobile/src/lib/hooks/local-session-create-recovery-actions.test.ts
  • apps/mobile/src/lib/hooks/local-session-create-request.ts
  • apps/mobile/src/lib/hooks/local-session-create-request.test.ts
  • apps/mobile/src/lib/hooks/local-session-request-id.ts
  • apps/mobile/src/lib/hooks/local-session-request-id.test.ts
  • apps/mobile/src/lib/hooks/runtime-discovery-logic.ts
  • apps/mobile/src/lib/hooks/runtime-discovery-logic.test.ts
  • apps/mobile/src/lib/hooks/use-local-runtime-catalog.ts - 1 issue
  • apps/mobile/src/lib/hooks/use-local-runtime-catalog.lifecycle.test.ts
  • apps/mobile/src/lib/hooks/use-local-runtime-catalog.query.test.ts
  • apps/mobile/src/lib/hooks/use-local-runtime-catalog.test-harness.ts
  • apps/mobile/src/lib/hooks/use-local-runtimes.ts
  • apps/mobile/src/lib/hooks/use-local-runtimes.test.ts
  • apps/mobile/src/lib/hooks/use-local-session-config-controller.ts
  • apps/mobile/src/lib/hooks/use-local-session-config-controller.test.ts
  • apps/mobile/src/lib/hooks/use-local-session-create-orchestrator.ts
  • apps/mobile/src/lib/hooks/use-local-session-create-orchestrator.polling.test.ts
  • apps/mobile/src/lib/hooks/use-local-session-create-orchestrator.recovery.test.ts
  • apps/mobile/src/lib/hooks/use-local-session-create-orchestrator.test-harness.ts
  • apps/mobile/src/lib/hooks/use-local-session-create-orchestrator.test.ts
  • apps/mobile/src/lib/hooks/use-local-session-create.ts - 1 issue
  • apps/mobile/src/lib/hooks/use-local-session-create.test.ts
  • apps/mobile/src/lib/picker-bridge-runtime-agent.ts
  • apps/mobile/src/lib/picker-bridge-runtime-agent.test.ts
  • apps/mobile/src/lib/picker-bridge-runtime-catalog.ts
  • apps/mobile/src/lib/picker-bridge-runtime-catalog.test.ts
  • apps/mobile/src/lib/picker-bridge-runtime.ts - 1 issue
  • apps/mobile/src/lib/picker-bridge-runtime.test.ts
  • apps/mobile/src/lib/picker-bridge.ts
  • apps/mobile/src/lib/picker-bridge.test.ts
  • apps/mobile/test-utils/read-source.ts
  • apps/web/package.json
  • apps/web/src/lib/local-runtime-control/client.ts
  • apps/web/src/lib/local-runtime-control/client.test.ts
  • apps/web/src/lib/local-runtime-control/readiness.ts
  • apps/web/src/lib/local-runtime-control/readiness.test.ts
  • apps/web/src/routers/cli-sessions-v2-router.ts
  • apps/web/src/routers/cli-sessions-v2-router.test.ts
  • apps/web/src/routers/local-runtime-control-router.ts
  • apps/web/src/routers/local-runtime-control-router.test.ts
  • apps/web/src/routers/root-router.ts
  • packages/session-ingest-contracts/src/index.ts
  • packages/session-ingest-contracts/src/runtime-control.ts
  • pnpm-lock.yaml
  • services/session-ingest/src/app.ts
  • services/session-ingest/src/dos/UserConnectionDO.ts - 2 issues
  • services/session-ingest/src/dos/UserConnectionDO.test.ts
  • services/session-ingest/src/middleware/runtime-control-auth.ts
  • services/session-ingest/src/middleware/runtime-control-auth.test.ts
  • services/session-ingest/src/routes/runtime-control.ts
  • services/session-ingest/src/routes/runtime-control.test.ts
  • services/session-ingest/src/types/user-connection-protocol.ts
  • services/session-ingest/src/types/user-connection-protocol.test.ts

Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-5 · Input: 24 · Output: 6.8K · Cached: 585.5K

Review guidance: REVIEW.md from base branch main

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant