refactor(core): replace legacy logger with Effect logging (#31310)
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Effect } from "effect"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
|
||||
@@ -7,8 +6,6 @@ import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
||||
import { ACPProfile } from "@/acp/profile"
|
||||
|
||||
const log = Log.create({ service: "acp-command" })
|
||||
|
||||
export const AcpCommand = effectCmd({
|
||||
command: "acp",
|
||||
describe: "start ACP (Agent Client Protocol) server",
|
||||
@@ -63,7 +60,7 @@ export const AcpCommand = effectCmd({
|
||||
return agent.create(conn)
|
||||
}, stream)
|
||||
|
||||
log.info("setup connection")
|
||||
yield* Effect.logInfo("setup connection")
|
||||
process.stdin.resume()
|
||||
yield* Effect.promise(
|
||||
() =>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { LSP } from "@/lsp/lsp"
|
||||
import { Effect } from "effect"
|
||||
import { effectCmd } from "../../effect-cmd"
|
||||
import { cmd } from "../cmd"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { EOL } from "os"
|
||||
|
||||
export const LSPCommand = cmd({
|
||||
@@ -33,7 +32,7 @@ export const SymbolsCommand = effectCmd({
|
||||
describe: "search workspace symbols",
|
||||
builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }),
|
||||
handler: Effect.fn("Cli.debug.lsp.symbols")(function* (args) {
|
||||
using _ = Log.Default.time("symbols")
|
||||
yield* Effect.logInfo("symbols")
|
||||
const results = yield* LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query))
|
||||
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
|
||||
}),
|
||||
@@ -44,7 +43,7 @@ export const DocumentSymbolsCommand = effectCmd({
|
||||
describe: "get symbols from a document",
|
||||
builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }),
|
||||
handler: Effect.fn("Cli.debug.lsp.documentSymbols")(function* (args) {
|
||||
using _ = Log.Default.time("document-symbols")
|
||||
yield* Effect.logInfo("document-symbols")
|
||||
const results = yield* LSP.Service.use((lsp) => lsp.documentSymbol(args.uri))
|
||||
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
|
||||
}),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { EOL } from "os"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { cmd } from "../cmd"
|
||||
|
||||
export const ScrapCommand = cmd({
|
||||
@@ -10,9 +9,7 @@ export const ScrapCommand = cmd({
|
||||
const { Project } = await import("@/project/project")
|
||||
const { makeRuntime } = await import("@opencode-ai/core/effect/runtime")
|
||||
const runtime = makeRuntime(Project.Service, Project.defaultLayer)
|
||||
const timer = Log.Default.time("scrap")
|
||||
const list = await runtime.runPromise((project) => project.list())
|
||||
process.stdout.write(JSON.stringify(list, null, 2) + EOL)
|
||||
timer.stop()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -30,7 +30,6 @@ import { render } from "@opentui/solid"
|
||||
import { createComponent, createSignal, type Accessor, type Setter } from "solid-js"
|
||||
import { createStore, reconcile } from "solid-js/store"
|
||||
import { OpencodeKeymapProvider } from "@opencode-ai/tui/keymap"
|
||||
import { withRunSpan } from "./otel"
|
||||
import { RUN_COMMAND_PANEL_ROWS, RUN_SUBAGENT_PANEL_ROWS } from "./footer.command"
|
||||
import { SUBAGENT_INSPECTOR_ROWS } from "./footer.subagent"
|
||||
import { PROMPT_MAX_ROWS, TEXTAREA_MIN_ROWS } from "./footer.prompt"
|
||||
@@ -513,20 +512,8 @@ export class RunFooter implements FooterApi {
|
||||
}
|
||||
|
||||
private completeScrollback(): void {
|
||||
const phase = this.state().phase
|
||||
this.flushing = this.flushing
|
||||
.then(() =>
|
||||
withRunSpan(
|
||||
"RunFooter.completeScrollback",
|
||||
{
|
||||
"opencode.footer.phase": phase,
|
||||
"session.id": this.options.sessionID() || undefined,
|
||||
},
|
||||
async () => {
|
||||
await this.scrollback.complete()
|
||||
},
|
||||
),
|
||||
)
|
||||
.then(() => this.scrollback.complete())
|
||||
.catch((error) => {
|
||||
this.flushError = error
|
||||
})
|
||||
@@ -1129,23 +1116,12 @@ export class RunFooter implements FooterApi {
|
||||
}
|
||||
|
||||
const batch = this.queue.splice(0)
|
||||
const phase = this.state().phase
|
||||
this.flushing = this.flushing
|
||||
.then(() =>
|
||||
withRunSpan(
|
||||
"RunFooter.flush",
|
||||
{
|
||||
"opencode.batch.commits": batch.length,
|
||||
"opencode.footer.phase": phase,
|
||||
"session.id": this.options.sessionID() || undefined,
|
||||
},
|
||||
async () => {
|
||||
for (const item of batch) {
|
||||
await this.scrollback.append(item)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.then(async () => {
|
||||
for (const item of batch) {
|
||||
await this.scrollback.append(item)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.flushError = error
|
||||
})
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { INVALID_SPAN_CONTEXT, context, trace, SpanStatusCode, type Span } from "@opentelemetry/api"
|
||||
import { Effect, ManagedRuntime } from "effect"
|
||||
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
||||
import { Observability } from "@opencode-ai/core/effect/observability"
|
||||
|
||||
type AttributeValue = string | number | boolean | undefined
|
||||
|
||||
export type RunSpanAttributes = Record<string, AttributeValue>
|
||||
|
||||
const noop = trace.wrapSpanContext(INVALID_SPAN_CONTEXT)
|
||||
const tracer = trace.getTracer("opencode.run")
|
||||
const runtime = ManagedRuntime.make(Observability.layer, { memoMap })
|
||||
let ready: Promise<void> | undefined
|
||||
|
||||
function attributes(input?: RunSpanAttributes): Record<string, string | number | boolean> | undefined {
|
||||
if (!input) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const out = Object.entries(input).flatMap(([key, value]) => (value === undefined ? [] : [[key, value] as const]))
|
||||
if (out.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return Object.fromEntries(out)
|
||||
}
|
||||
|
||||
function message(error: unknown) {
|
||||
if (typeof error === "string") {
|
||||
return error
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error.message || error.name
|
||||
}
|
||||
|
||||
return String(error)
|
||||
}
|
||||
|
||||
function ensure() {
|
||||
if (!Observability.enabled) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
if (ready) {
|
||||
return ready
|
||||
}
|
||||
|
||||
ready = runtime.runPromise(Effect.void).then(
|
||||
() => undefined,
|
||||
(error) => {
|
||||
ready = undefined
|
||||
throw error
|
||||
},
|
||||
)
|
||||
return ready
|
||||
}
|
||||
|
||||
function finish<A>(span: Span, out: Promise<A>) {
|
||||
return out.then(
|
||||
(value) => {
|
||||
span.end()
|
||||
return value
|
||||
},
|
||||
(error) => {
|
||||
recordRunSpanError(span, error)
|
||||
span.end()
|
||||
throw error
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export function setRunSpanAttributes(span: Span, input?: RunSpanAttributes): void {
|
||||
const next = attributes(input)
|
||||
if (!next) {
|
||||
return
|
||||
}
|
||||
|
||||
span.setAttributes(next)
|
||||
}
|
||||
|
||||
export function recordRunSpanError(span: Span, error: unknown): void {
|
||||
const next = message(error)
|
||||
span.recordException(error instanceof Error ? error : next)
|
||||
span.setStatus({
|
||||
code: SpanStatusCode.ERROR,
|
||||
message: next,
|
||||
})
|
||||
}
|
||||
|
||||
export function withRunSpan<A>(
|
||||
name: string,
|
||||
input: RunSpanAttributes | undefined,
|
||||
fn: (span: Span) => Promise<A> | A,
|
||||
): A | Promise<A> {
|
||||
if (!Observability.enabled) {
|
||||
return fn(noop)
|
||||
}
|
||||
|
||||
return ensure().then(
|
||||
() => {
|
||||
const span = tracer.startSpan(name, {
|
||||
attributes: attributes(input),
|
||||
})
|
||||
|
||||
return context.with(trace.setSpan(context.active(), span), () =>
|
||||
finish(
|
||||
span,
|
||||
new Promise<A>((resolve) => {
|
||||
resolve(fn(span))
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
() => fn(noop),
|
||||
)
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import { openEditor } from "@opencode-ai/tui/editor"
|
||||
import { registerOpencodeKeymap } from "@opencode-ai/tui/keymap"
|
||||
import { Session as SessionApi } from "@/session/session"
|
||||
import * as Locale from "@/util/locale"
|
||||
import { withRunSpan } from "./otel"
|
||||
import { resolveInteractiveStdin } from "./runtime.stdin"
|
||||
import { entrySplash, exitSplash, splashMeta } from "./splash"
|
||||
import { resolveRunTheme } from "./theme"
|
||||
@@ -175,18 +174,6 @@ function queueSplash(
|
||||
// scrollback commits and footer repaints happen in the same frame. After
|
||||
// the entry splash, RunFooter takes over the footer region.
|
||||
export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lifecycle> {
|
||||
return withRunSpan(
|
||||
"RunLifecycle.boot",
|
||||
{
|
||||
"opencode.agent.name": input.agent,
|
||||
"opencode.directory": input.directory,
|
||||
"opencode.first": input.first,
|
||||
"opencode.model.provider": input.model?.providerID,
|
||||
"opencode.model.id": input.model?.modelID,
|
||||
"opencode.model.variant": input.variant,
|
||||
"session.id": input.getSessionID?.() || input.sessionID || undefined,
|
||||
},
|
||||
async () => {
|
||||
const source = resolveInteractiveStdin()
|
||||
let unregisterKeymap: (() => void) | undefined
|
||||
|
||||
@@ -326,13 +313,6 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
|
||||
}
|
||||
|
||||
closed = true
|
||||
return withRunSpan(
|
||||
"RunLifecycle.close",
|
||||
{
|
||||
"opencode.show_exit": next.showExit,
|
||||
"session.id": next.sessionID || input.getSessionID?.() || input.sessionID || undefined,
|
||||
},
|
||||
async () => {
|
||||
detachSigint()
|
||||
let wroteExit = false
|
||||
|
||||
@@ -368,8 +348,6 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
|
||||
}
|
||||
source.cleanup?.()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -425,6 +403,4 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
|
||||
source.cleanup?.()
|
||||
throw error
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { MessageID } from "@/session/schema"
|
||||
import { createRunDemo } from "./demo"
|
||||
import { resolveModelInfo, resolveRunTuiConfig, resolveSessionInfo } from "./runtime.boot"
|
||||
import { createRuntimeLifecycle } from "./runtime.lifecycle"
|
||||
import { recordRunSpanError, setRunSpanAttributes, withRunSpan } from "./otel"
|
||||
import { trace } from "./trace"
|
||||
import { cycleVariant, formatModelLabel, resolveSavedVariant, resolveVariant, saveVariant } from "./variant.shared"
|
||||
import type { LocalReplayAnchor, LocalReplayRow, RunInput, RunPrompt, RunProvider, StreamCommit } from "./types"
|
||||
@@ -180,14 +179,6 @@ async function resolveExitTitle(
|
||||
// Files only attach on the first prompt turn -- after that, includeFiles
|
||||
// flips to false so subsequent turns don't re-send attachments.
|
||||
async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDeps = {}): Promise<void> {
|
||||
return withRunSpan(
|
||||
"RunInteractive.session",
|
||||
{
|
||||
"opencode.mode": input.resolveSession ? "local" : "attach",
|
||||
"opencode.initial_input": !!input.initialInput,
|
||||
"opencode.demo": input.demo,
|
||||
},
|
||||
async (span) => {
|
||||
const start = performance.now()
|
||||
const log = trace()
|
||||
const tuiConfigTask = resolveRunTuiConfig()
|
||||
@@ -217,15 +208,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
sessionTitle: ctx.sessionTitle,
|
||||
agent: ctx.agent,
|
||||
}
|
||||
setRunSpanAttributes(span, {
|
||||
"opencode.directory": ctx.directory,
|
||||
"opencode.resume": ctx.resume === true,
|
||||
"opencode.agent.name": state.agent,
|
||||
"opencode.model.provider": state.model?.providerID,
|
||||
"opencode.model.id": state.model?.modelID,
|
||||
"opencode.model.variant": state.activeVariant,
|
||||
"session.id": state.sessionID || undefined,
|
||||
})
|
||||
const ensureSession = () => {
|
||||
if (!input.resolveSession || state.sessionID) {
|
||||
return Promise.resolve()
|
||||
@@ -239,10 +221,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
state.sessionID = next.sessionID
|
||||
state.sessionTitle = next.sessionTitle ?? state.sessionTitle
|
||||
state.agent = next.agent
|
||||
setRunSpanAttributes(span, {
|
||||
"opencode.agent.name": state.agent,
|
||||
"session.id": state.sessionID,
|
||||
})
|
||||
})
|
||||
return state.session
|
||||
}
|
||||
@@ -297,9 +275,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
|
||||
state.activeVariant = cycleVariant(state.activeVariant, state.variants)
|
||||
saveVariant(state.model, state.activeVariant)
|
||||
setRunSpanAttributes(span, {
|
||||
"opencode.model.variant": state.activeVariant,
|
||||
})
|
||||
return {
|
||||
status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default",
|
||||
modelLabel: formatModelLabel(state.model, state.activeVariant, state.providers),
|
||||
@@ -333,11 +308,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
return
|
||||
}
|
||||
|
||||
setRunSpanAttributes(span, {
|
||||
"opencode.model.provider": model.providerID,
|
||||
"opencode.model.id": model.modelID,
|
||||
"opencode.model.variant": state.activeVariant,
|
||||
})
|
||||
return {
|
||||
modelLabel: formatModelLabel(model, state.activeVariant, state.providers),
|
||||
status: `model ${model.modelID}`,
|
||||
@@ -360,9 +330,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
|
||||
state.activeVariant = variant
|
||||
saveVariant(state.model, state.activeVariant)
|
||||
setRunSpanAttributes(span, {
|
||||
"opencode.model.variant": state.activeVariant,
|
||||
})
|
||||
return {
|
||||
status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default",
|
||||
modelLabel: formatModelLabel(state.model, state.activeVariant, state.providers),
|
||||
@@ -468,9 +435,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
const next = resolveVariant(ctx.variant, session.variant, savedVariant, state.variants)
|
||||
if (next !== state.activeVariant) {
|
||||
state.activeVariant = next
|
||||
setRunSpanAttributes(span, {
|
||||
"opencode.model.variant": state.activeVariant,
|
||||
})
|
||||
}
|
||||
|
||||
if (footer.isClosed) {
|
||||
@@ -624,13 +588,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
limits: () => state.limits,
|
||||
})
|
||||
: undefined
|
||||
setRunSpanAttributes(span, {
|
||||
"opencode.agent.name": state.agent,
|
||||
"opencode.model.provider": state.model?.providerID,
|
||||
"opencode.model.id": state.model?.modelID,
|
||||
"opencode.model.variant": state.activeVariant,
|
||||
"session.id": state.sessionID,
|
||||
})
|
||||
log?.write("session.new", {
|
||||
sessionID: state.sessionID,
|
||||
})
|
||||
@@ -688,29 +645,8 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
await state.switching?.catch(() => {})
|
||||
|
||||
let outputAnchor: LocalReplayAnchor | undefined
|
||||
return withRunSpan(
|
||||
"RunInteractive.turn",
|
||||
{
|
||||
"opencode.agent.name": state.agent,
|
||||
"opencode.model.provider": state.model?.providerID,
|
||||
"opencode.model.id": state.model?.modelID,
|
||||
"opencode.model.variant": state.activeVariant,
|
||||
"opencode.prompt.chars": prompt.text.length,
|
||||
"opencode.prompt.parts": prompt.parts.length,
|
||||
"opencode.prompt.include_files": includeFiles,
|
||||
"opencode.prompt.file_parts": includeFiles ? input.files.length : 0,
|
||||
"session.id": state.sessionID || undefined,
|
||||
},
|
||||
async (span) => {
|
||||
try {
|
||||
const next = await ensureStream()
|
||||
setRunSpanAttributes(span, {
|
||||
"opencode.agent.name": state.agent,
|
||||
"opencode.model.provider": state.model?.providerID,
|
||||
"opencode.model.id": state.model?.modelID,
|
||||
"opencode.model.variant": state.activeVariant,
|
||||
"session.id": state.sessionID || undefined,
|
||||
})
|
||||
await next.handle.runPromptTurn({
|
||||
agent: state.agent,
|
||||
model: state.model,
|
||||
@@ -734,7 +670,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
return
|
||||
}
|
||||
|
||||
recordRunSpanError(span, error)
|
||||
const text =
|
||||
(await state.stream?.then((item) => item.mod).catch(() => undefined))?.formatUnknownError(error) ??
|
||||
(error instanceof Error ? error.message : String(error))
|
||||
@@ -748,8 +683,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
rememberLocal(commit, outputAnchor)
|
||||
footer.append(commit)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -795,21 +728,11 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
|
||||
history: state.history,
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Local in-process mode. Creates an SDK client backed by a direct fetch to
|
||||
// the in-process server, so no external HTTP server is needed.
|
||||
export async function runInteractiveLocalMode(input: RunLocalInput): Promise<void> {
|
||||
return withRunSpan(
|
||||
"RunInteractive.localMode",
|
||||
{
|
||||
"opencode.directory": input.directory,
|
||||
"opencode.initial_input": !!input.initialInput,
|
||||
"opencode.demo": input.demo,
|
||||
},
|
||||
async () => {
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: "http://opencode.internal",
|
||||
fetch: input.fetch,
|
||||
@@ -858,8 +781,6 @@ export async function runInteractiveLocalMode(input: RunLocalInput): Promise<voi
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Attach mode. Uses the caller-provided SDK client directly.
|
||||
@@ -867,15 +788,7 @@ export async function runInteractiveMode(
|
||||
input: RunInput & { createSession?: CreateSession },
|
||||
deps?: RunRuntimeDeps,
|
||||
): Promise<void> {
|
||||
return withRunSpan(
|
||||
"RunInteractive.attachMode",
|
||||
{
|
||||
"opencode.directory": input.directory,
|
||||
"opencode.initial_input": !!input.initialInput,
|
||||
"session.id": input.sessionID,
|
||||
},
|
||||
async () =>
|
||||
runInteractiveRuntime(
|
||||
return runInteractiveRuntime(
|
||||
{
|
||||
files: input.files,
|
||||
initialInput: input.initialInput,
|
||||
@@ -897,6 +810,5 @@ export async function runInteractiveMode(
|
||||
createSession: createSessionResolver(input.createSession),
|
||||
},
|
||||
deps,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
type ScrollbackSurface,
|
||||
} from "@opentui/core"
|
||||
import { entryBody, entryCanStream, entryDone, entryFlags } from "./entry.body"
|
||||
import { withRunSpan } from "./otel"
|
||||
import { entryColor, entryLook, entrySyntax } from "./scrollback.shared"
|
||||
import { turnSummaryCommit } from "./turn-summary"
|
||||
import { entryWriter, sameEntryGroup, separatorRows, spacerWriter, turnSummaryWriter } from "./scrollback.writer"
|
||||
@@ -424,17 +423,7 @@ export class RunScrollbackStream {
|
||||
}
|
||||
|
||||
public async complete(trailingNewline = false): Promise<void> {
|
||||
return withRunSpan(
|
||||
"RunScrollbackStream.complete",
|
||||
{
|
||||
"opencode.entry.active": !!this.active,
|
||||
"opencode.trailing_newline": trailingNewline,
|
||||
"session.id": this.sessionID?.() || undefined,
|
||||
},
|
||||
async () => {
|
||||
this.markRendered(await this.finishActive(trailingNewline))
|
||||
},
|
||||
)
|
||||
this.markRendered(await this.finishActive(trailingNewline))
|
||||
}
|
||||
|
||||
public async writeTurnSummary(input: { agent: string; model: string; duration: string }): Promise<void> {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { type rpc } from "../tui/worker"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { UI } from "@/cli/ui"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { errorMessage } from "@opencode-ai/tui/util/error"
|
||||
import { withTimeout } from "@/util/timeout"
|
||||
import { withNetworkOptions, resolveNetworkOptionsNoConfig } from "@/cli/network"
|
||||
@@ -12,12 +11,6 @@ import { Filesystem } from "@/util/filesystem"
|
||||
import type { GlobalEvent } from "@opencode-ai/sdk/v2"
|
||||
import type { EventSource } from "@opencode-ai/tui/context/sdk"
|
||||
import { writeHeapSnapshot } from "v8"
|
||||
import {
|
||||
OPENCODE_PROCESS_ROLE,
|
||||
OPENCODE_RUN_ID,
|
||||
ensureRunID,
|
||||
sanitizedProcessEnv,
|
||||
} from "@opencode-ai/core/util/opencode-process"
|
||||
import { validateSession } from "../tui/validate-session"
|
||||
import { win32InstallCtrlCGuard } from "@opencode-ai/tui/terminal-win32"
|
||||
|
||||
@@ -132,51 +125,20 @@ export const TuiThreadCommand = cmd({
|
||||
return
|
||||
}
|
||||
const cwd = Filesystem.resolve(process.cwd())
|
||||
const env = sanitizedProcessEnv({
|
||||
[OPENCODE_PROCESS_ROLE]: "worker",
|
||||
[OPENCODE_RUN_ID]: ensureRunID(),
|
||||
})
|
||||
|
||||
const worker = new Worker(file, {
|
||||
env,
|
||||
})
|
||||
worker.onerror = (e) => {
|
||||
Log.Default.error("thread error", {
|
||||
message: e.message,
|
||||
filename: e.filename,
|
||||
lineno: e.lineno,
|
||||
colno: e.colno,
|
||||
error: e.error,
|
||||
})
|
||||
}
|
||||
|
||||
const worker = new Worker(file)
|
||||
const client = Rpc.client<typeof rpc>(worker)
|
||||
const error = (e: unknown) => {
|
||||
Log.Default.error("process error", { error: errorMessage(e) })
|
||||
}
|
||||
const reload = () => {
|
||||
client.call("reload", undefined).catch((err) => {
|
||||
Log.Default.warn("worker reload failed", {
|
||||
error: errorMessage(err),
|
||||
})
|
||||
})
|
||||
client.call("reload", undefined).catch(() => {})
|
||||
}
|
||||
process.on("uncaughtException", error)
|
||||
process.on("unhandledRejection", error)
|
||||
process.on("SIGUSR2", reload)
|
||||
|
||||
let stopped = false
|
||||
const stop = async () => {
|
||||
if (stopped) return
|
||||
stopped = true
|
||||
process.off("uncaughtException", error)
|
||||
process.off("unhandledRejection", error)
|
||||
process.off("SIGUSR2", reload)
|
||||
await withTimeout(client.call("shutdown", undefined), 5000).catch((error) => {
|
||||
Log.Default.warn("worker shutdown failed", {
|
||||
error: errorMessage(error),
|
||||
})
|
||||
})
|
||||
await withTimeout(client.call("shutdown", undefined), 5000).catch(() => {})
|
||||
worker.terminate()
|
||||
}
|
||||
|
||||
@@ -255,9 +217,7 @@ export const TuiThreadCommand = cmd({
|
||||
} finally {
|
||||
try {
|
||||
unguard?.()
|
||||
} catch (error) {
|
||||
Log.Default.warn("failed to restore terminal guard", { error: errorMessage(error) })
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,9 +2,6 @@ import path from "path"
|
||||
import { writeHeapSnapshot } from "node:v8"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
|
||||
const log = Log.create({ service: "heap" })
|
||||
const MINUTE = 60_000
|
||||
const LIMIT = 2 * 1024 * 1024 * 1024
|
||||
|
||||
@@ -32,20 +29,9 @@ export function start() {
|
||||
Global.Path.log,
|
||||
`heap-${process.pid}-${new Date().toISOString().replace(/[:.]/g, "")}.heapsnapshot`,
|
||||
)
|
||||
log.warn("heap usage exceeded limit", {
|
||||
rss: stat.rss,
|
||||
heap: stat.heapUsed,
|
||||
file,
|
||||
})
|
||||
|
||||
await Promise.resolve()
|
||||
.then(() => writeHeapSnapshot(file))
|
||||
.catch((err) => {
|
||||
log.error("failed to write heap snapshot", {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
file,
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
|
||||
lock = false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { Installation } from "@/installation"
|
||||
import { Server } from "@/server/server"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { InstanceRuntime } from "@/project/instance-runtime"
|
||||
import { Rpc } from "@/util/rpc"
|
||||
import { upgrade } from "@/cli/upgrade"
|
||||
@@ -10,35 +8,11 @@ import { ServerAuth } from "@/server/auth"
|
||||
import { writeHeapSnapshot } from "node:v8"
|
||||
import { Heap } from "@/cli/heap"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process"
|
||||
import { Effect } from "effect"
|
||||
import { disposeAllInstancesAndEmitGlobalDisposed } from "@/server/global-lifecycle"
|
||||
|
||||
ensureProcessMetadata("worker")
|
||||
|
||||
await Log.init({
|
||||
print: process.argv.includes("--print-logs"),
|
||||
dev: Installation.isLocal(),
|
||||
level: (() => {
|
||||
if (Installation.isLocal()) return "DEBUG"
|
||||
return "INFO"
|
||||
})(),
|
||||
})
|
||||
|
||||
Heap.start()
|
||||
|
||||
process.on("unhandledRejection", (e) => {
|
||||
Log.Default.error("rejection", {
|
||||
e: e instanceof Error ? e.message : e,
|
||||
})
|
||||
})
|
||||
|
||||
process.on("uncaughtException", (e) => {
|
||||
Log.Default.error("exception", {
|
||||
e: e instanceof Error ? e.message : e,
|
||||
})
|
||||
})
|
||||
|
||||
// Subscribe to global events and forward them via RPC
|
||||
GlobalBus.on("event", (event) => {
|
||||
Rpc.emit("global.event", event)
|
||||
@@ -89,8 +63,6 @@ export const rpc = {
|
||||
)
|
||||
},
|
||||
async shutdown() {
|
||||
Log.Default.info("worker shutting down")
|
||||
|
||||
await InstanceRuntime.disposeAllInstances()
|
||||
if (server) await server.stop(true)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user