feat(cli): advertise spawner instance in remote heartbeat - #12506
feat(cli): advertise spawner instance in remote heartbeat#12506iscekic wants to merge 4 commits into
Conversation
The cloud session-ingest DO lists connected kilo remote instances for the mobile Run-on picker from heartbeat instance metadata (cloud #4618), but no CLI build ever sent it, so the picker could never list a local CLI. Add instance { name, projectName, version } to the remote heartbeat schema and send it on every heartbeat (fresh and degraded). name is the sanitized OS hostname, projectName the launch-directory basename, version the CLI version clamped to the 32-char wire cap.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge The prior SUGGESTION (duplicated sanitize logic between Files Reviewed (incremental — 1 file changed since last review)
Previous Review Summaries (2 snapshots, latest commit 3d23537)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 3d23537)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (incremental — 2 files changed since last review)
Fix these issues in Kilo Cloud Previous review (commit 4f0df2b)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (6 files)
Reviewed by claude-sonnet-5 · Input: 38 · Output: 6.7K · Cached: 873.8K Review guidance: REVIEW.md from base branch |
The mobile Run-on spawn sends create_session with a connectionId and no sessionId; the handler required a sessionId and rejected it as invalid. Create the root session in the relay launch directory when sessionId is absent, keep the session-scoped directory when it decodes, and reject a present-but-undecodable sessionId.
|
(bot) Closing as superseded by #12327 ( 1. Heartbeat instance advertisement — main's 2. Connection-scoped Not ported — the one unique residue, should we want it as a small follow-up: this PR's label-sanitization hardening ( Deliberately not ported — design differences where main's choices are intentional: advertising only from the explicit |
Summary
The mobile
Run oninstance picker (cloud #4618) lists connectedkilo remoteCLI instances from the heartbeatinstancemetadata that the session-ingestUserConnectionDOpersists per CLI socket, and spawns sessions on a chosen instance via a connection-scopedcreate_sessioncommand (apps/webcreateRemoteSessionOnConnection:{ command: 'create_session', data: { protocolVersion: 1 } }+connectionId, nosessionIdon the wire). The cloud and mobile halves of that contract shipped, but the CLI half never did:instancein the remote heartbeat (verified:@kilocode/cli@7.4.15binary contains zeroprojectNamereferences;mainhas no such field).getConnectedInstances()excludes instance-less sockets by design, soGET /api/instances/activeis always empty and the picker can never list a local CLI.create_sessionhandler requires asessionId(session-scoped/new, feat(cli): support remote slash commands and session creation #12224) and rejects the connection-scoped command withinvalid create_session command(proven end-to-end against a local stack).(The earlier attempt, #12259, was closed in favor of a simpler shape; these are the two minimal pieces that still have to exist for the shipped cloud contract to function.)
Changes
Commit 1 — advertise spawner instance in the remote heartbeat
remote-protocol.ts:Instanceschema —{ name: string(1..64), projectName: string(1..64), version?: string(max 32) }— matching the cloudinstanceSchemaexactly; optionalinstancefield onHeartbeat.remote-ws.ts: newOptions.instance, included in both heartbeat sends (fresh and degraded); legacy callers keep the exact current wire shape (conditional spread, asserted by tests).kilo-sessions.ts:buildRemoteInstance()constructs and validates the instance once at remote-enable:name= sanitized OS hostname (fallbackKilo runtime),projectName= sanitized launch-directory basename (fallbackunknown-project),version=InstallationVersionclamped to the 32-char wire cap (feature-build versions exceed it — the clamp prevents the handshake failure class seen during the earlier attempt).Commit 2 — accept connection-scoped
create_sessionremote-sender.ts: three explicit scope cases —sessionIdabsent → connection-scoped, root session created in the relay launch directory (options.directory, the existingdirectoryForfallback);sessionIdpresent and decodable → unchanged session-scoped behavior;sessionIdpresent but undecodable →invalid create_session command. Success reply stays{ protocolVersion: 1, sessionID }; attach failure still rolls back viasessionRemoveand repliesfailed to create session.Tests
remote-instance.test.ts(new): helper fallbacks, sanitization, clamping, schema validation.remote-protocol.test.ts: heartbeat with/withoutinstanceparses; schema bounds enforced.remote-ws.test.ts: fresh and degraded heartbeats includeinstancewhen provided; theinstancekey is absent from the wire JSON when not.remote-sender.test.ts: connection-scoped success (launch directory, create/attach/heartbeat order), session-scoped success, invalid data on both scopes, undecodablesessionIdwith zero side effects, connection-scoped attach rollback.Verification
bun test ./test/kilocode/sessions/— 248 tests green;bun run typecheck(packages/opencode) green;check-opencode-annotations --worktreeclean.kilo remote,GET /api/instances/activereturns the instance (name,projectName, clampedversion), and a connection-scopedcreate_sessionvia the cloudcloud-agent-sdkreturns a strict{ protocolVersion: 1, sessionID }envelope.bun testwas attempted on a heavily shared machine and hit a pre-existing, environment-sensitive cascade (ManagedRuntime disposedacross untouched Instance/HttpApi/LSP suites); the same tests pass in isolation andmainCI on the base commit is green. Relying on CI for the full-suite signal.