feat(#2102): make pi a first-class installable runtime + fix its dispatch (ADR-1239)
Net-new EoS/pi installable runtime — purely additive (no prior runtime==='pi'
branches). pi is a bun-runtime programmatic-CLI whose /gsd command is registered
by a native ExtensionAPI extension and dispatches through the embedded engine.
Stage 1 (install plumbing):
- capabilities/pi/capability.json: full hostIntegration descriptor (imperative /
slash-programmatic / active-model / native-extension / bun) + hostBehaviors
{nativePlugin, pluginOnlyInstall}.
- --pi flag + interactive-menu renumber (All 17->18); pi added to RUNTIME_FLAG_IDS,
RUNTIME_LABELS, RUNTIME_META, allRuntimes/runtimeMap, model-catalog defaults.
- Install mirrors OpenCode: pi installs the gsd.cjs extension + the shared engine
payload (gsd-core + scripts + config markers) + the shared hooks bundle (spawned
by the extension at lifecycle events, like OpenCode's plugin). pluginOnlyInstall
EXCLUDES declarative command/agent/skill markdown, which pi has no host-read
surface for (its /gsd is programmatic). _installNativePluginIfDeclared (extracted
from the opencode-family path) copies pi/gsd.cjs -> ~/.pi/agent/extensions/gsd.cjs
(global) / .pi/extensions/ (local). pi added to package.json files.
- Golden: new pi.json (320 files: extension + engine + 27-file hooks bundle, no
markdown); the 16 other fixtures + claude-local change only by the shared
model-catalog hash line.
Stage 2 (real dispatch + upgrades):
- Shared dispatchGsdCommand() (shell-command-projection): bounded, no-throw
subprocess-shim to gsd-tools.cjs (the only full-surface dispatch path; no
in-process full-hub factory exists). Fixes pi/gsd.cjs's createHub()-no-args bug
(every dispatch was UnknownCommand) AND the identical bug in mcp-server.cts's
gsd_invoke_command, which a vacuous unknown-family-only test had masked (now has
a real dispatch regression test).
- pi/gsd.cjs: /gsd handler now (args, ctx) - tokenizes (quote-aware, via the
shipped hooks/lib/git-cmd.js) + dispatches real family/subcommand (not hardcoded
query/help); gsd_invoke gets a TypeBox (JSON-schema-fallback) parameters schema +
consumes params; getArgumentCompletions; before_provider_request active-model
steering (fail-open on null resolution); functional session_start /
before_agent_start / session_before_compact hook bridges (spawn the shipped GSD
hook scripts).
- EXTENSION_EVENT_SURFACES.pi expanded from ['tool_call'] to the full 30-event
vocabulary.
Docs (host-integration matrix + how-to) + changeset (Added).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -715,6 +715,19 @@ function installRuntimeArtifacts(
|
||||
const nestedGsdDirForCleanup = path.join(configDir, 'skills', 'gsd');
|
||||
_removeHermesBareStemDirs(nestedGsdDirForCleanup);
|
||||
}
|
||||
|
||||
// Generic-branch nativePlugin staging (ADR-1239 / #2102 Stage 1): runtimes
|
||||
// outside the OpenCode/Kilo combined-family install (e.g. pi, whose
|
||||
// artifactLayout is empty and which never sets combinedFamilyInstall) still
|
||||
// need their declared hostBehaviors.nativePlugin file copied into configDir.
|
||||
// findInstallSourceRoot resolves the repo/package root independent of
|
||||
// configDir contents (marker check, then a walk-up from __dirname), so this
|
||||
// is safe even when configDir has no .gsd-source marker (artifactLayout: []).
|
||||
if (behaviors.nativePlugin) {
|
||||
const commandsGsdDir = runtimeArtifactLayout.findInstallSourceRoot(configDir);
|
||||
const src = path.dirname(path.dirname(commandsGsdDir));
|
||||
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -879,6 +892,44 @@ function installOpencodeFamilyCommands(
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// _installNativePluginIfDeclared
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Copy a runtime's declared native-extension/plugin file (hostBehaviors.nativePlugin)
|
||||
* into its resolved config dir, when the runtime descriptor declares one.
|
||||
*
|
||||
* Extracted (ADR-1239 / #2102 Stage 1) from the body previously inlined in
|
||||
* installOpencodeFamilyArtifacts so a runtime that is NOT part of the
|
||||
* OpenCode/Kilo combined-family install (e.g. pi, whose artifactLayout is
|
||||
* empty and which never sets combinedFamilyInstall) can still get its
|
||||
* nativePlugin file staged via the generic installRuntimeArtifacts branch.
|
||||
* Behavior for opencode/kilo is unchanged — same source resolution, same
|
||||
* mkdir + copyFileSync call, same silent no-op when the source is missing.
|
||||
*
|
||||
* @param runtime - canonical runtime id (only used for the assertDestWithinConfigHome guard)
|
||||
* @param configDir - resolved runtime config directory
|
||||
* @param behaviors - the runtime's hostBehaviors descriptor
|
||||
* @param src - repo/package root (two levels up from the commands/gsd source dir)
|
||||
*/
|
||||
function _installNativePluginIfDeclared(
|
||||
runtime: string,
|
||||
configDir: string,
|
||||
behaviors: any,
|
||||
src: string,
|
||||
): void {
|
||||
const np = behaviors.nativePlugin;
|
||||
if (np && np.source) {
|
||||
const pluginSrc = path.join(src, np.source);
|
||||
if (fs.existsSync(pluginSrc)) {
|
||||
const destDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, np.dir);
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
fs.copyFileSync(pluginSrc, path.join(destDir, np.file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// installOpencodeFamilyArtifacts
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -927,15 +978,7 @@ function installOpencodeFamilyArtifacts(
|
||||
installOpencodeFamilyCommands(runtime, commandDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
||||
installOpencodeFamilySkills(runtime, configDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
||||
|
||||
const np = behaviors.nativePlugin;
|
||||
if (np && np.source) {
|
||||
const pluginSrc = path.join(src, np.source);
|
||||
if (fs.existsSync(pluginSrc)) {
|
||||
const destDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, np.dir);
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
fs.copyFileSync(pluginSrc, path.join(destDir, np.file));
|
||||
}
|
||||
}
|
||||
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1004,6 +1047,7 @@ export = {
|
||||
installOpencodeFamilySkills,
|
||||
installOpencodeFamilyCommands,
|
||||
installOpencodeFamilyArtifacts,
|
||||
_installNativePluginIfDeclared,
|
||||
_hostBehaviors,
|
||||
_copyStaged,
|
||||
hasExistingSymlinkBetween,
|
||||
|
||||
Reference in New Issue
Block a user