fix(extensions): route print mode through buildResourceLoader
Print mode was constructing DefaultResourceLoader directly, which
bypassed the GSD extension registry filter and let disabled bundled
extensions leak through. With the community @0xkobold/pi-ollama
installed, every `gsd -p` invocation printed an /ollama command
conflict because the bundled ollama extension (explicitly disabled
in ~/.gsd/extensions/registry.json) was still being loaded.
- Add extension-manifest.json for the bundled ollama extension so the
registry's id-keyed disable entry can actually target it.
- Extend buildResourceLoader() with an options bag for print-mode
callers (additionalExtensionPaths, appendSystemPrompt).
- Switch print mode to buildResourceLoader() so the registry filter
(extensionPathsTransform) runs in both TUI and print paths.
Also fix a stderr leak in the GSD codebase-generator: execSync("git
ls-files") was inheriting stderr to the parent, so running gsd from a
non-repo cwd (e.g. $HOME) printed "fatal: not a git repository" before
the catch silently returned []. Pipe stderr so it lands in the thrown
Error instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2f2f1845f7
commit
6612456934
4 changed files with 58 additions and 7 deletions
|
|
@ -544,8 +544,12 @@ if (isPrintMode) {
|
||||||
exitIfManagedResourcesAreNewer(agentDir)
|
exitIfManagedResourcesAreNewer(agentDir)
|
||||||
initResources(agentDir)
|
initResources(agentDir)
|
||||||
markStartup('initResources')
|
markStartup('initResources')
|
||||||
const resourceLoader = new DefaultResourceLoader({
|
// Route print mode through buildResourceLoader so the GSD extension registry
|
||||||
agentDir,
|
// filter (extensionPathsTransform) is applied consistently with TUI mode.
|
||||||
|
// Constructing DefaultResourceLoader directly bypassed the filter and let
|
||||||
|
// disabled bundled extensions (e.g. `ollama` superseded by `@0xkobold/pi-ollama`)
|
||||||
|
// leak through and emit `/ollama` command conflicts on every print invocation.
|
||||||
|
const resourceLoader = buildResourceLoader(agentDir, {
|
||||||
additionalExtensionPaths: cliFlags.extensions.length > 0 ? cliFlags.extensions : undefined,
|
additionalExtensionPaths: cliFlags.extensions.length > 0 ? cliFlags.extensions : undefined,
|
||||||
appendSystemPrompt,
|
appendSystemPrompt,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { DefaultResourceLoader, sortExtensionPaths } from '@gsd/pi-coding-agent'
|
import { DefaultResourceLoader, sortExtensionPaths } from '@gsd/pi-coding-agent'
|
||||||
|
if (process.env.GSD_DEBUG_EXTENSIONS) process.stderr.write("[gsd-debug] resource-loader.ts loaded\n")
|
||||||
import { createHash } from 'node:crypto'
|
import { createHash } from 'node:crypto'
|
||||||
import { homedir } from 'node:os'
|
import { homedir } from 'node:os'
|
||||||
import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, openSync, closeSync, readFileSync, readlinkSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs'
|
import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, openSync, closeSync, readFileSync, readlinkSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs'
|
||||||
|
|
@ -730,7 +731,22 @@ function getBundledExtensionKeys(): Set<string> {
|
||||||
return _bundledExtensionKeys
|
return _bundledExtensionKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildResourceLoader(agentDir: string): DefaultResourceLoader {
|
/**
|
||||||
|
* Optional overrides passed through to DefaultResourceLoader. Print mode
|
||||||
|
* needs these — it used to construct DefaultResourceLoader directly, which
|
||||||
|
* bypassed buildResourceLoader's extensionPathsTransform (= the GSD registry
|
||||||
|
* filter) and let disabled bundled extensions like `ollama` leak through and
|
||||||
|
* conflict with community replacements such as `@0xkobold/pi-ollama`.
|
||||||
|
*/
|
||||||
|
export interface BuildResourceLoaderOptions {
|
||||||
|
additionalExtensionPaths?: string[]
|
||||||
|
appendSystemPrompt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildResourceLoader(
|
||||||
|
agentDir: string,
|
||||||
|
options: BuildResourceLoaderOptions = {},
|
||||||
|
): DefaultResourceLoader {
|
||||||
const registry = loadRegistry()
|
const registry = loadRegistry()
|
||||||
const piAgentDir = join(homedir(), '.pi', 'agent')
|
const piAgentDir = join(homedir(), '.pi', 'agent')
|
||||||
const piExtensionsDir = join(piAgentDir, 'extensions')
|
const piExtensionsDir = join(piAgentDir, 'extensions')
|
||||||
|
|
@ -743,19 +759,30 @@ export function buildResourceLoader(agentDir: string): DefaultResourceLoader {
|
||||||
return isExtensionEnabled(registry, manifest.id)
|
return isExtensionEnabled(registry, manifest.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Print-mode callers pass their own additional extension paths (e.g. --extension
|
||||||
|
// flags). Non-print mode uses the implicit pi-extensions discovery above.
|
||||||
|
const additionalExtensionPaths =
|
||||||
|
options.additionalExtensionPaths && options.additionalExtensionPaths.length > 0
|
||||||
|
? options.additionalExtensionPaths
|
||||||
|
: piExtensionPaths
|
||||||
|
|
||||||
return new DefaultResourceLoader({
|
return new DefaultResourceLoader({
|
||||||
agentDir,
|
agentDir,
|
||||||
additionalExtensionPaths: piExtensionPaths,
|
additionalExtensionPaths,
|
||||||
|
appendSystemPrompt: options.appendSystemPrompt,
|
||||||
bundledExtensionKeys: bundledKeys,
|
bundledExtensionKeys: bundledKeys,
|
||||||
extensionPathsTransform: (paths: string[]) => {
|
extensionPathsTransform: (paths: string[]) => {
|
||||||
// 1. Filter community extensions through the GSD registry
|
// Filter community + bundled extensions through the GSD registry so
|
||||||
|
// explicitly-disabled entries (e.g. bundled `ollama` superseded by
|
||||||
|
// `@0xkobold/pi-ollama`) never reach the runtime and trigger command
|
||||||
|
// conflicts.
|
||||||
const filteredPaths = paths.filter((entryPath) => {
|
const filteredPaths = paths.filter((entryPath) => {
|
||||||
const manifest = readManifestFromEntryPath(entryPath)
|
const manifest = readManifestFromEntryPath(entryPath)
|
||||||
if (!manifest) return true // no manifest = always load
|
if (!manifest) return true // no manifest = always load
|
||||||
return isExtensionEnabled(registry, manifest.id)
|
return isExtensionEnabled(registry, manifest.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2. Sort in topological dependency order
|
// Sort in topological dependency order
|
||||||
const { sortedPaths, warnings } = sortExtensionPaths(filteredPaths)
|
const { sortedPaths, warnings } = sortExtensionPaths(filteredPaths)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,16 @@ function shouldExclude(filePath: string, excludes: string[]): boolean {
|
||||||
|
|
||||||
function lsFiles(basePath: string): string[] {
|
function lsFiles(basePath: string): string[] {
|
||||||
try {
|
try {
|
||||||
const result = execSync("git ls-files", { cwd: basePath, encoding: "utf-8", timeout: 10000 });
|
// stdio: "pipe" captures stderr into the thrown Error instead of
|
||||||
|
// inheriting it to the parent. Without it, running gsd from a non-repo
|
||||||
|
// cwd (e.g. `$HOME`) leaks a "fatal: not a git repository" line to the
|
||||||
|
// user's terminal before the catch silently falls through to [].
|
||||||
|
const result = execSync("git ls-files", {
|
||||||
|
cwd: basePath,
|
||||||
|
encoding: "utf-8",
|
||||||
|
timeout: 10000,
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
});
|
||||||
return result.split("\n").filter(Boolean);
|
return result.split("\n").filter(Boolean);
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
11
src/resources/extensions/ollama/extension-manifest.json
Normal file
11
src/resources/extensions/ollama/extension-manifest.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"id": "ollama",
|
||||||
|
"name": "Ollama",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Local Ollama model discovery and /ollama command",
|
||||||
|
"tier": "bundled",
|
||||||
|
"requires": { "platform": ">=2.29.0" },
|
||||||
|
"provides": {
|
||||||
|
"commands": ["ollama"]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue