@akira-tl/forgerelay 0.10.3 → 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.
Files changed (94) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +8 -8
  3. package/capabilities/shell-processes/GUIDE.md +3 -1
  4. package/dist/cli/setup-support.js +0 -15
  5. package/dist/cli/shell/setup.js +4 -1
  6. package/dist/cli.js +11 -3
  7. package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +18 -3
  8. package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +26 -0
  9. package/dist/mcp/server/workspace/runtime/workspace-open.js +7 -0
  10. package/dist/mcp/server-instructions.js +23 -1
  11. package/dist/server.js +24 -6
  12. package/dist/ui/.vite/manifest.json +275 -275
  13. package/dist/ui/activity-panel-app.html +1 -1
  14. package/dist/ui/assets/{activity-panel-app-BkODhDLg.js → activity-panel-app-XlqSanXT.js} +1 -1
  15. package/dist/ui/assets/{angular-html-DjuELMdF.js → angular-html-tB9EchbM.js} +1 -1
  16. package/dist/ui/assets/{angular-ts-2EXVZIIX.js → angular-ts-BearSZDb.js} +1 -1
  17. package/dist/ui/assets/{apl-Bb4IeT5V.js → apl-CYQtr-y6.js} +1 -1
  18. package/dist/ui/assets/{astro-Bjavu3nT.js → astro-DDzqKIjK.js} +1 -1
  19. package/dist/ui/assets/{blade-DKW2uHZo.js → blade-DkwEyHvQ.js} +1 -1
  20. package/dist/ui/assets/{c-oLeHmzbj.js → c-Dv-N5hjn.js} +1 -1
  21. package/dist/ui/assets/{cobol-CcqFMr5T.js → cobol-xBn5JxHE.js} +1 -1
  22. package/dist/ui/assets/{coffee-BA20zqRF.js → coffee-BaXXvoJw.js} +1 -1
  23. package/dist/ui/assets/{cpp-D750WmDa.js → cpp-C3unXX-3.js} +1 -1
  24. package/dist/ui/assets/{crystal-Cv5I6OMI.js → crystal-DjOpXVU1.js} +1 -1
  25. package/dist/ui/assets/{css-CjPPWPeH.js → css-8Xr4kNNH.js} +1 -1
  26. package/dist/ui/assets/{edge-aYRuudEZ.js → edge-DNaRizoR.js} +1 -1
  27. package/dist/ui/assets/{elixir-ChLitmz7.js → elixir-CwBMwZmy.js} +1 -1
  28. package/dist/ui/assets/{elm-Fs7L7u36.js → elm-gOUD2CW3.js} +1 -1
  29. package/dist/ui/assets/{erb-B-qXHxN1.js → erb-BcR-fjUp.js} +1 -1
  30. package/dist/ui/assets/{git-rebase-DOVN5OEg.js → git-rebase-BAQPKHF6.js} +1 -1
  31. package/dist/ui/assets/{glimmer-js-BFt_vCTW.js → glimmer-js-P_wOfSz_.js} +1 -1
  32. package/dist/ui/assets/{glimmer-ts-ufCHlR6C.js → glimmer-ts-B44aY1fZ.js} +1 -1
  33. package/dist/ui/assets/{glsl-Ex4JXGuC.js → glsl-BvCQg9MI.js} +1 -1
  34. package/dist/ui/assets/{graphql-pQPXtBk5.js → graphql-C9S90gn_.js} +1 -1
  35. package/dist/ui/assets/{hack-B5hDguhd.js → hack-ddU2k0nt.js} +1 -1
  36. package/dist/ui/assets/{haml-BrNeI29U.js → haml-Bb1w9rMG.js} +1 -1
  37. package/dist/ui/assets/{handlebars-DCqTsL9-.js → handlebars-BpzaUpmz.js} +1 -1
  38. package/dist/ui/assets/{heavy-payload-DsmmHgvt.js → heavy-payload-CJKqi8Ac.js} +3 -3
  39. package/dist/ui/assets/{html-D8k0CC9V.js → html-dUOs5pA9.js} +1 -1
  40. package/dist/ui/assets/{html-derivative-CC8flWmC.js → html-derivative-B2beVdmA.js} +1 -1
  41. package/dist/ui/assets/{http-BYnWqNep.js → http-iEskZy8U.js} +1 -1
  42. package/dist/ui/assets/{hurl-BND-z85s.js → hurl-BuUsLSKv.js} +1 -1
  43. package/dist/ui/assets/{java-Dug0oB9T.js → java-BFZCrORY.js} +1 -1
  44. package/dist/ui/assets/{javascript-oDJ7WITC.js → javascript-DaIQ0Zqj.js} +1 -1
  45. package/dist/ui/assets/{jinja-Cv0t_F1U.js → jinja-CRpkWwrH.js} +1 -1
  46. package/dist/ui/assets/{jison-Car3MF55.js → jison-BYfSnuHj.js} +1 -1
  47. package/dist/ui/assets/{json-CEViCsf4.js → json-BQC_Yp48.js} +1 -1
  48. package/dist/ui/assets/{jsx-BfZ0zgSH.js → jsx-Dd-CJRf9.js} +1 -1
  49. package/dist/ui/assets/{julia-CmX0b3Tn.js → julia-B8Rxy7vD.js} +1 -1
  50. package/dist/ui/assets/{just-BuugLCVk.js → just-DzhIWpFP.js} +1 -1
  51. package/dist/ui/assets/{latex-FpTXqO03.js → latex-CUNjUUL-.js} +1 -1
  52. package/dist/ui/assets/{liquid-B37OzUFu.js → liquid-hUg7nC61.js} +1 -1
  53. package/dist/ui/assets/{lua-BHjcb8oq.js → lua-BoZTiL2I.js} +1 -1
  54. package/dist/ui/assets/{marko-CApj_fsH.js → marko-CQmIkwMF.js} +1 -1
  55. package/dist/ui/assets/{mdc-Ed5vQWq8.js → mdc-CB-6dHYa.js} +1 -1
  56. package/dist/ui/assets/{nginx-u_rVEBox.js → nginx-CQORo_Bt.js} +1 -1
  57. package/dist/ui/assets/{nim-Ba9EdchT.js → nim-BNwBNzdn.js} +1 -1
  58. package/dist/ui/assets/{perl-BpU_6Wvz.js → perl-Cnz14fAJ.js} +1 -1
  59. package/dist/ui/assets/{php-bKabofky.js → php-YywDo63b.js} +1 -1
  60. package/dist/ui/assets/{pug-C_aLNkqe.js → pug-BwbJp4cM.js} +1 -1
  61. package/dist/ui/assets/{qml-D9VisnRy.js → qml-D2DY6dMH.js} +1 -1
  62. package/dist/ui/assets/{r-CYqB1sLx.js → r-B5nYsy-M.js} +1 -1
  63. package/dist/ui/assets/{razor-DNJKI8Rs.js → razor-pMTG7k41.js} +1 -1
  64. package/dist/ui/assets/{regexp-DHrTHXkz.js → regexp-oP0lbks_.js} +1 -1
  65. package/dist/ui/assets/{rst-CbNdSFHv.js → rst-CO_ndDm4.js} +1 -1
  66. package/dist/ui/assets/{ruby-FyKG-pkF.js → ruby-xzRn9gTh.js} +1 -1
  67. package/dist/ui/assets/{sas-BbSPDl3X.js → sas-CBdtFKLA.js} +1 -1
  68. package/dist/ui/assets/{scss-2nX9g6gD.js → scss-CEqgKQNf.js} +1 -1
  69. package/dist/ui/assets/{shellscript-CmON6x2x.js → shellscript-DzJJiWQC.js} +1 -1
  70. package/dist/ui/assets/{shellsession-DtqIGlAX.js → shellsession-BaZnm5rZ.js} +1 -1
  71. package/dist/ui/assets/{soy-BzUJMmJT.js → soy-IouG3xdB.js} +1 -1
  72. package/dist/ui/assets/{sql-CgVcFZmb.js → sql-DC2rdTzV.js} +1 -1
  73. package/dist/ui/assets/{stata-CNitlMot.js → stata-dgbkIZHP.js} +1 -1
  74. package/dist/ui/assets/{surrealql-BA8-xsRZ.js → surrealql-CBROnw_p.js} +1 -1
  75. package/dist/ui/assets/{svelte-El02yAxv.js → svelte-6r6BpX4-.js} +1 -1
  76. package/dist/ui/assets/{templ-DeUQ84aR.js → templ-BKD_PsbL.js} +1 -1
  77. package/dist/ui/assets/{tex-Cu-FqpPS.js → tex-CKY1bwHb.js} +1 -1
  78. package/dist/ui/assets/{ts-tags-X42N6TGA.js → ts-tags-BiYP5N_C.js} +1 -1
  79. package/dist/ui/assets/{tsx-C1aPp9rY.js → tsx-D2OMiPNF.js} +1 -1
  80. package/dist/ui/assets/{twig-Cj1Q4SQc.js → twig-BTe1oPFW.js} +1 -1
  81. package/dist/ui/assets/{typescript-Du9lVt1G.js → typescript-BGIABksB.js} +1 -1
  82. package/dist/ui/assets/{vue-_uMGbFv8.js → vue-CT0IiySh.js} +1 -1
  83. package/dist/ui/assets/{vue-html-D61naG-Q.js → vue-html-B-tXcbat.js} +1 -1
  84. package/dist/ui/assets/{vue-vine-CkFae4yV.js → vue-vine-C5ZyDCrs.js} +1 -1
  85. package/dist/ui/assets/{xml-Cbwsc6ws.js → xml-CfeBNlMP.js} +1 -1
  86. package/dist/ui/assets/{xsl-NyBx72XB.js → xsl-DvDSndNI.js} +1 -1
  87. package/dist/ui/assets/{yaml-Cgb6Uq9u.js → yaml-umbj2r1g.js} +1 -1
  88. package/dist/workspaces/presentation/workspace-presentation.js +22 -0
  89. package/dist/workspaces/relay/tests/test-support.js +24 -2
  90. package/dist/workspaces/relay/workspace-relay.js +6 -0
  91. package/docs/gotchas.md +1 -1
  92. package/docs/roadmap.md +3 -2
  93. package/package.json +2 -1
  94. package/scripts/ci/windows-product-acceptance.mjs +321 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ 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
