Merge pull request #391 from deseltrus/refactor/dynamic-extension-discovery
This commit is contained in:
commit
d22226e3b8
2 changed files with 57 additions and 38 deletions
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
import { fileURLToPath } from 'url'
|
||||
import { dirname, resolve, join, delimiter } from 'path'
|
||||
import { existsSync, readFileSync, mkdirSync, symlinkSync } from 'fs'
|
||||
import { existsSync, readFileSync, readdirSync, mkdirSync, symlinkSync } from 'fs'
|
||||
import { agentDir, appRoot } from './app-paths.js'
|
||||
import { serializeBundledExtensionPaths } from './bundled-extension-paths.js'
|
||||
import { renderLogo } from './logo.js'
|
||||
|
|
@ -78,27 +78,40 @@ const srcRes = join(loaderPackageRoot, 'src', 'resources')
|
|||
const resourcesDir = existsSync(distRes) ? distRes : srcRes
|
||||
process.env.GSD_WORKFLOW_PATH = join(resourcesDir, 'GSD-WORKFLOW.md')
|
||||
|
||||
// GSD_BUNDLED_EXTENSION_PATHS — platform-delimited list of bundled extension entry point absolute
|
||||
// paths, used by patched subagent to pass --extension <path> to spawned gsd processes.
|
||||
// IMPORTANT: paths point to agentDir (~/.gsd/agent/extensions/) NOT src/resources/extensions/.
|
||||
// initResources() syncs bundled extensions to agentDir before any extension loading occurs,
|
||||
// so these paths are always valid at runtime. Using agentDir paths matches what buildResourceLoader
|
||||
// discovers (it scans agentDir), so pi's deduplication works correctly and extensions are not
|
||||
// double-loaded in subagent child processes.
|
||||
// Note: shared/ is NOT included — it's a library imported by gsd and ask-user-questions, not an entry point.
|
||||
process.env.GSD_BUNDLED_EXTENSION_PATHS = serializeBundledExtensionPaths([
|
||||
join(agentDir, 'extensions', 'gsd', 'index.ts'),
|
||||
join(agentDir, 'extensions', 'bg-shell', 'index.ts'),
|
||||
join(agentDir, 'extensions', 'browser-tools', 'index.ts'),
|
||||
join(agentDir, 'extensions', 'context7', 'index.ts'),
|
||||
join(agentDir, 'extensions', 'search-the-web', 'index.ts'),
|
||||
join(agentDir, 'extensions', 'slash-commands', 'index.ts'),
|
||||
join(agentDir, 'extensions', 'subagent', 'index.ts'),
|
||||
join(agentDir, 'extensions', 'mac-tools', 'index.ts'),
|
||||
join(agentDir, 'extensions', 'async-jobs', 'index.ts'),
|
||||
join(agentDir, 'extensions', 'ask-user-questions.ts'),
|
||||
join(agentDir, 'extensions', 'get-secrets-from-user.ts'),
|
||||
])
|
||||
// GSD_BUNDLED_EXTENSION_PATHS — dynamically discovered bundled extension entry points.
|
||||
// Scans the bundled resources directory to find all extensions, then maps paths to
|
||||
// agentDir (~/.gsd/agent/extensions/) where initResources() will sync them.
|
||||
//
|
||||
// Discovery rules (mirroring resource-loader.ts discoverExtensionEntryPaths):
|
||||
// - Top-level .ts/.js files → extension entry point
|
||||
// - Directories with index.ts or index.js → extension entry point
|
||||
// - Directories without either (e.g. shared/, remote-questions/) → skipped
|
||||
//
|
||||
// Previously this was a hardcoded list that required manual updates whenever
|
||||
// extensions were added or removed — causing merge conflicts in forks and
|
||||
// falling out of sync with what buildResourceLoader() discovers at runtime.
|
||||
const bundledExtDir = join(resourcesDir, 'extensions')
|
||||
const agentExtDir = join(agentDir, 'extensions')
|
||||
const discoveredExtensionPaths: string[] = []
|
||||
|
||||
if (existsSync(bundledExtDir)) {
|
||||
for (const entry of readdirSync(bundledExtDir, { withFileTypes: true })) {
|
||||
if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.js'))) {
|
||||
discoveredExtensionPaths.push(join(agentExtDir, entry.name))
|
||||
} else if (entry.isDirectory()) {
|
||||
const srcIndex = existsSync(join(bundledExtDir, entry.name, 'index.ts'))
|
||||
? 'index.ts'
|
||||
: existsSync(join(bundledExtDir, entry.name, 'index.js'))
|
||||
? 'index.js'
|
||||
: null
|
||||
if (srcIndex) {
|
||||
discoveredExtensionPaths.push(join(agentExtDir, entry.name, srcIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.env.GSD_BUNDLED_EXTENSION_PATHS = serializeBundledExtensionPaths(discoveredExtensionPaths)
|
||||
|
||||
// Respect HTTP_PROXY / HTTPS_PROXY / NO_PROXY env vars for all outbound requests.
|
||||
// pi-coding-agent's cli.ts sets this, but GSD bypasses that entry point — so we
|
||||
|
|
|
|||
|
|
@ -94,22 +94,28 @@ test("loader sets all 4 GSD_ env vars and PI_PACKAGE_DIR", async () => {
|
|||
assert.ok(loaderSrc.includes("serializeBundledExtensionPaths"), "loader uses shared bundled path serializer");
|
||||
assert.ok(loaderSrc.includes("join(delimiter)"), "loader uses platform delimiter for NODE_PATH");
|
||||
|
||||
// Verify all 11 extension entry points are referenced in loader
|
||||
// Loader uses join() calls like join(agentDir, 'extensions', 'gsd', 'index.ts')
|
||||
// so we check for the distinguishing directory name of each extension
|
||||
const extNames = [
|
||||
"'gsd'",
|
||||
"'bg-shell'",
|
||||
"'browser-tools'",
|
||||
"'context7'",
|
||||
"'search-the-web'",
|
||||
"'slash-commands'",
|
||||
"'subagent'",
|
||||
"'ask-user-questions.ts'",
|
||||
"'get-secrets-from-user.ts'",
|
||||
];
|
||||
for (const name of extNames) {
|
||||
assert.ok(loaderSrc.includes(name), `loader references extension ${name}`);
|
||||
// Verify extension discovery mechanism is in place
|
||||
// loader.ts now dynamically discovers extensions via readdirSync instead of
|
||||
// hardcoding paths — verify the discovery infrastructure exists
|
||||
assert.ok(loaderSrc.includes("readdirSync"), "loader uses readdirSync for extension discovery");
|
||||
assert.ok(loaderSrc.includes("bundledExtDir"), "loader defines bundledExtDir for scanning");
|
||||
assert.ok(loaderSrc.includes("discoveredExtensionPaths"), "loader collects discovered paths");
|
||||
|
||||
// Verify that the env var is populated at runtime by checking the actual
|
||||
// extensions directory has discoverable entry points
|
||||
const { discoverExtensionEntryPaths } = await import("../resource-loader.ts");
|
||||
const bundledExtensionsDir = join(projectRoot, existsSync(join(projectRoot, "dist", "resources"))
|
||||
? "dist" : "src", "resources", "extensions");
|
||||
const discovered = discoverExtensionEntryPaths(bundledExtensionsDir);
|
||||
assert.ok(discovered.length >= 10, `expected >=10 extensions, found ${discovered.length}`);
|
||||
|
||||
// Spot-check that core extensions are discoverable
|
||||
const discoveredNames = discovered.map(p => {
|
||||
const rel = p.slice(bundledExtensionsDir.length + 1);
|
||||
return rel.split(/[\\/]/)[0].replace(/\.ts$/, "");
|
||||
});
|
||||
for (const core of ["gsd", "bg-shell", "browser-tools", "subagent", "search-the-web"]) {
|
||||
assert.ok(discoveredNames.includes(core), `core extension '${core}' is discoverable`);
|
||||
}
|
||||
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue