@akira-tl/forgerelay 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +8 -7
- package/capabilities/shell-processes/GUIDE.md +3 -1
- package/dist/cli/setup-support.js +0 -15
- package/dist/cli/shell/setup.js +4 -1
- package/dist/cli.js +11 -3
- package/dist/mcp/process/process-platform.js +4 -0
- package/dist/mcp/process/process-sessions.js +1 -1
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +18 -3
- package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +26 -0
- package/dist/mcp/server/workspace/runtime/workspace-open.js +7 -0
- package/dist/mcp/server-instructions.js +23 -1
- package/dist/runtime/shell/command-shell-runtime.js +3 -1
- package/dist/server.js +24 -6
- package/dist/ui/.vite/manifest.json +275 -275
- package/dist/ui/activity-panel-app.html +1 -1
- package/dist/ui/assets/{activity-panel-app-BkODhDLg.js → activity-panel-app-XlqSanXT.js} +1 -1
- package/dist/ui/assets/{angular-html-DjuELMdF.js → angular-html-tB9EchbM.js} +1 -1
- package/dist/ui/assets/{angular-ts-2EXVZIIX.js → angular-ts-BearSZDb.js} +1 -1
- package/dist/ui/assets/{apl-Bb4IeT5V.js → apl-CYQtr-y6.js} +1 -1
- package/dist/ui/assets/{astro-Bjavu3nT.js → astro-DDzqKIjK.js} +1 -1
- package/dist/ui/assets/{blade-DKW2uHZo.js → blade-DkwEyHvQ.js} +1 -1
- package/dist/ui/assets/{c-oLeHmzbj.js → c-Dv-N5hjn.js} +1 -1
- package/dist/ui/assets/{cobol-CcqFMr5T.js → cobol-xBn5JxHE.js} +1 -1
- package/dist/ui/assets/{coffee-BA20zqRF.js → coffee-BaXXvoJw.js} +1 -1
- package/dist/ui/assets/{cpp-D750WmDa.js → cpp-C3unXX-3.js} +1 -1
- package/dist/ui/assets/{crystal-Cv5I6OMI.js → crystal-DjOpXVU1.js} +1 -1
- package/dist/ui/assets/{css-CjPPWPeH.js → css-8Xr4kNNH.js} +1 -1
- package/dist/ui/assets/{edge-aYRuudEZ.js → edge-DNaRizoR.js} +1 -1
- package/dist/ui/assets/{elixir-ChLitmz7.js → elixir-CwBMwZmy.js} +1 -1
- package/dist/ui/assets/{elm-Fs7L7u36.js → elm-gOUD2CW3.js} +1 -1
- package/dist/ui/assets/{erb-B-qXHxN1.js → erb-BcR-fjUp.js} +1 -1
- package/dist/ui/assets/{git-rebase-DOVN5OEg.js → git-rebase-BAQPKHF6.js} +1 -1
- package/dist/ui/assets/{glimmer-js-BFt_vCTW.js → glimmer-js-P_wOfSz_.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-ufCHlR6C.js → glimmer-ts-B44aY1fZ.js} +1 -1
- package/dist/ui/assets/{glsl-Ex4JXGuC.js → glsl-BvCQg9MI.js} +1 -1
- package/dist/ui/assets/{graphql-pQPXtBk5.js → graphql-C9S90gn_.js} +1 -1
- package/dist/ui/assets/{hack-B5hDguhd.js → hack-ddU2k0nt.js} +1 -1
- package/dist/ui/assets/{haml-BrNeI29U.js → haml-Bb1w9rMG.js} +1 -1
- package/dist/ui/assets/{handlebars-DCqTsL9-.js → handlebars-BpzaUpmz.js} +1 -1
- package/dist/ui/assets/{heavy-payload-DsmmHgvt.js → heavy-payload-CJKqi8Ac.js} +3 -3
- package/dist/ui/assets/{html-D8k0CC9V.js → html-dUOs5pA9.js} +1 -1
- package/dist/ui/assets/{html-derivative-CC8flWmC.js → html-derivative-B2beVdmA.js} +1 -1
- package/dist/ui/assets/{http-BYnWqNep.js → http-iEskZy8U.js} +1 -1
- package/dist/ui/assets/{hurl-BND-z85s.js → hurl-BuUsLSKv.js} +1 -1
- package/dist/ui/assets/{java-Dug0oB9T.js → java-BFZCrORY.js} +1 -1
- package/dist/ui/assets/{javascript-oDJ7WITC.js → javascript-DaIQ0Zqj.js} +1 -1
- package/dist/ui/assets/{jinja-Cv0t_F1U.js → jinja-CRpkWwrH.js} +1 -1
- package/dist/ui/assets/{jison-Car3MF55.js → jison-BYfSnuHj.js} +1 -1
- package/dist/ui/assets/{json-CEViCsf4.js → json-BQC_Yp48.js} +1 -1
- package/dist/ui/assets/{jsx-BfZ0zgSH.js → jsx-Dd-CJRf9.js} +1 -1
- package/dist/ui/assets/{julia-CmX0b3Tn.js → julia-B8Rxy7vD.js} +1 -1
- package/dist/ui/assets/{just-BuugLCVk.js → just-DzhIWpFP.js} +1 -1
- package/dist/ui/assets/{latex-FpTXqO03.js → latex-CUNjUUL-.js} +1 -1
- package/dist/ui/assets/{liquid-B37OzUFu.js → liquid-hUg7nC61.js} +1 -1
- package/dist/ui/assets/{lua-BHjcb8oq.js → lua-BoZTiL2I.js} +1 -1
- package/dist/ui/assets/{marko-CApj_fsH.js → marko-CQmIkwMF.js} +1 -1
- package/dist/ui/assets/{mdc-Ed5vQWq8.js → mdc-CB-6dHYa.js} +1 -1
- package/dist/ui/assets/{nginx-u_rVEBox.js → nginx-CQORo_Bt.js} +1 -1
- package/dist/ui/assets/{nim-Ba9EdchT.js → nim-BNwBNzdn.js} +1 -1
- package/dist/ui/assets/{perl-BpU_6Wvz.js → perl-Cnz14fAJ.js} +1 -1
- package/dist/ui/assets/{php-bKabofky.js → php-YywDo63b.js} +1 -1
- package/dist/ui/assets/{pug-C_aLNkqe.js → pug-BwbJp4cM.js} +1 -1
- package/dist/ui/assets/{qml-D9VisnRy.js → qml-D2DY6dMH.js} +1 -1
- package/dist/ui/assets/{r-CYqB1sLx.js → r-B5nYsy-M.js} +1 -1
- package/dist/ui/assets/{razor-DNJKI8Rs.js → razor-pMTG7k41.js} +1 -1
- package/dist/ui/assets/{regexp-DHrTHXkz.js → regexp-oP0lbks_.js} +1 -1
- package/dist/ui/assets/{rst-CbNdSFHv.js → rst-CO_ndDm4.js} +1 -1
- package/dist/ui/assets/{ruby-FyKG-pkF.js → ruby-xzRn9gTh.js} +1 -1
- package/dist/ui/assets/{sas-BbSPDl3X.js → sas-CBdtFKLA.js} +1 -1
- package/dist/ui/assets/{scss-2nX9g6gD.js → scss-CEqgKQNf.js} +1 -1
- package/dist/ui/assets/{shellscript-CmON6x2x.js → shellscript-DzJJiWQC.js} +1 -1
- package/dist/ui/assets/{shellsession-DtqIGlAX.js → shellsession-BaZnm5rZ.js} +1 -1
- package/dist/ui/assets/{soy-BzUJMmJT.js → soy-IouG3xdB.js} +1 -1
- package/dist/ui/assets/{sql-CgVcFZmb.js → sql-DC2rdTzV.js} +1 -1
- package/dist/ui/assets/{stata-CNitlMot.js → stata-dgbkIZHP.js} +1 -1
- package/dist/ui/assets/{surrealql-BA8-xsRZ.js → surrealql-CBROnw_p.js} +1 -1
- package/dist/ui/assets/{svelte-El02yAxv.js → svelte-6r6BpX4-.js} +1 -1
- package/dist/ui/assets/{templ-DeUQ84aR.js → templ-BKD_PsbL.js} +1 -1
- package/dist/ui/assets/{tex-Cu-FqpPS.js → tex-CKY1bwHb.js} +1 -1
- package/dist/ui/assets/{ts-tags-X42N6TGA.js → ts-tags-BiYP5N_C.js} +1 -1
- package/dist/ui/assets/{tsx-C1aPp9rY.js → tsx-D2OMiPNF.js} +1 -1
- package/dist/ui/assets/{twig-Cj1Q4SQc.js → twig-BTe1oPFW.js} +1 -1
- package/dist/ui/assets/{typescript-Du9lVt1G.js → typescript-BGIABksB.js} +1 -1
- package/dist/ui/assets/{vue-_uMGbFv8.js → vue-CT0IiySh.js} +1 -1
- package/dist/ui/assets/{vue-html-D61naG-Q.js → vue-html-B-tXcbat.js} +1 -1
- package/dist/ui/assets/{vue-vine-CkFae4yV.js → vue-vine-C5ZyDCrs.js} +1 -1
- package/dist/ui/assets/{xml-Cbwsc6ws.js → xml-CfeBNlMP.js} +1 -1
- package/dist/ui/assets/{xsl-NyBx72XB.js → xsl-DvDSndNI.js} +1 -1
- package/dist/ui/assets/{yaml-Cgb6Uq9u.js → yaml-umbj2r1g.js} +1 -1
- package/dist/workspaces/presentation/workspace-presentation.js +22 -0
- package/dist/workspaces/relay/tests/test-support.js +24 -2
- package/dist/workspaces/relay/workspace-relay.js +6 -0
- package/docs/gotchas.md +1 -4
- package/docs/roadmap.md +3 -2
- package/package.json +4 -2
- package/scripts/ci/cmd-acceptance.mjs +295 -0
- package/scripts/ci/windows-product-acceptance.mjs +321 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable ForgeRelay changes are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.10.4] - 2026-09-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added execution-owned Command Shell Runtime identity across Relay and Composite Workspaces so mixed-platform and mixed-shell operations expose the shell of the ForgeRelay instance that actually owns execution.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Made Doctor, Agent/tool descriptions, Workspace UI, Instructions state, and product documentation consistently shell-aware while preserving the public Core `bash` tool name for Host compatibility.
|
|
16
|
+
- Made explicit non-Bash POSIX selections report their compatibility boundary instead of silently implying Bash semantics.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Completed real packaged Windows product acceptance for PowerShell 7, Windows PowerShell 5.1, and `cmd.exe`, including editable shell Instructions and elevated-startup protection, while hardening the acceptance harness against Windows native-module handle retention and host-specific fixture assumptions.
|
|
21
|
+
|
|
22
|
+
## [0.10.3] - 2026-09-06
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Added first-class `cmd.exe` execution for Agent commands and Hooks with explicit cmd shell identity, editable cmd Instructions, Windows-native quoting/escaping guidance, and package-level `.cmd` launcher acceptance.
|
|
27
|
+
- Added Windows cmd PTY/ConPTY lifecycle coverage for stdin, resize, background completion, timeout, interrupt, durable output, Unicode, exit codes, and child-process-tree cleanup without introducing a separate Windows Process Manager.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Preserved raw `cmd.exe /S /C` command-line quoting through node-pty by using the Windows pre-escaped CommandLine path for interactive cmd sessions, preventing ConPTY from rewriting quoted commands and exit semantics.
|
|
32
|
+
|
|
7
33
|
## [0.10.2] - 2026-09-06
|
|
8
34
|
|
|
9
35
|
### Added
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ on your machine.
|
|
|
25
25
|
|
|
26
26
|
## Quick start
|
|
27
27
|
|
|
28
|
-
ForgeRelay requires Node `>=22.19 <27`, npm, Git, and a Bash-
|
|
28
|
+
ForgeRelay requires Node `>=22.19 <27`, npm, Git, and a supported Command Shell Runtime. Bash is the primary POSIX compatibility target; zsh and POSIX sh can be selected explicitly. On Windows, PowerShell 7, Windows PowerShell 5.1, and cmd.exe are first-class native runtimes.
|
|
29
29
|
|
|
30
30
|
Install it globally:
|
|
31
31
|
|
|
@@ -139,7 +139,7 @@ bash({ workspaceId: "cws_...", member: "compute", command: "python train.py" })
|
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
ForgeRelay does not merge member filesystems, Git state, Hooks, Skills, processes,
|
|
142
|
-
or audit facts, and it never infers a member from the tool type or purpose text.
|
|
142
|
+
shell/platform identity, privilege state, or audit facts, and it never infers a member from the tool type or purpose text. `memberContext.executionContext` reports the selected member's actual execution platform and Command Shell Runtime; it overrides the Gateway shell identity for commands routed to that member.
|
|
143
143
|
The Composite Activity Panel presents member operations in one Host Turn while the
|
|
144
144
|
actual facts remain owned by the member Workspace. `close_workspace` on a Composite
|
|
145
145
|
Workspace now preserves the Composite identity and member topology as `closed`; a
|
|
@@ -293,11 +293,12 @@ See [Security Model](docs/security.md) for the full boundary and threat model.
|
|
|
293
293
|
|
|
294
294
|
| Platform | Status | Notes |
|
|
295
295
|
| --- | --- | --- |
|
|
296
|
-
| Linux | Supported |
|
|
297
|
-
| macOS | Supported |
|
|
298
|
-
| Windows with PowerShell 7 (`pwsh`) | Supported | Agent commands and
|
|
299
|
-
| Windows with
|
|
300
|
-
| Windows
|
|
296
|
+
| Linux | Supported | Bash is the primary POSIX compatibility target; zsh and POSIX sh are explicit native choices with shell-specific compatibility guidance. |
|
|
297
|
+
| macOS | Supported | Bash is the primary POSIX compatibility target; zsh and POSIX sh are explicit native choices with shell-specific compatibility guidance. |
|
|
298
|
+
| Windows with PowerShell 7 (`pwsh`) | Supported | Agent commands, Hooks, pipe/PTY execution, packaged launcher, and editable shell Instructions use one native `pwsh` runtime without loading the user profile. |
|
|
299
|
+
| Windows with Windows PowerShell 5.1 (`powershell.exe`) | Supported | Agent commands, Hooks, pipe/PTY execution, packaged launcher, and editable shell Instructions use the selected 5.1 runtime with 5.1-specific syntax guidance. |
|
|
300
|
+
| Windows with `cmd.exe` | Supported | Agent commands, Hooks, pipe/ConPTY execution, packaged `.cmd` launcher, and editable shell Instructions preserve cmd syntax and runtime identity. |
|
|
301
|
+
| Windows with Git Bash, WSL, MSYS2, or Cygwin Bash | Compatibility path | Bash remains available when the selected executable provides the expected Bash command language. |
|
|
301
302
|
|
|
302
303
|
You can check the local runtime with:
|
|
303
304
|
|
|
@@ -48,6 +48,8 @@ PTY 依赖 optional `node-pty`。缺少该依赖时 ForgeRelay 会明确报错
|
|
|
48
48
|
|
|
49
49
|
## Platform notes
|
|
50
50
|
|
|
51
|
-
ForgeRelay 在 Windows
|
|
51
|
+
ForgeRelay 在 Windows 上原生支持 PowerShell 7 (`pwsh`)、Windows PowerShell 5.1 (`powershell.exe`) 与 `cmd.exe` 的 Agent、Hook、pipe/PTY 生命周期和 packaged launcher。Agent 命令与 Hooks 始终复用已选择的 runtime,不会为了兼容性静默切换到 Bash、PowerShell 或 cmd;PowerShell runtime 不加载用户 profile。公共工具名仍保留为 `bash` 以兼容 Host contract,实际命令语言以 ForgeRelay 报告的 Command Shell Runtime 为准。Git Bash、WSL、MSYS2 或 Cygwin Bash 仍可作为 Bash 兼容路径。
|
|
52
|
+
|
|
53
|
+
Linux/macOS 以 Bash 作为主要 POSIX 兼容目标;zsh 与 POSIX sh 可以显式选择,并要求 Agent/Hook 使用对应语法。fish 可以被识别和配置用于显式兼容性测试,但本版本不启用 native fish execution,ForgeRelay 也不会静默改用 Bash。Relay / Composite 场景下,以 `open_workspace.executionContext` / `memberContext.executionContext` 中 Execution ForgeRelay 的 platform、shell 和 privilege 为准。用 `forgerelay doctor` 检查本机 shell identity、version、executable、compatibility boundary 与 Shell Instructions 状态。
|
|
52
54
|
|
|
53
55
|
Shell 可以作为用户开发任务的一部分修改普通项目文件,但始终受 ForgeRelay core mutation/safety contract 约束。涉及 privileged OS files、credentials、configuration 或外部硬件持久写入时,不要用本指南替代 core authorization/safety 规则。
|
|
@@ -185,18 +185,3 @@ export function checkGitAvailable() {
|
|
|
185
185
|
return `unavailable (${message})`;
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
-
export function checkBashShell() {
|
|
189
|
-
try {
|
|
190
|
-
const { execFileSync } = require("node:child_process");
|
|
191
|
-
const command = process.env.BASH?.trim() || "bash";
|
|
192
|
-
const version = execFileSync(command, ["--version"], {
|
|
193
|
-
encoding: "utf8",
|
|
194
|
-
windowsHide: true,
|
|
195
|
-
}).split(/\r?\n/, 1)[0]?.trim();
|
|
196
|
-
return version ? `${command} (${version})` : command;
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
200
|
-
return `unavailable (${message})`;
|
|
201
|
-
}
|
|
202
|
-
}
|
package/dist/cli/shell/setup.js
CHANGED
|
@@ -77,8 +77,11 @@ export function commandShellCompatibilityWarning(family) {
|
|
|
77
77
|
if (family === "zsh") {
|
|
78
78
|
return "zsh is supported as an explicit command runtime, but Bash remains ForgeRelay's primary POSIX compatibility target. Agent commands and Hooks must use zsh syntax.";
|
|
79
79
|
}
|
|
80
|
+
if (family === "sh") {
|
|
81
|
+
return "POSIX sh is supported as an explicit command runtime, but Bash remains ForgeRelay's primary POSIX compatibility target. Agent commands and Hooks must use portable sh syntax and must not assume Bash-only features.";
|
|
82
|
+
}
|
|
80
83
|
if (family === "fish") {
|
|
81
|
-
return "fish may be selected explicitly, but
|
|
84
|
+
return "fish may be selected explicitly for configuration/testing, but native fish command execution is not enabled in this release. ForgeRelay will not silently substitute Bash or another shell.";
|
|
82
85
|
}
|
|
83
86
|
return undefined;
|
|
84
87
|
}
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { readFile, rm } from "node:fs/promises";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join, resolve } from "node:path";
|
|
@@ -23,9 +23,10 @@ import { shutdownHttpServer } from "./mcp/server/transport/server-shutdown.js";
|
|
|
23
23
|
import { publicEndpointUrl } from "./mcp/oauth/public-url.js";
|
|
24
24
|
import { assertRuntimePrivilegeAllowed, detectRuntimePrivilege, elevatedRuntimeWarning, formatRuntimePrivilege, } from "./runtime/security/runtime-privilege.js";
|
|
25
25
|
import { formatCommandShellRuntime } from "./runtime/shell/command-shell-runtime.js";
|
|
26
|
+
import { commandShellCompatibilityWarning } from "./cli/shell/setup.js";
|
|
26
27
|
import { authenticateRemote, defaultRemoteAlias, isRemoteMcpUnauthorized, normalizeRemoteServiceTarget, refreshRemoteAuthentication, verifyRemoteMcp, } from "./workspaces/relay/auth/remote-auth.js";
|
|
27
28
|
import { defaultSshRouteAlias, parseSshRoute, readRemoteOwnerToken, withRemoteServiceEndpoint, } from "./workspaces/relay/transport/remote-transport.js";
|
|
28
|
-
import { assertSupportedNode,
|
|
29
|
+
import { assertSupportedNode, checkGitAvailable, checkSqliteNative, nodeVersionStatus, normalizeOptionalPublicBaseUrl, } from "./cli/setup-support.js";
|
|
29
30
|
const require = createRequire(import.meta.url);
|
|
30
31
|
async function main(argv) {
|
|
31
32
|
assertSupportedNode();
|
|
@@ -353,7 +354,6 @@ async function runDoctor() {
|
|
|
353
354
|
console.log(`Platform: ${process.platform} ${process.arch}`);
|
|
354
355
|
console.log(`Runtime privilege: ${formatRuntimePrivilege(detectRuntimePrivilege())}`);
|
|
355
356
|
console.log(`Git: ${checkGitAvailable()}`);
|
|
356
|
-
console.log(`Bash shell: ${checkBashShell()}`);
|
|
357
357
|
console.log(`SQLite native dependency: ${checkSqliteNative()}`);
|
|
358
358
|
try {
|
|
359
359
|
const config = loadConfig();
|
|
@@ -362,6 +362,14 @@ async function runDoctor() {
|
|
|
362
362
|
console.log(`Client-facing base URL: ${config.publicBaseUrl}`);
|
|
363
363
|
console.log(`Client-facing MCP URL: ${publicEndpointUrl(config.publicBaseUrl, "mcp").toString()}`);
|
|
364
364
|
console.log(`Command shell: ${formatCommandShellRuntime(config.commandShellRuntime)}`);
|
|
365
|
+
console.log(`Command shell executable: ${config.commandShellRuntime.executable}`);
|
|
366
|
+
console.log(`Command shell source: ${config.commandShellRuntime.source}`);
|
|
367
|
+
const shellCompatibilityWarning = commandShellCompatibilityWarning(config.commandShellRuntime.family);
|
|
368
|
+
console.log(`Command shell compatibility: ${shellCompatibilityWarning ?? "native supported runtime"}`);
|
|
369
|
+
console.log(`Shell Instructions: ${config.shellInstructionsEnabled ? "enabled" : "disabled"}` +
|
|
370
|
+
(config.shellInstructionPath
|
|
371
|
+
? ` (${config.shellInstructionPath}; ${existsSync(config.shellInstructionPath) ? "available" : "unavailable"})`
|
|
372
|
+
: " (not applicable)"));
|
|
365
373
|
console.log(`Tool mode: ${config.toolMode}`);
|
|
366
374
|
console.log(`Widgets: ${config.widgets}`);
|
|
367
375
|
console.log(`Trust proxy: ${config.proxyTrust === false ? "off" : config.proxyTrust.join(", ")}`);
|
|
@@ -27,6 +27,10 @@ export function resolveShellCommandForRuntime(command, runtime, options = {}) {
|
|
|
27
27
|
// cmd.exe /S applies special quote stripping, so the whole command must
|
|
28
28
|
// be wrapped even when the executable inside it is already quoted.
|
|
29
29
|
args: ["/d", "/s", "/c", `"${command}"`],
|
|
30
|
+
// node-pty accepts a pre-escaped Windows CommandLine string. Passing the
|
|
31
|
+
// cmd arguments as string[] makes node-pty escape embedded quotes using
|
|
32
|
+
// CommandLineToArgvW rules, which changes cmd.exe /S /C semantics.
|
|
33
|
+
...(options.interactive ? { ptyCommandLine: `/d /s /c "${command}"` } : {}),
|
|
30
34
|
windowsVerbatimArguments: true,
|
|
31
35
|
};
|
|
32
36
|
case "bash":
|
|
@@ -470,7 +470,7 @@ export class ProcessManager {
|
|
|
470
470
|
throw new Error("PTY support requires the optional node-pty dependency.");
|
|
471
471
|
}
|
|
472
472
|
const shell = resolveShellCommandForRuntime(input.command, this.commandShellRuntime, { interactive: true });
|
|
473
|
-
const pty = nodePty.spawn(shell.executable, shell.args, {
|
|
473
|
+
const pty = nodePty.spawn(shell.executable, shell.ptyCommandLine ?? shell.args, {
|
|
474
474
|
cwd: input.cwd,
|
|
475
475
|
env: processEnvironment({
|
|
476
476
|
workspaceId: input.workspaceId,
|
|
@@ -9,6 +9,7 @@ import { formatUnavailableSubagentProvider } from "../../../../subagents/provide
|
|
|
9
9
|
import { capabilityContextFor } from "../../core/capability-support.js";
|
|
10
10
|
import { redactSkillDiagnosticPaths } from "../../core/schemas.js";
|
|
11
11
|
import { logToolCall, workspaceLogContext } from "../../core/tool-support.js";
|
|
12
|
+
import { buildExecutionShellContext } from "../../../server-instructions.js";
|
|
12
13
|
export const workspaceTaskUsageInstruction = "Use workspace.tasks proactively for work that spans multiple steps or sessions: create or update Tasks to preserve useful next steps and current state, and read its summary when resuming relevant unfinished work. Do not query it mechanically on every open_workspace call.";
|
|
13
14
|
export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
14
15
|
const { config, forgerelayVersion: FORGERELAY_VERSION, workspaces, workspaceTasks, reviewCheckpoints, capabilityRegistry, subagentProviders, hooks, rememberWorkspacePanelState, } = options;
|
|
@@ -75,6 +76,15 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
75
76
|
path: formatAgentsPath(instruction.path, workspace.root),
|
|
76
77
|
status: instruction.status,
|
|
77
78
|
}));
|
|
79
|
+
const rawShellInstruction = config.shellInstructionPath
|
|
80
|
+
? workspace.workspaceInstructions.find((instruction) => instruction.path === config.shellInstructionPath)
|
|
81
|
+
: undefined;
|
|
82
|
+
const executionContext = buildExecutionShellContext(config, rawShellInstruction
|
|
83
|
+
? {
|
|
84
|
+
path: formatAgentsPath(rawShellInstruction.path, workspace.root),
|
|
85
|
+
status: rawShellInstruction.status,
|
|
86
|
+
}
|
|
87
|
+
: undefined);
|
|
78
88
|
const bootstrapComponents = new Set(effectiveBootstrapContextComponents);
|
|
79
89
|
const visibleSkills = bootstrapComponents.has("skills") ? cardSkills : [];
|
|
80
90
|
const visibleSkillDiagnostics = bootstrapComponents.has("skillDiagnostics")
|
|
@@ -95,8 +105,8 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
95
105
|
: undefined,
|
|
96
106
|
].filter(Boolean).join(" ");
|
|
97
107
|
const cardInstruction = config.skillsEnabled
|
|
98
|
-
? `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches an available skill, load it with read(path=\"skills://<name>\") before proceeding. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction}`
|
|
99
|
-
: `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction}`;
|
|
108
|
+
? `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches an available skill, load it with read(path=\"skills://<name>\") before proceeding. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction} ${executionContext.agentInstruction}`
|
|
109
|
+
: `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction} ${executionContext.agentInstruction}`;
|
|
100
110
|
const instruction = workspaceReused
|
|
101
111
|
? effectiveIncludeBootstrapContext
|
|
102
112
|
? [
|
|
@@ -107,6 +117,7 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
107
117
|
: "Only Workspace context deltas are included in this response; unchanged bootstrap context is not repeated.",
|
|
108
118
|
workspaceContextInstruction,
|
|
109
119
|
workspaceManagementInstruction,
|
|
120
|
+
executionContext.agentInstruction,
|
|
110
121
|
].join("\n\n")
|
|
111
122
|
: [
|
|
112
123
|
`Workspace already open as ${workspace.id}.`,
|
|
@@ -114,9 +125,10 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
114
125
|
"Continue following the project instructions, nested instruction files, skills, capability guides, agent profiles, and diagnostics previously provided for this workspace. They remain active and are not repeated here.",
|
|
115
126
|
workspaceContextInstruction,
|
|
116
127
|
workspaceManagementInstruction,
|
|
128
|
+
executionContext.agentInstruction,
|
|
117
129
|
].join("\n\n")
|
|
118
130
|
: workspace.mode === "worktree"
|
|
119
|
-
? `Use this workspaceId for subsequent tool calls. Follow the project instructions, nested instruction files, skills, agent profiles, and diagnostics returned for this isolated worktree. ${workspaceManagementInstruction}`
|
|
131
|
+
? `Use this workspaceId for subsequent tool calls. Follow the project instructions, nested instruction files, skills, agent profiles, and diagnostics returned for this isolated worktree. ${workspaceManagementInstruction} ${executionContext.agentInstruction}`
|
|
120
132
|
: cardInstruction;
|
|
121
133
|
const resultContent = [
|
|
122
134
|
{
|
|
@@ -190,6 +202,7 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
190
202
|
agentsFiles: cardAgentsFiles,
|
|
191
203
|
availableAgentsFiles: cardAvailableAgentsFiles,
|
|
192
204
|
workspaceInstructions: cardWorkspaceInstructions,
|
|
205
|
+
executionContext,
|
|
193
206
|
skills: cardSkills,
|
|
194
207
|
agentProviders: cardAgentProviders,
|
|
195
208
|
agents: cardAgents,
|
|
@@ -226,6 +239,8 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
226
239
|
capabilityFingerprint,
|
|
227
240
|
contextFingerprint,
|
|
228
241
|
capabilityCatalog,
|
|
242
|
+
workspaceInstructions: cardWorkspaceInstructions,
|
|
243
|
+
executionContext,
|
|
229
244
|
...(bootstrapComponents.has("capabilityGuides")
|
|
230
245
|
? { capabilityGuides: visibleCapabilityGuides }
|
|
231
246
|
: {}),
|
|
@@ -150,6 +150,32 @@ export const openWorkspaceToolDefinition = {
|
|
|
150
150
|
capabilityFingerprint: capabilityFingerprintOutputSchema.optional(),
|
|
151
151
|
contextFingerprint: z.string().optional(),
|
|
152
152
|
capabilityCatalog: z.array(capabilityCatalogOutputSchema).optional(),
|
|
153
|
+
executionContext: z.object({
|
|
154
|
+
platform: z.string(),
|
|
155
|
+
commandShellRuntime: z.object({
|
|
156
|
+
family: z.enum(["bash", "zsh", "fish", "sh", "pwsh", "powershell", "cmd"]),
|
|
157
|
+
executable: z.string(),
|
|
158
|
+
source: z.enum(["explicit", "launcher", "recorded", "compatibility-default"]),
|
|
159
|
+
version: z.string().optional(),
|
|
160
|
+
capabilities: z.array(z.string()),
|
|
161
|
+
}),
|
|
162
|
+
runtimePrivilege: z.object({
|
|
163
|
+
level: z.enum(["standard", "elevated", "unknown"]),
|
|
164
|
+
platform: z.string(),
|
|
165
|
+
source: z.enum(["posix-euid", "windows-token", "unsupported"]),
|
|
166
|
+
detail: z.string().optional(),
|
|
167
|
+
}).optional(),
|
|
168
|
+
shellInstructions: z.object({
|
|
169
|
+
enabled: z.boolean(),
|
|
170
|
+
path: z.string().optional(),
|
|
171
|
+
status: z.enum(["loaded", "disabled", "unavailable"]).optional(),
|
|
172
|
+
}),
|
|
173
|
+
agentInstruction: z.string(),
|
|
174
|
+
}).optional(),
|
|
175
|
+
workspaceInstructions: z.array(z.object({
|
|
176
|
+
path: z.string(),
|
|
177
|
+
status: z.enum(["loaded", "disabled", "unavailable"]),
|
|
178
|
+
})).optional(),
|
|
153
179
|
capabilityGuides: z.array(capabilityGuideOutputSchema).optional(),
|
|
154
180
|
agentsFiles: z.array(workspaceAgentsFileOutputSchema).optional(),
|
|
155
181
|
availableAgentsFiles: z.array(workspaceAvailableAgentsFileOutputSchema).optional(),
|
|
@@ -402,6 +402,7 @@ async function handleOpenWorkspace(options, input, extra) {
|
|
|
402
402
|
? `Members: ${composite.members.map((member) => `${member.name} — ${member.purpose}`).join("; ")}.`
|
|
403
403
|
: "This Composite Workspace currently has no members.",
|
|
404
404
|
"Member names and purposes are structural context and are always returned when this Composite Workspace is opened. context=auto/full/none controls only heavy member bootstrap context, not this Composite identity.",
|
|
405
|
+
"Composite members may execute on different operating systems and command shells. Never treat one member's shell syntax or privilege level as a Composite-wide default; memberContext.executionContext is authoritative for the selected member and overrides the Gateway shell identity.",
|
|
405
406
|
compositeCapabilityCatalog.length > 0
|
|
406
407
|
? `Composite-owned capabilities: ${compositeCapabilityCatalog.map((entry) => entry.name).join(", ")}. Use these without member because their state belongs to the Composite Workspace itself.`
|
|
407
408
|
: undefined,
|
|
@@ -523,6 +524,12 @@ async function handleOpenWorkspace(options, input, extra) {
|
|
|
523
524
|
...(opened.capabilityCatalog !== undefined
|
|
524
525
|
? { capabilityCatalog: opened.capabilityCatalog }
|
|
525
526
|
: {}),
|
|
527
|
+
...(opened.executionContext !== undefined
|
|
528
|
+
? { executionContext: opened.executionContext }
|
|
529
|
+
: {}),
|
|
530
|
+
...(opened.workspaceInstructions !== undefined
|
|
531
|
+
? { workspaceInstructions: opened.workspaceInstructions }
|
|
532
|
+
: {}),
|
|
526
533
|
...(opened.capabilityGuides !== undefined
|
|
527
534
|
? { capabilityGuides: opened.capabilityGuides }
|
|
528
535
|
: {}),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { commandShellAgentInstruction } from "../runtime/shell/command-shell-runtime.js";
|
|
1
|
+
import { commandShellAgentInstruction, formatCommandShellRuntime, snapshotCommandShellRuntime, } from "../runtime/shell/command-shell-runtime.js";
|
|
2
2
|
export const toolNames = {
|
|
3
3
|
openWorkspace: "open_workspace",
|
|
4
4
|
closeWorkspace: "close_workspace",
|
|
@@ -11,6 +11,28 @@ export const toolNames = {
|
|
|
11
11
|
writeStdin: "write_stdin",
|
|
12
12
|
capability: "capability",
|
|
13
13
|
};
|
|
14
|
+
export function buildExecutionShellContext(config, shellInstruction) {
|
|
15
|
+
const platform = config.runtimePrivilege?.platform ?? process.platform;
|
|
16
|
+
const commandShellRuntime = snapshotCommandShellRuntime(config.commandShellRuntime);
|
|
17
|
+
const runtimePrivilege = config.runtimePrivilege
|
|
18
|
+
? { ...config.runtimePrivilege }
|
|
19
|
+
: undefined;
|
|
20
|
+
const scopeInstruction = [
|
|
21
|
+
`Execution Workspace runtime: platform=${platform}; shell=${formatCommandShellRuntime(commandShellRuntime)}.`,
|
|
22
|
+
"For commands routed to this Workspace or Composite member, this execution context overrides the Gateway ForgeRelay shell identity.",
|
|
23
|
+
].join(" ");
|
|
24
|
+
const agentInstruction = joinInstructions(scopeInstruction, commandShellAgentInstruction(commandShellRuntime), runtimePrivilegeInstructions(config));
|
|
25
|
+
return {
|
|
26
|
+
platform,
|
|
27
|
+
commandShellRuntime,
|
|
28
|
+
...(runtimePrivilege ? { runtimePrivilege } : {}),
|
|
29
|
+
shellInstructions: {
|
|
30
|
+
enabled: config.shellInstructionsEnabled,
|
|
31
|
+
...(shellInstruction ? shellInstruction : {}),
|
|
32
|
+
},
|
|
33
|
+
agentInstruction,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
14
36
|
export function buildShellMutationPolicy() {
|
|
15
37
|
return "Shell commands may modify ordinary project files when that is a natural part of the user's requested development task. They may also perform external device or hardware mutations when the user's current request explicitly asks for the actual device-changing operation, including firmware flashing or equivalent persistent device updates; do not infer such authorization from a check, audit, probe, backup, verification, dry-run, or build-only request. Never use shell commands to modify security- or privilege-sensitive operating-system files or credential material such as /etc/sudoers, /etc/passwd, /etc/shadow, PAM or authentication policy, SSH private keys, or equivalent privileged system files. Modify configuration files through shell only when the user's request explicitly calls for that configuration change; do not infer permission merely because changing configuration would be convenient.";
|
|
16
38
|
}
|
|
@@ -203,7 +203,9 @@ export function commandShellAgentInstruction(runtime) {
|
|
|
203
203
|
"Write shell commands for this runtime rather than assuming Bash syntax.",
|
|
204
204
|
...(runtime.family === "powershell"
|
|
205
205
|
? ["Windows PowerShell 5.1 does not support PowerShell 7 pipeline-chain operators && or ||."]
|
|
206
|
-
:
|
|
206
|
+
: runtime.family === "cmd"
|
|
207
|
+
? ["cmd.exe uses %NAME% environment expansion and %ERRORLEVEL% process status. Do not emit PowerShell or Bash syntax unless explicitly launching those shells as child processes."]
|
|
208
|
+
: []),
|
|
207
209
|
].join(" ");
|
|
208
210
|
}
|
|
209
211
|
function runtimeFromSelection(selection, source) {
|
package/dist/server.js
CHANGED
|
@@ -13,7 +13,7 @@ import { CodeIntelligenceError } from "./lsp/code-intelligence.js";
|
|
|
13
13
|
import { installManagedLanguageServers, installedManagedLanguageServers, supportedManagedLanguageServers, } from "./lsp/runtime/managed-language-servers.js";
|
|
14
14
|
import { HookRunner } from "./mcp/hooks/hooks.js";
|
|
15
15
|
import { checkHookConfiguration } from "./mcp/hooks/hook-cli.js";
|
|
16
|
-
import { buildServerInstructions, buildToolDescriptions, toolNames } from "./mcp/server-instructions.js";
|
|
16
|
+
import { buildExecutionShellContext, buildServerInstructions, buildToolDescriptions, toolNames } from "./mcp/server-instructions.js";
|
|
17
17
|
import { IncomingArtifactAdapterRegistry } from "./mcp/artifacts/incoming-artifacts.js";
|
|
18
18
|
import { registerProcessTools } from "./mcp/process/tools.js";
|
|
19
19
|
import { attachCompletedProcessNotices, recordBashCompletion } from "./mcp/process/runtime.js";
|
|
@@ -303,6 +303,19 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
303
303
|
artifactDownloadSupported: isArtifactDownloadSupportedPlatform(),
|
|
304
304
|
});
|
|
305
305
|
const capabilityCatalog = capabilityRegistry.catalog(capabilityContextFor(workspace));
|
|
306
|
+
const workspaceInstructions = workspace.workspaceInstructions.map((instruction) => ({
|
|
307
|
+
path: formatAgentsPath(instruction.path, workspace.root),
|
|
308
|
+
status: instruction.status,
|
|
309
|
+
}));
|
|
310
|
+
const rawShellInstruction = config.shellInstructionPath
|
|
311
|
+
? workspace.workspaceInstructions.find((instruction) => instruction.path === config.shellInstructionPath)
|
|
312
|
+
: undefined;
|
|
313
|
+
const executionContext = buildExecutionShellContext(config, rawShellInstruction
|
|
314
|
+
? {
|
|
315
|
+
path: formatAgentsPath(rawShellInstruction.path, workspace.root),
|
|
316
|
+
status: rawShellInstruction.status,
|
|
317
|
+
}
|
|
318
|
+
: undefined);
|
|
306
319
|
const agentsFiles = opened.agentsFiles.map((file) => ({
|
|
307
320
|
path: formatAgentsPath(file.path, workspace.root),
|
|
308
321
|
content: file.content,
|
|
@@ -338,6 +351,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
338
351
|
contextFingerprint: opened.contextFingerprint,
|
|
339
352
|
capabilityFingerprint,
|
|
340
353
|
capabilityCatalog,
|
|
354
|
+
workspaceInstructions,
|
|
355
|
+
executionContext,
|
|
341
356
|
includeBootstrapContext: opened.includeBootstrapContext,
|
|
342
357
|
...(bootstrapComponents.has("capabilityGuides") ? { capabilityGuides } : {}),
|
|
343
358
|
...(bootstrapComponents.has("agentsFiles") ? { agentsFiles } : {}),
|
|
@@ -347,11 +362,14 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
347
362
|
...(bootstrapComponents.has("skillDiagnostics")
|
|
348
363
|
? { skillDiagnostics: redactSkillDiagnosticPaths(workspace.skillDiagnostics) }
|
|
349
364
|
: {}),
|
|
350
|
-
instruction:
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
365
|
+
instruction: [
|
|
366
|
+
opened.includeBootstrapContext
|
|
367
|
+
? `Bootstrap context for Composite member ${memberName}. Keep using Composite workspaceId ${compositeWorkspaceId} and pass member=${memberName} for work operations.`
|
|
368
|
+
: contextPolicy === "none"
|
|
369
|
+
? `Bootstrap context for Composite member ${memberName} was intentionally suppressed by context=none. Keep using Composite workspaceId ${compositeWorkspaceId} with member=${memberName}; request context=auto or context=full when member bootstrap is needed.`
|
|
370
|
+
: `Composite member ${memberName} context was already delivered for this Host context; keep using Composite workspaceId ${compositeWorkspaceId} with member=${memberName}.`,
|
|
371
|
+
executionContext.agentInstruction,
|
|
372
|
+
].join(" "),
|
|
355
373
|
};
|
|
356
374
|
};
|
|
357
375
|
const operationRuntime = createOperationRuntime({
|