@sema-agent/server 7.127.0 → 7.127.1
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/MIGRATION.md
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.127.1 —— Windows 宿主车道:策略链读 `cdPath` 不再因 shell 未落定而拒每条 Bash(行为修复,升级零动作)
|
|
11
|
+
|
|
12
|
+
升级零动作:无 DDL、无配置变化、无 wire 变化。**谁受影响**:Windows 宿主部署里 server 进程环境找不到 Git Bash 的(PATH 无 Git、`ProgramFiles` 不对、经 ssh 起服白名单环境等)—— 7.127.0 及之前每条 Bash 都被策略链拒成「policy check threw / aborted」且真因被吞;本版起策略链读 `cdPath` 不再碰 shell,命令真要跑时才以真因拒(装 Git for Windows 或设 `HOST_SHELL_PATH`)。装了 Git Bash 的普通部署行为不变。回滚到 7.127.0 安全。
|
|
13
|
+
|
|
10
14
|
## 7.127.0 —— 引擎 7.50.2(精确钉)· 🔴 `tool_end.output` 带图:SSE 整块、账本只存描述符 · 新帧 `task_started`(SSE 与账本同在)· 🔴 e2b / k8s 车道不再挂 LSP · 记忆整理按人的路径规则判写(自动腿)· 附件请求键 400 · `~/…` 规则在无家目录车道上转问
|
|
11
15
|
|
|
12
16
|
1. **`tool_end.output` 两形(BREAKING,消费方)**:`output` 可以是串,也可以是块数组;块里可有图像 / 文档 `{ type, data, mimeType }`。
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
* pair from case-sensitive Object.assign reaches CreateProcess as ONE undefined-which
|
|
19
19
|
* entry). Canonical casing = the first-seen key (process.env's native casing wins since the
|
|
20
20
|
* inherit base is spread first).
|
|
21
|
+
* - layering = env COMPOSITION is a pure function of config + process.env and never reads the resolved shell;
|
|
22
|
+
* the shell belongs to the SPAWN layer only ({@link pinHostShell}, applied by the spawn sites after
|
|
23
|
+
* `shellReady()`). A read surface (`cdPath`) therefore never depends on resolution having settled.
|
|
21
24
|
*
|
|
22
25
|
* ⚠️ The win32 branches are UNVERIFIED on a real machine until S5 (Windows CI runner) — design D5 discipline:
|
|
23
26
|
* structural tests only on mac/Linux; behavior-level bite happens on the first Windows-runner green.
|
|
@@ -39,10 +42,22 @@ export interface HostShell {
|
|
|
39
42
|
* read the cached value synchronously via {@link hostShell}.
|
|
40
43
|
*/
|
|
41
44
|
export declare function resolveHostShell(): Promise<HostShell>;
|
|
42
|
-
/** Synchronous read of the resolved shell — valid only AFTER {@link resolveHostShell} settled
|
|
43
|
-
* are all behind `shellReady()`, which awaits it).
|
|
44
|
-
*
|
|
45
|
+
/** Synchronous read of the resolved shell — a SPAWN-layer read, valid only AFTER {@link resolveHostShell} settled
|
|
46
|
+
* (the spawn sites are all behind `shellReady()`, which awaits it). It is never an env-composition or read-surface
|
|
47
|
+
* input (see {@link pinHostShell}). POSIX never needs the async step.
|
|
48
|
+
* win32, not settled: a resolve that FAILED throws that failure (the real cause + the way out, memoised above);
|
|
49
|
+
* only a resolve that never ran / is still in flight reaches the ordering sentence — on the spawn paths that arm
|
|
50
|
+
* is unreachable (they await the resolve first), so it stays as the loud guard against a future mis-ordered caller
|
|
51
|
+
* (a programming bug, not an operator condition — fail loud, not /bin/sh-on-windows). */
|
|
45
52
|
export declare function hostShell(): HostShell;
|
|
53
|
+
/**
|
|
54
|
+
* The SPAWN layer's one env edit: win32 pins `SHELL` to the resolved Git Bash (subshell scripts read `$SHELL`).
|
|
55
|
+
* Takes the {@link HostShell} the caller already read via {@link hostShell} — so a spawn site cannot reach the pin
|
|
56
|
+
* without a resolved shell (the type is the ordering guard), and env COMPOSITION never needs one. Mutates and
|
|
57
|
+
* returns `env` (call it BEFORE {@link collapseWin32EnvKeys} so an inherited case-variant `Shell` folds into one
|
|
58
|
+
* entry). POSIX: identity — `SHELL` is whatever the composed env already carried (byte-identical to pre-Windows).
|
|
59
|
+
*/
|
|
60
|
+
export declare function pinHostShell(env: NodeJS.ProcessEnv, sh: HostShell): NodeJS.ProcessEnv;
|
|
46
61
|
/** test-only: reset the shell caches (each test can re-drive resolution). */
|
|
47
62
|
export declare function resetHostShellCacheForTest(): void;
|
|
48
63
|
/** Per-platform spawn options for a group-killable child (design D3).
|
|
@@ -4,6 +4,7 @@ export const IS_WIN32 = process.platform === "win32";
|
|
|
4
4
|
const POSIX_SH_FALLBACK = { shell: "/bin/sh", args: ["-c"] };
|
|
5
5
|
let cachedWinShell;
|
|
6
6
|
let cachedPosixShell;
|
|
7
|
+
let winShellFailure;
|
|
7
8
|
export async function resolveHostShell() {
|
|
8
9
|
if (!IS_WIN32) {
|
|
9
10
|
if (cachedPosixShell)
|
|
@@ -15,26 +16,38 @@ export async function resolveHostShell() {
|
|
|
15
16
|
if (cachedWinShell)
|
|
16
17
|
return cachedWinShell;
|
|
17
18
|
const custom = process.env.HOST_SHELL_PATH;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
try {
|
|
20
|
+
const cfg = await getShellConfig(custom || undefined);
|
|
21
|
+
if (!cfg.ok) {
|
|
22
|
+
throw new Error(`host lane needs a POSIX shell on Windows and none was found${custom ? ` (HOST_SHELL_PATH=${custom})` : ""}: ` +
|
|
23
|
+
`${cfg.error.message}. Install Git for Windows (https://gitforwindows.org) or set HOST_SHELL_PATH to a bash.exe. ` +
|
|
24
|
+
`Note: System32\\bash.exe is the WSL launcher, not a Windows bash — it is deliberately rejected.`);
|
|
25
|
+
}
|
|
26
|
+
cachedWinShell = { shell: cfg.value.shell, args: cfg.value.args };
|
|
27
|
+
return cachedWinShell;
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
winShellFailure = e instanceof Error ? e : new Error(String(e));
|
|
31
|
+
throw winShellFailure;
|
|
23
32
|
}
|
|
24
|
-
cachedWinShell = { shell: cfg.value.shell, args: cfg.value.args };
|
|
25
|
-
return cachedWinShell;
|
|
26
33
|
}
|
|
27
34
|
export function hostShell() {
|
|
28
35
|
if (!IS_WIN32) {
|
|
29
36
|
return cachedPosixShell ?? POSIX_SH_FALLBACK;
|
|
30
37
|
}
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
if (cachedWinShell)
|
|
39
|
+
return cachedWinShell;
|
|
40
|
+
throw winShellFailure ?? new Error("host shell not resolved yet — resolveHostShell() must settle before any spawn (shellReady ordering bug)");
|
|
41
|
+
}
|
|
42
|
+
export function pinHostShell(env, sh) {
|
|
43
|
+
if (IS_WIN32)
|
|
44
|
+
env.SHELL = sh.shell;
|
|
45
|
+
return env;
|
|
34
46
|
}
|
|
35
47
|
export function resetHostShellCacheForTest() {
|
|
36
48
|
cachedWinShell = undefined;
|
|
37
49
|
cachedPosixShell = undefined;
|
|
50
|
+
winShellFailure = undefined;
|
|
38
51
|
}
|
|
39
52
|
export function spawnGroupOptions() {
|
|
40
53
|
return IS_WIN32 ? { detached: false, windowsHide: true } : { detached: true };
|
|
@@ -96,8 +96,10 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
96
96
|
readonly homeDir?: string | undefined;
|
|
97
97
|
/**
|
|
98
98
|
* core 7.49.0 #1554:shell 子进程**真拿到的** `CDPATH` 条目(`[]` = 没有)—— 删除守卫按它解裸 `cd <dir>`。本车道的命令就是本机子进程,
|
|
99
|
-
* env 是本类自己组合的(继承姿态 + `cfg.env`),所以说得出:读同一只组合({@link childEnv}
|
|
100
|
-
* (缺席 / 空串 ⇒ `[]`,否则按 `:` 切)。逐命令的 `options.env` 由引擎自己交,它知道。
|
|
99
|
+
* env 是本类自己组合的(继承姿态 + `cfg.env`),所以说得出:读同一只组合({@link childEnv} 的**读面**,不披露、不钉 shell),切法与 core
|
|
100
|
+
* `NodeExecutionEnv` 同(缺席 / 空串 ⇒ `[]`,否则按 `:` 切)。逐命令的 `options.env` 由引擎自己交,它知道。
|
|
101
|
+
* 🔴 读面**不依赖 shell 落定**:core 判 Bash 的策略链在任何 exec 之前就读它(shell 解析在 exec 路径上才发生),所以这里不能碰
|
|
102
|
+
* {@link hostShell}(win32 上未落定同步抛 —— 修前每条 Bash 的策略链都因此被拒)。
|
|
101
103
|
*/
|
|
102
104
|
get cdPath(): readonly string[];
|
|
103
105
|
private readonly workspaceDir;
|
|
@@ -422,11 +424,16 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
422
424
|
* source of the shape rule) minus the config catalog's `secret`-typed names (`scrubServerSecrets`, 7.99.0 F7). `"all"` = full inherit (opt-out); a `string[]` = strict allowlist. Legacy `inheritProcessEnv`
|
|
423
425
|
* maps `true`→`"all"` / `false`→`[]`. */
|
|
424
426
|
private inheritMode;
|
|
425
|
-
/** 每次 spawn 交给子进程的 env(组合 + 剥除披露)
|
|
427
|
+
/** 每次 spawn 交给子进程的 env(组合 + 剥除披露 + win32 的 SHELL 钉)。`sh` = 调用点刚从 {@link hostShell} 读到的那只(spawn 路径
|
|
428
|
+
* 都在 `shellReady()` 之后):没有已落定的 shell 就到不了这条腿 —— 类型即次序守卫。 */
|
|
426
429
|
private mergeEnv;
|
|
427
430
|
/**
|
|
428
|
-
* 子进程 env
|
|
429
|
-
*
|
|
431
|
+
* 子进程 env 的**唯一组合**,分两层(一条律:**env 组合是纯函数,shell 只属于 spawn**):
|
|
432
|
+
* · **组合层**(恒有)—— 继承姿态 + `cfg.env` + perCommand + win32 大小写折叠。**不碰 shell**,所以读面(如 {@link cdPath})任何时候
|
|
433
|
+
* 都能读,不依赖 shell 是否已落定。
|
|
434
|
+
* · **spawn 层**(`spawnShell` 在场)—— 这一次是真要交给子进程:scrub 腿经 `hostSubprocessEnv` 记剥除(`spawnShell` 缺席 ⇒ 只读组合,
|
|
435
|
+
* 不计数:一次没发生的 spawn 不进剥除账),win32 上把 `SHELL` 钉到已解析的 Git Bash({@link pinHostShell},在大小写折叠**之前**)。
|
|
436
|
+
* 披露与钉同一个开关(`spawnShell`)—— 「真 spawn」只有一种说法,不再有 `disclose` 与钉各自一个布尔。
|
|
430
437
|
*/
|
|
431
438
|
private childEnv;
|
|
432
439
|
private fileError;
|
|
@@ -7,7 +7,7 @@ import { openSync, closeSync, readSync, statSync, truncateSync, unlinkSync, mkdi
|
|
|
7
7
|
import { StringDecoder } from "node:string_decoder";
|
|
8
8
|
import { armPipeDestroyGrace, numEnvOr, SYMLINK_CHAIN_MAX_HOPS } from "./remote-shell.js";
|
|
9
9
|
import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
|
|
10
|
-
import { resolveHostShell, hostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
|
|
10
|
+
import { resolveHostShell, hostShell, pinHostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
|
|
11
11
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
12
12
|
import { FileError, NodeExecutionEnv, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
|
|
13
13
|
import { allowlistedEnv, composeHostSubprocessEnv, hostSubprocessEnv } from "../server-secret-env.js";
|
|
@@ -58,7 +58,7 @@ export class RemoteHostExecutionEnv {
|
|
|
58
58
|
cwd;
|
|
59
59
|
homeDir;
|
|
60
60
|
get cdPath() {
|
|
61
|
-
const value = this.childEnv(undefined,
|
|
61
|
+
const value = this.childEnv(undefined, undefined).CDPATH;
|
|
62
62
|
return value === undefined || value === "" ? [] : value.split(":");
|
|
63
63
|
}
|
|
64
64
|
workspaceDir;
|
|
@@ -233,7 +233,7 @@ export class RemoteHostExecutionEnv {
|
|
|
233
233
|
const sh = hostShell();
|
|
234
234
|
child = spawn(sh.shell, [...sh.args, command], {
|
|
235
235
|
cwd: self.resolve(options?.cwd ?? self.cwd),
|
|
236
|
-
env: self.mergeEnv(options?.env),
|
|
236
|
+
env: self.mergeEnv(options?.env, sh),
|
|
237
237
|
...spawnGroupOptions(),
|
|
238
238
|
stdio: ["ignore", outFh.fd, errFh.fd],
|
|
239
239
|
});
|
|
@@ -412,7 +412,7 @@ export class RemoteHostExecutionEnv {
|
|
|
412
412
|
const sh = hostShell();
|
|
413
413
|
const child = spawn(sh.shell, [...sh.args, command], {
|
|
414
414
|
cwd,
|
|
415
|
-
env: this.mergeEnv(options?.env),
|
|
415
|
+
env: this.mergeEnv(options?.env, sh),
|
|
416
416
|
...spawnGroupOptions(),
|
|
417
417
|
});
|
|
418
418
|
const ticket = adoptHostForegroundChild(child);
|
|
@@ -672,7 +672,7 @@ export class RemoteHostExecutionEnv {
|
|
|
672
672
|
try {
|
|
673
673
|
child = spawn(sh.shell, [...sh.args, command], {
|
|
674
674
|
cwd,
|
|
675
|
-
env: this.mergeEnv(options?.env),
|
|
675
|
+
env: this.mergeEnv(options?.env, sh),
|
|
676
676
|
...spawnGroupOptions(),
|
|
677
677
|
stdio: ["ignore", spool.fds[0], spool.fds[1]],
|
|
678
678
|
});
|
|
@@ -814,7 +814,7 @@ export class RemoteHostExecutionEnv {
|
|
|
814
814
|
let wake;
|
|
815
815
|
const signalReady = () => { const w = wake; wake = undefined; w?.(); };
|
|
816
816
|
const sh = hostShell();
|
|
817
|
-
const child = spawn(sh.shell, [...sh.args, command], { cwd, env: this.mergeEnv(options?.env), ...spawnGroupOptions() });
|
|
817
|
+
const child = spawn(sh.shell, [...sh.args, command], { cwd, env: this.mergeEnv(options?.env, sh), ...spawnGroupOptions() });
|
|
818
818
|
const ticket = adoptHostForegroundChild(child);
|
|
819
819
|
let pipeDestroyTimer;
|
|
820
820
|
const kill = () => {
|
|
@@ -1319,10 +1319,11 @@ export class RemoteHostExecutionEnv {
|
|
|
1319
1319
|
return "all";
|
|
1320
1320
|
return "scrub";
|
|
1321
1321
|
}
|
|
1322
|
-
mergeEnv(perCommand) {
|
|
1323
|
-
return this.childEnv(perCommand,
|
|
1322
|
+
mergeEnv(perCommand, sh) {
|
|
1323
|
+
return this.childEnv(perCommand, sh);
|
|
1324
1324
|
}
|
|
1325
|
-
childEnv(perCommand,
|
|
1325
|
+
childEnv(perCommand, spawnShell) {
|
|
1326
|
+
const disclose = spawnShell !== undefined;
|
|
1326
1327
|
const mode = this.inheritMode();
|
|
1327
1328
|
let base;
|
|
1328
1329
|
if (mode === "all")
|
|
@@ -1340,8 +1341,8 @@ export class RemoteHostExecutionEnv {
|
|
|
1340
1341
|
Object.assign(base, this.cfg.env);
|
|
1341
1342
|
if (perCommand)
|
|
1342
1343
|
Object.assign(base, perCommand);
|
|
1343
|
-
if (
|
|
1344
|
-
base
|
|
1344
|
+
if (spawnShell)
|
|
1345
|
+
pinHostShell(base, spawnShell);
|
|
1345
1346
|
return collapseWin32EnvKeys(base);
|
|
1346
1347
|
}
|
|
1347
1348
|
fileError(e, p, abortSignal) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.127.
|
|
3
|
+
"version": "7.127.1",
|
|
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",
|