fix: companion 改用 $.fs,修正 hooks 模組載入失敗

node-fs.ts 引用 node:fs/promises,hooks 沙箱不允許,導致 /companion 指令未註冊。
改為以引擎的 $.fs 包成 Fs adapter;rename 改為選用,無 rename 時直接覆寫。
版本號調整為 0.0.1。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
root
2026-10-12 14:06:53 +08:00
co-authored by Claude Sonnet 5.5
parent 6338b57df7
commit 94143a1f51
5 changed files with 32 additions and 30 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "companion",
"version": "0.1.0",
"version": "0.0.1",
"description": "小說角色陪伴:人設注入、語氣、心情與好感、互動記憶、主動關心與待辦,並可用 ingest skill 解析小說建立角色",
"license": "MIT",
"author": {
@@ -0,0 +1,26 @@
import type { Fs } from './fs'
// hooks 沙箱沒有 node:fs,改用引擎的 $.fs;它沒有 rename,所以不提供(writeAtomic 會直接覆寫)
type EngineFs = {
read(path: string): Promise<string>
write(path: string, text: string): Promise<void>
list(path?: string): Promise<{ name: string }[]>
}
export const engineFs = (fs: EngineFs): Fs => ({
async read(path) {
try {
return await fs.read(path)
} catch {
return null
}
},
write: (path, data) => fs.write(path, data),
async list(dir) {
try {
return (await fs.list(dir)).map(e => e.name).filter(n => !n.endsWith('.tmp')).sort()
} catch {
return []
}
},
})
+3 -1
View File
@@ -2,7 +2,8 @@
export interface Fs {
read(path: string): Promise<string | null>
write(path: string, data: string): Promise<void>
rename(from: string, to: string): Promise<void>
/** 沒有 rename 的環境(hooks 沙箱)會直接覆寫 */
rename?(from: string, to: string): Promise<void>
list(dir: string): Promise<string[]>
}
@@ -10,6 +11,7 @@ export type Clock = { now(): number }
// 先寫暫存檔再 rename,當機時不會留下寫到一半的檔案
export async function writeAtomic(fs: Fs, path: string, data: string): Promise<void> {
if (!fs.rename) return fs.write(path, data)
const tmp = `${path}.tmp`
await fs.write(tmp, data)
await fs.rename(tmp, path)
@@ -1,26 +0,0 @@
import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises'
import { dirname } from 'node:path'
import type { Fs } from './fs'
export const nodeFs: Fs = {
async read(path) {
try {
return await readFile(path, 'utf8')
} catch {
return null
}
},
async write(path, data) {
await mkdir(dirname(path), { recursive: true })
await writeFile(path, data, 'utf8')
},
rename,
async list(dir) {
try {
return (await readdir(dir)).filter(n => !n.endsWith('.tmp')).sort()
} catch {
return []
}
},
}
+2 -2
View File
@@ -2,7 +2,7 @@ import type { Register } from 'claude-code'
import { fitCompanion } from './companion/band'
import { createCompanion } from './companion/core'
import { nodeFs } from './companion/node-fs'
import { engineFs } from './companion/engine-fs'
let companion: ReturnType<typeof createCompanion> | undefined
@@ -14,7 +14,7 @@ export const register: Register = on => {
argumentHint: 'status | switch <角色> | off | setup',
})
const home = await $.env.get('HOME')
companion = createCompanion({ fs: nodeFs, clock: { now: Date.now }, root: `${home ?? ''}/.companion` })
companion = createCompanion({ fs: engineFs({ read: p => $.fs.read(p), write: (p, t) => $.fs.write(p, t), list: p => $.fs.list(p) }), clock: { now: Date.now }, root: `${home ?? ''}/.companion` })
const first = await companion.ensureActivated()
if (first.first) $.ui.toast(`已啟用 ${first.name}。用 /companion off 可關閉`)