chore: bump version to 2.80.27 and update core features

This commit is contained in:
g1nation
2026-05-09 01:16:12 +09:00
parent 5ffb472d22
commit 3220a126fd
41 changed files with 4457 additions and 72 deletions
+35
View File
@@ -0,0 +1,35 @@
import { resolveEngine, buildApiUrl, logError, logInfo } from '../utils';
/**
* Discover the model list exposed by the local AI engine at `baseUrl`.
*
* Same wire format as the sidebar's `_sendModels` (which still owns the
* sidebar-specific caching/UI logic) — extracted here so the settings panel
* can fetch the same list without depending on the sidebar provider.
*
* Returns an empty array on any failure (offline engine, parse error, etc.).
* Callers should treat the result as a hint, not a hard list.
*/
export async function discoverModels(baseUrl: string, timeoutMs: number = 5000): Promise<string[]> {
const url = (baseUrl || '').trim();
if (!url) return [];
const engine = resolveEngine(url);
const modelsUrl = buildApiUrl(url, engine, 'models');
try {
const res = await fetch(modelsUrl, { signal: AbortSignal.timeout(timeoutMs) });
if (!res.ok) {
logInfo('discoverModels: non-OK status', { engine, modelsUrl, status: res.status });
return [];
}
const text = await res.text();
if (!text) return [];
const data = JSON.parse(text) as any;
const list: string[] = engine === 'lmstudio'
? (data.data || []).map((m: any) => m.id)
: (data.models || []).map((m: any) => m.name);
return list.filter((m): m is string => typeof m === 'string' && m.length > 0);
} catch (e: any) {
logError('discoverModels failed.', { engine, modelsUrl, error: e?.message ?? String(e) });
return [];
}
}
+146
View File
@@ -0,0 +1,146 @@
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
/**
* Centralized path resolver for ConnectAI.
*
* Why this module exists:
* - Brain / agent-skills / workspace paths are read from many places (utils, sidebar,
* bridge, agent). Embedding the same `~`-expansion + abs-path-only check in each
* call site makes them drift over time.
* - New external integrations (skill-inject, future detached-company mode) need a
* single source of truth so they can't accidentally write outside the sandboxed
* user folders.
*
* Conventions:
* - All exported functions return absolute, normalized paths (or empty string if
* the user has not configured a value AND no fallback exists).
* - Relative-path inputs are silently rejected (returned as empty) to avoid
* surprising writes inside random workspaces.
* - This module never throws and never creates directories — callers ensure
* existence on their own (`fs.mkdirSync(..., { recursive: true })`).
*/
/** Expand a leading `~` / `~/` to the user's home directory. Pure function. */
export function expandTilde(raw: string): string {
const trimmed = (raw || '').trim();
if (!trimmed) return '';
if (trimmed === '~') return os.homedir();
if (trimmed.startsWith('~/')) return path.join(os.homedir(), trimmed.slice(2));
return trimmed;
}
/**
* Normalize a user-supplied path string. Returns an empty string for any input
* that is empty, blank, or non-absolute after `~` expansion. Relative paths are
* intentionally rejected — see module header.
*/
export function resolvePathInput(raw: string): string {
const expanded = expandTilde(raw);
if (!expanded) return '';
if (!path.isAbsolute(expanded)) return '';
return path.normalize(expanded);
}
/**
* Best-effort read of a string config value. Returns empty string when VS Code
* config is unavailable (e.g. unit tests not mocking workspace) so callers can
* fall through to defaults without try/catch noise.
*/
function _safeGetConfigString(section: string, key: string): string {
try {
return vscode.workspace.getConfiguration(section).get<string>(key, '') || '';
} catch {
return '';
}
}
/**
* Active brain directory.
*
* Resolution order:
* 1. VS Code config `g1nation.localBrainPath` (after `~` + abs-path normalization).
* 2. The first configured brain profile's `localBrainPath` (handled by callers).
* 3. Empty string — caller decides on a default (utils.ts already has the
* profile-aware logic; this function is only for the simple-path case).
*
* Note: this intentionally does NOT consult `g1nation.brainProfiles` — the
* profile-aware resolver lives in [src/utils.ts](../utils.ts) (`_getBrainDir`)
* and depends on the active-brain selection. Use this function only when you
* need a plain folder path without profile semantics (e.g. external HTTP
* endpoints injecting into the user's primary brain).
*/
export function resolveBrainDirFromConfig(): string {
const raw = _safeGetConfigString('g1nation', 'localBrainPath');
return resolvePathInput(raw);
}
/**
* Resolve the agent-skills directory used by `[.agent/skills/*.md]` markdown
* skill files (the per-workspace agent skill bank that the sidebar's
* `_sendAgentsList` and `_createAgent` operate on).
*
* Resolution order:
* 1. The first VS Code workspace folder + `/.agent/skills/` (creating the
* folder is the caller's responsibility).
* 2. Empty string when no workspace is open — callers must short-circuit.
*
* The legacy default `E:\Wiki\Agent\.agent\skills` from sidebarProvider.ts is
* preserved as a fall-through hint for the original author's machine.
*/
export function resolveAgentSkillsDir(): string {
const legacy = 'E:\\Wiki\\Agent\\.agent\\skills';
try {
const fs = require('fs') as typeof import('fs');
if (fs.existsSync(legacy)) return legacy;
} catch { /* fs unavailable in some isolated tests */ }
const folders = vscode.workspace.workspaceFolders;
if (folders && folders.length > 0) {
return path.join(folders[0].uri.fsPath, '.agent', 'skills');
}
return '';
}
/**
* Returns true iff `child` is the same as `parent` or a descendant of it
* (after path normalization). Used to harden file writes against `..` traversal.
*
* Both paths must be absolute.
*/
export function isInside(parent: string, child: string): boolean {
if (!parent || !child) return false;
const p = path.resolve(parent);
const c = path.resolve(child);
if (c === p) return true;
return c.startsWith(p + path.sep);
}
/**
* Pick the best `ConfigurationTarget` to write a key to: write into whichever
* scope already holds the value, falling back to Global.
*
* Why this matters: VS Code's `getConfiguration().get(key)` resolves through
* Folder → Workspace → User → default. If a Workspace value is set and we
* blindly write to Global, every subsequent read keeps returning the stale
* Workspace value — which is exactly the "sidebar shows e2b but Settings
* shows e4b" bug.
*
* Returns the section's effective inspect record alongside the target so
* callers can debug or surface conflicts to the user.
*/
export function pickConfigTarget(section: string, key: string): {
target: vscode.ConfigurationTarget;
inspect: ReturnType<vscode.WorkspaceConfiguration['inspect']>;
} {
const cfg = vscode.workspace.getConfiguration(section);
const inspect = cfg.inspect(key);
if (inspect?.workspaceFolderValue !== undefined) {
return { target: vscode.ConfigurationTarget.WorkspaceFolder, inspect };
}
if (inspect?.workspaceValue !== undefined) {
return { target: vscode.ConfigurationTarget.Workspace, inspect };
}
return { target: vscode.ConfigurationTarget.Global, inspect };
}