Compare commits

..

1 Commits

Author SHA1 Message Date
Jack 61742bc045 feat(console): restore referral rewards 2026-08-16 17:53:12 +00:00
25 changed files with 63 additions and 310 deletions
@@ -10,17 +10,15 @@ import { Updater } from "../../services/updater"
import { UpdatePreflight } from "../../services/update-preflight"
import { Npm } from "@opencode-ai/util/npm"
import { OPENCODE_CHANNEL, OPENCODE_VERSION } from "../../version"
import { Env } from "../../env"
export default Runtime.handler(Commands, (input) =>
Effect.gen(function* () {
const requestedDirectory = Option.getOrUndefined(input.directory)
const requestedServer = Option.getOrUndefined(input.server)
if (requestedDirectory !== undefined) process.chdir(requestedDirectory)
const preflight = UpdatePreflight.make()
yield* Effect.addFinalizer(() => Effect.promise(() => preflight.close()))
const server = yield* ServerConnection.resolve({
server: requestedServer,
server: Option.getOrUndefined(input.server),
standalone: input.standalone,
mismatch: "replace",
onStart: (reason, previousVersion) => {
@@ -77,7 +75,6 @@ export default Runtime.handler(Commands, (input) =>
resolve: (spec) =>
runPromise(npm.add(spec, { subpaths: ["tui"] }).pipe(Effect.map((result) => result.entrypoint))),
},
environment: requestedServer === undefined ? Env.session() : undefined,
terminalHandoff: () => preflight.finish(),
log: (level, message, tags) => {
const effect =
-9
View File
@@ -12,13 +12,4 @@ export const password = Config.redacted("OPENCODE_PASSWORD").pipe(
Config.withDefault(undefined),
)
export function session() {
return Object.fromEntries(
Object.entries(process.env).filter(
(entry): entry is [string, string] =>
entry[1] !== undefined && entry[0] !== "OPENCODE_PASSWORD" && entry[0] !== "OPENCODE_SERVER_PASSWORD",
),
)
}
export * as Env from "./env"
-4
View File
@@ -5,7 +5,6 @@ import { setTimeout } from "node:timers/promises"
import { readStdin } from "./util/io"
import { createMiniHost, INTERACTIVE_INPUT_ERROR, usingInteractiveStdin } from "./mini-host"
import { parseSessionTargetModel, resolveSessionTarget, type SessionTargetPreparation } from "./session-target"
import { Env } from "./env"
export type MiniCommandInput = {
server: {
@@ -39,7 +38,6 @@ export async function runMini(input: MiniCommandInput) {
const directory = localDirectory()
const connection = createMiniConnection(input.server)
const sdk = connection.sdk
const environment = input.server.reconnect ? Env.session() : undefined
const requested = parseModel(input.model)
const model = requested ? { providerID: requested.providerID, modelID: requested.id } : undefined
const prepare = prepareTarget(input.agent)
@@ -57,7 +55,6 @@ export async function runMini(input: MiniCommandInput) {
fork: input.fork,
model: requested,
agent: input.agent,
environment,
prepare,
signal,
}).catch((error) => {
@@ -92,7 +89,6 @@ export async function runMini(input: MiniCommandInput) {
client,
location: { directory: next.location.directory, workspace: next.location.workspaceID },
agent: next.agent,
environment,
model: next.model
? { providerID: next.model.providerID, id: next.model.modelID, variant: next.variant }
: undefined,
-2
View File
@@ -9,7 +9,6 @@ import { parseSessionTargetModel, resolveSessionTarget } from "../session-target
import { toolInlineInfo } from "@opencode-ai/tui/mini/tool"
import { runNonInteractivePrompt } from "./noninteractive"
import { UI } from "./ui"
import { Env } from "../env"
export type RunCommandInput = {
server: ServerConnection.Resolved
@@ -92,7 +91,6 @@ async function execute(input: RunCommandInput, prepared: Prepared, endpoint: End
? { providerID: explicit.model.providerID, id: explicit.model.modelID, variant: explicit.variant }
: undefined,
agent: input.agent,
environment: input.server.service ? Env.session() : undefined,
prepare: async (next) => {
const selected =
next.model ??
-7
View File
@@ -36,7 +36,6 @@ export async function resolveSessionTarget(input: {
fork?: boolean
model?: ModelRef
agent?: string
environment?: Readonly<Record<string, string>>
prepare: SessionTargetPreparation
signal?: AbortSignal
}): Promise<SessionTarget> {
@@ -71,12 +70,6 @@ export async function resolveSessionTarget(input: {
.catch((error) => {
throw new SessionTargetMutationError(error)
}))
if (input.environment !== undefined && location.workspaceID === undefined)
await input.client.session
.environment({ sessionID: session.id, variables: input.environment }, ...requestOptions(input.signal))
.catch((error) => {
throw new SessionTargetMutationError(error)
})
return {
session,
location,
-24
View File
@@ -1,24 +0,0 @@
import { expect, test } from "bun:test"
import { Env } from "../src/env"
test("session environment omits server credentials", () => {
const previousPassword = process.env.OPENCODE_PASSWORD
const previousLegacyPassword = process.env.OPENCODE_SERVER_PASSWORD
const previousValue = process.env.OPENCODE_SESSION_ENV_TEST
process.env.OPENCODE_PASSWORD = "password"
process.env.OPENCODE_SERVER_PASSWORD = "legacy"
process.env.OPENCODE_SESSION_ENV_TEST = "included"
const environment = Env.session()
if (previousPassword === undefined) delete process.env.OPENCODE_PASSWORD
else process.env.OPENCODE_PASSWORD = previousPassword
if (previousLegacyPassword === undefined) delete process.env.OPENCODE_SERVER_PASSWORD
else process.env.OPENCODE_SERVER_PASSWORD = previousLegacyPassword
if (previousValue === undefined) delete process.env.OPENCODE_SESSION_ENV_TEST
else process.env.OPENCODE_SESSION_ENV_TEST = previousValue
expect(environment.OPENCODE_PASSWORD).toBeUndefined()
expect(environment.OPENCODE_SERVER_PASSWORD).toBeUndefined()
expect(environment.OPENCODE_SESSION_ENV_TEST).toBe("included")
})
-20
View File
@@ -55,26 +55,6 @@ describe("session target resolver", () => {
expect(target.session.id).toBe("ses_implicit")
})
test("attaches the terminal environment to the resolved local Session", async () => {
const client = OpenCode.make({ baseUrl: "https://opencode.test" })
const selected = session("ses_resume", "/session")
spyOn(client.session, "get").mockResolvedValue(selected)
spyOn(client.location, "get").mockResolvedValue(location("/session"))
const environment = spyOn(client.session, "environment").mockResolvedValue()
await resolveSessionTarget({
client,
session: selected.id,
environment: { PATH: "/terminal/bin" },
prepare,
})
expect(environment).toHaveBeenCalledWith({
sessionID: selected.id,
variables: { PATH: "/terminal/bin" },
})
})
test("prepares a fresh Session at the server Location before creation", async () => {
const client = OpenCode.make({ baseUrl: "https://opencode.test" })
const order: string[] = []
-5
View File
@@ -914,10 +914,6 @@ export type Endpoint5_34Input = { readonly sessionID: Session.ID; readonly messa
export type Endpoint5_34Output = SessionMessage.Info
export type SessionMessageOperation<E = never> = (input: Endpoint5_34Input) => Effect.Effect<Endpoint5_34Output, E>
export type Endpoint5_35Input = { readonly sessionID: Session.ID; readonly variables: { readonly [x: string]: string } }
export type Endpoint5_35Output = void
export type SessionEnvironmentOperation<E = never> = (input: Endpoint5_35Input) => Effect.Effect<Endpoint5_35Output, E>
export interface SessionApi<E = never> {
readonly list: SessionListOperation<E>
readonly create: SessionCreateOperation<E>
@@ -962,7 +958,6 @@ export interface SessionApi<E = never> {
readonly interrupt: SessionInterruptOperation<E>
readonly background: SessionBackgroundOperation<E>
readonly message: SessionMessageOperation<E>
readonly environment: SessionEnvironmentOperation<E>
}
export type Endpoint6_0Input = {
@@ -86,8 +86,6 @@ import type {
Endpoint5_33Output,
Endpoint5_34Input,
Endpoint5_34Output,
Endpoint5_35Input,
Endpoint5_35Output,
Endpoint6_0Input,
Endpoint6_0Output,
Endpoint7_0Input,
@@ -612,14 +610,6 @@ const Endpoint5_34 = (raw: RawClient["server.session"]) => (input: Endpoint5_34I
),
)
const Endpoint5_35 = (raw: RawClient["server.session"]) => (input: Endpoint5_35Input) =>
preserveEffect<Endpoint5_35Output>()(
raw["session.environment"]({
params: { sessionID: input["sessionID"] },
payload: { variables: input["variables"] },
}).pipe(Effect.mapError(mapClientError)),
)
const adaptGroup5 = (raw: RawClient["server.session"]) => ({
list: Endpoint5_0(raw),
create: Endpoint5_1(raw),
@@ -649,7 +639,6 @@ const adaptGroup5 = (raw: RawClient["server.session"]) => ({
interrupt: Endpoint5_32(raw),
background: Endpoint5_33(raw),
message: Endpoint5_34(raw),
environment: Endpoint5_35(raw),
})
const Endpoint6_0 = (raw: RawClient["server.message"]) => (input: Endpoint6_0Input) =>
@@ -80,8 +80,6 @@ import type {
SessionBackgroundOutput,
SessionMessageInput,
SessionMessageOutput,
SessionEnvironmentInput,
SessionEnvironmentOutput,
MessageListInput,
MessageListOutput,
ModelListInput,
@@ -898,18 +896,6 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
environment: (input: SessionEnvironmentInput, requestOptions?: RequestOptions) =>
request<SessionEnvironmentOutput>(
{
method: "PUT",
path: `/api/session/${encodeURIComponent(input.sessionID)}/environment`,
body: { variables: input["variables"] },
successStatus: 204,
declaredStatuses: [404, 401, 400],
empty: true,
},
requestOptions,
),
},
message: {
list: (input: MessageListInput, requestOptions?: RequestOptions) =>
@@ -3936,13 +3936,6 @@ export type SessionMessageInput = {
export type SessionMessageOutput = { data: SessionMessageInfo }["data"]
export type SessionEnvironmentInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly variables: { readonly variables: { readonly [x: string]: string } }["variables"]
}
export type SessionEnvironmentOutput = void
export type MessageListInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly limit?: {
@@ -0,0 +1,24 @@
import type { APIEvent } from "@solidjs/start/server"
import { Referral } from "@opencode-ai/console-core/referral.js"
import { safeEqual } from "@opencode-ai/console-core/util/crypto.js"
import { Resource } from "@opencode-ai/console-resource"
import z from "zod"
const Body = z.object({
workspaceID: z.string().startsWith("wrk_"),
referralID: z.string().startsWith("ref_"),
})
export async function POST(event: APIEvent) {
if (!safeEqual(event.request.headers.get("authorization") ?? "", `Bearer ${Resource.SUPPORT_API_KEY.value}`)) {
return Response.json({ error: "Unauthorized" }, { status: 401 })
}
const body = Body.safeParse(await event.request.json().catch(() => undefined))
if (!body.success) {
return Response.json({ error: "Invalid request", issues: body.error.issues }, { status: 400 })
}
return Referral.restoreReward(body.data)
.then((result) => Response.json({ success: true, message: "Referral reward restored", result }))
.catch((error) => Response.json({ error: error instanceof Error ? error.message : String(error) }, { status: 400 }))
}
+32
View File
@@ -475,6 +475,38 @@ export namespace Referral {
})
}
export async function restoreReward(input: { workspaceID: string; referralID: string }) {
return Database.transaction(async (tx) => {
const reward = await tx
.select({ timeApplied: ReferralRewardTable.timeApplied })
.from(ReferralRewardTable)
.innerJoin(ReferralTable, eq(ReferralTable.id, ReferralRewardTable.referralID))
.where(
and(
eq(ReferralRewardTable.workspaceID, input.workspaceID),
eq(ReferralRewardTable.referralID, input.referralID),
isNull(ReferralRewardTable.timeDeleted),
isNull(ReferralTable.timeDeleted),
),
)
.then((rows) => rows[0])
if (!reward) throw new Error("Referral reward not found")
await tx
.update(ReferralRewardTable)
.set({ timeApplied: null })
.where(
and(
eq(ReferralRewardTable.workspaceID, input.workspaceID),
eq(ReferralRewardTable.referralID, input.referralID),
isNull(ReferralRewardTable.timeDeleted),
),
)
return { restored: reward.timeApplied !== null }
})
}
export async function completeFromLiteSubscription(input: { workspaceID: string; userID: string }) {
return Database.transaction(async (tx) => {
const invitee = await tx
+1 -19
View File
@@ -51,7 +51,6 @@ import { Global } from "@opencode-ai/util/global"
import { Shell as ShellSchema } from "@opencode-ai/schema/shell"
import { KeyedMutex } from "./effect/keyed-mutex.js"
import { fileURLToPath } from "url"
import { SessionEnvironment } from "./session/environment.js"
// get project -> project.locations
//
@@ -166,10 +165,6 @@ export interface Interface {
input: ForkInput,
) => Effect.Effect<SessionSchema.Info, NotFoundError | MessageNotFoundError | ForkEmptyError>
readonly get: (sessionID: SessionSchema.ID) => Effect.Effect<SessionSchema.Info, NotFoundError>
readonly environment: (input: {
readonly sessionID: SessionSchema.ID
readonly variables?: SessionEnvironment.Variables
}) => Effect.Effect<SessionEnvironment.Variables | undefined, NotFoundError>
readonly remove: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError>
readonly messages: (input: {
sessionID: SessionSchema.ID
@@ -309,7 +304,6 @@ const layer = Layer.effect(
const locations = yield* LocationServiceMap.Service
const fs = yield* FSUtil.Service
const jobs = yield* Job.Service
const environments = yield* SessionEnvironment.Service
const scope = yield* Scope.Scope
const activeShells = new Set<SessionSchema.ID>()
const shellLocks = KeyedMutex.makeUnsafe<SessionSchema.ID>()
@@ -453,11 +447,6 @@ const layer = Layer.effect(
if (!session) return yield* new NotFoundError({ sessionID })
return session
}),
environment: Effect.fn("Session.environment")(function* (input) {
yield* result.get(input.sessionID)
if (input.variables !== undefined) yield* environments.set(input.sessionID, input.variables)
return yield* environments.get(input.sessionID)
}),
remove: Effect.fn("Session.remove")(function* (sessionID) {
const session = yield* result.get(sessionID)
yield* execution.interrupt(sessionID)
@@ -465,7 +454,6 @@ const layer = Layer.effect(
yield* closeTransport(session)
const children = yield* result.list({ parentID: sessionID })
yield* Effect.forEach(children.data, (child) => result.remove(child.id), { concurrency: 1, discard: true })
yield* environments.clear(sessionID)
yield* bus.publish(SessionEvent.Deleted, { sessionID })
yield* bus.remove(sessionID)
}),
@@ -664,12 +652,7 @@ const layer = Layer.effect(
const started = yield* Effect.gen(function* () {
const shell = yield* Shell.Service
return yield* shell
.create({
command: input.command,
cwd: session.location.directory,
timeout: 0,
metadata: { sessionID: input.sessionID },
})
.create({ command: input.command, cwd: session.location.directory, timeout: 0 })
.pipe(Effect.orDie)
}).pipe(Effect.provide(locations.get(session.location)))
yield* bus.publish(
@@ -1119,7 +1102,6 @@ export const node = makeGlobalNode({
layer: layer.pipe(Layer.orDie),
deps: [
Job.node,
SessionEnvironment.node,
Database.node,
Bus.node,
Project.node,
-33
View File
@@ -1,33 +0,0 @@
export * as SessionEnvironment from "./environment.js"
import { Context, Effect, Layer } from "effect"
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
import { SessionSchema } from "./schema.js"
export type Variables = Readonly<Record<string, string>>
export interface Interface {
readonly get: (sessionID: SessionSchema.ID) => Effect.Effect<Variables | undefined>
readonly set: (sessionID: SessionSchema.ID, variables: Variables) => Effect.Effect<void>
readonly clear: (sessionID: SessionSchema.ID) => Effect.Effect<void>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionEnvironment") {}
const layer = Layer.sync(Service, () => {
const environments = new Map<SessionSchema.ID, Variables>()
return Service.of({
get: (sessionID) => Effect.sync(() => environments.get(sessionID)),
set: (sessionID, variables) =>
Effect.sync(() => {
environments.set(sessionID, { ...variables })
}),
clear: (sessionID) =>
Effect.sync(() => {
environments.delete(sessionID)
}),
})
})
export const node = makeGlobalNode({ service: Service, layer, deps: [] })
+2 -18
View File
@@ -15,8 +15,6 @@ import { Global } from "@opencode-ai/util/global"
import { ShellSelect } from "./shell/select.js"
import type { ShellCreateBefore } from "@opencode-ai/plugin/effect/shell"
import { PluginHooks } from "./plugin/hooks.js"
import { SessionEnvironment } from "./session/environment.js"
import { SessionSchema } from "./session/schema.js"
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("Shell.NotFoundError", {
id: Shell.ID,
@@ -78,7 +76,6 @@ export const layer = (options?: ShellSelect.Options) =>
const global = yield* Global.Service
const environment = yield* Environment.Service
const hooks = yield* PluginHooks.Service
const environments = yield* SessionEnvironment.Service
const context = yield* Effect.context()
const runFork = Effect.runForkWith(context)
const sessions = new Map<string, Active>()
@@ -187,18 +184,13 @@ export const layer = (options?: ShellSelect.Options) =>
input: Shell.CreateInput,
before?: (input: ShellCreateBefore) => Effect.Effect<void, E, R>,
) {
const sessionID = input.metadata?.sessionID
const sessionEnvironment =
location.workspaceID === undefined && Schema.is(SessionSchema.ID)(sessionID)
? yield* environments.get(sessionID)
: undefined
const invocation: ShellCreateBefore = {
command: input.command,
cwd: input.cwd ?? location.directory,
timeout: input.timeout,
shell: yield* resolve(),
env: {
...(sessionEnvironment ?? process.env),
...process.env,
TERM: "xterm-256color",
OPENCODE_TERMINAL: "1",
},
@@ -357,15 +349,7 @@ export function configured(options?: ShellSelect.Options) {
return makeLocationNode({
service: Service,
layer: layer(options),
deps: [
Bus.node,
Location.node,
Config.node,
Global.node,
Environment.node,
PluginHooks.node,
SessionEnvironment.node,
],
deps: [Bus.node, Location.node, Config.node, Global.node, Environment.node, PluginHooks.node],
})
}
+3 -8
View File
@@ -679,18 +679,13 @@ describe("Session.create", () => {
const created = yield* session.create({
location: Location.Ref.make({ directory: AbsolutePath.make(directory) }),
})
yield* session.environment({ sessionID: created.id, variables: { OPENCODE_SESSION_ENV_TEST: "attached" } })
const command =
process.platform === "win32"
? "[Console]::Out.Write($env:OPENCODE_SESSION_ENV_TEST)"
: 'printf %s "$OPENCODE_SESSION_ENV_TEST"'
yield* session.shell({ sessionID: created.id, command })
yield* session.shell({ sessionID: created.id, command: "echo hello" })
const messages = yield* session.messages({ sessionID: created.id, order: "asc" })
const shell = messages.find((message): message is SessionMessage.Shell => message.type === "shell")
expect(shell).toMatchObject({ type: "shell", command, status: "exited", exit: 0 })
expect(shell?.output?.output).toContain("attached")
expect(shell).toMatchObject({ type: "shell", command: "echo hello", status: "exited", exit: 0 })
expect(shell?.output?.output).toContain("hello")
expect(shell?.output?.truncated).toBe(false)
expect(shell?.time.completed).toBeDefined()
}),
@@ -1,30 +0,0 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Session } from "@opencode-ai/core/session"
import { SessionEnvironment } from "@opencode-ai/core/session/environment"
import { testEffect } from "./lib/effect"
const it = testEffect(AppNodeBuilder.build(SessionEnvironment.node))
describe("SessionEnvironment", () => {
it.effect("stores replacement snapshots by session", () =>
Effect.gen(function* () {
const environments = yield* SessionEnvironment.Service
const first = Session.ID.make("ses_environment_first")
const second = Session.ID.make("ses_environment_second")
yield* environments.set(first, { TOOLCHAIN: "first", PATH: "/first/bin" })
yield* environments.set(second, { TOOLCHAIN: "second" })
yield* environments.set(first, { TOOLCHAIN: "updated" })
expect(yield* environments.get(first)).toEqual({ TOOLCHAIN: "updated" })
expect(yield* environments.get(second)).toEqual({ TOOLCHAIN: "second" })
yield* environments.clear(first)
expect(yield* environments.get(first)).toBeUndefined()
expect(yield* environments.get(second)).toEqual({ TOOLCHAIN: "second" })
}),
)
})
@@ -12,7 +12,6 @@ import { SessionExecution } from "@opencode-ai/core/session/execution"
import { SessionModelTransport } from "@opencode-ai/core/session/model-transport"
import { SessionProjector } from "@opencode-ai/core/session/projector"
import { SessionStore } from "@opencode-ai/core/session/store"
import { SessionEnvironment } from "@opencode-ai/core/session/environment"
import { LocationServiceMap } from "@opencode-ai/core/location-services"
import { testEffect } from "./lib/effect"
import { globalProjectLayer } from "./lib/project"
@@ -33,7 +32,6 @@ const it = testEffect(
Bus.node,
SessionProjector.node,
SessionStore.node,
SessionEnvironment.node,
Session.node,
LocationServiceMap.node,
]),
@@ -52,8 +50,6 @@ describe("Session.remove", () => {
const session = yield* Session.Service
const parent = yield* session.create({ location })
const child = yield* session.create({ parentID: parent.id })
yield* session.environment({ sessionID: parent.id, variables: { SESSION_ENV: "parent" } })
yield* session.environment({ sessionID: child.id, variables: { SESSION_ENV: "child" } })
yield* (yield* LocationServiceMap.Service).contextEffect(location)
closed.length = 0
@@ -61,9 +57,6 @@ describe("Session.remove", () => {
expect((yield* session.list()).data).toEqual([])
expect(closed).toEqual([parent.id, child.id])
const environments = yield* SessionEnvironment.Service
expect(yield* environments.get(parent.id)).toBeUndefined()
expect(yield* environments.get(child.id)).toBeUndefined()
expect(yield* Effect.result(session.get(parent.id))).toMatchObject({ _tag: "Failure" })
expect(yield* Effect.result(session.get(child.id))).toMatchObject({ _tag: "Failure" })
}),
-30
View File
@@ -260,36 +260,6 @@ describe("ShellTool", () => {
{ timeout: 15_000 },
)
productionIt.live(
"uses the session environment instead of the server environment",
() =>
Effect.acquireUseRelease(
Effect.promise(() => tmpdir()),
(tmp) => {
reset()
return withSession(tmp.path, (registry) =>
Effect.gen(function* () {
const sessions = yield* Session.Service
yield* sessions.environment({
sessionID,
variables: { OPENCODE_SESSION_ENV_TEST: "from-session" },
})
const command = isWindows
? "[Console]::Out.Write($env:OPENCODE_SESSION_ENV_TEST)"
: 'printf %s "$OPENCODE_SESSION_ENV_TEST"'
const settled = yield* executeTool(registry, call({ command }))
expect(settled.status).toBe("completed")
expect(settled.content?.[0]).toEqual({ type: "text", text: "from-session" })
}),
)
},
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
),
{ timeout: 15_000 },
)
it.live("resolves a relative workdir from the active Location", () =>
Effect.acquireUseRelease(
Effect.promise(() => tmpdir()),
-14
View File
@@ -693,20 +693,6 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
}),
),
)
.add(
HttpApiEndpoint.put("session.environment", "/api/session/:sessionID/environment", {
params: { sessionID: Session.ID },
payload: Schema.Struct({ variables: Schema.Record(Schema.String, Schema.String) }),
success: HttpApiSchema.NoContent,
error: SessionNotFoundError,
}).annotateMerge(
OpenApi.annotations({
identifier: "v2.session.environment",
summary: "Set session environment",
description: "Replace the process environment used by local shell commands for this session.",
}),
),
)
.annotateMerge(
OpenApi.annotations({
title: "session",
-16
View File
@@ -196,22 +196,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
return HttpApiSchema.NoContent.make()
}),
)
.handle(
"session.environment",
Effect.fn(function* (ctx) {
yield* session.environment({ sessionID: ctx.params.sessionID, variables: ctx.payload.variables }).pipe(
Effect.catchTag(
"Session.NotFoundError",
(error) =>
new SessionNotFoundError({
sessionID: error.sessionID,
message: `Session not found: ${error.sessionID}`,
}),
),
)
return HttpApiSchema.NoContent.make()
}),
)
.handle(
"session.fork",
Effect.fn(function* (ctx) {
-14
View File
@@ -41,7 +41,6 @@ import {
useTuiApp,
useTuiPaths,
useTuiStartup,
useTuiTerminalEnvironment,
type TuiApp,
} from "./context/runtime"
import { DialogProvider, useDialog } from "./ui/dialog"
@@ -186,7 +185,6 @@ export type TuiInput = {
args: Args
config: Config.Interface
packages: PackageResolver
environment?: Readonly<Record<string, string>>
terminalHandoff?: () => Promise<
| {
readonly renderer: CliRenderer
@@ -334,7 +332,6 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
: process.env.DISPLAY
? "x11"
: undefined,
variables: input.environment,
}}
>
<TuiStartupProvider
@@ -483,17 +480,6 @@ function App(props: { pair?: DialogPairCredentials }) {
const promptRef = usePromptRef()
const plugins = usePlugin()
const clipboard = useClipboard()
const terminalEnvironment = useTuiTerminalEnvironment()
createEffect(() => {
if (client.connection.status() !== "connected") return
if (route.data.type !== "session") return
const session = data.session.get(route.data.sessionID)
if (!session) return
if (session.location.workspaceID !== undefined || terminalEnvironment.variables === undefined) return
void client.api.session
.environment({ sessionID: session.id, variables: terminalEnvironment.variables })
.catch(toast.error)
})
const [layout, updateLayout] = useStorage().store<{ verticalTabsWidth?: number }>("layout", {
initial: { verticalTabsWidth: SESSION_SIDEBAR_WIDTH },
})
@@ -1206,19 +1206,6 @@ export function Prompt(props: PromptProps) {
sessionID = created.id
session = created
if (created.location.workspaceID === undefined && terminalEnvironment.variables !== undefined) {
const error = await client.api.session
.environment({ sessionID, variables: terminalEnvironment.variables })
.then(
() => undefined,
(error) => error,
)
if (error) {
if (finishMoveProgress) move.finishSubmit()
toast.show({ title: "Failed to set session environment", message: errorMessage(error), variant: "error" })
return true
}
}
}
// Capture mode before it gets reset
-1
View File
@@ -17,7 +17,6 @@ export type TuiTerminalEnvironment = Readonly<{
platform: string
multiplexer?: "tmux" | "screen"
displayServer?: "wayland" | "x11"
variables?: Readonly<Record<string, string>>
}>
export type TuiStartup = Readonly<{