mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-30 17:14:40 +08:00
fix(cli): route share links through Kilo session API (#10736)
* fix(cli): route share links through Kilo session API Refactor shareSession and unshareSession to return Effect values using EffectBridge.fromPromise and replace the ShareNext dependency in SessionShare with KiloSession calls so CLI-generated share URLs point to app.kilo.ai/s/ public links. * test(session-share): replace EffectBridge usage with direct Storage service calls Update session-share test to use Storage.Service directly instead of wrapping Storage.write in EffectBridge.fromPromise, aligning test code with the refactored service-based architecture. Add Storage.defaultLayer to the test effect layer composition.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Use Kilo session share links when sharing conversations from the CLI.
|
||||
@@ -1,8 +1,9 @@
|
||||
// kilocode_change - new file
|
||||
import { remapChildren as _remapChildren } from "./fork"
|
||||
import z from "zod"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { Cause, Effect, Schema } from "effect"
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { Session } from "@/session/session"
|
||||
import { MessageID, SessionID } from "@/session/schema"
|
||||
import { fn } from "@/util/fn"
|
||||
@@ -230,14 +231,18 @@ export namespace KiloSession {
|
||||
// Session lifecycle hooks (share, unshare, remove)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function shareSession(id: string): Promise<{ url: string }> {
|
||||
const { KiloSessions } = await import("@/kilo-sessions/kilo-sessions")
|
||||
return KiloSessions.share(id)
|
||||
export function shareSession(id: SessionID) {
|
||||
return EffectBridge.fromPromise(async () => {
|
||||
const { KiloSessions } = await import("@/kilo-sessions/kilo-sessions")
|
||||
return KiloSessions.share(id)
|
||||
}).pipe(Effect.catchCause((cause) => Effect.fail(Cause.squash(cause))))
|
||||
}
|
||||
|
||||
export async function unshareSession(id: string): Promise<void> {
|
||||
const { KiloSessions } = await import("@/kilo-sessions/kilo-sessions")
|
||||
await KiloSessions.unshare(id)
|
||||
export function unshareSession(id: SessionID) {
|
||||
return EffectBridge.fromPromise(async () => {
|
||||
const { KiloSessions } = await import("@/kilo-sessions/kilo-sessions")
|
||||
await KiloSessions.unshare(id)
|
||||
}).pipe(Effect.catchCause((cause) => Effect.fail(Cause.squash(cause))))
|
||||
}
|
||||
|
||||
export async function removeSession(id: string): Promise<void> {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SyncEvent } from "@/sync"
|
||||
import { Effect, Layer, Scope, Context } from "effect"
|
||||
import { Config } from "@/config/config"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import * as ShareNext from "./share-next"
|
||||
import { KiloSession } from "@/kilocode/session" // kilocode_change
|
||||
|
||||
export interface Interface {
|
||||
readonly create: (input?: Session.CreateInput) => Effect.Effect<Session.Info>
|
||||
@@ -19,20 +19,19 @@ export const layer = Layer.effect(
|
||||
Effect.gen(function* () {
|
||||
const cfg = yield* Config.Service
|
||||
const session = yield* Session.Service
|
||||
const shareNext = yield* ShareNext.Service
|
||||
const scope = yield* Scope.Scope
|
||||
const sync = yield* SyncEvent.Service
|
||||
|
||||
const share = Effect.fn("SessionShare.share")(function* (sessionID: SessionID) {
|
||||
const conf = yield* cfg.get()
|
||||
if (conf.share === "disabled") throw new Error("Sharing is disabled in configuration")
|
||||
const result = yield* shareNext.create(sessionID)
|
||||
const result = yield* KiloSession.shareSession(sessionID) // kilocode_change - use Kilo public share URLs
|
||||
yield* sync.run(Session.Event.Updated, { sessionID, info: { share: { url: result.url } } })
|
||||
return result
|
||||
})
|
||||
|
||||
const unshare = Effect.fn("SessionShare.unshare")(function* (sessionID: SessionID) {
|
||||
yield* shareNext.remove(sessionID)
|
||||
yield* KiloSession.unshareSession(sessionID) // kilocode_change - use Kilo public share URLs
|
||||
yield* sync.run(Session.Event.Updated, { sessionID, info: { share: { url: null } } })
|
||||
})
|
||||
|
||||
@@ -50,7 +49,6 @@ export const layer = Layer.effect(
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(ShareNext.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(SyncEvent.defaultLayer),
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { expect, spyOn } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Auth } from "../../src/auth"
|
||||
import { Config } from "../../src/config/config"
|
||||
import { Session } from "../../src/session/session"
|
||||
import { SessionShare } from "../../src/share/session"
|
||||
import { Storage } from "../../src/storage/storage"
|
||||
import { SyncEvent } from "../../src/sync"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(Layer.mergeAll(Auth.defaultLayer, Storage.defaultLayer, CrossSpawnSpawner.defaultLayer))
|
||||
|
||||
const layer = SessionShare.layer.pipe(
|
||||
Layer.provideMerge(Session.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(SyncEvent.defaultLayer),
|
||||
)
|
||||
|
||||
it.instance("shares and unshares sessions through Kilo public URLs", () => {
|
||||
const urls: string[] = []
|
||||
const fetch: typeof globalThis.fetch = Object.assign(
|
||||
async (input: RequestInfo | URL) => {
|
||||
const url = String(input)
|
||||
urls.push(url)
|
||||
if (url.endsWith("/api/user")) return new Response("{}", { status: 200 })
|
||||
if (url.endsWith("/share")) return Response.json({ public_id: "public-1" })
|
||||
if (url.endsWith("/unshare")) return new Response(null, { status: 200 })
|
||||
return new Response("{}", { status: 200 })
|
||||
},
|
||||
{ preconnect: globalThis.fetch.preconnect },
|
||||
)
|
||||
const request = spyOn(globalThis, "fetch").mockImplementation(fetch)
|
||||
|
||||
return Effect.gen(function* () {
|
||||
const auth = yield* Auth.Service
|
||||
const share = yield* SessionShare.Service
|
||||
const session = yield* Session.Service
|
||||
const storage = yield* Storage.Service
|
||||
yield* auth.set("kilo", { type: "api", key: "test-token" })
|
||||
|
||||
const info = yield* share.create({ title: "share-test" })
|
||||
yield* storage.write(["session_share", info.id], { id: "remote-1", ingestPath: "/api/ingest/session-1" })
|
||||
|
||||
const result = yield* share.share(info.id)
|
||||
expect(result.url).toBe("https://app.kilo.ai/s/public-1")
|
||||
expect((yield* session.get(info.id)).share?.url).toBe("https://app.kilo.ai/s/public-1")
|
||||
|
||||
yield* share.unshare(info.id)
|
||||
expect((yield* session.get(info.id)).share).toBeUndefined()
|
||||
expect(urls.some((url) => url.endsWith(`/api/session/${info.id}/share`))).toBe(true)
|
||||
expect(urls.some((url) => url.endsWith(`/api/session/${info.id}/unshare`))).toBe(true)
|
||||
}).pipe(
|
||||
Effect.ensuring(
|
||||
Effect.gen(function* () {
|
||||
const auth = yield* Auth.Service
|
||||
yield* auth.remove("kilo").pipe(Effect.ignore)
|
||||
request.mockRestore()
|
||||
}),
|
||||
),
|
||||
Effect.provide(layer),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user