@sema-agent/server 7.127.0 → 7.127.2
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 +31 -0
- package/USAGE.md +6 -2
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/plugins/host-platform.d.ts +18 -3
- package/dist/plugins/host-platform.js +23 -10
- package/dist/plugins/remote-env-host.d.ts +12 -5
- package/dist/plugins/remote-env-host.js +12 -11
- package/dist/trace/project.d.ts +3 -1
- package/dist/trace/project.js +2 -2
- package/dist/trace/self-minted-wire.d.ts +19 -0
- package/dist/trace/self-minted-wire.js +2 -0
- package/dist/trace/tool-output-media.d.ts +16 -8
- package/dist/trace/tool-output-media.js +44 -17
- package/package.json +1 -1
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,37 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.127.2 —— 账本行 details 卡的媒体字节不落账(行为修复)· 缺省部署自动整理被引擎拒(披露)· 7.127.0 节三条订正(升级零动作)
|
|
11
|
+
|
|
12
|
+
升级零动作:无 DDL、无配置变化。本节是对 7.127.0 节的三条订正(已发节正文只读,订正记在此处)。
|
|
13
|
+
|
|
14
|
+
1. **订正 7.127.0 节第 1 条「账本面只存描述符 … 这类客户端本版拿不到整图」(行为修复,持久数据面,升级零动作)**:7.127.0 / 7.127.1 上只有 `output` 的媒体块在账本面是描述符,
|
|
15
|
+
同一行里 Read 图像 / PDF 的 details 卡 `structured`(`{ type: "image" | "document", file: { base64?, … } }`)的 `file.base64`(引擎只在 ≤48K 字符时随卡)仍随账本行落库,并在
|
|
16
|
+
`GET /v1/runs/:id/events`、`GET /v1/tasks/:id/turns`、`GET /v1/tasks/:id/stream` 的回放上出现 —— 描述符想省的那份字节还在行里。本版起一条律:**媒体字节不落账,不论它骑在 `output` 的块上还是 `structured` 卡的字段上**。
|
|
17
|
+
- **账本面**:卡上去掉 `file.base64` 这一个键(其余成员逐字;与引擎对超 48K 的卡自己略去该键的形一致),存量行在读面上同样不再带;帧级 `truncated` / `totalChars` 仍只说 `output`。
|
|
18
|
+
- **活面**:`structured` 逐字(`file.base64` 不过文本脱敏,卡上其余成员照常脱敏);连接积压判据把它与 `output` 媒体块的字节同一帧一起计,降级时一并去掉。
|
|
19
|
+
- **谁需要**:读账本回放面的消费方与存账本的部署;**谁受伤**:从回放面的 `structured.file.base64` 取图的客户端(整图本来只在 `/v1/tasks/stream` 与子代 tail 流上);**补偿**:要图读活流;按「`file.base64` 缺席」渲染(引擎超 48K 的卡本就是这个形)。
|
|
20
|
+
2. **订正 7.127.0 节漏项:k8s 车道沙箱出网策略(安全面,部署;7.127.0 起已生效)**:7.127.0 的 CHANGELOG 段写了这一条,随包的本文件 7.127.0 节漏了。`REMOTE_EXEC=k8s` 的部署读下面几行:
|
|
21
|
+
- **出网边界**:沙箱 ns 的 egress 白名单对集群外只放行 **IPv4 公网面** —— 私网 / 共享 / 链路本地 / 环回六段(`10/8`、`172.16/12`、`192.168/16`、`100.64/10`、`169.254/16`、`127/8`)整段不放行,
|
|
22
|
+
公网面上的集群地址按 `clusterCidrs` 与 `nodeCidrs`(`kube-up.sh` 遍历**全部节点**的全部 IPv4 Internal / External 地址自动填;读不出 ⇒ 拒装)再除外。此前是一张点名表,只盖第一只节点的第一只 InternalIP。
|
|
23
|
+
- **IPv6**:不写放行规则(NetworkPolicy 只有放行规则,写 `::/0` 只会放宽);缺省 `K8S_SANDBOX_IPV6=enabled` **不关** pod 的 IPv6 —— 只执行 IPv4 的策略实现(k3s 单栈缺省即是)管不到 pod 的 IPv6,链路本地 v6 照通,
|
|
24
|
+
起服自检每次起服一行 `sandbox_egress_policy_gap{kind: "ipv6_enabled"}` 提醒。要在 pod 侧关,设 `K8S_SANDBOX_IPV6=disabled`(chart `sandbox.ipv6: disabled`;前提:**每个节点**的 kubelet 带 `--allowed-unsafe-sysctls` 放行
|
|
25
|
+
`net.ipv6.conf.all.disable_ipv6` 与 `net.ipv6.conf.default.disable_ipv6`,沙箱 ns 不 enforce PodSecurity `baseline` / `restricted`;没放行的节点上沙箱 pod 起不来)。
|
|
26
|
+
- **谁受伤**:沙箱要访问集群外**私网地址**上的包源 / 代码托管 / 对象存储的部署(升级后到不了);手写 values 在 `nodeCidrs` / `clusterCidrs` 里写了 IPv6 或不带前缀的项的部署(chart 渲染拒并点名);
|
|
27
|
+
手写 RBAC 的部署(chart 多两只只读授权:沙箱 ns 的 `networkpolicies` get / list,与集群级 `nodes` get / list —— 没给时起服自检一行 `unreadable`)。
|
|
28
|
+
- **补偿**:在沙箱 ns 另建一条 NetworkPolicy 放行那个私网地址(或那台主机的 /32)—— 策略按并集叠加,chart 升级不碰它;手写 RBAC 的部署补上面两只只读授权;没有上报进节点对象的公网地址(1:1 NAT 的弹性 IP 一类)要自己写进 `nodeCidrs`。
|
|
29
|
+
这张策略是收窄、不是边界:所在节点的往来流量由 CNI 的实现决定,宿主上被 DNAT 的发布端口在策略之前改写目的地址,CNI 不执行 NetworkPolicy 时整张不生效(射程见 USAGE §9.3e)。
|
|
30
|
+
3. **订正 7.127.0 节第 4 条「记忆整理按人的路径规则判写」(披露:引擎缺陷,本版行为不变)**:那一条在缺省部署上不成立 —— 只要部署的敏感路径守卫在场(`SENSITIVE_WRITE_PATTERNS` 未设即在场),
|
|
31
|
+
自动整理**每一次**都被拒(`memory.consolidation_refused`,`detail.refusalCode = memory.consolidation_write_refused`),与有没有点名库路径的规则、权限模式无关:引擎问写判官时递给部署策略的是一只已中止的信号,
|
|
32
|
+
守卫因此判不了库目录的真实落点、按 `target_unresolved` 拒。通告的人读句只带拒因的前 200 字,`detail` 没有原因成员。修复在引擎,随下一次引擎提货生效。
|
|
33
|
+
- **谁受影响**:开着自动整理的部署(缺省开)。用户任务不受影响,库不被改写。
|
|
34
|
+
- **做法**:要整理就用手动阀门 `POST /v1/admin/memory/consolidation/run`(它不问这只判官,与第 4 条所写同);按 `memory.consolidation_refused` 告警的,知道它今天是这个成因。
|
|
35
|
+
不要为此设 `SENSITIVE_WRITE_PATTERNS=off`(那会撤掉敏感路径的写拦截)。
|
|
36
|
+
|
|
37
|
+
## 7.127.1 —— Windows 宿主车道:策略链读 `cdPath` 不再因 shell 未落定而拒每条 Bash(行为修复,升级零动作)
|
|
38
|
+
|
|
39
|
+
升级零动作:无 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 安全。
|
|
40
|
+
|
|
10
41
|
## 7.127.0 —— 引擎 7.50.2(精确钉)· 🔴 `tool_end.output` 带图:SSE 整块、账本只存描述符 · 新帧 `task_started`(SSE 与账本同在)· 🔴 e2b / k8s 车道不再挂 LSP · 记忆整理按人的路径规则判写(自动腿)· 附件请求键 400 · `~/…` 规则在无家目录车道上转问
|
|
11
42
|
|
|
12
43
|
1. **`tool_end.output` 两形(BREAKING,消费方)**:`output` 可以是串,也可以是块数组;块里可有图像 / 文档 `{ type, data, mimeType }`。
|
package/USAGE.md
CHANGED
|
@@ -1641,8 +1641,11 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1641
1641
|
> - **`tool_end.output` 两形**:串,或块数组。块里可有图像 / 文档 `{ type, data, mimeType }` —— 本流上整块逐字(`data` 是字节编码,不过文本脱敏),
|
|
1642
1642
|
> 一帧最大约为部署媒体预算的 4/3(缺省约 40 MiB),请按块 `data` 长界帧;读得慢的连接(本帧媒体 + 还没送出去的字节 > 64 MiB)上本帧媒体降成描述符。
|
|
1643
1643
|
> 媒体块 `data` 的内容由引擎准入负责:引擎本版只对自己产出的块验字节(读图像 / PDF、提交的图像、用户附件),MCP 服务器与宿主自定义工具交回的块不验(引擎下一 minor 收);本服务不在 wire 上对 `data` 做任何改写。
|
|
1644
|
-
>
|
|
1645
|
-
> `{ type, mimeType?, bytes, truncated: true }`(帧级 `truncated: true` 与 `totalChars` = 原值真实大小 —— 文本块字符数 + 媒体块 `data` 串长,引擎已截断的帧保留引擎给的值);无 `data` 且 `truncated: true` 的块渲染成「未送达」。
|
|
1644
|
+
> 账本面不存媒体字节 —— 账本行与 `GET /v1/runs/:id/events`(断线重连面;对 `POST /v1/runs` 提交的异步 run 它**就是活流**,所以这类客户端拿不到整图,整图只在本流与子代 tail 流上)
|
|
1645
|
+
> 里,带 `data` 的媒体块换成 `{ type, mimeType?, bytes, truncated: true }`(帧级 `truncated: true` 与 `totalChars` = 原值真实大小 —— 文本块字符数 + 媒体块 `data` 串长,引擎已截断的帧保留引擎给的值);无 `data` 且 `truncated: true` 的块渲染成「未送达」。
|
|
1646
|
+
> **同一条律也管 `tool_end.structured`**:Read 读图像 / PDF 的卡 `{ type: "image" | "document", file: { base64?, type, originalSize, … } }` 里 `file.base64` 是同一份字节的第二份编码(引擎只在 ≤48K 字符时随卡),
|
|
1647
|
+
> 本流上同样逐字(不过文本脱敏,卡上其余成员照常脱敏),积压判据把它与 `output` 块的字节同一帧一起计、降级时一并去掉;账本面(账本行与 `/events`、`GET /v1/tasks/:id/turns`、`GET /v1/tasks/:id/stream` 的回放)里卡上**没有** `file.base64`
|
|
1648
|
+
> (与引擎对超 48K 的卡自己略去该键的形一致,`originalSize` / `type` 仍在)。7.127.0 / 7.127.1 上这份字节(≤48K 字符)仍随账本行落库并回放,7.127.2 起不带。
|
|
1646
1649
|
|
|
1647
1650
|
## 7. 其它接口(按需)
|
|
1648
1651
|
| 端点 | 用途 |
|
|
@@ -1781,6 +1784,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1781
1784
|
> - **自动整理**(任务结束按建议起跑)用这条腿自己的判官 —— 该 run 主体的人的持久路径规则 + 部署的调用方策略,与模型自己的 `Write` 同一套判:
|
|
1782
1785
|
> 规则点名库目录 ⇒ 在任何占位与模型调用之前拒,运维行 `engine_notice` 一条 `memory.consolidation_refused`(`detail.refusalCode: "memory.consolidation_write_refused"`);
|
|
1783
1786
|
> 只点名产物文件 ⇒ 应用时拒、什么都不写,一条 `memory.consolidation_incomplete`(`stop: "refused"`);用户任务不受影响。没有主体的 run 读不到任何人的持久规则(只剩部署策略那一层)。
|
|
1787
|
+
> ⚠️ **引擎 7.50.x 上这条腿在缺省部署上每一次都被拒**:只要部署的敏感路径守卫在场(`SENSITIVE_WRITE_PATTERNS` 未设即在场),引擎问写判官时递给部署策略的是一只已中止的信号,守卫判不了库目录的真实落点、按 `target_unresolved` 拒 —— 与规则、权限模式无关(`memory.consolidation_refused`,人读句只带拒因前 200 字)。修复随引擎提货;要整理用手动阀门,不要为此设 `SENSITIVE_WRITE_PATTERNS=off`(见 MIGRATION 7.127.2 第 3 条)。
|
|
1784
1788
|
> - **手动阀门**(`POST /v1/admin/memory/consolidation/run`)没有腿,拿不到引擎的那只判官;本服务不另造一只 ⇒ 它的写判官答「无异议」(与 7.126.0 该口行为逐字同),
|
|
1785
1789
|
> 每次被问记一次 fail-open 计数 `server.memory-consolidation.operator-valve-unjudged`(计数 + 探针行 + 一次性告警)。驱动的每一条通告 —— 非收敛停的 `memory.consolidation_incomplete`、
|
|
1786
1790
|
> 问判官**之前**推不出库家落点时同码拒的 `memory.consolidation_refused`(生产上可达,与判官答什么无关)—— 都经同一只 `engine_notice` 转发器进运维行;早问拒仍按路由的码臂答 `409 memory.consolidation_refused`。
|
|
@@ -41,7 +41,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
41
41
|
};
|
|
42
42
|
readonly "server.tool-end-media.live-backlog-degraded": {
|
|
43
43
|
readonly cls: "F";
|
|
44
|
-
readonly note: "core 7.50.0 媒体块提货(`trace/tool-output-media.ts` 的 `liveToolEndForBacklog`,三处活面写口共用的那一只判据 —— 同步流出口 `sseData`、子代 tail 路由写环、tail 总线每订阅者队列):本帧媒体字节 + 这条连接 / 这个订阅者还没送出去的字节 > 上界(64 MiB)⇒
|
|
44
|
+
readonly note: "core 7.50.0 媒体块提货(`trace/tool-output-media.ts` 的 `liveToolEndForBacklog`,三处活面写口共用的那一只判据 —— 同步流出口 `sseData`、子代 tail 路由写环、tail 总线每订阅者队列):本帧媒体字节(`output` 媒体块的 `data` 与 `structured` 媒体卡的 `file.base64` 一起计)+ 这条连接 / 这个订阅者还没送出去的字节 > 上界(64 MiB)⇒ 本帧媒体降成持久面那只形(媒体块换描述符 `{ type, mimeType?, bytes, truncated: true }`,帧级 `truncated` + `totalChars`;媒体卡去掉 `file.base64`)。每降一帧记一次,只在这只函数里记(三处写口不各记)。放行的最坏后果 = 一个读得慢的观众在 SSE 上看不到这张图,只见「这里有一张多大的什么图、没送达」(账本面本就恒是描述符),不是门、不是凭据、不改任何裁决;反方向(不降)让一条停读的连接随帧数线性压住几百 MiB。帧上它与引擎自己的媒体预算降级同形不可分,所以计数是运维唯一能看出「多少观众丢了图」的位;计数持续非零 = 有消费端读流太慢(或网络瓶颈),去看那几条连接。detail = `backlog=<待发字节> media=<本帧媒体字节>`。";
|
|
45
45
|
};
|
|
46
46
|
readonly "server.memory-consolidation.operator-valve-unjudged": {
|
|
47
47
|
readonly cls: "P-DEBT";
|
|
@@ -22,7 +22,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
22
22
|
},
|
|
23
23
|
"server.tool-end-media.live-backlog-degraded": {
|
|
24
24
|
cls: "F",
|
|
25
|
-
note: "core 7.50.0 媒体块提货(`trace/tool-output-media.ts` 的 `liveToolEndForBacklog`,三处活面写口共用的那一只判据 —— 同步流出口 `sseData`、子代 tail 路由写环、tail 总线每订阅者队列):本帧媒体字节 + 这条连接 / 这个订阅者还没送出去的字节 > 上界(64 MiB)⇒
|
|
25
|
+
note: "core 7.50.0 媒体块提货(`trace/tool-output-media.ts` 的 `liveToolEndForBacklog`,三处活面写口共用的那一只判据 —— 同步流出口 `sseData`、子代 tail 路由写环、tail 总线每订阅者队列):本帧媒体字节(`output` 媒体块的 `data` 与 `structured` 媒体卡的 `file.base64` 一起计)+ 这条连接 / 这个订阅者还没送出去的字节 > 上界(64 MiB)⇒ 本帧媒体降成持久面那只形(媒体块换描述符 `{ type, mimeType?, bytes, truncated: true }`,帧级 `truncated` + `totalChars`;媒体卡去掉 `file.base64`)。每降一帧记一次,只在这只函数里记(三处写口不各记)。放行的最坏后果 = 一个读得慢的观众在 SSE 上看不到这张图,只见「这里有一张多大的什么图、没送达」(账本面本就恒是描述符),不是门、不是凭据、不改任何裁决;反方向(不降)让一条停读的连接随帧数线性压住几百 MiB。帧上它与引擎自己的媒体预算降级同形不可分,所以计数是运维唯一能看出「多少观众丢了图」的位;计数持续非零 = 有消费端读流太慢(或网络瓶颈),去看那几条连接。detail = `backlog=<待发字节> media=<本帧媒体字节>`。",
|
|
26
26
|
},
|
|
27
27
|
"server.memory-consolidation.operator-valve-unjudged": {
|
|
28
28
|
cls: "P-DEBT",
|
|
@@ -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/dist/trace/project.d.ts
CHANGED
|
@@ -266,7 +266,9 @@ export declare function reasoningEndEventData(ev: Record<string, unknown>): Segm
|
|
|
266
266
|
* core 1.203: `structured` = the tool's CC-shaped `details` card (edit/create/bash/… — core
|
|
267
267
|
* allowlists the card `type` and size-caps the whole card, omit-whole on overflow, so a subagent's child-TaskResult
|
|
268
268
|
* can never appear here) — SAME UNTRUSTED RAW contract as `output` (a card carries `originalFile`/stdout bodies),
|
|
269
|
-
* so it gets the same deep redaction before it is persisted or echoed
|
|
269
|
+
* so it gets the same deep redaction before it is persisted or echoed — except a media card's `file.base64` (image / document
|
|
270
|
+
* cards: the second encoding of the same bytes the `output` block carries), which, like the block's `data`, is an encoding of
|
|
271
|
+
* bytes and not text ({@link projectToolStructured}; the durable face drops it in the ledger confluence). `label`(core 展示名,同 tool_start 的
|
|
270
272
|
* label 待遇 verbatim)白名单排查(2026-07-24)补齐进函数本体——此前外包给调用点各自手动拼接,7 处只有
|
|
271
273
|
* 3 处记得拼,resume leg + ledger-sink 落盘两处漏了,持久化记录本身缺 label 一写盘就永久追不回来。 */
|
|
272
274
|
export declare function toolEndEventData(ev: {
|
package/dist/trace/project.js
CHANGED
|
@@ -3,7 +3,7 @@ import { isAskClass, isAskOrigin, isModeReleaseReason, isPermissionMode, isPersi
|
|
|
3
3
|
import { Value } from "typebox/value";
|
|
4
4
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
5
5
|
import { redactSecrets, redactDeep } from "./redact.js";
|
|
6
|
-
import { projectToolOutput } from "./tool-output-media.js";
|
|
6
|
+
import { projectToolOutput, projectToolStructured } from "./tool-output-media.js";
|
|
7
7
|
import { injectionTierOrAbsent } from "./injection-tier.js";
|
|
8
8
|
import { semaProvenanceOrAbsent } from "./sema-provenance.js";
|
|
9
9
|
import { resupplyFields, terminalFrameFacetFields } from "./task-notification-facets.js";
|
|
@@ -289,7 +289,7 @@ export function toolEndEventData(ev) {
|
|
|
289
289
|
...(ev.output !== undefined ? { output: projectToolOutput(ev.output) } : {}),
|
|
290
290
|
...(ev.truncated ? { truncated: true } : {}),
|
|
291
291
|
...(Number.isFinite(ev.totalChars) ? { totalChars: ev.totalChars } : {}),
|
|
292
|
-
...(ev.structured !== undefined ? { structured:
|
|
292
|
+
...(ev.structured !== undefined ? { structured: projectToolStructured(ev.structured) } : {}),
|
|
293
293
|
...(typeof ev.errorCode === "string" && ev.errorCode.length <= 128 ? { errorCode: ev.errorCode } : {}),
|
|
294
294
|
...(typeof ev.delivered === "string" && MCP_DELIVERY_VERDICTS.includes(ev.delivered) ? { delivered: ev.delivered } : {}),
|
|
295
295
|
...(gate !== undefined ? { gate } : {}),
|
|
@@ -509,6 +509,25 @@ export declare const SELF_MINTED_WIRE: {
|
|
|
509
509
|
readonly attr: "durableMediaDescriptor+totalChars";
|
|
510
510
|
readonly since: "7.127.0";
|
|
511
511
|
};
|
|
512
|
+
/**
|
|
513
|
+
* 7.127.2:同一条律的第二个座位 —— `tool_end.structured` 的**媒体卡**(Read 的图像 / 文档 details 卡 `{ type: "image" | "document", file: { base64?, … } }`)的
|
|
514
|
+
* `file.base64` 是同一份字节的第二份编码:活面逐字过境(不跑文本脱敏,卡上其余成员照常深度脱敏)、同一个连接积压判据一并计、降级时一并去掉;
|
|
515
|
+
* 钉 test/tool-end-media-durable.test.ts、test/live-media-backlog.test.ts。7.127.0 / 7.127.1 上这个键还过文本脱敏,且积压判据不计它。
|
|
516
|
+
*/
|
|
517
|
+
readonly "SSE tool_end#structured.file.base64(\u5A92\u4F53\u5361)": {
|
|
518
|
+
readonly attr: "verbatim-bytes;backlog>64MiB⇒key-dropped";
|
|
519
|
+
readonly since: "7.127.2";
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* 7.127.2:同一媒体卡在**持久面**(账本行与 `GET /v1/runs/:id/events` / `GET /v1/tasks/:id/turns` / trace SSE 回放)只剩卡形自己的「不带字节」形 ——
|
|
523
|
+
* `file` 上没有 `base64` 键,其余成员逐字;帧级 `truncated` / `totalChars` 不因剥卡而动(它们只说 `output`)(账本汇点同一次调用剥:
|
|
524
|
+
* `redactLedgerEventData` → `toolEndMediaAsDescriptors`;钉 test/tool-end-media-durable.test.ts、test/wire-structured-events.test.ts)。
|
|
525
|
+
* 7.127.0 / 7.127.1 写下的存量行在读面上同样不再带字节。
|
|
526
|
+
*/
|
|
527
|
+
readonly "GET /v1/runs/:id/events#tool_end.structured.file.base64(\u5A92\u4F53\u5361)": {
|
|
528
|
+
readonly attr: "durableMediaCardKeyDropped";
|
|
529
|
+
readonly since: "7.127.2";
|
|
530
|
+
};
|
|
512
531
|
};
|
|
513
532
|
};
|
|
514
533
|
};
|
|
@@ -171,6 +171,8 @@ export const SELF_MINTED_WIRE = {
|
|
|
171
171
|
"engine_notice permission.rule_row_dropped#detail.rows[].rule": { attr: "redactListEntry", since: "7.120.0" },
|
|
172
172
|
"SSE tool_end#output[].data(媒体块)": { attr: "verbatim-bytes;backlog>64MiB⇒descriptor+totalChars", since: "7.127.0" },
|
|
173
173
|
"GET /v1/runs/:id/events#tool_end.output[](媒体块)": { attr: "durableMediaDescriptor+totalChars", since: "7.127.0" },
|
|
174
|
+
"SSE tool_end#structured.file.base64(媒体卡)": { attr: "verbatim-bytes;backlog>64MiB⇒key-dropped", since: "7.127.2" },
|
|
175
|
+
"GET /v1/runs/:id/events#tool_end.structured.file.base64(媒体卡)": { attr: "durableMediaCardKeyDropped", since: "7.127.2" },
|
|
174
176
|
},
|
|
175
177
|
},
|
|
176
178
|
};
|
|
@@ -4,21 +4,29 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function projectToolOutput(output: unknown): unknown;
|
|
6
6
|
/**
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
* 两面共用的 `structured` 投影(与 {@link projectToolOutput} 同律):媒体卡逐成员过 `redactDeep`,`file` 里 `base64` 逐字、其余成员照脱;
|
|
8
|
+
* 其它一切整只过 `redactDeep`。保键序(逐键映射),不改入参。
|
|
9
|
+
*/
|
|
10
|
+
export declare function projectToolStructured(structured: unknown): unknown;
|
|
11
|
+
/**
|
|
12
|
+
* `tool_end` 载荷的媒体降级(持久面恒用;活面只在连接积压时用),**一次调用降两个座位**:
|
|
13
|
+
* · `output`:带 `data` 的媒体块换成描述符,有任何一块被换 ⇒ 帧级 `truncated: true` 且帧级 `totalChars` 在场 —— core 不变量
|
|
14
|
+
* 「Present iff truncated」:被界之前那个值的真实大小。帧上已有 `totalChars`(core 已截断:文本被切而媒体留着)⇒ **原样保留**
|
|
15
|
+
* (它说的是更早的原值,本仓手里的已是切过的值,重算只会低报);没有 ⇒ 按 {@link trueSizeOf} 逐块求和(core 没截,`output` 就是原值 ——
|
|
16
|
+
* 文本块是本服务脱敏后的字节,即本服务交付的内容)。已是描述符的块(无 `data`)原样 ⇒ 幂等。
|
|
17
|
+
* · `structured`:媒体卡去掉 `file.base64`(core 卡形自己的「不带字节」形),其余成员逐字。帧级 `truncated` / `totalChars` 只说 `output`,
|
|
18
|
+
* 单独剥卡**不动**它们(`output` 已是 core 描述符而卡仍带 ≤48K 字节的帧,和卡超 48K 时 core 自己略去 `base64` 的帧,在这两个键上读数一致)。
|
|
19
|
+
* 两处都没有媒体字节 ⇒ 原样返回同一引用(纯文本 / 串形 output / 非媒体卡一个字节不变)。
|
|
12
20
|
*/
|
|
13
21
|
export declare function toolEndMediaAsDescriptors(data: Record<string, unknown>): Record<string, unknown>;
|
|
14
22
|
/** 活面一条连接允许压住的待发字节上界(含本帧媒体)。一帧媒体最大约为部署媒体预算的 4/3(缺省约 40 MiB),
|
|
15
23
|
* 上界取它之上一点:空闲连接上一整帧最大的图照样整块过,第二帧起只在读者跟上之后才整块。 */
|
|
16
24
|
export declare const LIVE_MEDIA_BACKLOG_BYTES: number;
|
|
17
|
-
/** 一帧 `tool_end`
|
|
25
|
+
/** 一帧 `tool_end` 载荷里媒体字节的串长(base64):`output` 媒体块的 `data` 与 `structured` 媒体卡的 `file.base64` 一起计(同一帧、同一条连接上的字节;没有媒体 = 0)。 */
|
|
18
26
|
export declare function liveMediaBytesOf(frame: Record<string, unknown>): number;
|
|
19
27
|
/**
|
|
20
|
-
* 活面的积压判据(一只):`backlogBytes` = 这条连接 / 这个订阅者此刻还没送出去的字节。本帧媒体 + 积压 > 上界
|
|
21
|
-
*
|
|
28
|
+
* 活面的积压判据(一只):`backlogBytes` = 这条连接 / 这个订阅者此刻还没送出去的字节。本帧媒体(`output` 块 + `structured` 卡的字节一起计)+ 积压 > 上界
|
|
29
|
+
* ⇒ 媒体降持久形(块换描述符、卡去 `file.base64`,同一只 {@link toolEndMediaAsDescriptors}),否则原样(同一引用)。非 `tool_end` 载荷由调用方不送进来;没有媒体的 `tool_end` 恒原样。
|
|
22
30
|
* 降级是 F 类兜底(观众少见一张图),每降一帧在**这里**记一次 `server.tool-end-media.live-backlog-degraded` —— 三处写口都经这一只函数,
|
|
23
31
|
* 留痕因此只有一处;帧上它与引擎的媒体预算降级同形,计数是运维看出「多少观众丢了图」的唯一位。
|
|
24
32
|
*/
|
|
@@ -12,6 +12,21 @@ export function projectToolOutput(output) {
|
|
|
12
12
|
return redactDeep(output);
|
|
13
13
|
return output.map((b) => (isMediaBlock(b) ? Object.fromEntries(Object.entries(b).map(([k, v]) => [k, k === "data" ? v : redactDeep(v)])) : redactDeep(b)));
|
|
14
14
|
}
|
|
15
|
+
function isMediaCard(s) {
|
|
16
|
+
if (s === null || typeof s !== "object" || Array.isArray(s))
|
|
17
|
+
return false;
|
|
18
|
+
const card = s;
|
|
19
|
+
if (card.type !== "image" && card.type !== "document")
|
|
20
|
+
return false;
|
|
21
|
+
const file = card.file;
|
|
22
|
+
return file !== null && typeof file === "object" && !Array.isArray(file) && typeof file.base64 === "string";
|
|
23
|
+
}
|
|
24
|
+
export function projectToolStructured(structured) {
|
|
25
|
+
if (!isMediaCard(structured))
|
|
26
|
+
return redactDeep(structured);
|
|
27
|
+
const file = Object.fromEntries(Object.entries(structured.file).map(([k, v]) => [k, k === "base64" ? v : redactDeep(v)]));
|
|
28
|
+
return Object.fromEntries(Object.entries(structured).map(([k, v]) => [k, k === "file" ? file : redactDeep(v)]));
|
|
29
|
+
}
|
|
15
30
|
function trueSizeOf(b) {
|
|
16
31
|
if (isMediaBlock(b))
|
|
17
32
|
return b.data.length;
|
|
@@ -23,28 +38,40 @@ function trueSizeOf(b) {
|
|
|
23
38
|
}
|
|
24
39
|
export function toolEndMediaAsDescriptors(data) {
|
|
25
40
|
const output = data.output;
|
|
26
|
-
|
|
41
|
+
const outputHasMedia = Array.isArray(output) && output.some(isMediaBlock);
|
|
42
|
+
const structured = data.structured;
|
|
43
|
+
if (!outputHasMedia && !isMediaCard(structured))
|
|
27
44
|
return data;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
const next = { ...data };
|
|
46
|
+
if (outputHasMedia) {
|
|
47
|
+
const blocks = output;
|
|
48
|
+
const totalChars = typeof data.totalChars === "number" && Number.isFinite(data.totalChars) ? data.totalChars : blocks.reduce((n, b) => n + trueSizeOf(b), 0);
|
|
49
|
+
next.output = blocks.map((b) => isMediaBlock(b)
|
|
50
|
+
? {
|
|
51
|
+
type: b.type,
|
|
52
|
+
...(typeof b.mimeType === "string" && b.mimeType.length <= MIME_ECHO_MAX ? { mimeType: b.mimeType } : {}),
|
|
53
|
+
bytes: Math.floor((b.data.length * 3) / 4),
|
|
54
|
+
truncated: true,
|
|
55
|
+
}
|
|
56
|
+
: b);
|
|
57
|
+
next.truncated = true;
|
|
58
|
+
next.totalChars = totalChars;
|
|
59
|
+
}
|
|
60
|
+
if (isMediaCard(structured)) {
|
|
61
|
+
const { base64: _bytes, ...file } = structured.file;
|
|
62
|
+
next.structured = { ...structured, file };
|
|
63
|
+
}
|
|
64
|
+
return next;
|
|
38
65
|
}
|
|
39
66
|
export const LIVE_MEDIA_BACKLOG_BYTES = 64 * 1024 * 1024;
|
|
40
67
|
export function liveMediaBytesOf(frame) {
|
|
68
|
+
const structured = frame.structured;
|
|
69
|
+
let n = isMediaCard(structured) ? structured.file.base64.length : 0;
|
|
41
70
|
const output = frame.output;
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (isMediaBlock(b))
|
|
47
|
-
n += b.data.length;
|
|
71
|
+
if (Array.isArray(output))
|
|
72
|
+
for (const b of output)
|
|
73
|
+
if (isMediaBlock(b))
|
|
74
|
+
n += b.data.length;
|
|
48
75
|
return n;
|
|
49
76
|
}
|
|
50
77
|
export function liveToolEndForBacklog(frame, backlogBytes) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.127.
|
|
3
|
+
"version": "7.127.2",
|
|
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",
|