diff --git a/.changeset/proud-eagles-chatter.md b/.changeset/proud-eagles-chatter.md new file mode 100644 index 000000000..49f9b3016 --- /dev/null +++ b/.changeset/proud-eagles-chatter.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3097 +--- +**`/gsd` commands in Pi now display their output** — the command handler returned output as a bare string, which Pi's ExtensionAPI silently dropped. It now returns Pi's structured `{ content: [{ type: 'text', text }] }` display shape (matching the `gsd_invoke` tool's proven contract), so success output and error messages are visible. (#2991) diff --git a/pi/gsd.cjs b/pi/gsd.cjs index c30bd738a..9a3fdc702 100644 --- a/pi/gsd.cjs +++ b/pi/gsd.cjs @@ -287,11 +287,16 @@ module.exports = function gsdPiExtension(pi) { try { ({ dispatchGsdCommand } = require(path.join(GSD_CORE, 'bin', 'lib', 'shell-command-projection.cjs'))); } catch (e) { - return `GSD engine unavailable: ${e && e.message ? e.message : String(e)}`; + // #2991: return the structured { content } shape Pi's ExtensionAPI + // displays, not a bare string (which Pi silently drops). + return { content: [{ type: 'text', text: `GSD engine unavailable: ${e && e.message ? e.message : String(e)}` }] }; } const result = dispatchGsdCommand({ family, subcommand, args: rest, cwd }); - if (result.ok) return result.stdout; - return `GSD error: ${result.stderr || result.stdout || `dispatch failed (exit ${result.code})`}`; + // #2991: match gsd_invoke's proven output shape so Pi actually displays it. + const text = result.ok + ? result.stdout + : `GSD error: ${result.stderr || result.stdout || `dispatch failed (exit ${result.code})`}`; + return { content: [{ type: 'text', text }] }; }, }); diff --git a/tests/pi-extension-reachability.test.cjs b/tests/pi-extension-reachability.test.cjs index 8d51730cb..c2208f6b5 100644 --- a/tests/pi-extension-reachability.test.cjs +++ b/tests/pi-extension-reachability.test.cjs @@ -63,23 +63,28 @@ test('REACHABILITY: the /gsd handler dispatches a real family through gsd-tools. const dir = createTempDir(); try { const result = await pi._recorded.commands['gsd'].handler('progress json', { cwd: dir }); - assert.equal(typeof result, 'string', '/gsd handler returns a string result'); - const parsed = JSON.parse(result); + // #2991: handler returns { content: [{ type: 'text', text }] } (Pi's display shape), not a bare string. + assert.ok(result && Array.isArray(result.content) && result.content[0].type === 'text', + `/gsd handler must return Pi's display shape { content: [{ type: 'text', text }] }; got: ${JSON.stringify(result).slice(0, 200)}`); + const parsed = JSON.parse(result.content[0].text); assert.equal(typeof parsed.percent, 'number', '/gsd dispatch reached gsd-tools.cjs for real (the engine was reached)'); } finally { cleanup(dir); } }); -test('REACHABILITY: an unknown family surfaces a clear GSD error string, not a throw', async () => { +test('REACHABILITY: an unknown family surfaces a clear GSD error, not a throw', async () => { const pi = mockPi(); gsdPiExtension(pi); const dir = createTempDir(); try { const result = await pi._recorded.commands['gsd'].handler('no-such-family-8675309', { cwd: dir }); - assert.equal(typeof result, 'string'); - assert.match(result, /GSD error:/); - assert.match(result, /no-such-family-8675309|Unknown command/); + // #2991: handler returns { content: [{ type: 'text', text }] } (Pi's display shape). + assert.ok(result && Array.isArray(result.content) && result.content[0].type === 'text', + `error result must carry Pi's display shape; got: ${JSON.stringify(result).slice(0, 200)}`); + const text = result.content[0].text; + assert.match(text, /GSD error:/); + assert.match(text, /no-such-family-8675309|Unknown command/); } finally { cleanup(dir); }