fix: reuse bundled agent runtime resources
This commit is contained in:
@@ -2,12 +2,13 @@ import { join } from 'node:path'
|
||||
|
||||
export type BundledRuntimePaths = {
|
||||
opencode: string
|
||||
opencodeConfig?: string
|
||||
continue: string
|
||||
deepseekHarness: string
|
||||
}
|
||||
|
||||
export const bundledContinueVersion = '1.5.47'
|
||||
export const bundledDeepSeekHarnessVersion = '0.1.0-rc.8'
|
||||
export const bundledDeepSeekHarnessVersion = '0.1.2-rc.1'
|
||||
|
||||
export function resolveBundledRuntimePaths(input: {
|
||||
appPath: string
|
||||
@@ -27,6 +28,11 @@ export function resolveBundledRuntimePaths(input: {
|
||||
'opencode',
|
||||
packagedExecutable
|
||||
),
|
||||
opencodeConfig: join(
|
||||
input.resourcesPath,
|
||||
'runtimes',
|
||||
'opencode-config'
|
||||
),
|
||||
continue: join(
|
||||
input.resourcesPath,
|
||||
'runtimes',
|
||||
|
||||
@@ -44,13 +44,13 @@ import {
|
||||
} from '../execution-space'
|
||||
import type { LaunchEnvironmentProvider } from '../local-tool-environment/launch-environment-provider'
|
||||
|
||||
const noSubagentTools: ModelToolProviderLike = {
|
||||
const noModelTaskTools: ModelToolProviderLike = {
|
||||
listTools: async () => [],
|
||||
getApproval: () => {
|
||||
throw new Error('子专家不允许工具调用')
|
||||
throw new Error('此模型任务不允许工具调用')
|
||||
},
|
||||
callTool: async () => {
|
||||
throw new Error('子专家不允许工具调用')
|
||||
throw new Error('此模型任务不允许工具调用')
|
||||
},
|
||||
releaseConversation: async () => undefined,
|
||||
dispose: async () => undefined
|
||||
@@ -61,6 +61,7 @@ export type AgentCapabilityContext = {
|
||||
skillPackages?: RuntimeSkillPackage[]
|
||||
mcpServers?: ResolvedMcpServer[]
|
||||
continueHostCacheRoot?: string
|
||||
opencodeSharedCacheRoot?: string
|
||||
bundledRuntimePaths?: BundledRuntimePaths
|
||||
continueHostLauncher?: ContinueHostLauncher
|
||||
deepseekHarnessLauncher?: DeepSeekHarnessRuntimeOptions['launch']
|
||||
@@ -138,7 +139,7 @@ export function createDefaultModelRuntime(
|
||||
settings,
|
||||
currentProfile
|
||||
),
|
||||
toolProvider: noSubagentTools
|
||||
toolProvider: noModelTaskTools
|
||||
})
|
||||
}
|
||||
|
||||
@@ -162,7 +163,7 @@ export function createModelProfileRuntime(
|
||||
defaultRuntimeSettings.imageGenerationQuality,
|
||||
contextCompression: resolveContextCompression(settings, profile),
|
||||
defaultWorkspace: settings.workspacePath || defaultWorkspace,
|
||||
toolProvider: noSubagentTools
|
||||
toolProvider: noModelTaskTools
|
||||
})
|
||||
}
|
||||
|
||||
@@ -300,6 +301,8 @@ export function createAgentRuntime(
|
||||
process.env.GOODBUDDY_OPENCODE_BINARY?.trim() ??
|
||||
'',
|
||||
bundledBinaryPath: capabilities.bundledRuntimePaths?.opencode,
|
||||
bundledConfigPath:
|
||||
capabilities.bundledRuntimePaths?.opencodeConfig,
|
||||
configPath:
|
||||
settings?.opencodeConfigPath ??
|
||||
process.env.GOODBUDDY_OPENCODE_CONFIG?.trim() ??
|
||||
@@ -307,6 +310,7 @@ export function createAgentRuntime(
|
||||
modelProfile: settings?.opencodeModelProfile,
|
||||
skillInstructions: capabilities.skillInstructions,
|
||||
skillPackages: capabilities.skillPackages,
|
||||
sharedCacheRoot: capabilities.opencodeSharedCacheRoot,
|
||||
defaultWorkspace: workspace,
|
||||
knowledgeGateway: capabilities.knowledgeGateway,
|
||||
mcpServers: capabilities.mcpServers,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { join, resolve } from 'node:path'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createCanvas } from '@napi-rs/canvas'
|
||||
import {
|
||||
CallId,
|
||||
ToolCallId,
|
||||
type GenerateOptions,
|
||||
type StreamChunk
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
@@ -112,7 +112,7 @@ async function* toolCall(
|
||||
name: string,
|
||||
argumentsValue: Record<string, unknown>
|
||||
): AsyncGenerator<StreamChunk> {
|
||||
const id = CallId(callId)
|
||||
const id = ToolCallId(callId)
|
||||
const argumentsText = JSON.stringify(argumentsValue)
|
||||
yield {
|
||||
type: 'block-start',
|
||||
@@ -332,7 +332,7 @@ function createInProcessLaunch(
|
||||
model: options.model,
|
||||
supportsImageInput: options.supportsImageInput,
|
||||
requestHeaders: options.requestHeaders,
|
||||
harnessVersion: '0.1.0-rc.8',
|
||||
harnessVersion: '0.1.2-rc.1',
|
||||
credentialRefs: options.credentialRefs,
|
||||
skillPackages: options.skillPackages,
|
||||
extensionPackages: options.extensionPackages,
|
||||
|
||||
@@ -159,7 +159,7 @@ function setup(
|
||||
if (method === 'goodbuddy/handshake') {
|
||||
return {
|
||||
controlProtocolVersion: 1,
|
||||
harnessVersion: '0.1.0-rc.8',
|
||||
harnessVersion: '0.1.2-rc.1',
|
||||
acpProtocolVersion: 1,
|
||||
supports: {
|
||||
cancellation: true,
|
||||
|
||||
@@ -95,7 +95,7 @@ describe.skipIf(!enabled)('DSH marketplace live E2E', () => {
|
||||
api: 'openai-completions',
|
||||
provider: 'goodbuddy',
|
||||
model: 'deepseek-test',
|
||||
harnessVersion: '0.1.0-rc.8',
|
||||
harnessVersion: '0.1.2-rc.1',
|
||||
credentialRefs: ['GOODBUDDY_API_KEY'],
|
||||
skillPackages: [],
|
||||
extensionPackages: extensions,
|
||||
|
||||
@@ -22,7 +22,7 @@ function controlPlane() {
|
||||
provider: 'goodbuddy',
|
||||
model: 'deepseek-test',
|
||||
workspace: resolve('workspace'),
|
||||
harnessVersion: '0.1.0-rc.8',
|
||||
harnessVersion: '0.1.2-rc.1',
|
||||
execution: { mode: 'host' },
|
||||
credentialRefs: ['GOODBUDDY_API_KEY'],
|
||||
skills: []
|
||||
@@ -75,7 +75,7 @@ function stubAgentContext() {
|
||||
provider: 'goodbuddy',
|
||||
model: 'deepseek-test',
|
||||
workspace: resolve('workspace'),
|
||||
harnessVersion: '0.1.0-rc.8',
|
||||
harnessVersion: '0.1.2-rc.1',
|
||||
execution: { mode: 'host' },
|
||||
credentialRefs: ['GOODBUDDY_API_KEY'],
|
||||
skills: [],
|
||||
@@ -172,7 +172,7 @@ describe('GoodBuddy Harness internal control plane', () => {
|
||||
model: 'vision-test',
|
||||
supportsImageInput: true,
|
||||
workspace: resolve('workspace'),
|
||||
harnessVersion: '0.1.0-rc.8',
|
||||
harnessVersion: '0.1.2-rc.1',
|
||||
execution: { mode: 'host' },
|
||||
credentialRefs: ['GOODBUDDY_API_KEY'],
|
||||
skills: []
|
||||
|
||||
@@ -20,6 +20,10 @@ import {
|
||||
import {
|
||||
CredentialProvider,
|
||||
type CredentialInfo,
|
||||
type CredentialKey,
|
||||
type CredentialRecord,
|
||||
type CredentialRecordEntry,
|
||||
type CredentialRecordInfo,
|
||||
type CredentialRef,
|
||||
type ResolvedCredential
|
||||
} from '@deepseek-ai/dsh-credentials'
|
||||
@@ -292,6 +296,40 @@ export class GoodBuddyCredentialProvider extends CredentialProvider {
|
||||
void ref
|
||||
throw new Error('GoodBuddy Harness credentials are read-only')
|
||||
}
|
||||
|
||||
async readRecord(
|
||||
key: CredentialKey
|
||||
): Promise<CredentialRecord | undefined> {
|
||||
void key
|
||||
return undefined
|
||||
}
|
||||
|
||||
async describeRecord(
|
||||
key: CredentialKey
|
||||
): Promise<CredentialRecordInfo> {
|
||||
void key
|
||||
return { configured: false, writable: false }
|
||||
}
|
||||
|
||||
async listRecords(): Promise<readonly CredentialRecordEntry[]> {
|
||||
return []
|
||||
}
|
||||
|
||||
async modifyRecord(
|
||||
key: CredentialKey,
|
||||
mutate: (
|
||||
current: CredentialRecord | undefined
|
||||
) => Promise<CredentialRecord | undefined>
|
||||
): Promise<CredentialRecord | undefined> {
|
||||
void key
|
||||
void mutate
|
||||
throw new Error('GoodBuddy Harness credentials are read-only')
|
||||
}
|
||||
|
||||
async deleteRecord(key: CredentialKey): Promise<void> {
|
||||
void key
|
||||
throw new Error('GoodBuddy Harness credentials are read-only')
|
||||
}
|
||||
}
|
||||
|
||||
export function createBoundedAcpStream(
|
||||
|
||||
@@ -661,6 +661,140 @@ describe('OpenCodeRuntime embedded launcher', () => {
|
||||
await expect(stat(registrationRoot)).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('reuses shared OpenCode config and content-addressed Skills across projects', async () => {
|
||||
const sourceRoot = await mkdtemp(
|
||||
join(tmpdir(), 'goodbuddy-opencode-shared-skill-source-')
|
||||
)
|
||||
const sharedCacheRoot = await mkdtemp(
|
||||
join(tmpdir(), 'goodbuddy-opencode-shared-cache-')
|
||||
)
|
||||
const skillDirectory = join(sourceRoot, 'shared-skill')
|
||||
const bundledConfigPath = join(sourceRoot, 'bundled-config')
|
||||
await mkdir(skillDirectory, { recursive: true })
|
||||
await mkdir(
|
||||
join(
|
||||
bundledConfigPath,
|
||||
'node_modules',
|
||||
'@opencode-ai',
|
||||
'plugin'
|
||||
),
|
||||
{ recursive: true }
|
||||
)
|
||||
await writeFile(
|
||||
join(bundledConfigPath, '.goodbuddy-ready.json'),
|
||||
'{"version":"1.18.29"}\n',
|
||||
'utf8'
|
||||
)
|
||||
await writeFile(
|
||||
join(
|
||||
bundledConfigPath,
|
||||
'node_modules',
|
||||
'@opencode-ai',
|
||||
'plugin',
|
||||
'package.json'
|
||||
),
|
||||
'{"name":"@opencode-ai/plugin","version":"1.18.29"}\n',
|
||||
'utf8'
|
||||
)
|
||||
await writeFile(
|
||||
join(skillDirectory, 'SKILL.md'),
|
||||
[
|
||||
'---',
|
||||
'name: Shared Skill',
|
||||
'description: Shared test Skill',
|
||||
'---',
|
||||
'',
|
||||
'# Shared Skill'
|
||||
].join('\n'),
|
||||
'utf8'
|
||||
)
|
||||
const skillPackages = [
|
||||
{
|
||||
id: 'shared-skill',
|
||||
directory: skillDirectory,
|
||||
digest: 'shared-skill-digest'
|
||||
}
|
||||
]
|
||||
const firstChild = fakeChild()
|
||||
const first = dependencies(firstChild)
|
||||
const firstRuntime = new OpenCodeRuntime(
|
||||
options({
|
||||
sharedCacheRoot,
|
||||
skillPackages,
|
||||
bundledConfigPath
|
||||
}),
|
||||
first.deps
|
||||
)
|
||||
const secondChild = fakeChild()
|
||||
const second = dependencies(secondChild)
|
||||
const secondRuntime = new OpenCodeRuntime(
|
||||
options({
|
||||
sharedCacheRoot,
|
||||
skillPackages,
|
||||
bundledConfigPath
|
||||
}),
|
||||
second.deps
|
||||
)
|
||||
|
||||
try {
|
||||
await expect(firstRuntime.testConnection()).resolves.toMatchObject({
|
||||
available: true
|
||||
})
|
||||
const firstEnvironment = first.spawnMock.mock.calls[0]?.[2]?.env
|
||||
const firstConfigDirectory =
|
||||
firstEnvironment?.OPENCODE_CONFIG_DIR ?? ''
|
||||
const firstConfig = JSON.parse(
|
||||
firstEnvironment?.OPENCODE_CONFIG_CONTENT ?? '{}'
|
||||
) as { skills?: { paths?: string[] } }
|
||||
const firstSkillsRoot = firstConfig.skills?.paths?.[0] ?? ''
|
||||
expect(firstConfigDirectory).toBe(
|
||||
join(sharedCacheRoot, 'config', 'opencode')
|
||||
)
|
||||
expect(firstEnvironment?.XDG_CONFIG_HOME).toBe(
|
||||
join(sharedCacheRoot, 'config')
|
||||
)
|
||||
await expect(
|
||||
readFile(
|
||||
join(
|
||||
firstConfigDirectory,
|
||||
'node_modules',
|
||||
'@opencode-ai',
|
||||
'plugin',
|
||||
'package.json'
|
||||
),
|
||||
'utf8'
|
||||
)
|
||||
).resolves.toContain('1.18.29')
|
||||
expect(firstEnvironment?.XDG_CACHE_HOME).toBe(
|
||||
join(sharedCacheRoot, 'cache')
|
||||
)
|
||||
expect(firstEnvironment).toMatchObject({
|
||||
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: '1'
|
||||
})
|
||||
await firstRuntime.dispose()
|
||||
await expect(
|
||||
readFile(join(firstSkillsRoot, 'shared-skill', 'SKILL.md'), 'utf8')
|
||||
).resolves.toContain('name: shared-skill')
|
||||
|
||||
await expect(secondRuntime.testConnection()).resolves.toMatchObject({
|
||||
available: true
|
||||
})
|
||||
const secondEnvironment = second.spawnMock.mock.calls[0]?.[2]?.env
|
||||
const secondConfig = JSON.parse(
|
||||
secondEnvironment?.OPENCODE_CONFIG_CONTENT ?? '{}'
|
||||
) as { skills?: { paths?: string[] } }
|
||||
expect(secondEnvironment?.OPENCODE_CONFIG_DIR).toBe(
|
||||
firstConfigDirectory
|
||||
)
|
||||
expect(secondConfig.skills?.paths?.[0]).toBe(firstSkillsRoot)
|
||||
} finally {
|
||||
await firstRuntime.dispose()
|
||||
await secondRuntime.dispose()
|
||||
await rm(sourceRoot, { recursive: true, force: true })
|
||||
await rm(sharedCacheRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('injects an independent model profile without persisting its key', async () => {
|
||||
const child = fakeChild()
|
||||
const { deps, spawnMock } = dependencies(child)
|
||||
|
||||
@@ -9,13 +9,17 @@ import {
|
||||
import spawn from 'cross-spawn'
|
||||
import { createHash, randomBytes } from 'node:crypto'
|
||||
import {
|
||||
cp,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
rename,
|
||||
rm,
|
||||
stat,
|
||||
writeFile
|
||||
} from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'
|
||||
import type {
|
||||
AgentQuestionAnswer,
|
||||
@@ -173,6 +177,9 @@ type OpenCodeSkillRegistration = {
|
||||
skillsRoot: string
|
||||
}
|
||||
|
||||
const sharedSkillPreparations = new Map<string, Promise<string>>()
|
||||
const sharedConfigPreparations = new Map<string, Promise<void>>()
|
||||
|
||||
const executePermissionRules: PermissionRuleset = [
|
||||
{ permission: '*', pattern: '*', action: 'allow' }
|
||||
]
|
||||
@@ -499,11 +506,13 @@ export type OpenCodeRuntimeOptions = {
|
||||
embedded: boolean
|
||||
binaryPath: string
|
||||
bundledBinaryPath?: string
|
||||
bundledConfigPath?: string
|
||||
configPath: string
|
||||
defaultWorkspace: string
|
||||
modelProfile?: ResolvedModelProfile
|
||||
skillInstructions?: string
|
||||
skillPackages?: RuntimeSkillPackage[]
|
||||
sharedCacheRoot?: string
|
||||
knowledgeGateway?: KnowledgeMcpGateway
|
||||
mcpServers?: ResolvedMcpServer[]
|
||||
customization?: RuntimeCustomizationSettings['opencode']
|
||||
@@ -851,21 +860,101 @@ export class OpenCodeRuntime implements AgentRuntime {
|
||||
return ids
|
||||
}
|
||||
|
||||
private async normalizeSkillPackages(skillsRoot: string): Promise<void> {
|
||||
for (const skill of this.options.skillPackages ?? []) {
|
||||
await normalizeOpenCodeSkillManifest(
|
||||
join(skillsRoot, skill.id),
|
||||
skill.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private async prepareSharedSkills(sharedRoot: string): Promise<string> {
|
||||
const packages = this.options.skillPackages ?? []
|
||||
const fingerprint = createHash('sha256')
|
||||
.update(
|
||||
packages
|
||||
.map((skill) => ({
|
||||
id: skill.id,
|
||||
digest: skill.digest ?? resolve(skill.directory)
|
||||
}))
|
||||
.sort((left, right) => left.id.localeCompare(right.id))
|
||||
.map((skill) => `${skill.id}\0${skill.digest}`)
|
||||
.join('\0')
|
||||
)
|
||||
.digest('hex')
|
||||
const cacheRoot = join(sharedRoot, 'skills')
|
||||
const target = join(cacheRoot, fingerprint)
|
||||
const existing = sharedSkillPreparations.get(target)
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
const preparation = (async () => {
|
||||
try {
|
||||
if ((await stat(target)).isDirectory()) {
|
||||
return target
|
||||
}
|
||||
} catch {
|
||||
// The immutable Skill snapshot has not been prepared yet.
|
||||
}
|
||||
await mkdir(cacheRoot, { recursive: true, mode: 0o700 })
|
||||
const stagingRoot = await mkdtemp(join(cacheRoot, '.staging-'))
|
||||
try {
|
||||
const staged = await stageRuntimeSkillPackages(
|
||||
stagingRoot,
|
||||
packages,
|
||||
'OpenCode'
|
||||
)
|
||||
await this.normalizeSkillPackages(staged)
|
||||
try {
|
||||
await rename(staged, target)
|
||||
} catch (error) {
|
||||
try {
|
||||
if ((await stat(target)).isDirectory()) {
|
||||
return target
|
||||
}
|
||||
} catch {
|
||||
// Preserve the original rename failure below.
|
||||
}
|
||||
throw error
|
||||
}
|
||||
return target
|
||||
} finally {
|
||||
await rm(stagingRoot, { recursive: true, force: true })
|
||||
}
|
||||
})()
|
||||
sharedSkillPreparations.set(target, preparation)
|
||||
try {
|
||||
return await preparation
|
||||
} catch (error) {
|
||||
sharedSkillPreparations.delete(target)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private async createSkillRegistration(): Promise<OpenCodeSkillRegistration> {
|
||||
const root = await mkdtemp(join(tmpdir(), 'goodbuddy-opencode-'))
|
||||
const configDirectory = join(root, 'config')
|
||||
try {
|
||||
const sharedRoot = this.options.sharedCacheRoot?.trim()
|
||||
if (sharedRoot) {
|
||||
const configDirectory = join(
|
||||
resolve(sharedRoot),
|
||||
'config',
|
||||
'opencode'
|
||||
)
|
||||
await this.prepareSharedConfig(configDirectory)
|
||||
const skillsRoot = await this.prepareSharedSkills(
|
||||
resolve(sharedRoot)
|
||||
)
|
||||
return { root, configDirectory, skillsRoot }
|
||||
}
|
||||
const configDirectory = join(root, 'config')
|
||||
const skillsRoot = await stageRuntimeSkillPackages(
|
||||
configDirectory,
|
||||
this.options.skillPackages ?? [],
|
||||
'OpenCode'
|
||||
)
|
||||
for (const skill of this.options.skillPackages ?? []) {
|
||||
await normalizeOpenCodeSkillManifest(
|
||||
join(skillsRoot, skill.id),
|
||||
skill.id
|
||||
)
|
||||
}
|
||||
await this.normalizeSkillPackages(skillsRoot)
|
||||
return { root, configDirectory, skillsRoot }
|
||||
} catch (error) {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
@@ -873,6 +962,57 @@ export class OpenCodeRuntime implements AgentRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
private async prepareSharedConfig(
|
||||
configDirectory: string
|
||||
): Promise<void> {
|
||||
const bundledConfigPath = this.options.bundledConfigPath?.trim()
|
||||
if (!bundledConfigPath) {
|
||||
await mkdir(configDirectory, { recursive: true, mode: 0o700 })
|
||||
return
|
||||
}
|
||||
const target = resolve(configDirectory)
|
||||
const existing = sharedConfigPreparations.get(target)
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
const preparation = (async () => {
|
||||
const source = resolve(bundledConfigPath)
|
||||
const markerName = '.goodbuddy-ready.json'
|
||||
const sourceMarker = await readFile(join(source, markerName), 'utf8')
|
||||
try {
|
||||
if (
|
||||
(await readFile(join(target, markerName), 'utf8')) ===
|
||||
sourceMarker
|
||||
) {
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// Install the bundled dependency tree below.
|
||||
}
|
||||
await mkdir(dirname(target), { recursive: true, mode: 0o700 })
|
||||
const staging = `${target}.staging-${process.pid}-${randomBytes(6).toString('hex')}`
|
||||
await rm(staging, { recursive: true, force: true })
|
||||
try {
|
||||
await cp(source, staging, {
|
||||
recursive: true,
|
||||
errorOnExist: true,
|
||||
force: false
|
||||
})
|
||||
await rm(target, { recursive: true, force: true })
|
||||
await rename(staging, target)
|
||||
} finally {
|
||||
await rm(staging, { recursive: true, force: true })
|
||||
}
|
||||
})()
|
||||
sharedConfigPreparations.set(target, preparation)
|
||||
try {
|
||||
await preparation
|
||||
} catch (error) {
|
||||
sharedConfigPreparations.delete(target)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private async inspectEmbeddedConfiguration(
|
||||
validatePath = false
|
||||
): Promise<{
|
||||
@@ -957,12 +1097,17 @@ export class OpenCodeRuntime implements AgentRuntime {
|
||||
env.OPENCODE_DISABLE_CLAUDE_CODE_SKILLS = '1'
|
||||
env.OPENCODE_DISABLE_EMBEDDED_WEB_UI = '1'
|
||||
env.OPENCODE_DISABLE_EXTERNAL_SKILLS = '1'
|
||||
env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = '1'
|
||||
env.OPENCODE_DISABLE_LSP_DOWNLOAD = '1'
|
||||
env.OPENCODE_DISABLE_MODELS_FETCH = '1'
|
||||
env.OPENCODE_DISABLE_PROJECT_CONFIG = '1'
|
||||
env.OPENCODE_DISABLE_SHARE = '1'
|
||||
env.XDG_CACHE_HOME = join(registration.root, 'xdg-cache')
|
||||
env.XDG_CONFIG_HOME = join(registration.root, 'xdg-config')
|
||||
env.XDG_CACHE_HOME = this.options.sharedCacheRoot?.trim()
|
||||
? join(resolve(this.options.sharedCacheRoot), 'cache')
|
||||
: join(registration.root, 'xdg-cache')
|
||||
env.XDG_CONFIG_HOME = this.options.sharedCacheRoot?.trim()
|
||||
? dirname(registration.configDirectory)
|
||||
: join(registration.root, 'xdg-config')
|
||||
env.XDG_DATA_HOME = join(registration.root, 'xdg-data')
|
||||
env.XDG_STATE_HOME = join(registration.root, 'xdg-state')
|
||||
const skillConfig = createOpenCodeSkillConfig(
|
||||
|
||||
@@ -215,7 +215,7 @@ describe('runtime discovery', () => {
|
||||
binaryPath: '',
|
||||
bundledPath: bundledScript,
|
||||
bundledValidation: 'canonical-file',
|
||||
bundledVersion: '0.1.0-rc.8',
|
||||
bundledVersion: '0.1.2-rc.1',
|
||||
binaryNames: [],
|
||||
label: 'GoodBuddy DeepSeek Harness Host'
|
||||
})
|
||||
@@ -223,7 +223,7 @@ describe('runtime discovery', () => {
|
||||
expect(detection).toMatchObject({
|
||||
available: true,
|
||||
path: await realpath(bundledScript),
|
||||
version: '0.1.0-rc.8',
|
||||
version: '0.1.2-rc.1',
|
||||
source: 'bundled'
|
||||
})
|
||||
expect(detection.detail).toContain('内置')
|
||||
|
||||
Reference in New Issue
Block a user