fix: complete config path and review follow-ups (#3348)
This commit is contained in:
committed by
GitHub
parent
0543b954c3
commit
260bfe666b
@@ -172,3 +172,14 @@ Reachable filesystem routes: `api:fs:read` (attachments, config), `api:fs:search
|
||||
|
||||
Maintenance: reviews, changelog entries, and parity claims consult this map;
|
||||
whoever mounts or unmounts a surface updates it in the same change.
|
||||
|
||||
## Global OpenCode paths
|
||||
|
||||
`opencodeConfigPaths.ts` owns the global config directory for config CRUD,
|
||||
skill discovery/install, global AGENTS.md, and quota config-file lookup. It
|
||||
resolves `$XDG_CONFIG_HOME/opencode` at extension startup, falling back to
|
||||
`~/.config/opencode` when unset or blank. Project paths, the explicit
|
||||
`OPENCODE_CONFIG` file layer, and the auth data directory stay separate.
|
||||
No files are migrated. The behavior GET bridge response includes the effective
|
||||
`path` for both existing and missing AGENTS.md files; shared Settings uses it
|
||||
in the warning.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { OPENCODE_CONFIG_DIR } from './opencodeConfigPaths';
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
createAgent,
|
||||
@@ -78,7 +78,7 @@ type ConfigRuntimeDeps = {
|
||||
clientReloadDelayMs: number;
|
||||
};
|
||||
|
||||
const AGENTS_MD_PATH = path.join(os.homedir(), '.config', 'opencode', 'AGENTS.md');
|
||||
const AGENTS_MD_PATH = path.join(OPENCODE_CONFIG_DIR, 'AGENTS.md');
|
||||
const MAX_BEHAVIOR_PROMPT_SIZE = 1024 * 1024;
|
||||
|
||||
const resolveWorkingDirectory = (ctx: BridgeContext | undefined, directory?: string): string | undefined => (
|
||||
@@ -186,10 +186,10 @@ export async function handleConfigBridgeMessage(
|
||||
case 'api:behavior/agents-md:get': {
|
||||
try {
|
||||
const content = await fs.promises.readFile(AGENTS_MD_PATH, 'utf8');
|
||||
return { id, type, success: true, data: { content, exists: true } };
|
||||
return { id, type, success: true, data: { content, exists: true, path: AGENTS_MD_PATH } };
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
|
||||
return { id, type, success: true, data: { content: '', exists: false } };
|
||||
return { id, type, success: true, data: { content: '', exists: false, path: AGENTS_MD_PATH } };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OPENCODE_CONFIG_DIR } from './opencodeConfigPaths';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
@@ -76,7 +77,7 @@ const inferSkillScopeAndSourceFromLocation = (location: string, workingDirectory
|
||||
|
||||
const home = os.homedir();
|
||||
const userRoots = [
|
||||
path.join(home, '.config', 'opencode'),
|
||||
OPENCODE_CONFIG_DIR,
|
||||
path.join(home, '.opencode'),
|
||||
path.join(home, '.claude', 'skills'),
|
||||
path.join(home, '.agents', 'skills'),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { OPENCODE_CONFIG_DIR } from './opencodeConfigPaths';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import yaml from 'yaml';
|
||||
import { parse as parseJsonc, printParseErrorCode, type ParseError } from 'jsonc-parser';
|
||||
|
||||
const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
|
||||
const AGENT_DIR = path.join(OPENCODE_CONFIG_DIR, 'agents');
|
||||
const COMMAND_DIR = path.join(OPENCODE_CONFIG_DIR, 'commands');
|
||||
const GLOBAL_SNIPPET_DIR = path.join(OPENCODE_CONFIG_DIR, 'snippet');
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const moduleUrl = new URL('./opencodeConfigPaths.ts', import.meta.url).href;
|
||||
const configUrl = new URL('./opencodeConfig.ts', import.meta.url).href;
|
||||
|
||||
test('unset and blank XDG keep the existing global directory', () => {
|
||||
for (const value of [undefined, '', ' ']) {
|
||||
const env = { ...process.env };
|
||||
if (value === undefined) delete env.XDG_CONFIG_HOME;
|
||||
else env.XDG_CONFIG_HOME = value;
|
||||
const result = spawnSync(process.execPath, ['--eval', `
|
||||
const { OPENCODE_CONFIG_DIR } = await import(${JSON.stringify(moduleUrl)});
|
||||
process.stdout.write(OPENCODE_CONFIG_DIR);
|
||||
`], { env, encoding: 'utf8' });
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
assert.equal(result.stdout, path.join(os.homedir(), '.config', 'opencode'));
|
||||
}
|
||||
});
|
||||
|
||||
test('global CRUD uses XDG while project writes stay in the project', () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-vscode-xdg-'));
|
||||
const configHome = path.join(root, 'config');
|
||||
const project = path.join(root, 'project');
|
||||
try {
|
||||
const result = spawnSync(process.execPath, ['--eval', `
|
||||
const { createAgent, createCommand, createSkill } = await import(${JSON.stringify(configUrl)});
|
||||
const project = ${JSON.stringify(project)};
|
||||
for (const scope of ['user', 'project']) {
|
||||
createAgent(scope + '-agent', { description: 'Test', prompt: 'Test prompt' }, project, scope);
|
||||
createCommand(scope + '-command', { description: 'Test', template: 'Test command' }, project, scope);
|
||||
createSkill(scope + '-skill', { description: 'Test', instructions: 'Test skill' }, project, scope);
|
||||
}
|
||||
`], {
|
||||
env: { ...process.env, XDG_CONFIG_HOME: configHome, OPENCODE_CONFIG: '', OPENCODE_CONFIG_DIR: '' },
|
||||
encoding: 'utf8',
|
||||
});
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
for (const [scope, directory] of [['user', path.join(configHome, 'opencode')], ['project', path.join(project, '.opencode')]]) {
|
||||
assert.match(fs.readFileSync(path.join(directory, 'agents', `${scope}-agent.md`), 'utf8'), /Test prompt/);
|
||||
assert.match(fs.readFileSync(path.join(directory, 'commands', `${scope}-command.md`), 'utf8'), /Test command/);
|
||||
assert.match(fs.readFileSync(path.join(directory, 'skills', `${scope}-skill`, 'SKILL.md'), 'utf8'), /Test skill/);
|
||||
}
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
// Resolve once at extension startup, matching the web backend.
|
||||
export const OPENCODE_CONFIG_DIR = path.join(
|
||||
process.env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), '.config'),
|
||||
'opencode',
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OPENCODE_CONFIG_DIR } from './opencodeConfigPaths';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
@@ -191,7 +192,6 @@ export type ProviderResult = {
|
||||
planLabel?: string | null;
|
||||
};
|
||||
|
||||
const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
|
||||
const OPENCODE_DATA_DIR = path.join(os.homedir(), '.local', 'share', 'opencode');
|
||||
const AUTH_FILE = path.join(OPENCODE_DATA_DIR, 'auth.json');
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OPENCODE_CONFIG_DIR } from './opencodeConfigPaths';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
@@ -418,8 +419,8 @@ async function copyDirectoryNoSymlinks(srcDir: string, dstDir: string) {
|
||||
}
|
||||
|
||||
function getUserSkillBaseDir() {
|
||||
const pluralPath = path.join(os.homedir(), '.config', 'opencode', 'skills');
|
||||
const legacyPath = path.join(os.homedir(), '.config', 'opencode', 'skill');
|
||||
const pluralPath = path.join(OPENCODE_CONFIG_DIR, 'skills');
|
||||
const legacyPath = path.join(OPENCODE_CONFIG_DIR, 'skill');
|
||||
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
|
||||
return pluralPath;
|
||||
}
|
||||
|
||||
@@ -228,11 +228,11 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
|
||||
return {
|
||||
startingApi: 'OpenCode API başlatılıyor…',
|
||||
initializing: 'Başlatılıyor…',
|
||||
connecting: 'Bağlanılıyor…',
|
||||
connecting: 'Bağlanıyor…',
|
||||
connected: 'Bağlandı!',
|
||||
connectionError: 'Bağlantı hatası',
|
||||
reconnecting: 'Yeniden bağlanılıyor…',
|
||||
cliNotFound: 'OpenCode CLI bulunamadı. Lütfen önce yükleyin.',
|
||||
reconnecting: 'Yeniden bağlanıyor…',
|
||||
cliNotFound: 'OpenCode CLI bulunamadı. Lütfen önce kurun.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -310,8 +310,8 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
|
||||
}
|
||||
if (detected.indexOf('tr') === 0) {
|
||||
return {
|
||||
startingDevServer: (host) => 'Webview geliştirme sunucusu başlatılıyor (' + host + ')...',
|
||||
waitingDevServer: (host, attempt) => 'Webview geliştirme sunucusu bekleniyor (' + host + ')... deneme ' + attempt,
|
||||
startingDevServer: (host) => 'Webview dev sunucusu başlatılıyor (' + host + ')...',
|
||||
waitingDevServer: (host, attempt) => 'Webview dev sunucusu bekleniyor (' + host + ')... deneme ' + attempt,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user