+
7
22
  ## [0.10.3] - 2026-09-06
8
23
 
9
24
  ### 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-compatible shell.
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,12 +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 | Requires Node, npm, Git, and Bash. |
297
- | macOS | Supported | Requires Node, npm, Git, and Bash. |
298
- | Windows with PowerShell 7 (`pwsh`) | Supported | Agent commands, Hooks, and PTY execution use one native `pwsh` runtime without loading the user profile. |
299
- | Windows with Windows PowerShell 5.1 (`powershell.exe`) | Supported | Agent commands, Hooks, and PTY execution use the selected 5.1 runtime with 5.1-specific syntax guidance. |
300
- | Windows with `cmd.exe` | Supported for Agent commands and Hooks | ForgeRelay preserves cmd syntax and reports the selected runtime explicitly; PTY/packaged-launcher lifecycle coverage is completed separately. |
301
- | Windows with Git Bash, WSL, MSYS2, or Cygwin Bash | Supported | Bash remains available as a compatibility path. |
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. |
302
302
 
303
303
  You can check the local runtime with:
304
304
 
@@ -48,6 +48,8 @@ PTY 依赖 optional `node-pty`。缺少该依赖时 ForgeRelay 会明确报错
48
48
 
49
49
  ## Platform notes
50
50
 
