From 73ac24c882e7c9daceff9442ac0afd662e8ed7f8 Mon Sep 17 00:00:00 2001 From: ongspxm Date: Tue, 14 Jul 2026 20:37:44 +0800 Subject: [PATCH 1/4] feat(terminals): set PI WEB shell environment --- .changeset/add-pi-web-terminal-environment.md | 5 +++++ package.json | 3 +++ src/server/terminals/terminalService.test.ts | 18 ++++++++++++++++++ src/server/terminals/terminalService.ts | 4 ++-- 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 .changeset/add-pi-web-terminal-environment.md diff --git a/.changeset/add-pi-web-terminal-environment.md b/.changeset/add-pi-web-terminal-environment.md new file mode 100644 index 00000000..5fd24f2e --- /dev/null +++ b/.changeset/add-pi-web-terminal-environment.md @@ -0,0 +1,5 @@ +--- +"@jmfederico/pi-web": patch +--- + +Set `IS_PIWEB=1` in PI WEB terminal shells. diff --git a/package.json b/package.json index e84997d4..67a68f91 100644 --- a/package.json +++ b/package.json @@ -140,5 +140,8 @@ "./extensions" ], "image": "https://raw.githubusercontent.com/jmfederico/pi-web/main/docs/assets/pi-web-banner.png" + }, + "allowScripts": { + "node-pty@1.1.0": true } } diff --git a/src/server/terminals/terminalService.test.ts b/src/server/terminals/terminalService.test.ts index 0b55d1ae..3b314cf8 100644 --- a/src/server/terminals/terminalService.test.ts +++ b/src/server/terminals/terminalService.test.ts @@ -22,6 +22,24 @@ describe.skipIf(process.platform === "win32")("TerminalService command runs", () } }); + it("sets IS_PIWEB for terminal commands", async () => { + const service = new TerminalService(); + try { + const run = service.runCommand({ + origin: "core", + projectId: "p1", + workspaceId: "w1", + cwd: process.cwd(), + title: "Environment check", + command: "printf '%s' \"$IS_PIWEB\"", + }); + + expect(await terminalExit(service, run.terminalId)).toContain("1"); + } finally { + service.dispose(); + } + }); + it("tracks dedicated terminal command runs through completion", async () => { const service = new TerminalService(); try { diff --git a/src/server/terminals/terminalService.ts b/src/server/terminals/terminalService.ts index 63204ffd..db26e348 100644 --- a/src/server/terminals/terminalService.ts +++ b/src/server/terminals/terminalService.ts @@ -163,7 +163,7 @@ export class TerminalService { cwd: record.cwd, cols: 100, rows: 30, - env: { ...process.env, TERM: "xterm-256color" }, + env: { ...process.env, TERM: "xterm-256color", IS_PIWEB: "1" }, }); this.attachPtyEvents(record); const info = toInfo(record); @@ -196,7 +196,7 @@ export class TerminalService { cwd: options.cwd, cols: options.cols ?? 100, rows: options.rows ?? 30, - env: { ...process.env, TERM: "xterm-256color" }, + env: { ...process.env, TERM: "xterm-256color", IS_PIWEB: "1" }, }); const requestedName = options.name?.trim(); const record: TerminalRecord = { From 037b7c61bbb02b548cd8bc96c806e5cc01a1d655 Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Tue, 14 Jul 2026 23:43:06 +0200 Subject: [PATCH 2/4] fix(package): remove ineffective node-pty script approval --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 67a68f91..e84997d4 100644 --- a/package.json +++ b/package.json @@ -140,8 +140,5 @@ "./extensions" ], "image": "https://raw.githubusercontent.com/jmfederico/pi-web/main/docs/assets/pi-web-banner.png" - }, - "allowScripts": { - "node-pty@1.1.0": true } } From 1c22558105c8acc20f64c1de1e9d931f4024cdbb Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Tue, 14 Jul 2026 23:48:30 +0200 Subject: [PATCH 3/4] test(terminals): cover IS_PIWEB spawn paths --- src/server/terminals/terminalService.test.ts | 117 +++++++++++-------- 1 file changed, 66 insertions(+), 51 deletions(-) diff --git a/src/server/terminals/terminalService.test.ts b/src/server/terminals/terminalService.test.ts index 3b314cf8..ca3adedf 100644 --- a/src/server/terminals/terminalService.test.ts +++ b/src/server/terminals/terminalService.test.ts @@ -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 { WorkspaceActivityService } from "../activity/workspaceActivityService.js"; import { SessionEventHub } from "../realtime/sessionEventHub.js"; @@ -22,22 +22,71 @@ describe.skipIf(process.platform === "win32")("TerminalService command runs", () } }); - it("sets IS_PIWEB for terminal commands", async () => { - const service = new TerminalService(); - try { - const run = service.runCommand({ - origin: "core", - projectId: "p1", - workspaceId: "w1", - cwd: process.cwd(), - title: "Environment check", - command: "printf '%s' \"$IS_PIWEB\"", - }); - - expect(await terminalExit(service, run.terminalId)).toContain("1"); - } finally { - service.dispose(); - } + describe("IS_PIWEB propagation", () => { + let originalIsPiWeb: string | undefined; + + beforeEach(() => { + originalIsPiWeb = process.env["IS_PIWEB"]; + process.env["IS_PIWEB"] = "conflicting-parent-value"; + }); + + afterEach(() => { + if (originalIsPiWeb === undefined) { + delete process.env["IS_PIWEB"]; + } else { + process.env["IS_PIWEB"] = originalIsPiWeb; + } + }); + + it("sets IS_PIWEB 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' "$IS_PIWEB"`, + }); + + expect(await terminalExit(service, run.terminalId)).toContain(`${frame}1${frame}`); + } finally { + service.dispose(); + } + }); + + it("sets IS_PIWEB 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' "$IS_PIWEB"\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 () => { @@ -68,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 () => { const service = new TerminalService(); try { @@ -198,16 +223,6 @@ function requireTerminal(service: TerminalService, terminalId: string): Terminal return terminal; } -function terminalReplay(service: TerminalService, terminalId: string): Promise { - 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 { const output: string[] = []; return new Promise((resolve, reject) => { From 8da7b29ba60f9b18a2bc879d35e665c9ec7428c5 Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Tue, 14 Jul 2026 23:56:24 +0200 Subject: [PATCH 4/4] fix(terminals): namespace terminal environment marker --- .changeset/add-pi-web-terminal-environment.md | 2 +- src/server/terminals/terminalService.test.ts | 22 +++++++++---------- src/server/terminals/terminalService.ts | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.changeset/add-pi-web-terminal-environment.md b/.changeset/add-pi-web-terminal-environment.md index 5fd24f2e..16df1e5b 100644 --- a/.changeset/add-pi-web-terminal-environment.md +++ b/.changeset/add-pi-web-terminal-environment.md @@ -2,4 +2,4 @@ "@jmfederico/pi-web": patch --- -Set `IS_PIWEB=1` in PI WEB terminal shells. +Set `PI_WEB_TERMINAL=1` in PI WEB terminal shells. diff --git a/src/server/terminals/terminalService.test.ts b/src/server/terminals/terminalService.test.ts index ca3adedf..75f3957b 100644 --- a/src/server/terminals/terminalService.test.ts +++ b/src/server/terminals/terminalService.test.ts @@ -22,23 +22,23 @@ describe.skipIf(process.platform === "win32")("TerminalService command runs", () } }); - describe("IS_PIWEB propagation", () => { - let originalIsPiWeb: string | undefined; + describe("PI_WEB_TERMINAL propagation", () => { + let originalPiWebTerminal: string | undefined; beforeEach(() => { - originalIsPiWeb = process.env["IS_PIWEB"]; - process.env["IS_PIWEB"] = "conflicting-parent-value"; + originalPiWebTerminal = process.env["PI_WEB_TERMINAL"]; + process.env["PI_WEB_TERMINAL"] = "conflicting-parent-value"; }); afterEach(() => { - if (originalIsPiWeb === undefined) { - delete process.env["IS_PIWEB"]; + if (originalPiWebTerminal === undefined) { + delete process.env["PI_WEB_TERMINAL"]; } else { - process.env["IS_PIWEB"] = originalIsPiWeb; + process.env["PI_WEB_TERMINAL"] = originalPiWebTerminal; } }); - it("sets IS_PIWEB for terminal commands", async () => { + it("sets PI_WEB_TERMINAL for terminal commands", async () => { const service = new TerminalService(); try { const frame = "__PI_WEB_RUN_ENV_7F3A9C__"; @@ -48,7 +48,7 @@ describe.skipIf(process.platform === "win32")("TerminalService command runs", () workspaceId: "w1", cwd: process.cwd(), title: "Environment check", - command: `printf '${frame}%s${frame}\\n' "$IS_PIWEB"`, + command: `printf '${frame}%s${frame}\\n' "$PI_WEB_TERMINAL"`, }); expect(await terminalExit(service, run.terminalId)).toContain(`${frame}1${frame}`); @@ -57,7 +57,7 @@ describe.skipIf(process.platform === "win32")("TerminalService command runs", () } }); - it("sets IS_PIWEB in a continued interactive shell", async () => { + it("sets PI_WEB_TERMINAL in a continued interactive shell", async () => { const service = new TerminalService(); try { const run = service.runCommand({ @@ -78,7 +78,7 @@ describe.skipIf(process.platform === "win32")("TerminalService command runs", () const frame = "__PI_WEB_CONTINUE_ENV_42D8B1__"; const exit = terminalExit(service, run.terminalId); - service.write(run.terminalId, `printf '${frame}%s${frame}\\n' "$IS_PIWEB"\nexit\n`); + 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]"); diff --git a/src/server/terminals/terminalService.ts b/src/server/terminals/terminalService.ts index db26e348..7186893e 100644 --- a/src/server/terminals/terminalService.ts +++ b/src/server/terminals/terminalService.ts @@ -163,7 +163,7 @@ export class TerminalService { cwd: record.cwd, cols: 100, rows: 30, - env: { ...process.env, TERM: "xterm-256color", IS_PIWEB: "1" }, + env: { ...process.env, TERM: "xterm-256color", PI_WEB_TERMINAL: "1" }, }); this.attachPtyEvents(record); const info = toInfo(record); @@ -196,7 +196,7 @@ export class TerminalService { cwd: options.cwd, cols: options.cols ?? 100, rows: options.rows ?? 30, - env: { ...process.env, TERM: "xterm-256color", IS_PIWEB: "1" }, + env: { ...process.env, TERM: "xterm-256color", PI_WEB_TERMINAL: "1" }, }); const requestedName = options.name?.trim(); const record: TerminalRecord = {