From 69d7c2add7a9b42a2ebf3a92ecb6801c881021a1 Mon Sep 17 00:00:00 2001 From: Dax Date: Tue, 21 Jul 2026 12:32:23 -0400 Subject: [PATCH 1/3] feat(util): configure package publishing (#38129) --- bun.lock | 1 - packages/core/src/effect/app-node-platform.ts | 13 +++++ packages/core/src/generate.ts | 2 +- packages/core/src/session/compaction.ts | 2 +- packages/core/src/session/generate-node.ts | 2 +- packages/core/src/session/runner/llm.ts | 2 +- packages/core/src/session/title.ts | 2 +- packages/core/test/session-compaction.test.ts | 2 +- packages/core/test/session-generate.test.ts | 2 +- .../core/test/session-runner-recorded.test.ts | 2 +- packages/core/test/session-runner.test.ts | 2 +- packages/core/test/session-title.test.ts | 2 +- packages/util/package.json | 14 +++++- packages/util/script/build.ts | 9 ++++ packages/util/script/publish.ts | 49 +++++++++++++++++++ packages/util/src/cross-spawn-spawner.ts | 6 +-- packages/util/src/effect-flock.ts | 8 +-- packages/util/src/effect/app-node-platform.ts | 11 +---- packages/util/src/effect/app-node.ts | 4 +- packages/util/src/effect/layer-node.ts | 2 +- packages/util/src/effect/runtime.ts | 4 +- packages/util/src/flock.ts | 2 +- packages/util/src/fs-util.ts | 8 +-- packages/util/src/global.ts | 6 +-- packages/util/src/installation/version.ts | 6 +-- packages/util/src/npm-config.ts | 2 +- packages/util/src/npm.ts | 18 +++---- packages/util/src/observability.ts | 8 +-- packages/util/src/observability/logging.ts | 8 +-- packages/util/src/observability/otlp.ts | 6 +-- packages/util/src/patch.ts | 2 +- packages/util/src/process.ts | 6 +-- packages/util/tsconfig.build.json | 11 +++++ packages/util/tsconfig.json | 5 ++ script/publish.ts | 3 ++ 35 files changed, 161 insertions(+), 71 deletions(-) create mode 100644 packages/core/src/effect/app-node-platform.ts create mode 100644 packages/util/script/build.ts create mode 100644 packages/util/script/publish.ts create mode 100644 packages/util/tsconfig.build.json diff --git a/bun.lock b/bun.lock index 8992576c8f..c52d9e6b17 100644 --- a/bun.lock +++ b/bun.lock @@ -968,7 +968,6 @@ "@effect/platform-node": "catalog:", "@npmcli/arborist": "catalog:", "@npmcli/config": "10.8.1", - "@opencode-ai/ai": "workspace:*", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", diff --git a/packages/core/src/effect/app-node-platform.ts b/packages/core/src/effect/app-node-platform.ts new file mode 100644 index 0000000000..4ad725fcbb --- /dev/null +++ b/packages/core/src/effect/app-node-platform.ts @@ -0,0 +1,13 @@ +import { LLMClient, RequestExecutor } from "@opencode-ai/ai/route" +import { makeGlobalNode } from "@opencode-ai/util/effect/app-node" +import { httpClient } from "@opencode-ai/util/effect/app-node-platform" + +export const requestExecutor = makeGlobalNode({ + service: RequestExecutor.Service, + layer: RequestExecutor.layer, + deps: [httpClient], +}) + +export const llmClient = makeGlobalNode({ service: LLMClient.Service, layer: LLMClient.layer, deps: [requestExecutor] }) + +export * as LayerNodePlatform from "./app-node-platform" diff --git a/packages/core/src/generate.ts b/packages/core/src/generate.ts index 9304bbb001..f4a79e9aa2 100644 --- a/packages/core/src/generate.ts +++ b/packages/core/src/generate.ts @@ -4,7 +4,7 @@ import { LLM, LLMClient, LLMError } from "@opencode-ai/ai" import { Context, Effect, Layer, Schema } from "effect" import { Catalog } from "./catalog" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" -import { llmClient } from "@opencode-ai/util/effect/app-node-platform" +import { llmClient } from "./effect/app-node-platform" import { Integration } from "./integration" import { ModelV2 } from "./model" import { SessionRunnerModel } from "./session/runner/model" diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts index 8ae095c4b0..8a897e350b 100644 --- a/packages/core/src/session/compaction.ts +++ b/packages/core/src/session/compaction.ts @@ -6,7 +6,7 @@ import { Context, Effect, Layer, Stream } from "effect" import { Config } from "../config" import { EventV2 } from "../event" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" -import { llmClient } from "@opencode-ai/util/effect/app-node-platform" +import { llmClient } from "../effect/app-node-platform" import { SessionEvent } from "./event" import type { SessionMessage } from "./message" import { SessionModelHeaders } from "./model-headers" diff --git a/packages/core/src/session/generate-node.ts b/packages/core/src/session/generate-node.ts index fcde898c4c..4e3074640e 100644 --- a/packages/core/src/session/generate-node.ts +++ b/packages/core/src/session/generate-node.ts @@ -4,7 +4,7 @@ import { LLM, LLMClient, Message, SystemPart } from "@opencode-ai/ai" import { Effect, Layer } from "effect" import { Database } from "../database/database" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" -import { llmClient } from "@opencode-ai/util/effect/app-node-platform" +import { llmClient } from "../effect/app-node-platform" import { PluginHooks } from "../plugin/hooks" import { SessionContext } from "./context" import { SessionGenerate } from "./generate" diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 2885a24c14..2a8aee08cc 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -22,7 +22,7 @@ import { Service } from "./index" import { createLLMEventPublisher } from "./publish-llm-event" import { Snapshot } from "../../snapshot" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" -import { llmClient } from "@opencode-ai/util/effect/app-node-platform" +import { llmClient } from "../../effect/app-node-platform" import { StepFailedError } from "../error" import { toSessionError } from "../to-session-error" import { SessionRunnerRetry } from "./retry" diff --git a/packages/core/src/session/title.ts b/packages/core/src/session/title.ts index faa53ac01c..fd58a7c87d 100644 --- a/packages/core/src/session/title.ts +++ b/packages/core/src/session/title.ts @@ -6,7 +6,7 @@ import { AgentV2 } from "../agent" import { Database } from "../database/database" import { EventV2 } from "../event" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" -import { llmClient } from "@opencode-ai/util/effect/app-node-platform" +import { llmClient } from "../effect/app-node-platform" import { SessionEvent } from "./event" import { SessionHistory } from "./history" import { SessionModelHeaders } from "./model-headers" diff --git a/packages/core/test/session-compaction.test.ts b/packages/core/test/session-compaction.test.ts index a4959badf6..0634e42224 100644 --- a/packages/core/test/session-compaction.test.ts +++ b/packages/core/test/session-compaction.test.ts @@ -4,7 +4,7 @@ import { OpenAIChat } from "@opencode-ai/ai/protocols" import { Config } from "@opencode-ai/core/config" import { Database } from "@opencode-ai/core/database/database" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" -import { llmClient } from "@opencode-ai/util/effect/app-node-platform" +import { llmClient } from "@opencode-ai/core/effect/app-node-platform" import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { EventV2 } from "@opencode-ai/core/event" import { EventTable } from "@opencode-ai/core/event/sql" diff --git a/packages/core/test/session-generate.test.ts b/packages/core/test/session-generate.test.ts index 647122ef34..b8be85433f 100644 --- a/packages/core/test/session-generate.test.ts +++ b/packages/core/test/session-generate.test.ts @@ -4,7 +4,7 @@ import { OpenAIChat } from "@opencode-ai/ai/protocols" import { AgentV2 } from "@opencode-ai/core/agent" import { Database } from "@opencode-ai/core/database/database" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" -import { llmClient } from "@opencode-ai/util/effect/app-node-platform" +import { llmClient } from "@opencode-ai/core/effect/app-node-platform" import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { EventV2 } from "@opencode-ai/core/event" import { EventTable } from "@opencode-ai/core/event/sql" diff --git a/packages/core/test/session-runner-recorded.test.ts b/packages/core/test/session-runner-recorded.test.ts index bf5b538122..ba4329ca78 100644 --- a/packages/core/test/session-runner-recorded.test.ts +++ b/packages/core/test/session-runner-recorded.test.ts @@ -4,7 +4,7 @@ import { Auth, LLMClient, RequestExecutor } from "@opencode-ai/ai/route" import { Catalog } from "@opencode-ai/core/catalog" import { Database } from "@opencode-ai/core/database/database" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" -import { LayerNodePlatform } from "@opencode-ai/util/effect/app-node-platform" +import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform" import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { EventV2 } from "@opencode-ai/core/event" import { EventTable } from "@opencode-ai/core/event/sql" diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index e38875cffa..6de3b0d070 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -19,7 +19,7 @@ import { Catalog } from "@opencode-ai/core/catalog" import { Database } from "@opencode-ai/core/database/database" import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" -import { LayerNodePlatform } from "@opencode-ai/util/effect/app-node-platform" +import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform" import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { EventV2 } from "@opencode-ai/core/event" import { InstallationVersion } from "@opencode-ai/util/installation/version" diff --git a/packages/core/test/session-title.test.ts b/packages/core/test/session-title.test.ts index d1cebe3d40..3263930e1e 100644 --- a/packages/core/test/session-title.test.ts +++ b/packages/core/test/session-title.test.ts @@ -4,7 +4,7 @@ import { OpenAIChat } from "@opencode-ai/ai/protocols" import { AgentV2 } from "@opencode-ai/core/agent" import { Database } from "@opencode-ai/core/database/database" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" -import { llmClient } from "@opencode-ai/util/effect/app-node-platform" +import { llmClient } from "@opencode-ai/core/effect/app-node-platform" import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { EventV2 } from "@opencode-ai/core/event" import { SessionEvent } from "@opencode-ai/core/session/event" diff --git a/packages/util/package.json b/packages/util/package.json index 817c94328f..70dab458df 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -2,9 +2,19 @@ "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/util", "version": "1.18.3", - "private": true, "type": "module", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/anomalyco/opencode.git", + "directory": "packages/util" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], "exports": { "./effect/layer-node": "./src/effect/layer-node.ts", "./*": "./src/*.ts" @@ -17,6 +27,7 @@ } }, "scripts": { + "build": "bun run script/build.ts", "typecheck": "tsgo --noEmit" }, "dependencies": { @@ -24,7 +35,6 @@ "@effect/platform-node": "catalog:", "@npmcli/arborist": "catalog:", "@npmcli/config": "10.8.1", - "@opencode-ai/ai": "workspace:*", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", diff --git a/packages/util/script/build.ts b/packages/util/script/build.ts new file mode 100644 index 0000000000..323a63ddf9 --- /dev/null +++ b/packages/util/script/build.ts @@ -0,0 +1,9 @@ +#!/usr/bin/env bun + +import { $ } from "bun" +import { fileURLToPath } from "node:url" + +process.chdir(fileURLToPath(new URL("..", import.meta.url))) + +await $`rm -rf dist` +await $`bun tsc -p tsconfig.build.json` diff --git a/packages/util/script/publish.ts b/packages/util/script/publish.ts new file mode 100644 index 0000000000..ec6cff16c4 --- /dev/null +++ b/packages/util/script/publish.ts @@ -0,0 +1,49 @@ +#!/usr/bin/env bun + +import { Script } from "@opencode-ai/script" +import { $ } from "bun" +import { rm } from "node:fs/promises" +import { fileURLToPath } from "node:url" + +process.chdir(fileURLToPath(new URL("..", import.meta.url))) + +const originalText = await Bun.file("package.json").text() +const pkg = JSON.parse(originalText) as { + name: string + version: string + exports: Record + imports: Record> +} +const tarball = `${pkg.name.replace("@", "").replace("/", "-")}-${pkg.version}.tgz` + +if ((await $`npm view ${pkg.name}@${pkg.version} version`.nothrow()).exitCode === 0) { + console.log(`already published ${pkg.name}@${pkg.version}`) + process.exit(0) +} + +const output = (value: string, extension: ".js" | ".d.ts") => + value.replace("./src/", "./dist/").replace(/\.ts$/, extension) + +try { + await $`bun run typecheck` + await $`bun run build` + pkg.exports = Object.fromEntries( + Object.entries(pkg.exports).map(([key, value]) => { + if (typeof value !== "string") return [key, value] + return [key, { import: output(value, ".js"), types: output(value, ".d.ts") }] + }), + ) + pkg.imports = Object.fromEntries( + Object.entries(pkg.imports).map(([key, conditions]) => [ + key, + Object.fromEntries(Object.entries(conditions).map(([condition, value]) => [condition, output(value, ".js")])), + ]), + ) + await Bun.write("package.json", JSON.stringify(pkg, null, 2) + "\n") + await rm(tarball, { force: true }) + await $`bun pm pack` + await $`npm publish ${tarball} --tag ${Script.channel} --access public` +} finally { + await Bun.write("package.json", originalText) + await rm(tarball, { force: true }) +} diff --git a/packages/util/src/cross-spawn-spawner.ts b/packages/util/src/cross-spawn-spawner.ts index f6a04ffd93..306c72bc5c 100644 --- a/packages/util/src/cross-spawn-spawner.ts +++ b/packages/util/src/cross-spawn-spawner.ts @@ -15,8 +15,8 @@ import { import * as NodeChildProcess from "node:child_process" import { PassThrough } from "node:stream" import launch from "cross-spawn" -import { makeGlobalNode } from "./effect/app-node" -import { filesystem, path } from "./effect/app-node-platform" +import { makeGlobalNode } from "./effect/app-node.js" +import { filesystem, path } from "./effect/app-node-platform.js" const toError = (err: unknown): Error => (err instanceof globalThis.Error ? err : new globalThis.Error(String(err))) @@ -495,4 +495,4 @@ const layer: Layer.Layer = LayerNode.Node(service: Context.Service, layer: Layer.Layer) { let rt: ManagedRuntime.ManagedRuntime | undefined diff --git a/packages/util/src/flock.ts b/packages/util/src/flock.ts index 958bd9fd1d..3868cf1ed9 100644 --- a/packages/util/src/flock.ts +++ b/packages/util/src/flock.ts @@ -2,7 +2,7 @@ import path from "path" import os from "os" import { randomBytes, randomUUID } from "crypto" import { mkdir, readFile, rm, stat, utimes, writeFile } from "fs/promises" -import { Hash } from "./hash" +import { Hash } from "./hash.js" import { Effect } from "effect" export type FlockGlobal = { diff --git a/packages/util/src/fs-util.ts b/packages/util/src/fs-util.ts index 152d2599fd..46d408a827 100644 --- a/packages/util/src/fs-util.ts +++ b/packages/util/src/fs-util.ts @@ -5,10 +5,10 @@ import { readdir } from "fs/promises" import { lookup } from "mime-types" import { Context, Effect, FileSystem, Layer, Schema } from "effect" import type { PlatformError } from "effect/PlatformError" -import { Glob } from "./glob" -import { serviceUse } from "./effect/service-use" -import { makeGlobalNode } from "./effect/app-node" -import { filesystem } from "./effect/app-node-platform" +import { Glob } from "./glob.js" +import { serviceUse } from "./effect/service-use.js" +import { makeGlobalNode } from "./effect/app-node.js" +import { filesystem } from "./effect/app-node-platform.js" export namespace FSUtil { export class FileSystemError extends Schema.TaggedErrorClass()("FileSystemError", { diff --git a/packages/util/src/global.ts b/packages/util/src/global.ts index bced17b5b3..3edbde5ce2 100644 --- a/packages/util/src/global.ts +++ b/packages/util/src/global.ts @@ -3,8 +3,8 @@ import fs from "fs/promises" import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir" import os from "os" import { Context, Effect, Layer } from "effect" -import { Flock } from "./flock" -import { makeGlobalNode } from "./effect/app-node" +import { Flock } from "./flock.js" +import { makeGlobalNode } from "./effect/app-node.js" const app = "opencode" const data = path.join(xdgData!, app) @@ -83,4 +83,4 @@ export const layerWith = (input: Partial) => Effect.sync(() => Service.of(make(input))), ) -export * as Global from "./global" +export * as Global from "./global.js" diff --git a/packages/util/src/installation/version.ts b/packages/util/src/installation/version.ts index 25d9cd99aa..d142b46a8b 100644 --- a/packages/util/src/installation/version.ts +++ b/packages/util/src/installation/version.ts @@ -1,7 +1,5 @@ -declare global { - const OPENCODE_VERSION: string - const OPENCODE_CHANNEL: string -} +declare const OPENCODE_VERSION: string +declare const OPENCODE_CHANNEL: string export const InstallationVersion = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "local" export const InstallationChannel = typeof OPENCODE_CHANNEL === "string" ? OPENCODE_CHANNEL : "local" diff --git a/packages/util/src/npm-config.ts b/packages/util/src/npm-config.ts index da0f0f2632..bb127edb35 100644 --- a/packages/util/src/npm-config.ts +++ b/packages/util/src/npm-config.ts @@ -1,4 +1,4 @@ -export * as NpmConfig from "./npm-config" +export * as NpmConfig from "./npm-config.js" import { fileURLToPath } from "url" // @ts-expect-error npm does not publish types for this internal config API. diff --git a/packages/util/src/npm.ts b/packages/util/src/npm.ts index fa4e40332b..8a3fc77ec1 100644 --- a/packages/util/src/npm.ts +++ b/packages/util/src/npm.ts @@ -1,17 +1,17 @@ -export * as Npm from "./npm" +export * as Npm from "./npm.js" import path from "path" import npa from "npm-package-arg" import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect" import { NodeFileSystem } from "@effect/platform-node" -import { FSUtil } from "./fs-util" -import { Global } from "./global" -import { EffectFlock } from "./effect-flock" -import { makeGlobalNode } from "./effect/app-node" -import { filesystem } from "./effect/app-node-platform" -import { LayerNode } from "./effect/layer-node" -import { makeRuntime } from "./effect/runtime" -import { NpmConfig } from "./npm-config" +import { FSUtil } from "./fs-util.js" +import { Global } from "./global.js" +import { EffectFlock } from "./effect-flock.js" +import { makeGlobalNode } from "./effect/app-node.js" +import { filesystem } from "./effect/app-node-platform.js" +import { LayerNode } from "./effect/layer-node.js" +import { makeRuntime } from "./effect/runtime.js" +import { NpmConfig } from "./npm-config.js" import { resolveModule } from "#runtime-import" export class InstallFailedError extends Schema.TaggedErrorClass()("NpmInstallFailedError", { diff --git a/packages/util/src/observability.ts b/packages/util/src/observability.ts index 8581eab42e..ecc2363e40 100644 --- a/packages/util/src/observability.ts +++ b/packages/util/src/observability.ts @@ -1,12 +1,12 @@ -export * as Observability from "./observability" +export * as Observability from "./observability.js" import { NodeFileSystem } from "@effect/platform-node" -import { LayerNode } from "./effect/layer-node" +import { LayerNode } from "./effect/layer-node.js" import { Effect, Layer, Logger, References, Schema } from "effect" import { FetchHttpClient } from "effect/unstable/http" import { OtlpSerialization } from "effect/unstable/observability" -import { Logging } from "./observability/logging" -import { Otlp } from "./observability/otlp" +import { Logging } from "./observability/logging.js" +import { Otlp } from "./observability/otlp.js" export const Options = Schema.Struct({ endpoint: Schema.optional(Schema.String), diff --git a/packages/util/src/observability/logging.ts b/packages/util/src/observability/logging.ts index 1588c6f764..b83c0cc150 100644 --- a/packages/util/src/observability/logging.ts +++ b/packages/util/src/observability/logging.ts @@ -1,8 +1,8 @@ import { Formatter, Logger, type LogLevel } from "effect" import path from "path" -import { Global } from "../global" -import { InstallationChannel, InstallationLocal } from "../installation/version" -import { runID } from "./shared" +import { Global } from "../global.js" +import { InstallationChannel, InstallationLocal } from "../installation/version.js" +import { runID } from "./shared.js" function formatter(id: string = runID) { return Logger.map(Logger.formatStructured, (output) => { @@ -74,4 +74,4 @@ export function loggers() { return process.env.OPENCODE_PRINT_LOGS === "1" ? [fileLogger(), stderrLogger] : [fileLogger()] } -export * as Logging from "./logging" +export * as Logging from "./logging.js" diff --git a/packages/util/src/observability/otlp.ts b/packages/util/src/observability/otlp.ts index edf2e71254..eb6771e54c 100644 --- a/packages/util/src/observability/otlp.ts +++ b/packages/util/src/observability/otlp.ts @@ -1,7 +1,7 @@ import { Layer } from "effect" import { OtlpLogger } from "effect/unstable/observability" -import { InstallationChannel, InstallationVersion } from "../installation/version" -import { runID } from "./shared" +import { InstallationChannel, InstallationVersion } from "../installation/version.js" +import { runID } from "./shared.js" export interface Options { readonly endpoint?: string @@ -87,4 +87,4 @@ export async function tracingLayer(options?: Options) { })) } -export * as Otlp from "./otlp" +export * as Otlp from "./otlp.js" diff --git a/packages/util/src/patch.ts b/packages/util/src/patch.ts index 3ab103fa97..600c604401 100644 --- a/packages/util/src/patch.ts +++ b/packages/util/src/patch.ts @@ -1,4 +1,4 @@ -export * as Patch from "./patch" +export * as Patch from "./patch.js" import { Result, Schema } from "effect" diff --git a/packages/util/src/process.ts b/packages/util/src/process.ts index 16e9118d1f..790bfa3be4 100644 --- a/packages/util/src/process.ts +++ b/packages/util/src/process.ts @@ -2,8 +2,8 @@ import { Context, Duration, Effect, Fiber, Layer, Schema, Stream } from "effect" import type { PlatformError } from "effect/PlatformError" import { ChildProcess } from "effect/unstable/process" import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" -import { CrossSpawnSpawner } from "./cross-spawn-spawner" -import { makeGlobalNode } from "./effect/app-node" +import { CrossSpawnSpawner } from "./cross-spawn-spawner.js" +import { makeGlobalNode } from "./effect/app-node.js" export class AppProcessError extends Schema.TaggedErrorClass()("AppProcessError", { command: Schema.String, @@ -258,4 +258,4 @@ const layer = Layer.effect( export const node = makeGlobalNode({ service: Service, layer: layer, deps: [CrossSpawnSpawner.node] }) -export * as AppProcess from "./process" +export * as AppProcess from "./process.js" diff --git a/packages/util/tsconfig.build.json b/packages/util/tsconfig.build.json new file mode 100644 index 0000000000..e235ae78cf --- /dev/null +++ b/packages/util/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "noEmit": false, + "declaration": true + }, + "include": ["src"] +} diff --git a/packages/util/tsconfig.json b/packages/util/tsconfig.json index fe5c4d217b..189db6f3e9 100644 --- a/packages/util/tsconfig.json +++ b/packages/util/tsconfig.json @@ -2,6 +2,11 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "@tsconfig/bun/tsconfig.json", "compilerOptions": { + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "allowImportingTsExtensions": false, + "allowJs": false, "noUncheckedIndexedAccess": false } } diff --git a/script/publish.ts b/script/publish.ts index 7b5ca8a39d..cfb3aace13 100755 --- a/script/publish.ts +++ b/script/publish.ts @@ -41,6 +41,9 @@ await $`bun ./packages/schema/script/publish.ts` console.log("\n=== ai ===\n") await $`bun ./packages/ai/script/publish.ts` +console.log("\n=== util ===\n") +await $`bun ./packages/util/script/publish.ts` + console.log("\n=== protocol ===\n") await $`bun ./packages/protocol/script/publish.ts` From 023f76f6fc5fea9e1ae8442d4c0e0bbb0e92f0e5 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:34:54 +0000 Subject: [PATCH 2/3] fix(core): refresh model catalog every five minutes (#38142) Co-authored-by: Victor Navarro <36263538+vimtor@users.noreply.github.com> --- packages/core/src/models-dev.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/models-dev.ts b/packages/core/src/models-dev.ts index 494032c64e..15541cc307 100644 --- a/packages/core/src/models-dev.ts +++ b/packages/core/src/models-dev.ts @@ -651,7 +651,7 @@ export const layer = (options?: Options) => Layer.effect( if (fetch && !process.argv.includes("--get-yargs-completions")) { // Schedule.spaced runs the effect once, then waits between completions. - yield* Effect.forkScoped(refresh().pipe(Effect.repeat(Schedule.spaced("60 minutes")), Effect.ignore)) + yield* Effect.forkScoped(refresh().pipe(Effect.repeat(Schedule.spaced(ttl)), Effect.ignore)) } return Service.of({ get, refresh }) From 99b2e78d750275fa13af5f81b7f3ed7a21610b5d Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Tue, 21 Jul 2026 19:00:21 +0200 Subject: [PATCH 3/3] fix(tui): improve responsive footer details (#38116) --- packages/tui/src/mini/footer.view.tsx | 36 +++++++++++++++++-- packages/tui/src/mini/footer.width.ts | 6 +++- packages/tui/test/mini/footer.view.test.tsx | 38 ++++++++++++++++----- packages/tui/test/mini/footer.width.test.ts | 11 +++--- 4 files changed, 73 insertions(+), 18 deletions(-) diff --git a/packages/tui/src/mini/footer.view.tsx b/packages/tui/src/mini/footer.view.tsx index 2ddeef1ec0..ff5199e89f 100644 --- a/packages/tui/src/mini/footer.view.tsx +++ b/packages/tui/src/mini/footer.view.tsx @@ -29,6 +29,7 @@ import { RunFormBody } from "./footer.form" import { createFormBodyState, type FormBodyState } from "./form.shared" import { footerWidthPolicy } from "./footer.width" import { Keymap } from "../context/keymap" +import { modelInfo } from "./variant.shared" import type { FooterPromptRoute, @@ -157,6 +158,10 @@ export function RunFooterView(props: RunFooterViewProps) { return tabs().findIndex((item) => item.sessionID === sessionID) + 1 }) const foregroundSubagents = createMemo(() => activeTabs().some((item) => !item.background)) + const model = createMemo(() => { + const current = props.currentModel() + return current ? modelInfo(props.providers(), current).model : undefined + }) const detail = createMemo(() => { const current = route() return current.type === "subagent" ? subagent().details[current.sessionID] : undefined @@ -364,6 +369,14 @@ export function RunFooterView(props: RunFooterViewProps) { return usage() }) + const modelStatus = createMemo(() => { + const current = model() + if (!prompt() || !responsive().statusline.showModel || !current) return + return { + model: current, + variant: responsive().statusline.showModelVariant ? props.currentVariant() : undefined, + } + }) const statusColor = createMemo(() => { if (exiting()) { return theme().error @@ -381,6 +394,7 @@ export function RunFooterView(props: RunFooterViewProps) { }) const statuslineBackground = createMemo(() => theme().status) const hasActivityMeta = createMemo(() => activityMeta().length > 0) + const hasModelStatus = createMemo(() => Boolean(modelStatus())) const contextHints = createMemo(() => { if (!prompt() || shell() || !responsive().statusline.showContextHints) { return [] @@ -819,11 +833,29 @@ export function RunFooterView(props: RunFooterViewProps) { + + {(info) => ( + + + {info().model} + + {(variant) => {variant()}} + + + + )} + + {(hint, index) => ( - 0 || (hasActivityMeta() && index() === 0)}> + 0 || ((hasActivityMeta() || hasModelStatus()) && index() === 0)}> {sectionSeparator()} {hint.key}{" "} @@ -837,7 +869,7 @@ export function RunFooterView(props: RunFooterViewProps) { {(hint) => ( - + {sectionSeparator()} {hint().key}{" "} diff --git a/packages/tui/src/mini/footer.width.ts b/packages/tui/src/mini/footer.width.ts index b0979858e4..35670d0ef7 100644 --- a/packages/tui/src/mini/footer.width.ts +++ b/packages/tui/src/mini/footer.width.ts @@ -1,8 +1,10 @@ // Shared responsive width policy const FOOTER_WIDTH_BREAKPOINTS = { + commandHint: 24, + model: 32, + modelVariant: 40, compact: 80, - commandHint: 66, context: 120, spacious: 150, } as const @@ -19,6 +21,8 @@ export function footerWidthPolicy(width: number) { statusline: { showActivityMeta: compact, showCommandHint: width >= FOOTER_WIDTH_BREAKPOINTS.commandHint, + showModel: width >= FOOTER_WIDTH_BREAKPOINTS.model, + showModelVariant: width >= FOOTER_WIDTH_BREAKPOINTS.modelVariant, showContextHints: compact, contextHintLimit: !compact ? 0 : spacious ? undefined : context ? 2 : 1, }, diff --git a/packages/tui/test/mini/footer.view.test.tsx b/packages/tui/test/mini/footer.view.test.tsx index b8c191ceb0..41e3e13568 100644 --- a/packages/tui/test/mini/footer.view.test.tsx +++ b/packages/tui/test/mini/footer.view.test.tsx @@ -1074,8 +1074,8 @@ test("direct footer shows authoritative pending work while running", async () => const statusItems = statusline.getChildren().filter((item): item is BoxRenderable => item instanceof BoxRenderable) const main = statusItems[0] const spinner = main.getChildren()[0] - const background = statusItems[1] - const queued = statusItems[2] + const background = statusItems[2] + const queued = statusItems[3] const hint = statusItems.at(-1)! expect(spinner).toBeDefined() @@ -1098,11 +1098,36 @@ test("direct footer shows authoritative pending work while running", async () => } }) +test("direct footer progressively adds model details after the command hint", async () => { + for (const expected of [ + { width: 24, model: false, variant: false }, + { width: 32, model: true, variant: false }, + { width: 40, model: true, variant: true }, + ]) { + const app = await renderFooter({ + providers: [provider()], + currentModel: { providerID: "opencode", modelID: "gpt-5" }, + currentVariant: "xhigh", + width: expected.width, + }) + + try { + await app.renderOnce() + const frame = app.captureCharFrame() + expect({ + width: expected.width, + command: frame.includes("ctrl+p cmd"), + model: frame.includes("GPT-5"), + variant: frame.includes("xhigh"), + }).toEqual({ ...expected, command: true }) + } finally { + app.cleanup() + } + } +}) + test("direct footer always offers backgrounding for a foreground subagent", async () => { const app = await renderFooter({ - providers: [provider()], - currentModel: { providerID: "opencode", modelID: "gpt-5" }, - currentVariant: "xhigh", subagents: { tabs: [subagent({ sessionID: "s-1", label: "Explore", description: "Inspect auth flow" })], details: {}, @@ -1125,9 +1150,6 @@ test("direct footer always offers backgrounding for a foreground subagent", asyn test("direct footer hides the subagent hint when only completed subagents remain", async () => { const app = await renderFooter({ - providers: [provider()], - currentModel: { providerID: "opencode", modelID: "gpt-5" }, - currentVariant: "xhigh", subagents: { tabs: [subagent({ sessionID: "s-1", label: "Explore", description: "Inspect auth flow", status: "completed" })], details: {}, diff --git a/packages/tui/test/mini/footer.width.test.ts b/packages/tui/test/mini/footer.width.test.ts index 3f9809d356..fa8ec5d31a 100644 --- a/packages/tui/test/mini/footer.width.test.ts +++ b/packages/tui/test/mini/footer.width.test.ts @@ -3,19 +3,16 @@ import { footerWidthPolicy } from "../../src/mini/footer.width" describe("run footer width", () => { test("preserves shared dialog and statusline breakpoints", () => { + expect([23, 24].map((width) => footerWidthPolicy(width).statusline.showCommandHint)).toEqual([false, true]) + expect([31, 32].map((width) => footerWidthPolicy(width).statusline.showModel)).toEqual([false, true]) + expect([39, 40].map((width) => footerWidthPolicy(width).statusline.showModelVariant)).toEqual([false, true]) + const narrow = footerWidthPolicy(79) expect(narrow.dialog.narrow).toBe(true) expect(narrow.statusline.showActivityMeta).toBe(false) - expect(narrow.statusline.showCommandHint).toBe(true) expect(narrow.statusline.showContextHints).toBe(false) expect(narrow.statusline.contextHintLimit).toBe(0) - const command = footerWidthPolicy(65) - expect(command.statusline.showCommandHint).toBe(false) - - const commandHint = footerWidthPolicy(66) - expect(commandHint.statusline.showCommandHint).toBe(true) - const compact = footerWidthPolicy(80) expect(compact.dialog.narrow).toBe(false) expect(compact.statusline.showActivityMeta).toBe(true)