@sema-agent/server 1.293.0 → 1.294.0

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.
@@ -1,8 +1,10 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { hostShell, resolveHostShell } from "../plugins/host-platform.js";
2
3
  import { HooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_EVENT_OWNER, } from "@sema-agent/registry-core/hooks";
3
4
  import { redactSecrets } from "../trace/redact.js";
4
5
  import { ccPromptSystemFor, wrapCondition, parseCcVerdict, CC_EVALUATOR_MAX_OUTPUT_TOKENS } from "./cc-stop-prompt.js";
5
6
  import { renderBranchTranscript } from "./branch-transcript.js";
7
+ void resolveHostShell().catch(() => undefined);
6
8
  export const MAX_HOOK_ENTRIES_PER_EVENT = 32;
7
9
  export const MAX_HOOK_COMMAND_CHARS = 8_192;
8
10
  export const MAX_HOOK_TIMEOUT_SECONDS = 600;
@@ -144,7 +146,8 @@ function runCommandHook(entry, payload, ctx) {
144
146
  const timeoutMs = Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
145
147
  let child;
146
148
  try {
147
- child = spawn("/bin/sh", ["-c", entry.command], {
149
+ const sh = hostShell();
150
+ child = spawn(sh.shell, [...sh.args, entry.command], {
148
151
  cwd: ctx.cwd,
149
152
  env: hookEnv(ctx),
150
153
  stdio: ["pipe", "pipe", "pipe"],
@@ -203,7 +206,8 @@ function fireAsyncCommandHook(entry, payload, ctx, event) {
203
206
  try {
204
207
  const rewake = entry.asyncRewake === true;
205
208
  const timeoutMs = Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
206
- const child = spawn("/bin/sh", ["-c", entry.command], {
209
+ const sh = hostShell();
210
+ const child = spawn(sh.shell, [...sh.args, entry.command], {
207
211
  cwd: ctx.cwd,
208
212
  env: hookEnv(ctx),
209
213
  stdio: ["pipe", "ignore", rewake ? "pipe" : "ignore"],
@@ -1,10 +1,16 @@
1
1
  import { getShellConfig, killProcessTree, signalProcessTree } from "@sema-agent/core";
2
2
  export const IS_WIN32 = process.platform === "win32";
3
- const POSIX_SHELL = { shell: "/bin/sh", args: ["-c"] };
3
+ const POSIX_SH_FALLBACK = { shell: "/bin/sh", args: ["-c"] };
4
4
  let cachedWinShell;
5
+ let cachedPosixShell;
5
6
  export async function resolveHostShell() {
6
- if (!IS_WIN32)
7
- return POSIX_SHELL;
7
+ if (!IS_WIN32) {
8
+ if (cachedPosixShell)
9
+ return cachedPosixShell;
10
+ const posixCfg = await getShellConfig(process.env.HOST_SHELL_PATH || undefined);
11
+ cachedPosixShell = posixCfg.ok ? { shell: posixCfg.value.shell, args: posixCfg.value.args } : POSIX_SH_FALLBACK;
12
+ return cachedPosixShell;
13
+ }
8
14
  if (cachedWinShell)
9
15
  return cachedWinShell;
10
16
  const custom = process.env.HOST_SHELL_PATH;
@@ -18,14 +24,16 @@ export async function resolveHostShell() {
18
24
  return cachedWinShell;
19
25
  }
20
26
  export function hostShell() {
21
- if (!IS_WIN32)
22
- return POSIX_SHELL;
27
+ if (!IS_WIN32) {
28
+ return cachedPosixShell ?? POSIX_SH_FALLBACK;
29
+ }
23
30
  if (!cachedWinShell)
24
31
  throw new Error("host shell not resolved yet — resolveHostShell() must settle before any spawn (shellReady ordering bug)");
25
32
  return cachedWinShell;
26
33
  }
27
34
  export function resetHostShellCacheForTest() {
28
35
  cachedWinShell = undefined;
36
+ cachedPosixShell = undefined;
29
37
  }
30
38
  export function spawnGroupOptions() {
31
39
  return IS_WIN32 ? { detached: false, windowsHide: true } : { detached: true };
@@ -114,6 +114,9 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
114
114
  private execArgv;
115
115
  private execRaw;
116
116
  private docker;
117
+ private containerShell;
118
+ private containerShellProbed;
119
+ private probeContainerShell;
117
120
  private ensureConnectedExec;
118
121
  private fsReady;
119
122
  private tempResult;
@@ -480,7 +480,7 @@ export class RemoteLocalDockerExecutionEnv {
480
480
  const mergedEnv = { ...(this.cfg.env ?? {}), ...(env ?? {}) };
481
481
  for (const [k, v] of Object.entries(mergedEnv))
482
482
  args.push("-e", `${k}=${v}`);
483
- args.push(this.containerName, "/bin/sh", "-c", command);
483
+ args.push(this.containerName, this.containerShell, "-c", command);
484
484
  return args;
485
485
  }
486
486
  execRaw(command) {
@@ -547,14 +547,31 @@ export class RemoteLocalDockerExecutionEnv {
547
547
  });
548
548
  });
549
549
  }
550
+ containerShell = "/bin/sh";
551
+ containerShellProbed = false;
552
+ async probeContainerShell() {
553
+ if (this.containerShellProbed)
554
+ return;
555
+ this.containerShellProbed = true;
556
+ try {
557
+ const r = await this.docker(this.withHostFlag(["exec", this.containerName, "/bin/sh", "-c", "command -v bash"]), { timeoutMs: this.cfg.controlTimeoutMs });
558
+ if (r.ok && r.value.exitCode === 0 && r.value.stdout.trim().length > 0)
559
+ this.containerShell = "bash";
560
+ }
561
+ catch {
562
+ }
563
+ }
550
564
  async ensureConnectedExec() {
551
565
  if (this.destroyed)
552
566
  return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
553
- if (this.containerId)
567
+ if (this.containerId) {
568
+ await this.probeContainerShell();
554
569
  return ok(undefined);
570
+ }
555
571
  const c = await this.connect();
556
572
  if (!c.ok)
557
573
  return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}`, c.error) };
574
+ await this.probeContainerShell();
558
575
  return ok(undefined);
559
576
  }
560
577
  async fsReady(p) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "1.293.0",
3
+ "version": "1.294.0",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",