Skip to content

feat(gateway): add Kilo device login on the v2 plugin API - #14998

Closed
marius-kilocode wants to merge 4 commits into
v2from
gateway-login
Closed

marius-kilocode wants to merge 4 commits into
v2from
gateway-login

Conversation

@marius-kilocode

@marius-kilocode marius-kilocode commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #14991 (PR 1 of the Gateway series: login). Plan unit U-E1-03.

What Problem This Solves

The v2 base reaches the Kilo Gateway only through the generic models.dev kilo entry. Upstream's KiloPlugin sends HTTP-Referer: https://opencode.ai/ and X-Title: opencode, there is no Kilo login, and bun dev borrows the v1 token and team from ~/.local/share/kilo/auth.json.

Why This Change Was Made

#14991 asks for a Kilo-owned Gateway layer with a clear boundary to OpenCode. This PR adds the first part:

  • packages/kilo-gateway-core (@kilocode/gateway-core), with no @opencode/* import. It contains the Kilo logic ported from v1 packages/kilo-gateway and the old port (kilo-v2-checkpoints-2026-09):
    • server URL: KILO_API_URL, and the server in the token (v1 getKiloUrlFromToken)
    • device auth: POST /api/device-auth/codes, poll GET /api/device-auth/codes/{code}, local expiry, stops when interrupted. A network failure, a timeout, 408, 429 or 5xx does not end the login: the poll retries with backoff (up to 30 s) until the code expires. v1 ends the login on these, so this is a change from v1
    • every Kilo request has a 10 s timeout, which also covers reading the body. A poll caps it at the remaining code lifetime, so a request that hangs cannot outlive the code
    • profile and the default team (v1 defaultOrganizationId: the backend selectedOrganizationId, else the first team when there is no personal account)
    • account resolution in v1 order (KILO_ORG_ID wins over the stored team)
    • provider headers: HTTP-Referer: https://kilocode.ai, X-Title: Kilo Code (v1 DEFAULT_HEADERS), X-KILOCODE-EDITORNAME, X-KILOCODE-FEATURE and X-KILOCODE-ORGANIZATIONID, with the v1 overrides KILOCODE_VERSION, KILOCODE_EDITOR_NAME and KILOCODE_FEATURE
    • a boundary test that fails on any @opencode import or dependency. turbo.json gets a @kilocode/gateway-core#test task, so CI runs it.
  • The kilocode.gateway adapter (packages/core/src/kilocode/gateway.ts) uses only the public plugin API:
    • The device OAuth method on the kilo integration ("Kilo Gateway (Device Authorization)"). The credential is Credential.OAuth with metadata.server, metadata.organizationID and metadata.email (connection label). As in v1, the token is also the refresh token and is valid for one year, with no refresh.
    • A provider transform sets the Gateway base URL and the Kilo headers, including the team header of the active account.
    • When signed out, the provider is enabled with the v1 anonymous key, so the Gateway serves its free models.
    • The adapter reloads on credential.switched (login, account switch, logout).
    • The RPC kilocode.gateway (profile, organization.set), like v1 kilo.profile and kilo.organization.set. The team choice is stored per connection in plugin storage. It wins over the login default, and KILO_ORG_ID still wins over both, as in v1. Plugin storage is global, but each location runs its own plugin instance. So an RPC event makes the instances of all locations reload, as v1 reloaded all instances after it changed accountId.
  • Account choice in the TUI, as in v1 (packages/tui/src/kilocode/gateway.tsx):
    • After a Kilo login, "Select Account ()" shows "Personal Account" (only when the account has one) and the teams with their roles. The first team is preselected and the current account is marked "→ (current)". Then the model dialog opens. Without teams there is no picker. If the profile request fails, a toast says "Couldn't fetch organizations, using personal account". Escape closes the flow and keeps the login default. These are the v1 rules of KiloAutoMethod and getOrganizationOptions.
    • /teams (aliases team, org, orgs), only when Kilo is connected: "Select Team", the current account marked, then "Switched to: ". Without teams it shows "No Teams Available", as in v1.
    • v1 replaced the connect dialog component for Kilo. In v2 the connect flow is upstream code, so one marked call in connected() of dialog-integration.tsx opens the Kilo picker before the model dialog. /teams is a built-in TUI plugin.
  • bun dev no longer reads the v1 auth file. It starts signed out and keeps kilo/kilo-auto/free as the default model. The skill doc tells how to run a signed-in test with KILO_API_KEY and KILO_ORG_ID.

Packaging

@opencode/core is publishable, and its build keeps packages external. So the core dist imports @kilocode/gateway-core at run time. packages/sdk/script/verify-package.ts packs a fixed list of packages and rejects a workspace: dependency that is not in that list. Without a fix, the check fails with @opencode/core has unpacked workspace dependencies: @kilocode/gateway-core.

This PR packs the Kilo package: it gets a build to dist, and the verify list names it before core (one marked line). I chose this option for two reasons:

  • The Kilo logic stays a separate package with no OpenCode imports, as Port Kilo Gateway, login and enterprise support to v2 as a Kilo-owned layer #14991 asks, and the packed check tests the real dependency.
  • The other options are worse:
    • Inlining the package into the core dist needs a bundler change in the core build script and hides a real dependency.
    • Moving the code into packages/core/src/kilocode/ removes the package boundary.

The package stays private. If Kilo ever publishes @opencode/core, it must publish this package too. That is not planned.

Changes to shared upstream files

File Change
packages/core/src/plugin/provider.ts Registration: KiloGatewayPlugin replaces KiloPlugin (2 marked lines)
packages/core/test/plugin/provider-kilo.test.ts The registration assertion now expects the replacement (marked). The other tests of the unused upstream plugin stay
packages/core/package.json, bun.lock Workspace dependency on @kilocode/gateway-core
packages/sdk/script/verify-package.ts Packs kilo-gateway-core before core (1 marked line)
packages/tui/src/component/dialog-integration.tsx Opens the Kilo account picker after a Kilo login, before the model dialog (import and 3 marked lines)
packages/tui/src/plugin/builtins.ts Registers the Kilo /teams plugin (2 marked lines)
turbo.json Test task for the new package

Upstream provider/kilo.ts stays unchanged and unregistered, to avoid merge conflicts.

#14991 scope covered by this PR

Section Item State
1 Login Device login, token stored in the v2 credential store Done
1 Login Logout and account switch Partial. The generic credential removal and switch work: the adapter goes back to the anonymous key, or follows the new active Kilo credential (unit test). There is no Kilo-specific UI
1 Login KILO_API_KEY and an API key method Partial. KILO_API_KEY is tested live. The API key method (from the models.dev seed) is listed in /connect, and the adapter reads stored keys, but I did not test it live
1 Login Server in the token and KILO_API_URL Done
1 Login Token lifetime as in v1, no refresh Done. The follow-up issue for server-side refresh is not filed yet
1 Login Profile, balance, Kilo Pass Partial. The profile is read at login (default team) and by the profile RPC (account choice). No balance or Kilo Pass
1 Login Remove the token reuse from bun dev Done
2 Teams Default team from the profile, KILO_ORG_ID, team header on Gateway requests Done
2 Teams Choosing and switching the team Done in the TUI (picker after login, /teams) and the RPC (organization.set). No CLI command yet
2 Teams Team catalog, defaults, modes, allow and deny lists, data collection, managed config, BYOK Not in this PR
3 Catalog All items Not in this PR (next PR, U-E1-04). Signed out, the picker still shows the models.dev list. Paid models fail with the Gateway message
4 Requests Provider headers Partial. X-KILOCODE-EDITORNAME, X-KILOCODE-FEATURE, and the Kilo referer and title are sent. The user agent is still upstream's opencode/<channel>/<version>/<name>. v1 sends no X-KILOCODE-VERSION: only the Gateway docs list it as optional (packages/kilo-docs/pages/gateway/authentication.md:128 on main), and v1 puts the version into the editor name and the user agent. It is not added
4 Requests Per-request headers, error contract, provider cost Not in this PR
5 Client contract All items Not in this PR

User Impact

  • /connect, then Kilo Gateway, offers "Kilo Gateway (Device Authorization)" and "API key". The device screen shows the verification link and the code.
  • Signed out, Kilo free models work without setup. A paid model fails with the Gateway message "You need to sign in to use this model."
  • Requests carry Kilo attribution instead of the OpenCode referer and title.
  • After a Kilo login, the TUI asks for the personal account or a team, as in v1. /teams switches later. The choice survives a restart.

Evidence

Automated (tests with XDG_DATA_HOME, XDG_CONFIG_HOME, XDG_STATE_HOME and XDG_CACHE_HOME set to a temporary directory):

  • packages/kilo-gateway-core: bun typecheck passes. bun test: 17 pass. The tests cover:
    • the device protocol, denied, expired, rate limit and an invalid verification URL
    • retry after 503, 429, 408 and 500
    • expiry while pending, while the Gateway keeps failing, and while a request hangs (ends in less than 1 s for a 50 ms code)
    • interruption with a 1 ms interval: no request after the interrupt
    • profile shapes, default team, server and token rules, headers, and the boundary check
  • packages/core: bun typecheck passes. bun run script/test.ts test/kilocode/gateway.test.ts test/plugin/provider-kilo.test.ts: 9 pass. The tests cover:
    • registration
    • device login through Integration.oauth.connect against a local Gateway fixture, with the stored metadata and label
    • signed-out anonymous access, the team header, and credential switch and logout
    • KILO_API_KEY with KILO_ORG_ID
  • packages/core, test/kilocode/gateway.test.ts also covers the RPC: profile against the fixture, organization.set changes the team header, null removes it, a new login uses its own default again, and signed out both methods fail with unauthorized.
  • packages/tui: bun typecheck passes (in a copy outside the v1 checkout, because the nested worktree picks up the v1 @types/node). bun test test/kilocode test/cli/tui/dialog-integration.test.tsx test/cli/cmd/tui/integration-options.test.ts: 18 pass (option list rules from v1 and the existing connect dialog tests).
  • packages/sdk: bun run verify:package in a copy outside the v1 checkout. It now gets past the dependency check, the packed bun and node imports and the wrangler dry run. The last step (miniflare boot) fails locally with createRequire(undefined) in a core schema chunk. A clean origin/v2 copy fails at the same step on this machine (Node 25), so that failure is local. CI runs the full check.
  • bun run lint and the Effect ast-grep rules pass on the changed paths.

Real CLI against the deployed Gateway (bun dev, fresh profile each run):

Scenario Result
Signed out, run "Reply with exactly: PONG" (default kilo/kilo-auto/free) Pass: PONG
Signed out, -m kilo/deepseek/deepseek-v4.1-flash Pass: Error: You need to sign in to use this model.
KILO_API_KEY and KILO_ORG_ID from the v1 auth file, paid model Pass: PONG
Request capture through a local proxy (KILO_API_URL), signed in and signed out Pass: Bearer token and team header when signed in, Bearer anonymous when signed out. HTTP-Referer: https://kilocode.ai, X-Title: Kilo Code, X-KILOCODE-EDITORNAME, X-KILOCODE-FEATURE in both. No opencode.ai referer or opencode title
TUI (skill recipe, headless), signed out: home screen Pass: "Build · Auto Free Kilo Gateway"
TUI, signed out: prompt that reads math.ts with the free model Pass: answer 1.2.3, no errors in the log
TUI: /connect, Kilo Gateway Pass: "Kilo Gateway (Device Authorization)" and "API key"
TUI: device method Pass: link https://app.kilo.ai/device-auth?code=…, "Enter code: …", "Waiting for authorization…". Stopped before approval
TUI, signed in (KILO_API_KEY, KILO_ORG_ID), kilo/deepseek/deepseek-v4.1-flash Pass: "Build · DeepSeek V4.1 Flash Kilo Gateway", answer 1.2.3, no errors in the log

Account choice, TUI with a local stand-in for the Kilo API (KILO_API_URL). The stand-in approves the device code after two polls, returns a profile with two teams, records the chat request headers and answers with a short stream:

Scenario Result
Signed out, type /team Pass: /teams is not offered
/connect, Kilo Gateway, device method Pass: code screen, then "Connected Kilo Gateway"
Account picker after login Pass: "Select Account (tester@example.test)", "Personal Account → (current)", teams "Acme Inc owner" (preselected) and "Beta Team member"
Choose Beta Team Pass: the model dialog opens. The next chat request has X-KILOCODE-ORGANIZATIONID: org-beta
/teams Pass: "Select Team", "Beta Team → (current) member". Personal Account gives "Switched to: Personal", and the next request has no team header
/teams to Acme Inc, then restart the CLI Pass: the first request after the restart has org-acme
Fresh login, Escape in the picker Pass: no model dialog. The login default applies
/teams with a real device login against the deployed Gateway (team-only account) Pass: only the teams, no personal account (hasPersonalAccount: false), the login default team marked current

Fix during self-test: the first version of the picker requested /api/profile in a loop. The dialog component returned <Show> as its root. The dialog renders a function root inside a render effect, so each change of the profile signal created the component again. The root is now a <box>, like the other dialogs.

Limits:

  • Device approval in the browser and the stored OAuth credential were not tested against the deployed Gateway, because that needs an interactive sign-in. The fixture test covers that flow with the deployed response shapes.
  • The headless harness cannot take screenshots. The evidence is terminal text.

@github-actions github-actions Bot added area:cli CLI, TUI and core engine area:gateway Kilo Gateway and auth labels Oct 12, 2026
"@ai-sdk/vercel": "2.0.39",
"@ff-labs/fff-bun": "0.10.5",
"@ff-labs/fff-node": "0.10.5",
"@kilocode/gateway-core": "workspace:*",

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: @kilocode/gateway-core is a private, unpublished package ("private": true, no build script, exports points at ./src/index.ts), yet it is added to the runtime dependencies of the publishable @opencode/core.

packages/core/script/build.ts bundles with packages: "external", so the built Core output keeps an import ... from "@kilocode/gateway-core", and packages/sdk/script/verify-package.ts throws @opencode/core has unpacked workspace dependencies: @kilocode/gateway-core because the package is not in its names list. That check runs in CI (test.yml "Verify packed workerd SDK" → bun turbo verify:package --affected --filter=@opencode/sdk), and the same gap breaks the published @opencode/core, which would reference a version that is never published.

Fix: either add gateway-core to the packed/published set (give it a build + dist exports and add it to verify-package.ts/script/publish.ts), or make it a devDependency and bundle it into Core instead of leaving it external.


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

const response = yield* request(`${server}/api/device-auth/codes/${encodeURIComponent(code.code)}`)
if (response.status === 403) return yield* Effect.fail(new Error("Authorization denied by user"))
if (response.status === 410) return yield* Effect.fail(new Error("Authorization code expired"))
if (!response.ok) return yield* Effect.fail(new Error(`Failed to poll device authorization: ${response.status}`))

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: A single transient poll response aborts the whole login. Any non-ok, non-403/410 status (429, 408, 5xx) fails immediately with Failed to poll device authorization: <status>, even though start() (line 39) treats 429 as retryable. Since poll hits this endpoint every 3s, a momentary rate limit or server error permanently fails an otherwise valid authorization.


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


// Interrupting the returned effect aborts the request.
export function request(url: string, init?: RequestInit) {
return Effect.tryPromise({

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.

SUGGESTION: request has no timeout, so the poll deadline (device.ts:85) is only checked between requests. A server that accepts the connection but never responds leaves fetch pending, so login can hang past expiresIn/expiresAt instead of failing at the deadline. Consider bounding the fetch with AbortSignal.timeout(...), as other providers do.


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

await Effect.runPromise(
Effect.gen(function* () {
const code = yield* KiloDevice.start(backend.url)
const fiber = yield* KiloDevice.login(backend.url, code, 1000).pipe(Effect.forkScoped({ startImmediately: true }))

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.

SUGGESTION: This does not actually verify that interruption stops polling: the fiber is forked with interval = 1000, but the test only waits 20ms before checking that no new request arrived (lines 67-71). No request could appear in that window even if the loop kept running. Fork with a small interval (e.g. 1) so a non-interruptible loop would add requests during the observation window.


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

@kilo-code-bot

kilo-code-bot Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
packages/core/src/kilocode/gateway.ts 92 The organization RPC event is published on the location-scoped bus (no global: true), and the plugin's ctx.event.subscribe() is location-filtered, so instances in other locations never reload — contradicting the line 22 comment and the PR's cross-location claim. credential.switched reaches every location only because it is published with { global: true }.

SUGGESTION

File Line Issue
packages/kilo-gateway-core/package.json 12 files: ["dist"] packs only dist, but the packed ./testing export is rewritten to ./test/fixture.js/.d.ts, which is not in the tarball. Inert today (only an in-repo test imports it), but the shipped manifest advertises a subpath it does not include.
Files Reviewed (8 files, incremental)
  • packages/core/src/kilocode/gateway-rpc.ts - 0 issues
  • packages/core/src/kilocode/gateway.ts - 1 issue
  • packages/core/test/kilocode/gateway.test.ts - 0 issues
  • packages/tui/src/component/dialog-integration.tsx - 0 issues
  • packages/tui/src/kilocode/gateway.tsx - 0 issues
  • packages/tui/src/plugin/builtins.ts - 0 issues
  • packages/tui/test/kilocode/gateway.test.ts - 0 issues
  • packages/kilo-gateway-core/package.json - 1 issue (unchanged, re-verified still present)

This commit adds the kilocode.gateway RPC (profile, organization.set), the per-connection team selection in plugin storage, and the TUI account picker plus /teams. The RPC contract, KiloProfile mapping, KILO_ORG_ID/selection precedence, per-connection storage keying, and the new accountOptions logic match their tests. One functional gap was found in the cross-location reload path (see above).

Fix these issues in Kilo Cloud

Previous Review Summaries (3 snapshots, latest commit 09a5cda)

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

Previous review (commit 09a5cda)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

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

SUGGESTION

File Line Issue
packages/kilo-gateway-core/package.json 12 files: ["dist"] packs only dist, but the packed ./testing export is rewritten to ./test/fixture.js/.d.ts, which is not in the tarball. Inert today (only an in-repo test imports it), but the shipped manifest advertises a subpath it does not include.
Files Reviewed (8 files, incremental)
  • bun.lock - 0 issues (lockfile)
  • packages/kilo-gateway-core/package.json - 1 issue
  • packages/kilo-gateway-core/src/device.ts - 0 issues
  • packages/kilo-gateway-core/src/http.ts - 0 issues
  • packages/kilo-gateway-core/test/device.test.ts - 0 issues
  • packages/kilo-gateway-core/test/fixture.ts - 0 issues
  • packages/kilo-gateway-core/tsconfig.build.json - 0 issues
  • packages/sdk/script/verify-package.ts - 0 issues

The three prior findings are resolved at HEAD 09a5cdac: device.ts now retries network failures, timeouts, 408/429/5xx with backoff until the code expires (interruption still stops the loop); http.ts bounds every request with AbortSignal.timeout, and the poll caps it at the remaining code lifetime; the interruption test now uses a 1 ms interval and asserts request growth, so a non-interruptible loop would be caught. The SDK pack list now names @kilocode/gateway-core before core, and the package has a build to dist, so the packed dependency check no longer fails.

Fix these issues in Kilo Cloud

Previous review (commit 71c7188)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

CRITICAL

File Line Issue
packages/core/package.json 105 Adds private/unpublished @kilocode/gateway-core to the runtime dependencies of the publishable @opencode/core. packages/core/script/build.ts bundles with packages: \"external\", so the built Core keeps an import ... from \"@kilocode/gateway-core\", and packages/sdk/script/verify-package.ts throws @opencode/core has unpacked workspace dependencies: @kilocode/gateway-core because the package is not in its names list. That check runs in CI, and the published package would reference a version that is never published.

WARNING

File Line Issue
packages/kilo-gateway-core/src/device.ts 77 A single transient poll response aborts the whole login. Any non-ok, non-403/410 status (429, 408, 5xx) fails immediately, even though start() treats 429 as retryable. A momentary rate limit or server error permanently fails an otherwise valid authorization.

SUGGESTION

File Line Issue
packages/kilo-gateway-core/src/http.ts 5 request has no timeout, so the poll deadline is only checked between requests; a stalled response lets login hang past expiresIn.
packages/kilo-gateway-core/test/device.test.ts 66 The interruption test forks with interval = 1000 but only observes a 20ms window, so it cannot detect a non-interruptible loop.
Files Reviewed (23 files)
  • .kilo/skills/v2-migration/SKILL.md - 0 issues
  • bun.lock - 0 issues
  • packages/core/package.json - 1 issue
  • packages/core/src/kilocode/gateway.ts - 0 issues
  • packages/core/src/plugin/provider.ts - 0 issues
  • packages/core/test/kilocode/gateway.test.ts - 0 issues
  • packages/core/test/plugin/provider-kilo.test.ts - 0 issues
  • packages/kilo-gateway-core/package.json - 0 issues
  • packages/kilo-gateway-core/src/account.ts - 0 issues
  • packages/kilo-gateway-core/src/device.ts - 1 issue
  • packages/kilo-gateway-core/src/headers.ts - 0 issues
  • packages/kilo-gateway-core/src/http.ts - 1 issue
  • packages/kilo-gateway-core/src/index.ts - 0 issues
  • packages/kilo-gateway-core/src/profile.ts - 0 issues
  • packages/kilo-gateway-core/src/server.ts - 0 issues
  • packages/kilo-gateway-core/test/account.test.ts - 0 issues
  • packages/kilo-gateway-core/test/boundary.test.ts - 0 issues
  • packages/kilo-gateway-core/test/device.test.ts - 1 issue
  • packages/kilo-gateway-core/test/fixture.ts - 0 issues
  • packages/kilo-gateway-core/test/profile.test.ts - 0 issues
  • packages/kilo-gateway-core/tsconfig.json - 0 issues
  • script/dev-isolated.ts - 0 issues
  • turbo.json - 0 issues

Notes: The plugin's ctx.event.subscribe() stream is forkScoped into the plugin activation scope, so no subscription/memory leak was found. KiloAccount.resolve precedence, KiloProfile.defaultOrganizationID edge cases, the Kilo header set, and the env-credential path (resolved as Credential.Key) match their tests and the stated v1 parity. The verificationUrl is validated as HTTP(S) before it is shown/opened.

Fix these issues in Kilo Cloud

Previous review (commit 9428130)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

CRITICAL

File Line Issue
packages/core/package.json 105 Adds private/unpublished @kilocode/gateway-core to the runtime dependencies of the publishable @opencode/core. With packages: "external" in packages/core/script/build.ts, packages/sdk/script/verify-package.ts will throw @opencode/core has unpacked workspace dependencies: @kilocode/gateway-core in the CI "Verify packed workerd SDK" step and the same gap breaks the published package (references a version that is never published).

WARNING

File Line Issue
packages/kilo-gateway-core/src/device.ts 77 A transient poll response (429/408/5xx) aborts the entire login immediately, even though start() treats 429 as retryable. A momentary rate limit or server error permanently fails a valid authorization.

SUGGESTION

File Line Issue
packages/kilo-gateway-core/src/http.ts 5 request has no timeout, so the poll deadline is only checked between requests; a stalled response lets login hang past expiresIn.
packages/kilo-gateway-core/test/device.test.ts 66 The interruption test forks with interval = 1000 but only observes a 20ms window, so it cannot detect a non-interruptible loop.
Files Reviewed (23 files)
  • .kilo/skills/v2-migration/SKILL.md - 0 issues
  • bun.lock - 0 issues
  • packages/core/package.json - 1 issue
  • packages/core/src/kilocode/gateway.ts - 0 issues
  • packages/core/src/plugin/provider.ts - 0 issues
  • packages/core/test/kilocode/gateway.test.ts - 0 issues
  • packages/core/test/plugin/provider-kilo.test.ts - 0 issues
  • packages/kilo-gateway-core/package.json - 0 issues
  • packages/kilo-gateway-core/src/account.ts - 0 issues
  • packages/kilo-gateway-core/src/device.ts - 1 issue
  • packages/kilo-gateway-core/src/headers.ts - 0 issues
  • packages/kilo-gateway-core/src/http.ts - 1 issue
  • packages/kilo-gateway-core/src/index.ts - 0 issues
  • packages/kilo-gateway-core/src/profile.ts - 0 issues
  • packages/kilo-gateway-core/src/server.ts - 0 issues
  • packages/kilo-gateway-core/test/account.test.ts - 0 issues
  • packages/kilo-gateway-core/test/boundary.test.ts - 0 issues
  • packages/kilo-gateway-core/test/device.test.ts - 1 issue
  • packages/kilo-gateway-core/test/fixture.ts - 0 issues
  • packages/kilo-gateway-core/test/profile.test.ts - 0 issues
  • packages/kilo-gateway-core/tsconfig.json - 0 issues
  • script/dev-isolated.ts - 0 issues
  • turbo.json - 0 issues

Notes: The plugin's ctx.event.subscribe() fiber is correctly forkScoped into the plugin activation scope, so no subscription/memory leak was found. KiloAccount.resolve precedence, KiloProfile.defaultOrganizationID edge cases, and the Kilo header set match their tests and the stated v1 parity. The verificationUrl is validated as HTTP(S) before it is shown/opened.

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 140.4K · Output: 31.9K · Cached: 3.1M

@WebReflection

WebReflection commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

too many Kilo warnings/suggestions/critical for me to review (yet), I'll let you fix those and then have a second look at this, thanks!

Base automatically changed from v2-migration-skill to kilo-v2-clean-bootstrap October 13, 2026 04:05
@fpliger

fpliger commented Oct 13, 2026

Copy link
Copy Markdown

PR scope looks good and in the right direction (initial step to cover #14991 ). +1 on merging after we fix the kilobot complaints.

The main thing I'm missing is a clear list of the scope items in #14991 that this PR is directly addressing. It's minor but helpful to keep track of things

Add @kilocode/gateway-core with server and token handling, device auth, profile, default team and provider headers, without OpenCode imports. Replace the upstream KiloPlugin with the kilocode.gateway adapter: device login stores server and organizationID in the credential, requests send Kilo headers and the team header, and signed-out users get the free models with the anonymous key. bun dev no longer reads the v1 login.
@marius-kilocode
marius-kilocode changed the base branch from kilo-v2-clean-bootstrap to v2 October 13, 2026 08:39
@opencode/core depends on the Kilo package at runtime, so verify-package must pack it before core. Add a build to dist and list the package before core (marked kilocode_change).
A network failure, timeout, 408, 429 or 5xx poll response no longer ends the login. Polls retry with backoff until the code expires, and every Kilo request has a timeout that a poll caps at the remaining code lifetime.
"./testing": "./test/fixture.ts"
},
"files": [
"dist"

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.

SUGGESTION: The packed tarball only ships dist, but the ./testing export still points at the test fixture.

packages/sdk/script/verify-package.ts rewrites exports from ./src/... to ./dist/... before packing, but "./testing": "./test/fixture.ts" does not match ./src/, so output() turns it into ./test/fixture.js / ./test/fixture.d.ts. Neither is in dist, and files: ["dist"] excludes test/, so the packed manifest advertises a @kilocode/gateway-core/testing subpath whose target is not in the tarball. It is inert today because only the in-repo core test imports it, but the shipped manifest is inconsistent. Either add the fixture to files or drop ./testing from the packed exports.


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

After a Kilo login, the TUI shows the v1 account picker (Select Account) when the account has teams, then the model dialog. /teams (team, org, orgs) switches later. The choice goes through the kilocode.gateway RPC (profile, organization.set), is stored per connection in plugin storage and wins over the login default. KILO_ORG_ID still wins, as in v1.
}),
})
.pipe(Effect.orDie)
emit = (organizationID) => rpc.events.emit("organization", { organizationID }).pipe(Effect.ignore)

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: The cross-location reload this event is meant to drive does not happen.

rpc.events.emit("organization", ...) publishes on the location-scoped bus: Rpc.register's emit calls bus.publish(definition, data, { location: <the emitter's location ref> }) with no global: true (packages/core/src/rpc.ts:98-102), so the event carries the emitting location. ctx.event.subscribe() is bus.subscribe() wrapped in Bus.local(...) (packages/core/src/plugin/host.ts:255-265), and local drops any event whose location is not the subscriber's location (packages/core/src/bus.ts:726-746). A plugin instance in another location therefore never receives rpc.kilocode.gateway.organization, so its provider keeps the previous team header until it reloads for another reason. Compare credential.switched, which is published with { global: true } and does reach every location (packages/core/src/credential.ts:163-168). The line 22 comment and the PR's "instances of all locations reload" claim are not achieved. Emit on the global bus (or drop the cross-location claim).


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

@marius-kilocode

Copy link
Copy Markdown
Collaborator Author

Superseded by #15064 (stacked on #15060).

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

Labels

area:cli CLI, TUI and core engine area:gateway Kilo Gateway and auth area:sdk SDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants