fix: address Cubic CLI and agent issues — URL encode, JSONC leading comments, config clone, untracked files, parse error handling, cache path, message-dir dedup

This commit is contained in:
YeonGyu-Kim 2026-02-09 11:17:51 +09:00
parent 5ca3d9c489
commit d6fbe7bd8d
4 changed files with 45 additions and 24 deletions

View File

@ -1,3 +1,6 @@
import { join } from "node:path"
import { getOpenCodeCacheDir } from "../../../shared"
import type { AvailableModelsInfo, ModelResolutionInfo, OmoConfig } from "./model-resolution-types" import type { AvailableModelsInfo, ModelResolutionInfo, OmoConfig } from "./model-resolution-types"
import { formatModelWithVariant, getCategoryEffectiveVariant, getEffectiveVariant } from "./model-resolution-variant" import { formatModelWithVariant, getCategoryEffectiveVariant, getEffectiveVariant } from "./model-resolution-variant"
@ -7,6 +10,7 @@ export function buildModelResolutionDetails(options: {
config: OmoConfig config: OmoConfig
}): string[] { }): string[] {
const details: string[] = [] const details: string[] = []
const cacheFile = join(getOpenCodeCacheDir(), "models.json")
details.push("═══ Available Models (from cache) ═══") details.push("═══ Available Models (from cache) ═══")
details.push("") details.push("")
@ -16,7 +20,7 @@ export function buildModelResolutionDetails(options: {
` Sample: ${options.available.providers.slice(0, 6).join(", ")}${options.available.providers.length > 6 ? "..." : ""}` ` Sample: ${options.available.providers.slice(0, 6).join(", ")}${options.available.providers.length > 6 ? "..." : ""}`
) )
details.push(` Total models: ${options.available.modelCount}`) details.push(` Total models: ${options.available.modelCount}`)
details.push(` Cache: ~/.cache/opencode/models.json`) details.push(` Cache: ${cacheFile}`)
details.push(` Runtime: only connected providers used`) details.push(` Runtime: only connected providers used`)
details.push(` Refresh: opencode models --refresh`) details.push(` Refresh: opencode models --refresh`)
} else { } else {

View File

@ -1,18 +1 @@
import { existsSync, readdirSync } from "node:fs" export { getMessageDir } from "./message-storage-locator"
import { join } from "node:path"
import { MESSAGE_STORAGE } from "../hook-message-injector"
export function getMessageDir(sessionID: string): string | null {
if (!existsSync(MESSAGE_STORAGE)) return null
const directPath = join(MESSAGE_STORAGE, sessionID)
if (existsSync(directPath)) return directPath
for (const dir of readdirSync(MESSAGE_STORAGE)) {
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
if (existsSync(sessionPath)) return sessionPath
}
return null
}

View File

@ -15,7 +15,11 @@ const execFileSyncMock = mock((file: string, args: string[], _opts: { cwd?: stri
} }
if (subcommand === "status") { if (subcommand === "status") {
return " M file.ts\n" return " M file.ts\n?? new-file.ts\n"
}
if (subcommand === "ls-files") {
return "new-file.ts\n"
} }
throw new Error(`unexpected args: ${args.join(" ")}`) throw new Error(`unexpected args: ${args.join(" ")}`)
@ -38,7 +42,7 @@ describe("collectGitDiffStats", () => {
//#then //#then
expect(execSyncMock).not.toHaveBeenCalled() expect(execSyncMock).not.toHaveBeenCalled()
expect(execFileSyncMock).toHaveBeenCalledTimes(2) expect(execFileSyncMock).toHaveBeenCalledTimes(3)
const [firstCallFile, firstCallArgs, firstCallOpts] = execFileSyncMock.mock const [firstCallFile, firstCallArgs, firstCallOpts] = execFileSyncMock.mock
.calls[0]! as unknown as [string, string[], { cwd?: string }] .calls[0]! as unknown as [string, string[], { cwd?: string }]
@ -54,6 +58,13 @@ describe("collectGitDiffStats", () => {
expect(secondCallOpts.cwd).toBe(directory) expect(secondCallOpts.cwd).toBe(directory)
expect(secondCallArgs.join(" ")).not.toContain(directory) expect(secondCallArgs.join(" ")).not.toContain(directory)
const [thirdCallFile, thirdCallArgs, thirdCallOpts] = execFileSyncMock.mock
.calls[2]! as unknown as [string, string[], { cwd?: string }]
expect(thirdCallFile).toBe("git")
expect(thirdCallArgs).toEqual(["ls-files", "--others", "--exclude-standard"])
expect(thirdCallOpts.cwd).toBe(directory)
expect(thirdCallArgs.join(" ")).not.toContain(directory)
expect(result).toEqual([ expect(result).toEqual([
{ {
path: "file.ts", path: "file.ts",
@ -61,6 +72,12 @@ describe("collectGitDiffStats", () => {
removed: 2, removed: 2,
status: "modified", status: "modified",
}, },
{
path: "new-file.ts",
added: 0,
removed: 0,
status: "added",
},
]) ])
}) })
}) })

View File

@ -12,8 +12,6 @@ export function collectGitDiffStats(directory: string): GitFileStat[] {
stdio: ["pipe", "pipe", "pipe"], stdio: ["pipe", "pipe", "pipe"],
}).trim() }).trim()
if (!diffOutput) return []
const statusOutput = execFileSync("git", ["status", "--porcelain"], { const statusOutput = execFileSync("git", ["status", "--porcelain"], {
cwd: directory, cwd: directory,
encoding: "utf-8", encoding: "utf-8",
@ -21,8 +19,27 @@ export function collectGitDiffStats(directory: string): GitFileStat[] {
stdio: ["pipe", "pipe", "pipe"], stdio: ["pipe", "pipe", "pipe"],
}).trim() }).trim()
const untrackedOutput = execFileSync("git", ["ls-files", "--others", "--exclude-standard"], {
cwd: directory,
encoding: "utf-8",
timeout: 5000,
stdio: ["pipe", "pipe", "pipe"],
}).trim()
const untrackedNumstat = untrackedOutput
? untrackedOutput
.split("\n")
.filter(Boolean)
.map((filePath) => `0\t0\t${filePath}`)
.join("\n")
: ""
const combinedNumstat = [diffOutput, untrackedNumstat].filter(Boolean).join("\n").trim()
if (!combinedNumstat) return []
const statusMap = parseGitStatusPorcelain(statusOutput) const statusMap = parseGitStatusPorcelain(statusOutput)
return parseGitDiffNumstat(diffOutput, statusMap) return parseGitDiffNumstat(combinedNumstat, statusMap)
} catch { } catch {
return [] return []
} }