feat: enable type-aware no-base-to-string rule, fix 56 violations (#22750)

This commit is contained in:
Kit Langton
2026-04-15 23:50:47 -04:00
committed by GitHub
parent c802695ee9
commit 8aa0f9fe95
26 changed files with 87 additions and 55 deletions
+4
View File
@@ -1,9 +1,13 @@
{ {
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json", "$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json",
"options": {
"typeAware": true
},
"categories": { "categories": {
"suspicious": "warn" "suspicious": "warn"
}, },
"rules": { "rules": {
"typescript/no-base-to-string": "warn",
// Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield // Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield
"require-yield": "off", "require-yield": "off",
// SolidJS uses `let ref: T | undefined` for JSX ref bindings assigned at runtime // SolidJS uses `let ref: T | undefined` for JSX ref bindings assigned at runtime
@@ -116,9 +116,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setUseBalance = action(async (form: FormData) => { const setUseBalance = action(async (form: FormData) => {
"use server" "use server"
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const useBalance = form.get("useBalance")?.toString() === "true" const useBalance = (form.get("useBalance") as string | null) === "true"
return json( return json(
await withActor(async () => { await withActor(async () => {
@@ -10,11 +10,11 @@ import { formError, localizeError } from "~/lib/form-error"
const setMonthlyLimit = action(async (form: FormData) => { const setMonthlyLimit = action(async (form: FormData) => {
"use server" "use server"
const limit = form.get("limit")?.toString() const limit = form.get("limit") as string | null
if (!limit) return { error: formError.limitRequired } if (!limit) return { error: formError.limitRequired }
const numericLimit = parseInt(limit) const numericLimit = parseInt(limit)
if (numericLimit < 0) return { error: formError.monthlyLimitInvalid } if (numericLimit < 0) return { error: formError.monthlyLimitInvalid }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
@@ -12,7 +12,7 @@ import { formError, formErrorReloadAmountMin, formErrorReloadTriggerMin, localiz
const reload = action(async (form: FormData) => { const reload = action(async (form: FormData) => {
"use server" "use server"
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Billing.reload(), workspaceID), { return json(await withActor(() => Billing.reload(), workspaceID), {
revalidate: queryBillingInfo.key, revalidate: queryBillingInfo.key,
@@ -21,11 +21,11 @@ const reload = action(async (form: FormData) => {
const setReload = action(async (form: FormData) => { const setReload = action(async (form: FormData) => {
"use server" "use server"
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const reloadValue = form.get("reload")?.toString() === "true" const reloadValue = (form.get("reload") as string | null) === "true"
const amountStr = form.get("reloadAmount")?.toString() const amountStr = form.get("reloadAmount") as string | null
const triggerStr = form.get("reloadTrigger")?.toString() const triggerStr = form.get("reloadTrigger") as string | null
const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null
const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null
@@ -91,8 +91,8 @@ export function ReloadSection() {
const info = billingInfo()! const info = billingInfo()!
setStore("show", true) setStore("show", true)
setStore("reload", true) setStore("reload", true)
setStore("reloadAmount", info.reloadAmount.toString()) setStore("reloadAmount", String(info.reloadAmount))
setStore("reloadTrigger", info.reloadTrigger.toString()) setStore("reloadTrigger", String(info.reloadTrigger))
} }
function hide() { function hide() {
@@ -152,11 +152,11 @@ export function ReloadSection() {
data-component="input" data-component="input"
name="reloadAmount" name="reloadAmount"
type="number" type="number"
min={billingInfo()?.reloadAmountMin.toString()} min={String(billingInfo()?.reloadAmountMin ?? "")}
step="1" step="1"
value={store.reloadAmount} value={store.reloadAmount}
onInput={(e) => setStore("reloadAmount", e.currentTarget.value)} onInput={(e) => setStore("reloadAmount", e.currentTarget.value)}
placeholder={billingInfo()?.reloadAmount.toString()} placeholder={String(billingInfo()?.reloadAmount ?? "")}
disabled={!store.reload} disabled={!store.reload}
/> />
</div> </div>
@@ -166,11 +166,11 @@ export function ReloadSection() {
data-component="input" data-component="input"
name="reloadTrigger" name="reloadTrigger"
type="number" type="number"
min={billingInfo()?.reloadTriggerMin.toString()} min={String(billingInfo()?.reloadTriggerMin ?? "")}
step="1" step="1"
value={store.reloadTrigger} value={store.reloadTrigger}
onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)} onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)}
placeholder={billingInfo()?.reloadTrigger.toString()} placeholder={String(billingInfo()?.reloadTrigger ?? "")}
disabled={!store.reload} disabled={!store.reload}
/> />
</div> </div>
@@ -120,9 +120,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setLiteUseBalance = action(async (form: FormData) => { const setLiteUseBalance = action(async (form: FormData) => {
"use server" "use server"
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const useBalance = form.get("useBalance")?.toString() === "true" const useBalance = (form.get("useBalance") as string | null) === "true"
return json( return json(
await withActor(async () => { await withActor(async () => {
@@ -12,18 +12,18 @@ import { formError, localizeError } from "~/lib/form-error"
const removeKey = action(async (form: FormData) => { const removeKey = action(async (form: FormData) => {
"use server" "use server"
const id = form.get("id")?.toString() const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired } if (!id) return { error: formError.idRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key }) return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key })
}, "key.remove") }, "key.remove")
const createKey = action(async (form: FormData) => { const createKey = action(async (form: FormData) => {
"use server" "use server"
const name = form.get("name")?.toString().trim() const name = (form.get("name") as string | null)?.trim()
if (!name) return { error: formError.nameRequired } if (!name) return { error: formError.nameRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
@@ -24,13 +24,13 @@ const listMembers = query(async (workspaceID: string) => {
const inviteMember = action(async (form: FormData) => { const inviteMember = action(async (form: FormData) => {
"use server" "use server"
const email = form.get("email")?.toString().trim() const email = (form.get("email") as string | null)?.trim()
if (!email) return { error: formError.emailRequired } if (!email) return { error: formError.emailRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const role = form.get("role")?.toString() as (typeof UserRole)[number] const role = form.get("role") as (typeof UserRole)[number] | null
if (!role) return { error: formError.roleRequired } if (!role) return { error: formError.roleRequired }
const limit = form.get("limit")?.toString() const limit = form.get("limit") as string | null
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid } if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
return json( return json(
@@ -47,9 +47,9 @@ const inviteMember = action(async (form: FormData) => {
const removeMember = action(async (form: FormData) => { const removeMember = action(async (form: FormData) => {
"use server" "use server"
const id = form.get("id")?.toString() const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired } if (!id) return { error: formError.idRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
@@ -66,13 +66,13 @@ const removeMember = action(async (form: FormData) => {
const updateMember = action(async (form: FormData) => { const updateMember = action(async (form: FormData) => {
"use server" "use server"
const id = form.get("id")?.toString() const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired } if (!id) return { error: formError.idRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const role = form.get("role")?.toString() as (typeof UserRole)[number] const role = form.get("role") as (typeof UserRole)[number] | null
if (!role) return { error: formError.roleRequired } if (!role) return { error: formError.roleRequired }
const limit = form.get("limit")?.toString() const limit = form.get("limit") as string | null
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid } if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
@@ -118,7 +118,7 @@ function MemberRow(props: {
} }
setStore("editing", true) setStore("editing", true)
setStore("selectedRole", props.member.role) setStore("selectedRole", props.member.role)
setStore("limit", props.member.monthlyLimit?.toString() ?? "") setStore("limit", props.member.monthlyLimit != null ? String(props.member.monthlyLimit) : "")
} }
function hide() { function hide() {
@@ -67,11 +67,11 @@ const getModelsInfo = query(async (workspaceID: string) => {
const updateModel = action(async (form: FormData) => { const updateModel = action(async (form: FormData) => {
"use server" "use server"
const model = form.get("model")?.toString() const model = form.get("model") as string | null
if (!model) return { error: formError.modelRequired } if (!model) return { error: formError.modelRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const enabled = form.get("enabled")?.toString() === "true" const enabled = (form.get("enabled") as string | null) === "true"
return json( return json(
withActor(async () => { withActor(async () => {
if (enabled) { if (enabled) {
@@ -163,7 +163,7 @@ export function ModelSection() {
<form action={updateModel} method="post"> <form action={updateModel} method="post">
<input type="hidden" name="model" value={id} /> <input type="hidden" name="model" value={id} />
<input type="hidden" name="workspaceID" value={params.id} /> <input type="hidden" name="workspaceID" value={params.id} />
<input type="hidden" name="enabled" value={isEnabled().toString()} /> <input type="hidden" name="enabled" value={String(isEnabled())} />
<label data-slot="model-toggle-label"> <label data-slot="model-toggle-label">
<input <input
type="checkbox" type="checkbox"
@@ -21,9 +21,9 @@ function maskCredentials(credentials: string) {
const removeProvider = action(async (form: FormData) => { const removeProvider = action(async (form: FormData) => {
"use server" "use server"
const provider = form.get("provider")?.toString() const provider = form.get("provider") as string | null
if (!provider) return { error: formError.providerRequired } if (!provider) return { error: formError.providerRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Provider.remove({ provider }), workspaceID), { return json(await withActor(() => Provider.remove({ provider }), workspaceID), {
revalidate: listProviders.key, revalidate: listProviders.key,
@@ -32,11 +32,11 @@ const removeProvider = action(async (form: FormData) => {
const saveProvider = action(async (form: FormData) => { const saveProvider = action(async (form: FormData) => {
"use server" "use server"
const provider = form.get("provider")?.toString() const provider = form.get("provider") as string | null
const credentials = form.get("credentials")?.toString() const credentials = form.get("credentials") as string | null
if (!provider) return { error: formError.providerRequired } if (!provider) return { error: formError.providerRequired }
if (!credentials) return { error: formError.apiKeyRequired } if (!credentials) return { error: formError.apiKeyRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
@@ -59,10 +59,13 @@ function ProviderRow(props: { provider: Provider }) {
const params = useParams() const params = useParams()
const i18n = useI18n() const i18n = useI18n()
const providers = createAsync(() => listProviders(params.id!)) const providers = createAsync(() => listProviders(params.id!))
const saveSubmission = useSubmission(saveProvider, ([fd]) => fd.get("provider")?.toString() === props.provider.key) const saveSubmission = useSubmission(
saveProvider,
([fd]) => (fd.get("provider") as string | null) === props.provider.key,
)
const removeSubmission = useSubmission( const removeSubmission = useSubmission(
removeProvider, removeProvider,
([fd]) => fd.get("provider")?.toString() === props.provider.key, ([fd]) => (fd.get("provider") as string | null) === props.provider.key,
) )
const [store, setStore] = createStore({ editing: false }) const [store, setStore] = createStore({ editing: false })
@@ -30,10 +30,10 @@ const getWorkspaceInfo = query(async (workspaceID: string) => {
const updateWorkspace = action(async (form: FormData) => { const updateWorkspace = action(async (form: FormData) => {
"use server" "use server"
const name = form.get("name")?.toString().trim() const name = (form.get("name") as string | null)?.trim()
if (!name) return { error: formError.workspaceNameRequired } if (!name) return { error: formError.workspaceNameRequired }
if (name.length > 255) return { error: formError.nameTooLong } if (name.length > 255) return { error: formError.nameTooLong }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
+1 -1
View File
@@ -33,7 +33,7 @@ function generate(schema: z.ZodType) {
schema.examples = [schema.default] schema.examples = [schema.default]
} }
schema.description = [schema.description || "", `default: \`${schema.default}\``] schema.description = [schema.description || "", `default: \`${String(schema.default)}\``]
.filter(Boolean) .filter(Boolean)
.join("\n\n") .join("\n\n")
.trim() .trim()
+2
View File
@@ -15,7 +15,9 @@ const clean = (input?: Fields): Fields =>
Object.fromEntries(Object.entries(input ?? {}).filter((entry) => entry[1] !== undefined && entry[1] !== null)) Object.fromEntries(Object.entries(input ?? {}).filter((entry) => entry[1] !== undefined && entry[1] !== null))
const text = (input: unknown): string => { const text = (input: unknown): string => {
// oxlint-disable-next-line no-base-to-string
if (Array.isArray(input)) return input.map((item) => String(item)).join(" ") if (Array.isArray(input)) return input.map((item) => String(item)).join(" ")
// oxlint-disable-next-line no-base-to-string
return input === undefined ? "" : String(input) return input === undefined ? "" : String(input)
} }
@@ -93,7 +93,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
const info = await getAuth() const info = await getAuth()
if (info.type !== "oauth") return fetch(request, init) if (info.type !== "oauth") return fetch(request, init)
const url = request instanceof URL ? request.href : request.toString() const url = request instanceof URL ? request.href : typeof request === "string" ? request : request.url
const { isVision, isAgent } = iife(() => { const { isVision, isAgent } = iife(() => {
try { try {
const body = typeof init?.body === "string" ? JSON.parse(init.body) : init?.body const body = typeof init?.body === "string" ? JSON.parse(init.body) : init?.body
+2 -2
View File
@@ -274,7 +274,7 @@ export namespace ProviderTransform {
// Check for empty base64 image data // Check for empty base64 image data
if (part.type === "image") { if (part.type === "image") {
const imageStr = part.image.toString() const imageStr = String(part.image)
if (imageStr.startsWith("data:")) { if (imageStr.startsWith("data:")) {
const match = imageStr.match(/^data:([^;]+);base64,(.*)$/) const match = imageStr.match(/^data:([^;]+);base64,(.*)$/)
if (match && (!match[2] || match[2].length === 0)) { if (match && (!match[2] || match[2].length === 0)) {
@@ -286,7 +286,7 @@ export namespace ProviderTransform {
} }
} }
const mime = part.type === "image" ? part.image.toString().split(";")[0].replace("data:", "") : part.mediaType const mime = part.type === "image" ? String(part.image).split(";")[0].replace("data:", "") : part.mediaType
const filename = part.type === "file" ? part.filename : undefined const filename = part.type === "file" ? part.filename : undefined
const modality = mimeToModality(mime) const modality = mimeToModality(mime)
if (!modality) return part if (!modality) return part
+1 -1
View File
@@ -346,7 +346,7 @@ export const layer = Layer.effect(
return { return {
onMessage: (message: string | ArrayBuffer) => { onMessage: (message: string | ArrayBuffer) => {
session.process.write(String(message)) session.process.write(typeof message === "string" ? message : new TextDecoder().decode(message))
}, },
onClose: () => { onClose: () => {
log.info("client disconnected from session", { id }) log.info("client disconnected from session", { id })
+1 -1
View File
@@ -116,7 +116,7 @@ export const layer: Layer.Layer<
Effect.succeed({ Effect.succeed({
code: ChildProcessSpawner.ExitCode(1), code: ChildProcessSpawner.ExitCode(1),
text: "", text: "",
stderr: String(err), stderr: err instanceof Error ? err.message : String(err),
}), }),
), ),
) )
+9 -1
View File
@@ -145,7 +145,15 @@ export const ApplyPatchTool = Tool.define(
case "delete": { case "delete": {
const contentToDelete = yield* afs const contentToDelete = yield* afs
.readFileString(filePath) .readFileString(filePath)
.pipe(Effect.catch((error) => Effect.fail(new Error(`apply_patch verification failed: ${error}`)))) .pipe(
Effect.catch((error) =>
Effect.fail(
new Error(
`apply_patch verification failed: ${error instanceof Error ? error.message : String(error)}`,
),
),
),
)
const deleteDiff = trimDiff(createTwoFilesPatch(filePath, filePath, contentToDelete, "")) const deleteDiff = trimDiff(createTwoFilesPatch(filePath, filePath, contentToDelete, ""))
const deletions = contentToDelete.split("\n").length const deletions = contentToDelete.split("\n").length
+1
View File
@@ -60,6 +60,7 @@ export function errorData(error: unknown) {
acc[key] = value acc[key] = value
return acc return acc
} }
// oxlint-disable-next-line no-base-to-string -- intentional coercion of arbitrary error properties
acc[key] = value instanceof Error ? value.message : String(value) acc[key] = value instanceof Error ? value.message : String(value)
return acc return acc
}, {}) }, {})
@@ -202,6 +202,7 @@ export namespace SessionEntry {
case "tool.input.delta": { case "tool.input.delta": {
if (!pendingAssistant) break if (!pendingAssistant) break
const match = pendingAssistant.content.findLast((x) => x.type === "tool") const match = pendingAssistant.content.findLast((x) => x.type === "tool")
// oxlint-disable-next-line no-base-to-string -- event.delta is a Schema.String (runtime string)
if (match) match.state.input += event.delta if (match) match.state.input += event.delta
break break
} }
+2 -2
View File
@@ -1801,7 +1801,7 @@ test("project config overrides remote well-known config", async () => {
const originalFetch = globalThis.fetch const originalFetch = globalThis.fetch
let fetchedUrl: string | undefined let fetchedUrl: string | undefined
globalThis.fetch = mock((url: string | URL | Request) => { globalThis.fetch = mock((url: string | URL | Request) => {
const urlStr = url.toString() const urlStr = url instanceof Request ? url.url : url instanceof URL ? url.href : url
if (urlStr.includes(".well-known/opencode")) { if (urlStr.includes(".well-known/opencode")) {
fetchedUrl = urlStr fetchedUrl = urlStr
return Promise.resolve( return Promise.resolve(
@@ -1858,7 +1858,7 @@ test("wellknown URL with trailing slash is normalized", async () => {
const originalFetch = globalThis.fetch const originalFetch = globalThis.fetch
let fetchedUrl: string | undefined let fetchedUrl: string | undefined
globalThis.fetch = mock((url: string | URL | Request) => { globalThis.fetch = mock((url: string | URL | Request) => {
const urlStr = url.toString() const urlStr = url instanceof Request ? url.url : url instanceof URL ? url.href : url
if (urlStr.includes(".well-known/opencode")) { if (urlStr.includes(".well-known/opencode")) {
fetchedUrl = urlStr fetchedUrl = urlStr
return Promise.resolve( return Promise.resolve(
+1
View File
@@ -19,6 +19,7 @@ const TRANSIENT_MESSAGES = [
function isTransientError(error: unknown): boolean { function isTransientError(error: unknown): boolean {
if (!error) return false if (!error) return false
// oxlint-disable-next-line no-base-to-string -- error is unknown, intentional coercion for message matching
const message = String(error instanceof Error ? error.message : error).toLowerCase() const message = String(error instanceof Error ? error.message : error).toLowerCase()
return TRANSIENT_MESSAGES.some((m) => message.includes(m)) return TRANSIENT_MESSAGES.some((m) => message.includes(m))
} }
@@ -317,6 +317,7 @@ describe("util.effect-flock", () => {
}) })
const result = yield* flock.withLock(Effect.void, "eflock:perm", dir).pipe(Effect.exit) const result = yield* flock.withLock(Effect.void, "eflock:perm", dir).pipe(Effect.exit)
// oxlint-disable-next-line no-base-to-string -- Exit has a useful toString for test assertions
expect(String(result)).toContain("PermissionDenied") expect(String(result)).toContain("PermissionDenied")
yield* Effect.promise(() => fs.chmod(dir, 0o700).then(() => fs.rm(tmp, { recursive: true, force: true }))) yield* Effect.promise(() => fs.chmod(dir, 0o700).then(() => fs.rm(tmp, { recursive: true, force: true })))
}), }),
@@ -60,6 +60,7 @@ function render(template: string, params?: Record<string, unknown>) {
return template.replace(/\{\{([^}]+)\}\}/g, (_, key: string) => { return template.replace(/\{\{([^}]+)\}\}/g, (_, key: string) => {
const value = params[key.trim()] const value = params[key.trim()]
if (value === undefined || value === null) return "" if (value === undefined || value === null) return ""
// oxlint-disable-next-line no-base-to-string -- value is Record<string, unknown>, always coerced intentionally
return String(value) return String(value)
}) })
} }
+3
View File
@@ -698,6 +698,7 @@ function TextViewer<T>(props: TextFileProps<T>) {
if (typeof value === "string") return value if (typeof value === "string") return value
if (Array.isArray(value)) return value.join("\n") if (Array.isArray(value)) return value.join("\n")
if (value == null) return "" if (value == null) return ""
// oxlint-disable-next-line no-base-to-string -- file contents cast to unknown, coercion is intentional
return String(value) return String(value)
} }
@@ -712,11 +713,13 @@ function TextViewer<T>(props: TextFileProps<T>) {
if (typeof value === "string") return value.length if (typeof value === "string") return value.length
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value.reduce( return value.reduce(
// oxlint-disable-next-line no-base-to-string -- array parts coerced intentionally
(sum, part) => sum + (typeof part === "string" ? part.length + 1 : String(part).length + 1), (sum, part) => sum + (typeof part === "string" ? part.length + 1 : String(part).length + 1),
0, 0,
) )
} }
if (value == null) return 0 if (value == null) return 0
// oxlint-disable-next-line no-base-to-string -- file contents cast to unknown, coercion is intentional
return String(value).length return String(value).length
}) })
@@ -313,6 +313,7 @@ export function SessionTurn(
const msg = error()?.data?.message const msg = error()?.data?.message
if (typeof msg === "string") return unwrap(msg) if (typeof msg === "string") return unwrap(msg)
if (msg === undefined || msg === null) return "" if (msg === undefined || msg === null) return ""
// oxlint-disable-next-line no-base-to-string -- msg is unknown from error data, coercion is intentional
return unwrap(String(msg)) return unwrap(String(msg))
}) })
+7 -1
View File
@@ -730,7 +730,13 @@ export function FallbackTool(props: ToolProps) {
<> <>
<div></div> <div></div>
<div>{arg[0]}</div> <div>{arg[0]}</div>
<div>{String(arg[1] ?? "")}</div> <div>
{typeof arg[1] === "string" || typeof arg[1] === "number" || typeof arg[1] === "boolean"
? String(arg[1])
: arg[1] == null
? ""
: JSON.stringify(arg[1])}
</div>
</> </>
)} )}
</For> </For>