Merge pull request #58 from ongspxm/feat_addtermflag

feat(terminals): set PI WEB shell environment
This commit is contained in:
Federico Jaramillo Martinez
2026-07-14 23:58:52 +02:00
committed by GitHub
3 changed files with 75 additions and 37 deletions
@@ -0,0 +1,5 @@
---
"@jmfederico/pi-web": patch
---
Set `PI_WEB_TERMINAL=1` in PI WEB terminal shells.
+68 -35
View File
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest"; import { afterEach, beforeEach, describe, expect, it } from "vitest";
import type { RealtimeEvent, TerminalInfo } from "../../shared/apiTypes.js"; import type { RealtimeEvent, TerminalInfo } from "../../shared/apiTypes.js";
import type { WorkspaceActivityService } from "../activity/workspaceActivityService.js"; import type { WorkspaceActivityService } from "../activity/workspaceActivityService.js";
import { SessionEventHub } from "../realtime/sessionEventHub.js"; import { SessionEventHub } from "../realtime/sessionEventHub.js";
@@ -22,6 +22,73 @@ describe.skipIf(process.platform === "win32")("TerminalService command runs", ()
} }
}); });
describe("PI_WEB_TERMINAL propagation", () => {
let originalPiWebTerminal: string | undefined;
beforeEach(() => {
originalPiWebTerminal = process.env["PI_WEB_TERMINAL"];
process.env["PI_WEB_TERMINAL"] = "conflicting-parent-value";
});
afterEach(() => {
if (originalPiWebTerminal === undefined) {
delete process.env["PI_WEB_TERMINAL"];
} else {
process.env["PI_WEB_TERMINAL"] = originalPiWebTerminal;
}
});
it("sets PI_WEB_TERMINAL for terminal commands", async () => {
const service = new TerminalService();
try {
const frame = "__PI_WEB_RUN_ENV_7F3A9C__";
const run = service.runCommand({
origin: "core",
projectId: "p1",
workspaceId: "w1",
cwd: process.cwd(),
title: "Environment check",
command: `printf '${frame}%s${frame}\\n' "$PI_WEB_TERMINAL"`,
});
expect(await terminalExit(service, run.terminalId)).toContain(`${frame}1${frame}`);
} finally {
service.dispose();
}
});
it("sets PI_WEB_TERMINAL in a continued interactive shell", async () => {
const service = new TerminalService();
try {
const run = service.runCommand({
origin: "core",
projectId: "p1",
workspaceId: "w1",
cwd: process.cwd(),
title: "Done command",
command: "true",
});
await terminalExit(service, run.terminalId);
const continued = service.continue(run.terminalId);
expect(continued).toMatchObject({ id: run.terminalId, exited: false });
expect(continued.commandRunId).toBeUndefined();
expect(service.get(run.terminalId)?.commandRunId).toBeUndefined();
const frame = "__PI_WEB_CONTINUE_ENV_42D8B1__";
const exit = terminalExit(service, run.terminalId);
service.write(run.terminalId, `printf '${frame}%s${frame}\\n' "$PI_WEB_TERMINAL"\nexit\n`);
const output = await exit;
expect(output).toContain("[continued in interactive shell]");
expect(output).toContain(`${frame}1${frame}`);
} finally {
service.dispose();
}
});
});
it("tracks dedicated terminal command runs through completion", async () => { it("tracks dedicated terminal command runs through completion", async () => {
const service = new TerminalService(); const service = new TerminalService();
try { try {
@@ -50,30 +117,6 @@ describe.skipIf(process.platform === "win32")("TerminalService command runs", ()
} }
}); });
it("continues an exited command-run terminal as an interactive shell", async () => {
const service = new TerminalService();
try {
const run = service.runCommand({
origin: "core",
projectId: "p1",
workspaceId: "w1",
cwd: process.cwd(),
title: "Done command",
command: "true",
});
await terminalExit(service, run.terminalId);
const continued = service.continue(run.terminalId);
expect(continued).toMatchObject({ id: run.terminalId, exited: false });
expect(continued.commandRunId).toBeUndefined();
expect(service.get(run.terminalId)?.commandRunId).toBeUndefined();
expect(await terminalReplay(service, run.terminalId)).toContain("[continued in interactive shell]");
} finally {
service.dispose();
}
});
it("marks failed command runs when the command exits non-zero", async () => { it("marks failed command runs when the command exits non-zero", async () => {
const service = new TerminalService(); const service = new TerminalService();
try { try {
@@ -180,16 +223,6 @@ function requireTerminal(service: TerminalService, terminalId: string): Terminal
return terminal; return terminal;
} }
function terminalReplay(service: TerminalService, terminalId: string): Promise<string> {
let output = "";
const detach = service.attach(terminalId, {
output: (data) => { output += data; },
exit: () => undefined,
});
detach();
return Promise.resolve(output);
}
function terminalExit(service: TerminalService, terminalId: string): Promise<string> { function terminalExit(service: TerminalService, terminalId: string): Promise<string> {
const output: string[] = []; const output: string[] = [];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
+2 -2
View File
@@ -163,7 +163,7 @@ export class TerminalService {
cwd: record.cwd, cwd: record.cwd,
cols: 100, cols: 100,
rows: 30, rows: 30,
env: { ...process.env, TERM: "xterm-256color" }, env: { ...process.env, TERM: "xterm-256color", PI_WEB_TERMINAL: "1" },
}); });
this.attachPtyEvents(record); this.attachPtyEvents(record);
const info = toInfo(record); const info = toInfo(record);
@@ -196,7 +196,7 @@ export class TerminalService {
cwd: options.cwd, cwd: options.cwd,
cols: options.cols ?? 100, cols: options.cols ?? 100,
rows: options.rows ?? 30, rows: options.rows ?? 30,
env: { ...process.env, TERM: "xterm-256color" }, env: { ...process.env, TERM: "xterm-256color", PI_WEB_TERMINAL: "1" },
}); });
const requestedName = options.name?.trim(); const requestedName = options.name?.trim();
const record: TerminalRecord = { const record: TerminalRecord = {