From 126cc725d77f73bfb3de9e4c56bc01134960666a Mon Sep 17 00:00:00 2001 From: cafeai <116491182+cafeai@users.noreply.github.com> Date: Sun, 24 May 2026 07:44:52 +0900 Subject: [PATCH] Prepare Cafe Code 0.0.25 release --- README.md | 3 ++ apps/desktop/scripts/dev-electron.mjs | 1 + apps/desktop/scripts/start-electron.mjs | 3 ++ apps/desktop/src/app/DesktopConfig.ts | 1 + .../src/app/DesktopEnvironment.test.ts | 20 +++++++++ apps/desktop/src/app/DesktopEnvironment.ts | 2 +- .../src/app/DesktopObservability.test.ts | 1 + .../DesktopBackendConfiguration.test.ts | 3 ++ .../backend/DesktopBackendConfiguration.ts | 3 ++ apps/desktop/src/window/DesktopWindow.test.ts | 1 + apps/server/README.md | 40 +++++++++++++++++ apps/server/package.json | 20 ++++++++- apps/server/scripts/cli.ts | 16 +++++++ apps/server/src/cli/config.test.ts | 44 +++++++++++++++++++ apps/server/src/cli/config.ts | 4 +- .../Layers/ProjectionPipeline.ts | 37 ++-------------- .../Layers/ProviderRuntimeIngestion.test.ts | 24 +++++----- apps/web/src/store.ts | 22 ++-------- scripts/dev-runner.test.ts | 4 ++ scripts/dev-runner.ts | 4 +- 20 files changed, 184 insertions(+), 69 deletions(-) create mode 100644 apps/server/README.md diff --git a/README.md b/README.md index d639817b..4eccdd96 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ![Cafe Code desktop screenshot](./docs/images/cafe-code-desktop.png) +Made in Japan with love. + _Cafe Code is very small, barely does a thing at all. Chat goes in and chat comes out, soft and sweet, without a shout._ Cafe Code is a tiny desktop GUI for coding agents. It is a fork of [T3 Code](https://github.com/pingdotgg/t3code), with a basket of bug fixes, a little sweep-up, and some very opinionated trimming for people who want the agent chat and not much else. @@ -36,6 +38,7 @@ dashboard, a project-management suite, or a museum of buttons, no. This is the practical working list. It will probably get cleaned up later. +- Numerous bug fixes. - Rebranded the app around Cafe Code. - Moved local app data into `~/.cafe-code`. - Removed the in-app terminal drawer and terminal UI. diff --git a/apps/desktop/scripts/dev-electron.mjs b/apps/desktop/scripts/dev-electron.mjs index dc3e402c..34e4a697 100644 --- a/apps/desktop/scripts/dev-electron.mjs +++ b/apps/desktop/scripts/dev-electron.mjs @@ -38,6 +38,7 @@ await waitForResources({ const childEnv = { ...process.env }; delete childEnv.ELECTRON_RUN_AS_NODE; +childEnv.CAFE_CODE_DESKTOP_DEV = "true"; let shuttingDown = false; let restartTimer = null; diff --git a/apps/desktop/scripts/start-electron.mjs b/apps/desktop/scripts/start-electron.mjs index 0fd8dd6a..3bf3f338 100644 --- a/apps/desktop/scripts/start-electron.mjs +++ b/apps/desktop/scripts/start-electron.mjs @@ -4,6 +4,9 @@ import { desktopDir, resolveElectronPath } from "./electron-launcher.mjs"; const childEnv = { ...process.env }; delete childEnv.ELECTRON_RUN_AS_NODE; +delete childEnv.CAFE_CODE_DESKTOP_DEV; +delete childEnv.VITE_DEV_SERVER_URL; +delete childEnv.CAFE_CODE_DEV_URL; const child = spawn(resolveElectronPath(), ["dist-electron/main.cjs", ...process.argv.slice(2)], { stdio: "inherit", diff --git a/apps/desktop/src/app/DesktopConfig.ts b/apps/desktop/src/app/DesktopConfig.ts index c70ff4f0..beb57801 100644 --- a/apps/desktop/src/app/DesktopConfig.ts +++ b/apps/desktop/src/app/DesktopConfig.ts @@ -40,6 +40,7 @@ export const DesktopConfig = Config.all({ appDataDirectory: trimmedString("APPDATA"), xdgConfigHome: trimmedString("XDG_CONFIG_HOME"), cafeCodeHome: trimmedString("CAFE_CODE_HOME"), + desktopDevelopmentMode: optionalBoolean("CAFE_CODE_DESKTOP_DEV"), devServerUrl: Config.url("VITE_DEV_SERVER_URL").pipe(Config.option), devRemoteServerEntryPath: trimmedString("CAFE_CODE_DEV_REMOTE_SERVER_ENTRY_PATH"), configuredBackendPort: cafeCodeOptionalConfig("CAFE_CODE_PORT", Config.port), diff --git a/apps/desktop/src/app/DesktopEnvironment.test.ts b/apps/desktop/src/app/DesktopEnvironment.test.ts index 286fda34..57589a9a 100644 --- a/apps/desktop/src/app/DesktopEnvironment.test.ts +++ b/apps/desktop/src/app/DesktopEnvironment.test.ts @@ -44,6 +44,7 @@ describe("DesktopEnvironment", () => { { CAFE_CODE_HOME: " /tmp/t3 ", CAFE_CODE_COMMIT_HASH: " 0123456789abcdef ", + CAFE_CODE_DESKTOP_DEV: "true", CAFE_CODE_PORT: "4949", VITE_DEV_SERVER_URL: "http://localhost:5173", CAFE_CODE_DEV_REMOTE_SERVER_ENTRY_PATH: " /remote/server.mjs ", @@ -83,6 +84,25 @@ describe("DesktopEnvironment", () => { }), ); + it.effect("does not switch app identity to development from an inherited Vite URL", () => + Effect.gen(function* () { + const environment = yield* makeEnvironment( + {}, + { + CAFE_CODE_HOME: "/tmp/t3", + VITE_DEV_SERVER_URL: "http://localhost:5173", + }, + ); + + assert.equal(environment.isDevelopment, false); + assert.equal(environment.stateDir, "/tmp/t3/userdata"); + assert.equal(environment.branding.stageLabel, "Alpha"); + assert.equal(environment.displayName, "Cafe Code (Alpha)"); + assert.equal(environment.userDataDirName, "cafecode"); + assert.equal(environment.appUserModelId, "com.cafeai.cafecode"); + }), + ); + it.effect("derives production state paths under userdata", () => Effect.gen(function* () { const environment = yield* makeEnvironment( diff --git a/apps/desktop/src/app/DesktopEnvironment.ts b/apps/desktop/src/app/DesktopEnvironment.ts index 2ae4cf19..02897e97 100644 --- a/apps/desktop/src/app/DesktopEnvironment.ts +++ b/apps/desktop/src/app/DesktopEnvironment.ts @@ -142,7 +142,7 @@ const makeDesktopEnvironment = Effect.fn("desktop.environment.make")(function* ( const config = yield* DesktopConfig.DesktopConfig; const homeDirectory = input.homeDirectory; const devServerUrl = config.devServerUrl; - const isDevelopment = Option.isSome(devServerUrl); + const isDevelopment = config.desktopDevelopmentMode; const appDataDirectory = input.platform === "win32" ? Option.getOrElse(config.appDataDirectory, () => diff --git a/apps/desktop/src/app/DesktopObservability.test.ts b/apps/desktop/src/app/DesktopObservability.test.ts index 1cf7e187..804a1c77 100644 --- a/apps/desktop/src/app/DesktopObservability.test.ts +++ b/apps/desktop/src/app/DesktopObservability.test.ts @@ -56,6 +56,7 @@ const makeEnvironmentLayer = (baseDir: string) => NodeServices.layer, DesktopConfig.layerTest({ CAFE_CODE_HOME: baseDir, + CAFE_CODE_DESKTOP_DEV: "true", VITE_DEV_SERVER_URL: "http://127.0.0.1:5733", }), ), diff --git a/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts b/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts index 9fbe01df..18d1c57a 100644 --- a/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts +++ b/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts @@ -113,6 +113,9 @@ describe("DesktopBackendConfiguration", () => { assert.isUndefined(first.env.CAFE_CODE_PORT); assert.isUndefined(first.env.CAFE_CODE_MODE); assert.isUndefined(first.env.CAFE_CODE_DESKTOP_LAN_HOST); + assert.isUndefined(first.env.CAFE_CODE_DESKTOP_DEV); + assert.isUndefined(first.env.CAFE_CODE_DEV_URL); + assert.isUndefined(first.env.VITE_DEV_SERVER_URL); assert.equal(first.bootstrap.mode, "desktop"); assert.equal(first.bootstrap.noBrowser, true); diff --git a/apps/desktop/src/backend/DesktopBackendConfiguration.ts b/apps/desktop/src/backend/DesktopBackendConfiguration.ts index 55b089e0..93c6e9e4 100644 --- a/apps/desktop/src/backend/DesktopBackendConfiguration.ts +++ b/apps/desktop/src/backend/DesktopBackendConfiguration.ts @@ -36,12 +36,15 @@ const DESKTOP_BACKEND_ENV_NAMES = [ "CAFE_CODE_MODE", "CAFE_CODE_NO_BROWSER", "CAFE_CODE_HOST", + "CAFE_CODE_DEV_URL", + "CAFE_CODE_DESKTOP_DEV", "CAFE_CODE_DESKTOP_WS_URL", "CAFE_CODE_DESKTOP_LAN_ACCESS", "CAFE_CODE_DESKTOP_LAN_HOST", "CAFE_CODE_DESKTOP_HTTPS_ENDPOINTS", "CAFE_CODE_TAILSCALE_SERVE", "CAFE_CODE_TAILSCALE_SERVE_PORT", + "VITE_DEV_SERVER_URL", ] as const; const backendChildEnvPatch = (): Record => diff --git a/apps/desktop/src/window/DesktopWindow.test.ts b/apps/desktop/src/window/DesktopWindow.test.ts index e5f9180f..8bce7880 100644 --- a/apps/desktop/src/window/DesktopWindow.test.ts +++ b/apps/desktop/src/window/DesktopWindow.test.ts @@ -120,6 +120,7 @@ const desktopEnvironmentLayer = DesktopEnvironment.layer(environmentInput).pipe( Layer.mergeAll( NodeServices.layer, DesktopConfig.layerTest({ + CAFE_CODE_DESKTOP_DEV: "true", CAFE_CODE_PORT: "3773", VITE_DEV_SERVER_URL: "http://127.0.0.1:5733", }), diff --git a/apps/server/README.md b/apps/server/README.md new file mode 100644 index 00000000..3f484dd1 --- /dev/null +++ b/apps/server/README.md @@ -0,0 +1,40 @@ +# Cafe Code + +A minimal AI chat harness for coding agents. + +Cafe Code is a tiny local UI for Codex and Claude. It is a fork of +[T3 Code](https://github.com/pingdotgg/t3code), trimmed down around one idea: +type a prompt, let the agent work, and keep the interface quiet, fast, and out +of your way. + +No terminal drawer. No pretend IDE. No release dashboard. If you want a console, +use a real console. If you want to inspect code, open it in VS Code. + +## Run + +```bash +npx @cafeai/cafe-code +``` + +`npx` downloads the package if needed and starts Cafe Code immediately. + +If you want a normal command on your machine: + +```bash +npm install -g @cafeai/cafe-code +cafe-code +``` + +Cafe Code expects providers to already be installed and authenticated: + +- Codex: install Codex CLI and run `codex login` +- Claude: install Claude Code and run `claude auth login` + +## Notes + +The npm package runs the local Cafe Code server and bundled UI. Desktop +installers are not published yet. + +## License + +AGPL-3.0-or-later. diff --git a/apps/server/package.json b/apps/server/package.json index 8876c97c..fe4549ae 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,20 @@ { "name": "@cafeai/cafe-code", "version": "0.0.25", + "description": "A minimal AI chat harness for coding agents.", + "keywords": [ + "agent", + "ai", + "claude", + "codex", + "coding-agent", + "desktop", + "electron" + ], + "homepage": "https://github.com/cafeai/cafe-code#readme", + "bugs": { + "url": "https://github.com/cafeai/cafe-code/issues" + }, "license": "AGPL-3.0-or-later", "repository": { "type": "git", @@ -11,9 +25,13 @@ "cafe-code": "./dist/bin.mjs" }, "files": [ - "dist" + "dist", + "README.md" ], "type": "module", + "publishConfig": { + "access": "public" + }, "scripts": { "dev": "node --watch src/bin.ts", "build": "node scripts/cli.ts build", diff --git a/apps/server/scripts/cli.ts b/apps/server/scripts/cli.ts index 42b25d43..055c24e6 100644 --- a/apps/server/scripts/cli.ts +++ b/apps/server/scripts/cli.ts @@ -22,16 +22,26 @@ import serverPackageJson from "../package.json" with { type: "json" }; interface PackageJson { name: string; + description?: string; + license: string; + homepage?: string; + bugs?: { + url: string; + }; repository: { type: string; url: string; directory: string; }; + keywords?: string[]; bin: Record; type: string; version: string; engines: Record; files: string[]; + publishConfig?: { + access: string; + }; dependencies: Record; overrides: Record; } @@ -217,12 +227,18 @@ const publishCmd = Command.make( const version = Option.getOrElse(config.appVersion, () => serverPackageJson.version); const pkg: PackageJson = { name: serverPackageJson.name, + description: serverPackageJson.description, + license: serverPackageJson.license, + homepage: serverPackageJson.homepage, + bugs: serverPackageJson.bugs, repository: serverPackageJson.repository, + keywords: serverPackageJson.keywords, bin: serverPackageJson.bin, type: serverPackageJson.type, version, engines: serverPackageJson.engines, files: serverPackageJson.files, + publishConfig: serverPackageJson.publishConfig, dependencies: resolveCatalogDependencies( serverPackageJson.dependencies, rootPackageJson.workspaces.catalog, diff --git a/apps/server/src/cli/config.test.ts b/apps/server/src/cli/config.test.ts index 3d2224da..f6c4097e 100644 --- a/apps/server/src/cli/config.test.ts +++ b/apps/server/src/cli/config.test.ts @@ -98,6 +98,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { CAFE_CODE_PORT: "4001", CAFE_CODE_HOST: "0.0.0.0", CAFE_CODE_HOME: baseDir, + CAFE_CODE_DEV_URL: "http://127.0.0.1:5173", VITE_DEV_SERVER_URL: "http://127.0.0.1:5173", CAFE_CODE_NO_BROWSER: "true", CAFE_CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false", @@ -132,6 +133,48 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { }), ); + it.effect("ignores renderer-only Vite dev URL unless Cafe Code dev URL is explicit", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-config-vite-only-" }); + const derivedPaths = yield* deriveServerPaths(baseDir, undefined); + const resolved = yield* resolveServerConfig( + { + mode: Option.some("desktop"), + port: Option.none(), + host: Option.none(), + baseDir: Option.none(), + cwd: Option.none(), + devUrl: Option.none(), + noBrowser: Option.none(), + bootstrapFd: Option.none(), + autoBootstrapProjectFromCwd: Option.none(), + logWebSocketEvents: Option.none(), + tailscaleServeEnabled: Option.none(), + tailscaleServePort: Option.none(), + }, + Option.none(), + ).pipe( + Effect.provide( + Layer.mergeAll( + ConfigProvider.layer( + ConfigProvider.fromEnv({ + env: { + CAFE_CODE_HOME: baseDir, + VITE_DEV_SERVER_URL: "http://127.0.0.1:5173", + }, + }), + ), + NetService.layer, + ), + ), + ); + + expect(resolved.devUrl).toBeUndefined(); + expect(resolved.stateDir).toBe(derivedPaths.stateDir); + }), + ); + it.effect("uses CLI flags when provided", () => Effect.gen(function* () { const { join } = yield* Path.Path; @@ -164,6 +207,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { CAFE_CODE_PORT: "4001", CAFE_CODE_HOST: "0.0.0.0", CAFE_CODE_HOME: join(NodeOS.tmpdir(), "ignored-base"), + CAFE_CODE_DEV_URL: "http://127.0.0.1:5173", VITE_DEV_SERVER_URL: "http://127.0.0.1:5173", CAFE_CODE_NO_BROWSER: "false", CAFE_CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false", diff --git a/apps/server/src/cli/config.ts b/apps/server/src/cli/config.ts index c7ca926d..50061a70 100644 --- a/apps/server/src/cli/config.ts +++ b/apps/server/src/cli/config.ts @@ -45,7 +45,7 @@ export const baseDirFlag = Flag.string("base-dir").pipe( ); export const devUrlFlag = Flag.string("dev-url").pipe( Flag.withSchema(Schema.URLFromString), - Flag.withDescription("Dev web URL to proxy/redirect to (equivalent to VITE_DEV_SERVER_URL)."), + Flag.withDescription("Development web URL to proxy/redirect to."), Flag.optional, ); export const noBrowserFlag = Flag.boolean("no-browser").pipe( @@ -114,7 +114,7 @@ const EnvServerConfig = Config.all({ port: cafeCodeOptionalValueConfig("CAFE_CODE_PORT", Config.port), host: cafeCodeOptionalValueConfig("CAFE_CODE_HOST", Config.string), cafeCodeHome: cafeCodeOptionalValueConfig("CAFE_CODE_HOME", Config.string), - devUrl: Config.url("VITE_DEV_SERVER_URL").pipe(Config.option, Config.map(Option.getOrUndefined)), + devUrl: Config.url("CAFE_CODE_DEV_URL").pipe(Config.option, Config.map(Option.getOrUndefined)), noBrowser: cafeCodeOptionalValueConfig("CAFE_CODE_NO_BROWSER", Config.boolean), bootstrapFd: cafeCodeOptionalValueConfig("CAFE_CODE_BOOTSTRAP_FD", Config.int), autoBootstrapProjectFromCwd: cafeCodeOptionalValueConfig( diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts index 51ff2d08..30fee176 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts @@ -1128,14 +1128,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti if (event.payload.turnId === null || event.payload.role !== "assistant") { return; } - const activeSession = yield* projectionThreadSessionRepository.getByThreadId({ - threadId: event.payload.threadId, - }); - const sessionIsStillRunningThisTurn = - Option.isSome(activeSession) && - activeSession.value.status === "running" && - activeSession.value.activeTurnId === event.payload.turnId; - const shouldHoldTurnOpen = event.payload.streaming || sessionIsStillRunningThisTurn; + const shouldHoldTurnOpen = event.payload.streaming; const existingTurn = yield* projectionTurnRepository.getByTurnId({ threadId: event.payload.threadId, turnId: event.payload.turnId, @@ -1223,27 +1216,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti threadId: event.payload.threadId, turnId: event.payload.turnId, }); - const activeSession = yield* projectionThreadSessionRepository.getByThreadId({ - threadId: event.payload.threadId, - }); - const activeSessionStillOwnsTurn = - Option.isSome(activeSession) && - activeSession.value.activeTurnId === event.payload.turnId && - activeSession.value.status !== "error" && - activeSession.value.status !== "interrupted" && - activeSession.value.status !== "stopped"; - const existingTurnStillRunning = - Option.isSome(existingTurn) && - existingTurn.value.state === "running" && - existingTurn.value.completedAt === null; - const shouldHoldTurnOpen = - event.payload.status !== "error" && - (activeSessionStillOwnsTurn || existingTurnStillRunning); - const nextState = shouldHoldTurnOpen - ? "running" - : event.payload.status === "error" - ? "error" - : "completed"; + const nextState = event.payload.status === "error" ? "error" : "completed"; yield* projectionTurnRepository.clearCheckpointTurnConflict({ threadId: event.payload.threadId, turnId: event.payload.turnId, @@ -1261,9 +1234,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti checkpointFiles: event.payload.files, startedAt: existingTurn.value.startedAt ?? event.payload.completedAt, requestedAt: existingTurn.value.requestedAt ?? event.payload.completedAt, - completedAt: shouldHoldTurnOpen - ? (existingTurn.value.completedAt ?? null) - : event.payload.completedAt, + completedAt: event.payload.completedAt, }); return; } @@ -1277,7 +1248,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti state: nextState, requestedAt: event.payload.completedAt, startedAt: event.payload.completedAt, - completedAt: shouldHoldTurnOpen ? null : event.payload.completedAt, + completedAt: event.payload.completedAt, checkpointTurnCount: event.payload.checkpointTurnCount, checkpointRef: event.payload.checkpointRef, checkpointStatus: event.payload.status, diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts index d9bb4851..82f66778 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts @@ -2362,7 +2362,7 @@ describe("ProviderRuntimeIngestion", () => { expect(completionEvents).toHaveLength(1); }); - it("keeps a turn running after assistant message completion until provider thread idle", async () => { + it("completes assistant output independently from later provider thread idle/completion events", async () => { const harness = await createHarness(); const turnId = asTurnId("turn-provider-idle"); @@ -2408,22 +2408,21 @@ describe("ProviderRuntimeIngestion", () => { }, }); - const stillRunning = await waitForThread( + const completedOutput = await waitForThread( harness.readModel, (thread) => thread.latestTurn?.turnId === turnId && - thread.latestTurn.state === "running" && - thread.latestTurn.completedAt === null && - thread.session?.status === "running" && - thread.session.activeTurnId === turnId && + thread.latestTurn.state === "completed" && + thread.latestTurn.completedAt === "2026-01-01T00:00:02.000Z" && + thread.session?.status === "ready" && + thread.session.activeTurnId === null && thread.messages.some( (message: ProviderRuntimeTestMessage) => message.id === "assistant:item-provider-idle" && !message.streaming, ), ); - expect(stillRunning.latestTurn?.state).toBe("running"); - expect(stillRunning.latestTurn?.completedAt).toBeNull(); - expect(stillRunning.session?.status).toBe("running"); + expect(completedOutput.latestTurn?.state).toBe("completed"); + expect(completedOutput.session?.status).toBe("ready"); harness.emit({ type: "thread.state.changed", @@ -2440,9 +2439,10 @@ describe("ProviderRuntimeIngestion", () => { harness.readModel, (thread) => thread.latestTurn?.turnId === turnId && - thread.latestTurn.state === "running" && + thread.latestTurn.state === "completed" && + thread.latestTurn.completedAt === "2026-01-01T00:00:02.000Z" && thread.session?.status === "ready" && - thread.session.activeTurnId === turnId, + thread.session.activeTurnId === null, ); expect(readyAfterIdle.session?.status).toBe("ready"); @@ -2463,7 +2463,7 @@ describe("ProviderRuntimeIngestion", () => { (thread) => thread.latestTurn?.turnId === turnId && thread.latestTurn.state === "completed" && - thread.latestTurn.completedAt === "2026-01-01T00:00:04.000Z" && + thread.latestTurn.completedAt === "2026-01-01T00:00:02.000Z" && thread.session?.status === "ready", ); expect(completed.latestTurn?.state).toBe("completed"); diff --git a/apps/web/src/store.ts b/apps/web/src/store.ts index 5d3eec93..48ea4bb6 100644 --- a/apps/web/src/store.ts +++ b/apps/web/src/store.ts @@ -1440,10 +1440,7 @@ function applyEnvironmentOrchestrationEvent( event.payload.turnId !== null && (thread.latestTurn === null || thread.latestTurn.turnId === event.payload.turnId) ? (() => { - const sessionIsStillRunningThisTurn = - thread.session?.orchestrationStatus === "running" && - thread.session.activeTurnId === event.payload.turnId; - const shouldHoldTurnOpen = event.payload.streaming || sessionIsStillRunningThisTurn; + const shouldHoldTurnOpen = event.payload.streaming; return buildLatestTurn({ previous: thread.latestTurn, turnId: event.payload.turnId, @@ -1580,26 +1577,13 @@ function applyEnvironmentOrchestrationEvent( const latestTurn = thread.latestTurn === null || thread.latestTurn.turnId === event.payload.turnId ? (() => { - const shouldHoldTurnOpen = - event.payload.status !== "error" && - ((thread.session?.activeTurnId === event.payload.turnId && - thread.session.orchestrationStatus !== "error" && - thread.session.orchestrationStatus !== "interrupted" && - thread.session.orchestrationStatus !== "stopped") || - (thread.latestTurn?.turnId === event.payload.turnId && - thread.latestTurn.state === "running" && - thread.latestTurn.completedAt === null)); return buildLatestTurn({ previous: thread.latestTurn, turnId: event.payload.turnId, - state: shouldHoldTurnOpen - ? "running" - : checkpointStatusToLatestTurnState(event.payload.status), + state: checkpointStatusToLatestTurnState(event.payload.status), requestedAt: thread.latestTurn?.requestedAt ?? event.payload.completedAt, startedAt: thread.latestTurn?.startedAt ?? event.payload.completedAt, - completedAt: shouldHoldTurnOpen - ? (thread.latestTurn?.completedAt ?? null) - : event.payload.completedAt, + completedAt: event.payload.completedAt, assistantMessageId: event.payload.assistantMessageId, sourceProposedPlan: thread.pendingSourceProposedPlan, }); diff --git a/scripts/dev-runner.test.ts b/scripts/dev-runner.test.ts index bf8d4f7a..2628bac1 100644 --- a/scripts/dev-runner.test.ts +++ b/scripts/dev-runner.test.ts @@ -101,7 +101,9 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { assertEnvValue(env, "CAFE_CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD", "0"); assertEnvValue(env, "CAFE_CODE_LOG_WS_EVENTS", "1"); assertEnvValue(env, "CAFE_CODE_HOST", "0.0.0.0"); + assertEnvValue(env, "CAFE_CODE_DESKTOP_DEV", undefined); assert.equal(env.VITE_DEV_SERVER_URL, "http://localhost:7331/"); + assertEnvValue(env, "CAFE_CODE_DEV_URL", "http://localhost:7331/"); }), ); @@ -197,6 +199,8 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { assertEnvValue(env, "CAFE_CODE_HOME", path.resolve("/tmp/my-t3")); assert.equal(env.PORT, "5733"); assert.equal(env.VITE_DEV_SERVER_URL, "http://127.0.0.1:5733"); + assertEnvValue(env, "CAFE_CODE_DEV_URL", "http://127.0.0.1:5733"); + assertEnvValue(env, "CAFE_CODE_DESKTOP_DEV", "true"); assert.equal(env.HOST, "127.0.0.1"); assertEnvValue(env, "CAFE_CODE_PORT", "4222"); assert.equal(env.VITE_HTTP_URL, "http://127.0.0.1:4222"); diff --git a/scripts/dev-runner.ts b/scripts/dev-runner.ts index 26854e15..61fb954c 100644 --- a/scripts/dev-runner.ts +++ b/scripts/dev-runner.ts @@ -167,6 +167,7 @@ export function createDevRunnerEnv({ devUrl?.toString() ?? `http://${isDesktopMode ? DESKTOP_DEV_LOOPBACK_HOST : "localhost"}:${webPort}`, }; + writeCafeCodeEnv(output, "CAFE_CODE_DEV_URL", output.VITE_DEV_SERVER_URL); writeCafeCodeEnv(output, "CAFE_CODE_HOME", resolvedBaseDir); if (!isDesktopMode) { @@ -174,6 +175,7 @@ export function createDevRunnerEnv({ output.VITE_HTTP_URL = `http://localhost:${serverPort}`; output.VITE_WS_URL = `ws://localhost:${serverPort}`; } else { + writeCafeCodeEnv(output, "CAFE_CODE_DESKTOP_DEV", "true"); writeCafeCodeEnv(output, "CAFE_CODE_PORT", String(serverPort)); output.VITE_HTTP_URL = `http://${DESKTOP_DEV_LOOPBACK_HOST}:${serverPort}`; output.VITE_WS_URL = `ws://${DESKTOP_DEV_LOOPBACK_HOST}:${serverPort}`; @@ -515,7 +517,7 @@ const devRunnerCli = Command.make("dev-runner", { ), devUrl: Flag.string("dev-url").pipe( Flag.withSchema(Schema.URLFromString), - Flag.withDescription("Web dev URL override (forwards to VITE_DEV_SERVER_URL)."), + Flag.withDescription("Web dev URL override for development server wiring."), Flag.withFallbackConfig(optionalUrlConfig("VITE_DEV_SERVER_URL")), ), dryRun: Flag.boolean("dry-run").pipe(