51
- ForgeRelay 在 Windows 上原生支持 PowerShell 7 (`pwsh`)Windows PowerShell 5.1 (`powershell.exe`) Agent、Hook 和 PTY 执行,也支持 `cmd.exe` 作为 Agent 命令与 Hook 的一等 Command Shell Runtime。Agent 命令与 Hooks 始终复用已选择的 runtime,不会为了兼容性静默切换到 Bash 或 PowerShell;PowerShell runtime 不加载用户 profile。公共工具名仍保留为 `bash` 以兼容 Host contract,实际命令语言以 ForgeRelay 报告的 Command Shell Runtime 为准。`cmd.exe` 的 PTY/ConPTY 与 packaged `.cmd` launcher lifecycle 属于下一阶段验收,不应由本阶段的非 TTY 支持推断。Git Bash、WSL、MSYS2 或 Cygwin Bash 仍可作为 Bash 兼容路径。用 `forgerelay doctor` 检查当前 shell identity、version 与 executable。
51
+ ForgeRelay 在 Windows 上原生支持 PowerShell 7 (`pwsh`)Windows PowerShell 5.1 (`powershell.exe`) `cmd.exe` AgentHook、pipe/PTY 生命周期和 packaged launcher。Agent 命令与 Hooks 始终复用已选择的 runtime,不会为了兼容性静默切换到 Bash、PowerShellcmd;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
- }
@@ -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 ForgeRelay compatibility is less mature than Bash and native fish execution is not enabled until its runtime adapter is available. Agent commands and Hooks must use fish syntax when enabled.";
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, checkBashShell, checkGitAvailable, checkSqliteNative, nodeVersionStatus, normalizeOptionalPublicBaseUrl, } from "./cli/setup-support.js";
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(", ")}`);
@@ -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
  }
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: opened.includeBootstrapContext
351
- ? `Bootstrap context for Composite member ${memberName}. Keep using Composite workspaceId ${compositeWorkspaceId} and pass member=${memberName} for work operations.`
352
- : contextPolicy === "none"
353
- ? `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.`
354
- : `Composite member ${memberName} context was already delivered for this Host context; keep using Composite workspaceId ${compositeWorkspaceId} with member=${memberName}.`,
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({