fix(directory-agents-injector): guard against non-string output.output
MCP tool responses (e.g. Serena via mcpm) can deliver output.output as undefined or a non-string at runtime despite the TypeScript interface declaring it as string. Add the same typeof guard already used in tool-output-truncator and other hooks, preventing a TypeError crash. Fixes #3800
This commit is contained in:
@@ -26,6 +26,10 @@ export async function processFilePathForAgentsInjection(input: {
|
|||||||
sessionID: string;
|
sessionID: string;
|
||||||
output: { title: string; output: string; metadata: unknown };
|
output: { title: string; output: string; metadata: unknown };
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
|
// Guard: output.output may be non-string at runtime (e.g. MCP bridge format changes).
|
||||||
|
// Consistent with the pattern used in tool-output-truncator and other hooks.
|
||||||
|
if (typeof input.output.output !== "string") return;
|
||||||
|
|
||||||
const resolved = resolveFilePath(input.ctx.directory, input.filePath);
|
const resolved = resolveFilePath(input.ctx.directory, input.filePath);
|
||||||
if (!resolved) return;
|
if (!resolved) return;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
|
||||||
|
describe("scheduleDeferredModelOverride bun:sqlite unavailable", () => {
|
||||||
|
test("#given source code #when inspected #then bun:sqlite is loaded dynamically with an unavailable-runtime fallback", async () => {
|
||||||
|
//#given
|
||||||
|
const source = await Bun.file(new URL("./ultrawork-db-model-override.ts", import.meta.url)).text()
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const hasStaticBunSqliteImport = source.includes('from "bun:sqlite"')
|
||||||
|
|| source.includes("from 'bun:sqlite'")
|
||||||
|
|| source.includes('import "bun:sqlite"')
|
||||||
|
|| source.includes("import 'bun:sqlite'")
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(hasStaticBunSqliteImport).toBe(false)
|
||||||
|
expect(source).toContain('await import("bun:sqlite").catch(() => null)')
|
||||||
|
expect(source).toContain("bun:sqlite unavailable")
|
||||||
|
expect(source).toContain("return")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Database } from "bun:sqlite"
|
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
import { existsSync } from "node:fs"
|
import { existsSync } from "node:fs"
|
||||||
import { getDataDir } from "../shared/data-path"
|
import { getDataDir } from "../shared/data-path"
|
||||||
import { log } from "../shared"
|
import { log } from "../shared"
|
||||||
|
|
||||||
|
type BunDatabase = import("bun:sqlite").Database
|
||||||
|
|
||||||
function getDbPath(): string {
|
function getDbPath(): string {
|
||||||
return join(getDataDir(), "opencode", "opencode.db")
|
return join(getDataDir(), "opencode", "opencode.db")
|
||||||
}
|
}
|
||||||
@@ -11,7 +12,7 @@ function getDbPath(): string {
|
|||||||
const MAX_MICROTASK_RETRIES = 10
|
const MAX_MICROTASK_RETRIES = 10
|
||||||
|
|
||||||
function tryUpdateMessageModel(
|
function tryUpdateMessageModel(
|
||||||
db: InstanceType<typeof Database>,
|
db: BunDatabase,
|
||||||
messageId: string,
|
messageId: string,
|
||||||
targetModel: { providerID: string; modelID: string },
|
targetModel: { providerID: string; modelID: string },
|
||||||
variant?: string,
|
variant?: string,
|
||||||
@@ -30,7 +31,7 @@ function tryUpdateMessageModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function retryViaMicrotask(
|
function retryViaMicrotask(
|
||||||
db: InstanceType<typeof Database>,
|
db: BunDatabase,
|
||||||
messageId: string,
|
messageId: string,
|
||||||
targetModel: { providerID: string; modelID: string },
|
targetModel: { providerID: string; modelID: string },
|
||||||
variant: string | undefined,
|
variant: string | undefined,
|
||||||
@@ -112,14 +113,21 @@ export function scheduleDeferredModelOverride(
|
|||||||
targetModel: { providerID: string; modelID: string },
|
targetModel: { providerID: string; modelID: string },
|
||||||
variant?: string,
|
variant?: string,
|
||||||
): void {
|
): void {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(async () => {
|
||||||
|
const sqliteModule = await import("bun:sqlite").catch(() => null)
|
||||||
|
const Database = sqliteModule?.Database
|
||||||
|
if (typeof Database !== "function") {
|
||||||
|
log("[ultrawork-db-override] bun:sqlite unavailable, skipping deferred override", { messageId })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const dbPath = getDbPath()
|
const dbPath = getDbPath()
|
||||||
if (!existsSync(dbPath)) {
|
if (!existsSync(dbPath)) {
|
||||||
log("[ultrawork-db-override] DB not found, skipping deferred override")
|
log("[ultrawork-db-override] DB not found, skipping deferred override")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let db: InstanceType<typeof Database>
|
let db: BunDatabase
|
||||||
try {
|
try {
|
||||||
db = new Database(dbPath)
|
db = new Database(dbPath)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user