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>
89 lines
4.3 KiB
JavaScript
89 lines
4.3 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* pi imperative reference host — ADR-1239 Phase D / #1682 Slice 3.
|
|
*
|
|
* Proves the Programmatic-CLI reference binding for pi via the ExtensionAPI
|
|
* imperative adapter (#1682 AC: "invoke gsd tools/commands in pi via the
|
|
* ExtensionAPI imperative adapter"):
|
|
* 1. createImperativeAdapter({runtime:'pi'}) classifies as imperative + composes the registry.
|
|
* 2. pi's axes (imperative + bun) classify as 'programmatic-cli' (the reference profile).
|
|
* 3. the reference pi host-plugin binds GSD via ExtensionAPI (registers command + tool + event).
|
|
*
|
|
* Full `--pi` installable-runtime integration shipped in #2102 Stage 1
|
|
* (capabilities/pi/capability.json + bin/install.js wiring + golden parity
|
|
* 16→17, see tests/fixtures/golden-install-parity/pi.json); this slice
|
|
* continues to prove the imperative ExtensionAPI binding in isolation.
|
|
*/
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const { createImperativeAdapter } = require('../gsd-core/bin/lib/adapter-imperative.cjs');
|
|
const { profileOf } = require('../gsd-core/bin/lib/host-integration.cjs');
|
|
const gsdPiPlugin = require('./fixtures/pi-host-plugin.cjs');
|
|
|
|
// Mock pi ExtensionAPI: records registrations so the plugin is testable without
|
|
// a live pi runtime. Records the full command/tool definitions (not just
|
|
// names) so #2102's handler/execute SHAPE can be asserted, not just presence.
|
|
function mockPi() {
|
|
const recorded = { commands: [], tools: [], events: [], commandDefs: {}, toolDefs: {} };
|
|
return {
|
|
registerCommand(name, def) { recorded.commands.push(name); recorded.commandDefs[name] = def; },
|
|
registerTool(def) { if (def && def.name) { recorded.tools.push(def.name); recorded.toolDefs[def.name] = def; } },
|
|
registerShortcut() {},
|
|
registerFlag() {},
|
|
on(event) { recorded.events.push(event); },
|
|
_recorded: recorded,
|
|
};
|
|
}
|
|
|
|
test('createImperativeAdapter classifies pi as an imperative host + composes the registry', () => {
|
|
const adapter = createImperativeAdapter({ runtime: 'pi' });
|
|
assert.equal(adapter.kind, 'imperative');
|
|
assert.equal(adapter.runtime, 'pi');
|
|
assert.ok(adapter.registry, 'imperative adapter exposes the composed capability registry');
|
|
assert.equal(typeof adapter.install, 'function');
|
|
assert.equal(typeof adapter.uninstall, 'function');
|
|
});
|
|
|
|
test('pi axes classify as the programmatic-cli reference profile', () => {
|
|
// pi: imperative embedding, bun runtime (NOT sandboxed-web) → programmatic-cli.
|
|
assert.equal(profileOf({ embeddingMode: 'imperative', runtime: 'bun' }), 'programmatic-cli');
|
|
assert.notEqual(profileOf({ embeddingMode: 'imperative', runtime: 'bun' }), 'ide');
|
|
});
|
|
|
|
test('the pi reference host-plugin binds GSD via the ExtensionAPI (command + tool + event)', () => {
|
|
const pi = mockPi();
|
|
gsdPiPlugin(pi);
|
|
assert.ok(pi._recorded.commands.includes('gsd'), 'registers the /gsd command');
|
|
assert.ok(pi._recorded.tools.includes('gsd_invoke'), 'registers the gsd_invoke tool');
|
|
assert.ok(pi._recorded.events.includes('tool_call'), 'subscribes to the tool_call event');
|
|
});
|
|
|
|
// #2102 Stage 2: pi's REAL ExtensionAPI shape is `handler(args, ctx)` for
|
|
// registerCommand (NOT `execute(ctx)`, which the original #1682 cut used) and
|
|
// a 5-arg `execute(toolCallId, params, signal, onUpdate, ctx)` for
|
|
// registerTool. Locks the shape so a future drift back to the wrong contract
|
|
// is a visible test failure.
|
|
test('the /gsd command registers a handler(args, ctx) — not execute(ctx)', () => {
|
|
const pi = mockPi();
|
|
gsdPiPlugin(pi);
|
|
const def = pi._recorded.commandDefs['gsd'];
|
|
assert.equal(typeof def.handler, 'function', 'registerCommand takes handler(args, ctx), pi\'s real ExtensionAPI shape');
|
|
assert.equal(def.execute, undefined, 'must not use the wrong execute(ctx) shape');
|
|
});
|
|
|
|
test('the gsd_invoke tool registers a 5-arg execute(toolCallId, params, signal, onUpdate, ctx)', () => {
|
|
const pi = mockPi();
|
|
gsdPiPlugin(pi);
|
|
const def = pi._recorded.toolDefs['gsd_invoke'];
|
|
assert.equal(typeof def.execute, 'function');
|
|
assert.equal(def.execute.length, 5, 'gsd_invoke.execute must declare pi\'s real 5-arg tool-execute signature');
|
|
});
|
|
|
|
test('gsdPiPlugin throws if the pi ExtensionAPI is not provided (fail-closed)', () => {
|
|
assert.throws(() => gsdPiPlugin(null), /ExtensionAPI is required/);
|
|
assert.throws(() => gsdPiPlugin(undefined), /ExtensionAPI is required/);
|
|
});
|