From 2f98a20b5ff967c42fc2246e12b560431fa28a99 Mon Sep 17 00:00:00 2001 From: Kyle Mistele Date: Wed, 26 Aug 2026 20:43:59 -0700 Subject: [PATCH] feat(fold-agent): pass host environment to bash HumanLayer-Session: https://app.dev.codelayer.gg/sessions/01a04147-8de0-7e74-9371-1012b821fab4 --- packages/fold-agent/src/Tools/BashTool.ts | 6 +++- packages/fold-agent/src/Tools/CodingTools.ts | 6 ++-- .../fold-agent/test/Tools/BashTool.vi.test.ts | 36 ++++++++++++++++++- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/fold-agent/src/Tools/BashTool.ts b/packages/fold-agent/src/Tools/BashTool.ts index ea5b732..bc6be24 100644 --- a/packages/fold-agent/src/Tools/BashTool.ts +++ b/packages/fold-agent/src/Tools/BashTool.ts @@ -85,6 +85,8 @@ export type BashToolOptions = FsToolOptions & { readonly spillDir?: string /** Deterministic per-session output store. When absent, bash uses the legacy temp spill file. */ readonly outputStore?: OutputStoreService + /** Environment entries inherited by every Bash subprocess created by this tool. */ + readonly processEnvironment?: Readonly> } type AccumulatorState = { @@ -328,12 +330,14 @@ export const bashTool = (options?: BashToolOptions): FoldTool => }) const run = Effect.gen(function* () { + const inheritedPath = options?.processEnvironment?.PATH ?? process.env.PATH ?? '' const handle = yield* spawner .spawn( ChildProcess.make('bash', ['-c', params.command], { cwd, env: { - PATH: `${pathService.join(homedir(), '.fold', 'bin')}:${process.env.PATH ?? ''}`, + ...options?.processEnvironment, + PATH: `${pathService.join(homedir(), '.fold', 'bin')}:${inheritedPath}`, }, extendEnv: true, }), diff --git a/packages/fold-agent/src/Tools/CodingTools.ts b/packages/fold-agent/src/Tools/CodingTools.ts index c83ce88..8edbba1 100644 --- a/packages/fold-agent/src/Tools/CodingTools.ts +++ b/packages/fold-agent/src/Tools/CodingTools.ts @@ -14,8 +14,10 @@ import { readTool } from './ReadTool' import { webTools, type WebToolsOptions } from './WebTools' import { writeTool } from './WriteTool' -/** Options for {@link codingTools}: the shared filesystem seam plus bash output-spill configuration. */ -export type CodingToolsOptions = FsToolOptions & Pick & WebToolsOptions +/** Options for {@link codingTools}: the shared filesystem seam plus Bash process configuration. */ +export type CodingToolsOptions = FsToolOptions & + Pick & + WebToolsOptions /** * The standard coding toolset: read, write, edit, apply_patch, bash, and web tools. The model-family policy decides diff --git a/packages/fold-agent/test/Tools/BashTool.vi.test.ts b/packages/fold-agent/test/Tools/BashTool.vi.test.ts index fc84fcb..b15ad2d 100644 --- a/packages/fold-agent/test/Tools/BashTool.vi.test.ts +++ b/packages/fold-agent/test/Tools/BashTool.vi.test.ts @@ -11,7 +11,7 @@ import { expect, it } from '@effect/vitest' import { SessionId, ToolCallId } from '@humanlayer/fold-core' import { Duration, Effect, Fiber } from 'effect' -import { bashTool, decodeBashOutputDelta, makeOutputStore, toolOutputPathFor } from '../../src/index' +import { bashTool, codingTools, decodeBashOutputDelta, makeOutputStore, toolOutputPathFor } from '../../src/index' import { handlerOf, makeAmbientServices, messageOf, outputOf, runHandler, tempDir } from '../TestHelpers' it.live('captures stdout and reports success on exit 0', () => @@ -23,6 +23,40 @@ it.live('captures stdout and reports success on exit 0', () => }), ) +it.live('passes host-provided session environment to Bash subprocesses', () => + Effect.gen(function* () { + const dir = yield* tempDir + const result = yield* runHandler( + handlerOf( + bashTool({ + cwd: dir, + processEnvironment: { + HUMANLAYER_SESSION_ID: 'session-from-host', + HUMANLAYER_SESSION_EXPIRES: '4102444800000', + }, + }), + )({ command: 'printf "%s:%s" "$HUMANLAYER_SESSION_ID" "$HUMANLAYER_SESSION_EXPIRES"' }), + ) + + expect(outputOf(result)).toBe('session-from-host:4102444800000') + }), +) + +it.live('forwards host-provided session environment through codingTools', () => + Effect.gen(function* () { + const dir = yield* tempDir + const bash = codingTools({ + cwd: dir, + processEnvironment: { HUMANLAYER_SESSION_ID: 'coding-tools-session' }, + }).find((tool) => tool.name === 'bash') + if (bash === undefined) throw new Error('expected codingTools to include Bash') + + const result = yield* runHandler(handlerOf(bash)({ command: 'printf "%s" "$HUMANLAYER_SESSION_ID"' })) + + expect(outputOf(result)).toBe('coding-tools-session') + }), +) + it.live('interleaves stderr into the same output buffer', () => Effect.gen(function* () { const dir = yield* tempDir