@sema-agent/server 7.48.0 → 7.50.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.
- package/USAGE.md +39 -0
- package/dist/approval-card.d.ts +1 -1
- package/dist/auth-bridge.d.ts +2 -2
- package/dist/auth-bridge.js +1 -1
- package/dist/boot/config-center.js +2 -2
- package/dist/boot/engine-lease.d.ts +95 -0
- package/dist/boot/engine-lease.js +56 -0
- package/dist/boot/runner-deps.d.ts +4 -2
- package/dist/boot/shutdown.d.ts +2 -0
- package/dist/boot/shutdown.js +16 -0
- package/dist/config-center/apply-effective.js +1 -1
- package/dist/config-center/http-client.d.ts +1 -1
- package/dist/config-center/http-client.js +1 -1
- package/dist/config-center/restart-signal.js +1 -1
- package/dist/config-center/skills-mcp.js +1 -1
- package/dist/config-provider.d.ts +2 -2
- package/dist/config-provider.js +2 -2
- package/dist/config-types.d.ts +21 -2
- package/dist/config.js +24 -2
- package/dist/fleet/subagent-tail-bus.d.ts +1 -1
- package/dist/fleet/subagent-tail-bus.js +10 -0
- package/dist/fleet-client.d.ts +1 -1
- package/dist/fleet-client.js +1 -1
- package/dist/fleet-lease.d.ts +1 -1
- package/dist/fleet-lease.js +1 -1
- package/dist/hooks/hook-runner.d.ts +1 -1
- package/dist/hooks/hook-runner.js +1 -1
- package/dist/http/routes/approvals-assistant.js +65 -0
- package/dist/http/routes/notify-wake.js +1 -1
- package/dist/http/routes/runs.d.ts +1 -1
- package/dist/http/routes/runs.js +1 -1
- package/dist/http/routes/sessions.js +1 -1
- package/dist/http/routes/tasks.js +15 -9
- package/dist/http/send.d.ts +14 -0
- package/dist/http/send.js +5 -0
- package/dist/http/server.d.ts +5 -0
- package/dist/http/server.js +24 -8
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/orchestration/workflow-completion-inbox.d.ts +12 -1
- package/dist/orchestration/workflow-completion-inbox.js +6 -1
- package/dist/org-memory-admission.js +1 -1
- package/dist/plugins/checkpoint-store-sql.d.ts +108 -1
- package/dist/plugins/checkpoint-store-sql.js +65 -0
- package/dist/plugins/local-checkpoint-store.d.ts +14 -1
- package/dist/plugins/local-checkpoint-store.js +22 -1
- package/dist/plugins/scheduler-support.d.ts +1 -1
- package/dist/plugins/scheduler-support.js +1 -1
- package/dist/plugins/usage-window-store-sql.d.ts +2 -2
- package/dist/plugins/usage-window-store-sql.js +17 -6
- package/dist/run-local.js +1 -1
- package/dist/runs.js +3 -3
- package/dist/runtime-caps-resolver.d.ts +1 -1
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/task-settings.d.ts +1 -1
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +13 -3
- package/dist/trace/engine-notice-wire.js +4 -0
- package/dist/trace/injection-tier.d.ts +33 -0
- package/dist/trace/injection-tier.js +7 -0
- package/dist/trace/project.d.ts +5 -1
- package/dist/trace/project.js +5 -0
- package/package.json +3 -3
package/USAGE.md
CHANGED
|
@@ -554,6 +554,45 @@ SEMA_PARENT_PID=$$ # 壳把自己的 pid 传给它 spawn 出来的引擎
|
|
|
554
554
|
PID 1 是典型;终端 / launchd / systemd 不在此列),pid 表项仍在,探活同样判"活着"。两条同源:
|
|
555
555
|
`kill(pid, 0)` 是**弱身份**判据。这条腿是**尽力自愈**,不是强一致的父子生命周期绑定。
|
|
556
556
|
|
|
557
|
+
**可选 — 附着租约自退(`ENGINE_AUTO_EXIT`,默认关)**
|
|
558
|
+
|
|
559
|
+
上一节那条腿盯的是「**生我的那个 pid** 还在吗」。多个壳会话共享**同一只**引擎时它问错了问题:首壳退了、
|
|
560
|
+
peer 还在用。这条腿问的是那个真正的问题 —— **还有没有人附着**。
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
ENGINE_AUTO_EXIT=true # 壳 spawn 引擎时注入;服务器部署**不要**设
|
|
564
|
+
ENGINE_LINGER_MS=60000 # 可选,默认 60s,下界 1000
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
- **默认 `false` = 整件不装配**,存量部署逐字零变化。一台 k8s 上的 replica 半夜没人用是**正常低谷**,
|
|
568
|
+
不是「该退了」;自退只对**壳自 spawn 的本机引擎**有意义,所以由壳在 spawn 时显式打开。
|
|
569
|
+
- **自退判据 = 三合取,且必须连续满 `ENGINE_LINGER_MS`**:① 没有附着的 SSE 流;② 没有在飞的 run
|
|
570
|
+
(与 `/health` 的 `inflight` **同一个读数**);③ 没有非探针 HTTP 触点(`/health`、`/metrics*` 不算 ——
|
|
571
|
+
探针不是「有人在用」),**且**最后一条附着流断开也已满窗。任何一条不满足即把计时**清零**。
|
|
572
|
+
(最后那半句不是修辞:一条活得比 15s 拍频还短的流整个生命周期都落在两拍之间,自查腿从没看见过它 ——
|
|
573
|
+
只有在**关闭点**记一笔离场时刻,「已经没人附着满 N 秒」才是精确的。)
|
|
574
|
+
- **「run 在跑就不退」是结构性保护,没有旋钮可以关掉它** —— 最后一个壳关掉、后台 run 还在烧的场景里,
|
|
575
|
+
引擎必须活到那条 run 落地。
|
|
576
|
+
- **`ENGINE_LINGER_MS` 防的是壳重启窗**:壳崩了/重启的 1-3s 里附着数确实是 0,窗太短就在用户眼皮底下
|
|
577
|
+
把引擎杀了。窗内任何一次重连即清零重新起算。
|
|
578
|
+
- **实际等待 = `[ENGINE_LINGER_MS, ENGINE_LINGER_MS + 15s)`**:自查拍频固定 15s(不开旋钮),最后一条
|
|
579
|
+
租约灭在两拍之间时要到下一拍才被看见。方向是**保守**的(只会等更久)。
|
|
580
|
+
- 判死后走的是**和 `SIGTERM` 逐字同一条**优雅排空腿:拒新提交(503 + `Retry-After`)→ 候在途腿跑完 →
|
|
581
|
+
停机。in-flight 的 run 照常结算 / park,**不是**裸退出;而且因为是受控退,`crash-last.json` 会被清掉
|
|
582
|
+
⇒ 下次启动**不会**把这次自退误读成崩溃。
|
|
583
|
+
- 日志:装配时一行 `engine_lease_armed`(带 `lingerMs`),自退时一行 `engine_lease_auto_exit`
|
|
584
|
+
(带 `attached` / `inflight` / `idleForMs` 三个读数),随后就是那条 `draining_started`。
|
|
585
|
+
- **坏值拒启**:`ENGINE_LINGER_MS` 必须是落在 **`[1000, 86400000]`(1s…24h)** 的整数毫秒,**两端都承重**:
|
|
586
|
+
下界防 `ENGINE_LINGER_MS=60`(以为单位是秒)—— 60ms 的窗等于把整条防误杀腿静默摘掉;上界防 `1e100`
|
|
587
|
+
这类值 —— 它能过朴素的「是正整数吗」检查,而计时**永远追不上它**,于是配置看着好好的、自退其实整件
|
|
588
|
+
失效。两端一律**拒启并指路**而不是夹取。本键**没有**「关掉」的取值 —— 关自退的唯一开关是
|
|
589
|
+
`ENGINE_AUTO_EXIT=false`。校验**不看** `ENGINE_AUTO_EXIT` 的表态:否则手滑值可以在一台今天关着自退的
|
|
590
|
+
机器上一直躺着,等哪天有人打开时当场生效。
|
|
591
|
+
- 🔴 **已知收窄(成文,现版不收)**:**parked-待赎回** 的 run 不在判据里,只看 running。数 parked 要发
|
|
592
|
+
SQL,而这是一只每 15s 无限期跑下去的定时器 —— 不值得为它引入周期性 SQL 轮询。parked 行是 **durable**
|
|
593
|
+
的,引擎退掉不丢:下次起来(壳的下一次 spawn)仍可赎回,伤害面是**赎回被推迟**而不是丢失。
|
|
594
|
+
- 与 `SEMA_PARENT_PID` 是**两件**,可以同时设,各走各的判据。
|
|
595
|
+
|
|
557
596
|
**布尔旋钮的取值与极性(运维必读)**
|
|
558
597
|
|
|
559
598
|
布尔 env **只认 `true` / `false` 两个字面量**。写成 `1` / `yes` / `TRUE` ⇒ 该旋钮退回自己的缺省值,并在启动时
|
package/dist/approval-card.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 合法、语义上空」的卡。`safeParse` 让这种行**当场落地为「跳过 + 一次 warn」**(§5.3)。
|
|
14
14
|
*
|
|
15
15
|
* schema 属主裁定(设计稿 §12-2,属主 §14 裁 (a) 变体):v1 的 schema 属主 = server 本仓,server 加
|
|
16
|
-
* **zod 直依赖**(此前 zod 只经 `@sema-agent/
|
|
16
|
+
* **zod 直依赖**(此前 zod 只经 `@sema-agent/settings-schema` 传递到场)。抽进 `@sema-agent/settings-schema`
|
|
17
17
|
* 的时机 = 出现第二个**运行期**消费者(cli 呈卡校验排期时)——届时是「属主迁移」而不是「复制形状」,
|
|
18
18
|
* 不违单一属主宪法;此刻抽包 = 为不存在的消费者发一轮 registry-core 版本 + floor bump,零收益。
|
|
19
19
|
*
|
package/dist/auth-bridge.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type AuthBridgeErrorCode, type VerifiedIdentity } from "@sema-agent/
|
|
1
|
+
import { type AuthBridgeErrorCode, type VerifiedIdentity } from "@sema-agent/settings-schema/api/auth-bridge";
|
|
2
2
|
import type { Logger } from "./observability/logger.js";
|
|
3
3
|
import type { Metrics } from "./observability/metrics.js";
|
|
4
|
-
export type { VerifiedIdentity } from "@sema-agent/
|
|
4
|
+
export type { VerifiedIdentity } from "@sema-agent/settings-schema/api/auth-bridge";
|
|
5
5
|
export interface RegistryJwtVerifierOptions {
|
|
6
6
|
/** R4 — 期望 iss(registry 的 SSO_ISSUER;默认=SEMA_REGISTRY_URL,双侧去尾斜杠比较)。 */
|
|
7
7
|
issuer: string;
|
package/dist/auth-bridge.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createPublicKey, verify as cryptoVerify } from "node:crypto";
|
|
2
|
-
import { AUTH_BRIDGE_ALGS, DEFAULT_CLOCK_SKEW_SEC, DEFAULT_JWKS_REFRESH_MIN_INTERVAL_SEC, DEFAULT_OFFLINE_GRACE_SEC, REGISTRY_JWKS_PATH, RegistryJwks, classifyTimeClaims, jwksWithinOfflineGrace, shouldForceJwksRefresh, toVerifiedIdentity, } from "@sema-agent/
|
|
2
|
+
import { AUTH_BRIDGE_ALGS, DEFAULT_CLOCK_SKEW_SEC, DEFAULT_JWKS_REFRESH_MIN_INTERVAL_SEC, DEFAULT_OFFLINE_GRACE_SEC, REGISTRY_JWKS_PATH, RegistryJwks, classifyTimeClaims, jwksWithinOfflineGrace, shouldForceJwksRefresh, toVerifiedIdentity, } from "@sema-agent/settings-schema/api/auth-bridge";
|
|
3
3
|
const DEFAULT_JWKS_TTL_SEC = 15 * 60;
|
|
4
4
|
const MAX_JWKS_BYTES = 256 * 1024;
|
|
5
5
|
const MAX_TOKEN_CHARS = 16 * 1024;
|
|
@@ -2,8 +2,8 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile as fsReadFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { loadRemoteExec } from "@sema-agent/
|
|
6
|
-
import { skillContentHash } from "@sema-agent/
|
|
5
|
+
import { loadRemoteExec } from "@sema-agent/settings-schema/node";
|
|
6
|
+
import { skillContentHash } from "@sema-agent/settings-schema";
|
|
7
7
|
import { CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, } from "@sema-agent/core";
|
|
8
8
|
import { buildPricing } from "../budget.js";
|
|
9
9
|
import { applyCenterPlugins } from "../capabilities/center-plugins.js";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #118(黑板 [5371]②,cli [5394] 认领消费)—— 引擎**附着租约自退**。
|
|
3
|
+
*
|
|
4
|
+
* ## 防的是什么
|
|
5
|
+
*
|
|
6
|
+
* 壳自 spawn 引擎的本机形里,壳正常退出之后引擎**继续活着**:占端口、占库连接池、占模型配额,而且
|
|
7
|
+
* 用户已经离开了、再没有人会关心它。#219 的 parent 监视只覆盖「生我的那个 pid 没了」这一形,而
|
|
8
|
+
* **多壳共享一只引擎**时它结构性地问错了问题 —— 首壳退了 peer 还在用,监视父 pid 要么误杀要么永不触发。
|
|
9
|
+
* 本件问的是那个真正的问题:**还有没有人附着**。
|
|
10
|
+
*
|
|
11
|
+
* ## 机制(逐条都是契约)
|
|
12
|
+
*
|
|
13
|
+
* 1. **opt-in**:`ENGINE_AUTO_EXIT` 默认 **false** ⇒ 整件不装配(`boot/shutdown.ts` 连本函数都不调)。
|
|
14
|
+
* 服务器部署形逐字零变化 —— 一台 k8s 上的 replica 半夜没人用不是「该退了」,是**正常的低谷**。
|
|
15
|
+
* 壳 spawn 形由壳在 spawn 时显式注入 `true`。**部署级旋钮无条件生效**:它不派生自任何客户端表态
|
|
16
|
+
* (本仓 operator-knob 铁律,#153 同病两犯史),`ENGINE_LINGER_MS` 的坏值校验也不挂在本键的表态上。
|
|
17
|
+
* 2. **自退判据 = 三合取,且必须持续满 `lingerMs`**:
|
|
18
|
+
* · 附着租约集空(`attachedStreams() === 0`);
|
|
19
|
+
* · 无在飞 run(`inflight() === 0` —— **与 /health 的 `inflight` 同一只 getter**,不另立第二判据);
|
|
20
|
+
* · HTTP 触点静默(`now() - lastActivityAt() >= lingerMs`),**且**最后一条附着流的**离场**
|
|
21
|
+
* 也已满窗(`now() - lastAttachEndedAt() >= lingerMs` —— codex R1-F1,见该 dep 的注)。
|
|
22
|
+
* 任何一条不满足即把计时清零(连续性判据,不是累计)。
|
|
23
|
+
* 3. **为什么 HTTP 触点是独立的第三条**(不是冗余):纯轮询壳(零 SSE,每 2s 一次 `GET /v1/sessions`)
|
|
24
|
+
* 的请求是毫秒级的,而本腿 15s 采样一拍 —— 采样点几乎必然落在两次轮询之间,`attached` 与
|
|
25
|
+
* `inflight` **同时读到 0**。只看那两条的实现会在一只**正被使用**的引擎底下把它关掉。租约的
|
|
26
|
+
* 建立面按设计就是「首个 HTTP/SSE 接触」,不只是 SSE。
|
|
27
|
+
* 4. **退出 = 走 `drain`**,即 `boot/shutdown.ts` 里 SIGTERM 用的那一条 `drainThenShutdown`:拒新提交
|
|
28
|
+
* (503+Retry-After)→ 候在途腿跑完 → `hardShutdown`。in-flight run 的结算 / park 语义与人工停机
|
|
29
|
+
* **逐字一致**。本模块自己**从不** `process.exit`。
|
|
30
|
+
* 5. **crash-last 判别位**([5371] ④「flush 判别位标 auto-exit 非崩溃」):走上面那条受控退,
|
|
31
|
+
* `hardShutdown` 一进来就 `clearCrashLast` ⇒ 遗言文件被删 ⇒「文件在场 ⇔ 上次退出=崩溃」这条契约
|
|
32
|
+
* 对自退**自动成立**。刻意**不**另写一条 `reason:"auto-exit"` 的遗言行:那会把该文件的语义从
|
|
33
|
+
* 「崩溃判别位」稀释成「上次退出记录」,而运维读它就是为了判前者(见 `crash-last.ts` 顶注)。
|
|
34
|
+
* 自退的可观察面是 `engine_lease_auto_exit` + 随后的 `draining_started` 两行日志。
|
|
35
|
+
* 6. **interval 必须 unref**:自退检查腿不得成为「进程本该退出却退不掉」的理由(与 drain tick 刻意
|
|
36
|
+
* 不 unref 的取向相反 —— 那一只 tick 就是停机驱动,这一只只是观察者)。
|
|
37
|
+
* 7. **装配即起算**:armed 那一刻若已空闲,计时**当场**开始。覆盖的是「壳 spawn 了引擎,自己却在
|
|
38
|
+
* 第一次接触之前就崩了」这一形 —— 那只引擎一辈子没有过租约,正是本件要收的孤儿。
|
|
39
|
+
* 8. **实际窗 = `[lingerMs, lingerMs + tickMs)`**(采样腿的固有滞后,成文而非隐藏):最后一条租约是在
|
|
40
|
+
* **两拍之间**灭的,本腿要到下一拍才看见,空闲计时从**那一拍**起表。⇒ 真实等待比 `lingerMs` 多出
|
|
41
|
+
* 0~1 拍。方向是**保守**的(等得更久,不会更早),所以不修 —— 要修就得让 SSE 关闭点反向通知本腿,
|
|
42
|
+
* 那是把一只自洽的观察者改成有状态的被通知者,不值当。运维读数以 `engine_lease_auto_exit.idleForMs`
|
|
43
|
+
* 为准(它报的是真实空闲时长,不是 `lingerMs`)。
|
|
44
|
+
*
|
|
45
|
+
* ## 已知收窄(成文,现版不收)
|
|
46
|
+
*
|
|
47
|
+
* · **parked-待赎回 run 不入判据**:设计稿 ② 原文含「无 running/parked-待赎回 run」,本版**只看
|
|
48
|
+
* running**。理由 = 廉价读口不存在:parked 是 store 里的**durable 行**,数它必须发 SQL,而本腿是一只
|
|
49
|
+
* 每 15s 无限期跑下去的定时器 —— 为它引入周期性 SQL 轮询,代价压过收益。**bg run 在跑不退**那一臂
|
|
50
|
+
* 已由 `inflight()` 结构性守住;parked 行是 durable 的,引擎退掉不丢:下次起来仍可赎回(spawn 形里
|
|
51
|
+
* 「下次」就是壳的下一次 spawn)。伤害面 = 一条 parked run 的赎回被推迟到下次启动,不是丢失。
|
|
52
|
+
* 这是**允许的收窄,不是静默**:有了廉价 parked 计数口(或本腿改成事件驱动)之后应当补上。
|
|
53
|
+
* · **`/health` 探针不算触点**:`lastActivityAt` 的属主(`http/server.ts` 的 `noteActivity`)按既有契约
|
|
54
|
+
* 把 `/health` 与 `/metrics*` 排除在活性时钟之外(常态采样噪音不得把空闲窗永远撑开)。⇒ 一只**只**
|
|
55
|
+
* 轮询 `/health` 的壳不构成租约。这与 SIGHUP 空闲窗的判据同源,刻意保持一致而非在本腿另开一份。
|
|
56
|
+
*/
|
|
57
|
+
/** 自退检查拍频。**固定值,不开 env 旋钮**(与 `PARENT_WATCH_INTERVAL_MS` 同纪律:有真需求再议,防旋钮增殖)。 */
|
|
58
|
+
export declare const ENGINE_LEASE_TICK_MS = 15000;
|
|
59
|
+
export interface EngineLeaseDeps {
|
|
60
|
+
/** 活附着数 = 本副本当前**打开着的 SSE 流**条数(`drainState.attachedStreams`,由 `createHttpServer` 自赋)。 */
|
|
61
|
+
attachedStreams: () => number;
|
|
62
|
+
/** 在飞腿数 —— **`/health` 的 `inflight` 同一只 getter**(`drainState.inflight`)。不自建第二套计数:
|
|
63
|
+
* 两套计数迟早分叉,而分叉的那一侧恰好是「以为没 run 了」就把引擎关掉。 */
|
|
64
|
+
inflight: () => number;
|
|
65
|
+
/** 最后一次**非探针** HTTP 触点的单调时刻(`drainState.lastActivityAt`)。
|
|
66
|
+
* ⚠️ 时基契约:必须与本模块的 `now` 同为 `performance.now()` 单调域(`server.ts` 的打点即该域)——
|
|
67
|
+
* 混入 `Date.now()` 会让 `now() - lastActivityAt` 差出 epoch 量级,静默判据恒真。 */
|
|
68
|
+
lastActivityAt: () => number;
|
|
69
|
+
/** 最后一条**附着流断开**的单调时刻(`drainState.lastAttachEndedAt`)。
|
|
70
|
+
*
|
|
71
|
+
* 🔴 codex 对抗复审 R1-F1(验真后修):`attachedStreams` 是**采样**面,`lastActivityAt` 记的是
|
|
72
|
+
* **入场**(admission)时刻 —— 两者合起来仍漏一个形:一条**整个生命周期都落在两拍之间**的流
|
|
73
|
+
* (活得比拍频短)从没被任何一拍看见,于是 `idleSince` 保持着这条流出现**之前**的旧值,而
|
|
74
|
+
* 「已经没人附着满 lingerMs」这句承诺可以提前至多一整拍兑现。补这条**离场**触点之后,判据变成
|
|
75
|
+
* 精确的「距最后一次附着结束已满 lingerMs」。缺席(0)= 本进程还没有过附着流结束。 */
|
|
76
|
+
lastAttachEndedAt: () => number;
|
|
77
|
+
/** 已有停机属主(`closing || draining`)时本腿站下:一次正在进行的优雅 drain 不该被「也没人附着了」
|
|
78
|
+
* 升级成第二信号硬停(那会斩掉 in-flight leg)。 */
|
|
79
|
+
isStopped: () => boolean;
|
|
80
|
+
/** 自退出口 = SIGTERM 那条 graceful drain(见顶注 4/5)。 */
|
|
81
|
+
drain: () => void;
|
|
82
|
+
/** 三合取**持续**满这么久才触发(`config.engineLingerMs`,默认 60s)。 */
|
|
83
|
+
lingerMs: number;
|
|
84
|
+
log: (event: string, fields: Record<string, unknown>) => void;
|
|
85
|
+
/** 检查拍频 seam(测试)。缺省 {@link ENGINE_LEASE_TICK_MS}。 */
|
|
86
|
+
tickMs?: number;
|
|
87
|
+
/** 时钟 seam,缺省 `() => performance.now()`(**单调**域)。用 `Date.now()` 会让 NTP/手动跳变吞掉或
|
|
88
|
+
* 拉长 linger 窗 —— 前跳把窗直接跳过去 = 在一只还有人用的引擎底下提前关停。 */
|
|
89
|
+
now?: () => number;
|
|
90
|
+
}
|
|
91
|
+
/** 装配并**立刻**起自退检查(装配即 `engine_lease_armed`)。返回停机链用的 `stop()`。 */
|
|
92
|
+
export declare function createEngineLeaseTracker(deps: EngineLeaseDeps): {
|
|
93
|
+
stop(): void;
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=engine-lease.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const ENGINE_LEASE_TICK_MS = 15_000;
|
|
2
|
+
export function createEngineLeaseTracker(deps) {
|
|
3
|
+
const tickMs = deps.tickMs ?? ENGINE_LEASE_TICK_MS;
|
|
4
|
+
const now = deps.now ?? (() => performance.now());
|
|
5
|
+
let stopped = false;
|
|
6
|
+
let idleSince = now();
|
|
7
|
+
deps.log("engine_lease_armed", {
|
|
8
|
+
lingerMs: deps.lingerMs,
|
|
9
|
+
tickMs,
|
|
10
|
+
note: "engine self-exits once NO shell session is attached AND no run is in flight AND no HTTP touch, " +
|
|
11
|
+
"continuously for lingerMs — via the same graceful drain as SIGTERM. Set ENGINE_AUTO_EXIT=false to disable.",
|
|
12
|
+
});
|
|
13
|
+
const timer = setInterval(() => {
|
|
14
|
+
if (stopped)
|
|
15
|
+
return;
|
|
16
|
+
if (deps.isStopped()) {
|
|
17
|
+
stop();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const attached = deps.attachedStreams();
|
|
21
|
+
const inflight = deps.inflight();
|
|
22
|
+
if (attached > 0 || inflight > 0) {
|
|
23
|
+
idleSince = null;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const t = now();
|
|
27
|
+
if (idleSince === null) {
|
|
28
|
+
idleSince = t;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (t - deps.lastActivityAt() < deps.lingerMs)
|
|
32
|
+
return;
|
|
33
|
+
if (t - deps.lastAttachEndedAt() < deps.lingerMs)
|
|
34
|
+
return;
|
|
35
|
+
if (t - idleSince < deps.lingerMs)
|
|
36
|
+
return;
|
|
37
|
+
stop();
|
|
38
|
+
deps.log("engine_lease_auto_exit", {
|
|
39
|
+
attached,
|
|
40
|
+
inflight,
|
|
41
|
+
lingerMs: deps.lingerMs,
|
|
42
|
+
idleForMs: Math.round(t - Math.max(idleSince, deps.lastActivityAt(), deps.lastAttachEndedAt())),
|
|
43
|
+
note: "no attached shell session, no in-flight run and no HTTP touch for the full linger window — entering the SIGTERM graceful drain path",
|
|
44
|
+
});
|
|
45
|
+
deps.drain();
|
|
46
|
+
}, tickMs);
|
|
47
|
+
timer.unref?.();
|
|
48
|
+
function stop() {
|
|
49
|
+
if (stopped)
|
|
50
|
+
return;
|
|
51
|
+
stopped = true;
|
|
52
|
+
clearInterval(timer);
|
|
53
|
+
}
|
|
54
|
+
return { stop };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=engine-lease.js.map
|
|
@@ -175,8 +175,10 @@ export type SharedRunnerDepsCtx = Pick<RunnerDepsCtx, "config" | "brain" | "pric
|
|
|
175
175
|
* 1. **日志(全族,逐字保留)** —— 上面那段契约一个字节不变:任何 code、detail 原样、缺 detail 不铸键。
|
|
176
176
|
* 运维观察面零回退是本腿的硬前提(wire 半场是**增量**,不是搬家)。
|
|
177
177
|
* 2. **wire(白名单 + sessionId 路由)** —— `router.route(notice)`:码在
|
|
178
|
-
* {@link import("../trace/engine-notice-wire.js").ENGINE_NOTICE_WIRE_CODES}
|
|
179
|
-
*
|
|
178
|
+
* {@link import("../trace/engine-notice-wire.js").ENGINE_NOTICE_WIRE_CODES} 内**且**归属键在场时,
|
|
179
|
+
* 投给那条会话的 run 腿(SSE + durable 账本);否则一帧不出。归属键 = 顶层 `EngineNotice.sessionId`
|
|
180
|
+
* **优先**、缺席回落 `detail.sessionId`(单点 `engineNoticeSessionId`;此前这一行只写了 detail 那一半,
|
|
181
|
+
* 顶层键 core 5.46+ 就到了)。判据整条住在
|
|
180
182
|
* `src/trace/engine-notice-wire.ts`(白名单/归属/消毒/隔离四件同源),这里只负责**先日志后分流**的次序。
|
|
181
183
|
*
|
|
182
184
|
* 🔴 次序是承重的:日志在前 ⇒ 即便 wire 半场整条坏掉(路由抛、口抛),事实仍然完整落在运维面。
|
package/dist/boot/shutdown.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface ShutdownCtx {
|
|
|
64
64
|
since?: number;
|
|
65
65
|
inflight?: () => number;
|
|
66
66
|
lastActivityAt?: () => number;
|
|
67
|
+
attachedStreams?: () => number;
|
|
68
|
+
lastAttachEndedAt?: () => number;
|
|
67
69
|
};
|
|
68
70
|
/** #131-2:store 活体探针(main 建)——不停的话 hardShutdown 后仍对正在关闭的池发探针,刷假
|
|
69
71
|
* store_probe_dead 告警(文件头契约 3 的同族漏网)。缺席 = 该部署形没建探针。 */
|
package/dist/boot/shutdown.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Runner, defaultTaskRegistry } from "@sema-agent/core";
|
|
|
2
2
|
import { writeCrashLast, clearCrashLast, readCrashLast } from "./crash-last.js";
|
|
3
3
|
import { createSighupIdleHandler } from "../sighup-idle.js";
|
|
4
4
|
import { createParentWatch } from "../parent-watch.js";
|
|
5
|
+
import { createEngineLeaseTracker } from "./engine-lease.js";
|
|
5
6
|
import { isScriptRealmRejection, describeRejectionReason } from "../orchestration/hardened-vm-runner.js";
|
|
6
7
|
export function installShutdownHandlers(ctx) {
|
|
7
8
|
const { config, logger, server, reaper, fleetReconcile, retentionLane, releaseRetentionLease, otelExporter, breakerState, costQuota, rateLimiter, runner, subRunner, lspManager, workflowNotifyJournal, fleetClient, backend, drainState, storeLiveProbe, configCenter, version, } = ctx;
|
|
@@ -45,6 +46,7 @@ export function installShutdownHandlers(ctx) {
|
|
|
45
46
|
process.on("unhandledRejection", onUnhandledRejection);
|
|
46
47
|
let closing = false;
|
|
47
48
|
let parentWatch;
|
|
49
|
+
let engineLease;
|
|
48
50
|
const hardShutdown = () => {
|
|
49
51
|
if (closing)
|
|
50
52
|
return;
|
|
@@ -62,6 +64,7 @@ export function installShutdownHandlers(ctx) {
|
|
|
62
64
|
.catch(() => undefined);
|
|
63
65
|
fleetReconcile.reconciler.stop();
|
|
64
66
|
parentWatch?.stop();
|
|
67
|
+
engineLease?.stop();
|
|
65
68
|
storeLiveProbe?.stop();
|
|
66
69
|
configCenter?.stopRefreshLoop();
|
|
67
70
|
otelExporter?.stop();
|
|
@@ -145,6 +148,18 @@ export function installShutdownHandlers(ctx) {
|
|
|
145
148
|
log: (event, fields) => logger.info(event, fields),
|
|
146
149
|
});
|
|
147
150
|
}
|
|
151
|
+
if (config.engineAutoExit) {
|
|
152
|
+
engineLease = createEngineLeaseTracker({
|
|
153
|
+
attachedStreams: () => drainState.attachedStreams?.() ?? 0,
|
|
154
|
+
inflight: () => drainState.inflight?.() ?? 0,
|
|
155
|
+
lastActivityAt: () => drainState.lastActivityAt?.() ?? 0,
|
|
156
|
+
lastAttachEndedAt: () => drainState.lastAttachEndedAt?.() ?? 0,
|
|
157
|
+
isStopped: () => closing || draining,
|
|
158
|
+
drain: drainThenShutdown,
|
|
159
|
+
lingerMs: config.engineLingerMs,
|
|
160
|
+
log: (event, fields) => logger.info(event, fields),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
148
163
|
let disposed = false;
|
|
149
164
|
return {
|
|
150
165
|
dispose: () => {
|
|
@@ -162,6 +177,7 @@ export function installShutdownHandlers(ctx) {
|
|
|
162
177
|
drainTick = undefined;
|
|
163
178
|
}
|
|
164
179
|
parentWatch?.stop();
|
|
180
|
+
engineLease?.stop();
|
|
165
181
|
},
|
|
166
182
|
};
|
|
167
183
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isThinkingLevel } from "@sema-agent/core";
|
|
2
|
-
import { resolveActiveTiers, SEALED_BOX_ALG } from "@sema-agent/
|
|
2
|
+
import { resolveActiveTiers, SEALED_BOX_ALG } from "@sema-agent/settings-schema";
|
|
3
3
|
import { sealedKeyPoison } from "../sealed-key.js";
|
|
4
4
|
import { applyAutoCompactWindow, findUnmatchableToolNames, formatUnmatchableToolNames, parseAutonomy } from "../config.js";
|
|
5
5
|
import { validateCommandRules } from "../runtime-governance.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EntitlementRuntimeCaps } from "@sema-agent/
|
|
1
|
+
import { type EntitlementRuntimeCaps } from "@sema-agent/settings-schema";
|
|
2
2
|
import type { ScenarioRuling } from "../capabilities/scenarios.js";
|
|
3
3
|
import type { EffectiveConfig, ExecutionRuling } from "./types.js";
|
|
4
4
|
/** GET the effective config (Bearer + ETag). null = 304 (unchanged). Throws on transport/HTTP error, and on a
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { readEffectiveWire, PrincipalCapsWire } from "@sema-agent/
|
|
2
|
+
import { readEffectiveWire, PrincipalCapsWire } from "@sema-agent/settings-schema";
|
|
3
3
|
import { centerPromptsFromEffective } from "../prompts-domain-validate.js";
|
|
4
4
|
export async function fetchEffective(baseUrl, token, etag, fetchImpl = fetch, worker) {
|
|
5
5
|
const scheme = new URL(baseUrl).protocol;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveActiveTiers } from "@sema-agent/
|
|
1
|
+
import { resolveActiveTiers } from "@sema-agent/settings-schema";
|
|
2
2
|
import { RUNTIME_GATE_KEYS, HOT_RUNTIME_GATE_KEYS, runtimeGatePresent, resolveDefaultModelName } from "./apply-effective.js";
|
|
3
3
|
const RESTART_SLICES = ["skills", "mcp", "a2a", "scenarios", "runtime-gates", "models-tiers", "degrade-route", "read-face"];
|
|
4
4
|
function stableStringify(v) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { skillContentHash } from "@sema-agent/
|
|
1
|
+
import { skillContentHash } from "@sema-agent/settings-schema";
|
|
2
2
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "../mcp-content-origin.js";
|
|
3
3
|
import { fetchSkillContent } from "./http-client.js";
|
|
4
4
|
import { SHA256_HEX_RE } from "../digest-form.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FileConfigStore } from "@sema-agent/
|
|
2
|
-
import { type EffectiveConfig as AgentConfigEffective } from "@sema-agent/
|
|
1
|
+
import { FileConfigStore } from "@sema-agent/settings-schema/node";
|
|
2
|
+
import { type EffectiveConfig as AgentConfigEffective } from "@sema-agent/settings-schema";
|
|
3
3
|
import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, type EffectiveConfig } from "./config-center/facade.js";
|
|
4
4
|
/** Result of a `fetchEffective` — EXACTLY the config-center's return: the effective config + its etag, or
|
|
5
5
|
* `null` for "unchanged" (remote 304; local: the version matched the caller's prior etag).
|
package/dist/config-provider.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHmac, randomBytes } from "node:crypto";
|
|
2
|
-
import { FileConfigStore } from "@sema-agent/
|
|
3
|
-
import { findSkillContent, skillContentHash, refIntegrityIssues, siblingResolver, } from "@sema-agent/
|
|
2
|
+
import { FileConfigStore } from "@sema-agent/settings-schema/node";
|
|
3
|
+
import { findSkillContent, skillContentHash, refIntegrityIssues, siblingResolver, } from "@sema-agent/settings-schema";
|
|
4
4
|
import { redactSecrets } from "./trace/redact.js";
|
|
5
5
|
import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, ConfigCenterHttpError, } from "./config-center/facade.js";
|
|
6
6
|
import { createLogger } from "./observability/logger.js";
|
package/dist/config-types.d.ts
CHANGED
|
@@ -1223,6 +1223,25 @@ export interface ServiceConfigFlat {
|
|
|
1223
1223
|
* 已知残余(成文,不收窄):两拍窗口内 pid 被系统复用 ⇒ 探活看到的是**新**进程,监视判"父还活着"
|
|
1224
1224
|
* 而不自退。监视是**尽力自愈**、不是强一致的父子生命周期绑定。 Env: `SEMA_PARENT_PID`。 */
|
|
1225
1225
|
parentPid?: number;
|
|
1226
|
+
/** #118(黑板 [5371]②):引擎**附着租约自退**总开关。壳自 spawn 引擎的本机形里,壳退出之后引擎
|
|
1227
|
+
* 继续活着 = 占端口/占库连接/占模型配额的孤儿。设了本键 = 引擎每 15s 自查一次「还有没有人附着」,
|
|
1228
|
+
* **三合取**(无附着 SSE 流 ∧ 无在飞 run ∧ 无非探针 HTTP 触点)**持续**满 {@link engineLingerMs}
|
|
1229
|
+
* 才自退,且自退走的是与 SIGTERM **逐字同一条** graceful drain 腿(不是裸 `process.exit`)。
|
|
1230
|
+
* **默认 false** —— 服务器部署形永不自退(k8s replica 半夜没人用是正常低谷,不是「该退了」);
|
|
1231
|
+
* 壳 spawn 形由壳显式注入 `true`。**部署级旋钮无条件生效**,不派生自任何客户端表态。
|
|
1232
|
+
* 与 {@link parentPid} 是**两件**:那条问「生我的 pid 还在吗」(多壳共享一引擎时结构性问错),
|
|
1233
|
+
* 这条问「还有没有人附着」。两条可同时装,各走各的判据。 Env: `ENGINE_AUTO_EXIT`(默认 off)。 */
|
|
1234
|
+
engineAutoExit: boolean;
|
|
1235
|
+
/** #118:自退前三合取必须**持续**成立的静默窗。默认 60s —— 防的是**壳重启窗**:壳崩了/重启的 1-3s
|
|
1236
|
+
* 里附着数确实是 0,窗太短就在用户眼皮底下把引擎杀了,窗内任何一次重连即把计时清零。
|
|
1237
|
+
* {@link engineAutoExit} 关着时本值不生效,但**照样解析、坏值照样拒启**(部署级旋钮的解析不挂在
|
|
1238
|
+
* 另一个旋钮的表态上 —— 否则手滑值躺在配置里,等哪天有人打开自退时当场生效且从没人报过警)。
|
|
1239
|
+
* **坏值拒启**(#210 A 档):非安全整数 / 落在 `[1000, 86400000]`(1s…24h)之外一律 boot 期 throw
|
|
1240
|
+
* 带指路 —— 「把 60(秒)填进一个毫秒键」是这类旋钮最常见的手滑,而 60ms 的 linger 等于把整条防误杀
|
|
1241
|
+
* 腿静默摘掉;上界那一侧同样承重(codex R1-F2):`1e100` 能过朴素的「正整数吗」检查,而 elapsed
|
|
1242
|
+
* **永远追不上它** ⇒ 看着配好了、自退其实整件失效。要「永不自退」请用 `ENGINE_AUTO_EXIT=false`。
|
|
1243
|
+
* Env: `ENGINE_LINGER_MS`(默认 60000,闭区间 [1000, 86400000])。 */
|
|
1244
|
+
engineLingerMs: number;
|
|
1226
1245
|
/** E23 (shell-host contract): inbound MCP elicitation HITL. When enabled, the service mounts `RunnerDeps.onElicit` + the
|
|
1227
1246
|
* `POST /v1/elicitations/:id/respond` route so an opted-in MCP server (`McpServerSpec.elicitation`, itself default
|
|
1228
1247
|
* OFF) can ask the END USER for input mid-tool-call. LIVE-ONLY (no durable suspend; lost on crash/replica-change).
|
|
@@ -1471,7 +1490,7 @@ export interface ServiceConfigFlat {
|
|
|
1471
1490
|
worker?: string;
|
|
1472
1491
|
};
|
|
1473
1492
|
/** Dual-mode config SOURCE (DUAL-MODE-DESIGN §4): "remote" (sema-registry HTTP) | "local" (.env + config.d
|
|
1474
|
-
* via @sema-agent/
|
|
1493
|
+
* via @sema-agent/settings-schema FileConfigStore) | unset → remote if SEMA_REGISTRY_URL (legacy CONFIG_CENTER_URL now boot-rejects, no dual-read) is set, else pure-env.
|
|
1475
1494
|
* Env: CONFIG_PROVIDER. The same resolver/EffectiveConfig either way (can't drift). */
|
|
1476
1495
|
configProvider?: string;
|
|
1477
1496
|
/** Root dir holding `config.d/<domain>.json` for the local config source. Env: CONFIG_LOCAL_DIR. */
|
|
@@ -1496,7 +1515,7 @@ export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authToken
|
|
|
1496
1515
|
/** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
|
|
1497
1516
|
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
|
|
1498
1517
|
/** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
|
|
1499
|
-
export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "sighupIdleGraceMs" | "parentPid" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
|
|
1518
|
+
export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
|
|
1500
1519
|
/** 组:observability(可观测)。 */
|
|
1501
1520
|
export type ServiceObservabilityConfig = Pick<ServiceConfigFlat, "metricsToken" | "traceToken" | "toolTrace" | "traceThinking" | "logLevel" | "otel">;
|
|
1502
1521
|
/** 组:integrations(外部集成)。`mcpServers` / `a2aPeers` 由 sema-registry 适配器填(无 env 腿),归本组。 */
|
package/dist/config.js
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, formatUserScope, isThinkingLevel, PROTOCOL_TABLE, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES, resolveLockedKeys, validatePermissionRules } from "@sema-agent/core";
|
|
6
|
-
import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES } from "@sema-agent/
|
|
6
|
+
import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES } from "@sema-agent/settings-schema";
|
|
7
7
|
import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
|
|
8
8
|
import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
9
9
|
import { isV2ScopeKey } from "./memory-scope.js";
|
|
@@ -962,6 +962,24 @@ function parentPidEnv() {
|
|
|
962
962
|
}
|
|
963
963
|
return { parentPid: n };
|
|
964
964
|
}
|
|
965
|
+
const ENGINE_LINGER_FLOOR_MS = 1_000;
|
|
966
|
+
const ENGINE_LINGER_CEIL_MS = 24 * 60 * 60 * 1_000;
|
|
967
|
+
function engineLingerMsEnv() {
|
|
968
|
+
const raw = process.env.ENGINE_LINGER_MS;
|
|
969
|
+
if (raw === undefined || raw.trim() === "")
|
|
970
|
+
return 60_000;
|
|
971
|
+
const n = parseNumOrFail("ENGINE_LINGER_MS", raw);
|
|
972
|
+
if (!Number.isSafeInteger(n) || n < ENGINE_LINGER_FLOOR_MS || n > ENGINE_LINGER_CEIL_MS) {
|
|
973
|
+
throw new Error(`env ENGINE_LINGER_MS="${raw}" must be an INTEGER number of MILLISECONDS in ` +
|
|
974
|
+
`[${ENGINE_LINGER_FLOOR_MS}, ${ENGINE_LINGER_CEIL_MS}] (1s … 24h) — it is how long ` +
|
|
975
|
+
`"nobody attached, no run in flight, no HTTP touch" must hold CONTINUOUSLY before the engine self-exits. ` +
|
|
976
|
+
`A value like "60" is the seconds-for-milliseconds slip: a 60ms window kills the engine inside a shell's own ` +
|
|
977
|
+
`restart gap. A value like "1e100" passes a naive "is it a positive integer" check but the elapsed clock can ` +
|
|
978
|
+
`NEVER reach it, which silently turns self-exit off while looking configured. ` +
|
|
979
|
+
`There is no "disable" value here — set ENGINE_AUTO_EXIT=false to turn self-exit off entirely.`);
|
|
980
|
+
}
|
|
981
|
+
return n;
|
|
982
|
+
}
|
|
965
983
|
function parseMemoryEmbedder(ctx) {
|
|
966
984
|
const endpoint = process.env.MEMORY_EMBEDDER_ENDPOINT || undefined;
|
|
967
985
|
const model = process.env.MEMORY_EMBEDDER_MODEL || undefined;
|
|
@@ -1472,6 +1490,8 @@ function parseLimitsHttpDomain() {
|
|
|
1472
1490
|
drainGraceMs: clampEnvWithWarn("DRAIN_GRACE_MS", String(10 * 60 * 1000), LIMITS_HTTP_FLOORS.drainGraceMs),
|
|
1473
1491
|
sighupIdleGraceMs: clampEnvWithWarn("SIGHUP_IDLE_GRACE_MS", String(120_000), 5_000),
|
|
1474
1492
|
...parentPidEnv(),
|
|
1493
|
+
engineAutoExit: boolEnv("ENGINE_AUTO_EXIT", false),
|
|
1494
|
+
engineLingerMs: engineLingerMsEnv(),
|
|
1475
1495
|
rateLimitPerMin: numEnv("RATE_LIMIT_RPM", "0"),
|
|
1476
1496
|
maxTaskCostUsd: numEnv("MAX_TASK_COST_USD", "0"),
|
|
1477
1497
|
maxTaskTokens: numEnv("MAX_TASK_TOKENS", "0"),
|
|
@@ -1667,7 +1687,9 @@ const ORCHESTRATION_GROUP_KEYS = [
|
|
|
1667
1687
|
];
|
|
1668
1688
|
const LIMITS_HTTP_GROUP_KEYS = [
|
|
1669
1689
|
"port", "attachmentOrphanGraceMs", "workspaceFileMaxBytes", "attachmentMaxBytes", "attachmentMimeAllowlist",
|
|
1670
|
-
"attachmentUnboundTtlMs", "infraCostRates", "drainGraceMs", "sighupIdleGraceMs", "parentPid",
|
|
1690
|
+
"attachmentUnboundTtlMs", "infraCostRates", "drainGraceMs", "sighupIdleGraceMs", "parentPid",
|
|
1691
|
+
"engineAutoExit", "engineLingerMs",
|
|
1692
|
+
"rateLimitPerMin", "maxTaskCostUsd",
|
|
1671
1693
|
"maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec", "reapIntervalSec", "runStaleSec", "toolResultTtlSec",
|
|
1672
1694
|
];
|
|
1673
1695
|
const OBSERVABILITY_GROUP_KEYS = ["metricsToken", "traceToken", "toolTrace", "traceThinking", "logLevel", "otel"];
|
|
@@ -7,7 +7,7 @@ export type SubagentTailFrame = Record<string, unknown> & {
|
|
|
7
7
|
type: string;
|
|
8
8
|
};
|
|
9
9
|
/** core forward 事件 → wire 投影(sync 主流五分支同源;不认识的帧类型=null 不发——tail 面只承诺
|
|
10
|
-
* content
|
|
10
|
+
* content 六类,新 core 帧类型先经这里显式收编再出 wire)。 */
|
|
11
11
|
export declare function projectTailFrame(e: Record<string, unknown> & {
|
|
12
12
|
type?: string;
|
|
13
13
|
}): SubagentTailFrame | null;
|
|
@@ -18,6 +18,16 @@ export function projectTailFrame(e) {
|
|
|
18
18
|
const td = e;
|
|
19
19
|
return { type: "text_delta", delta: td.delta, ...(td.eventId ? { eventId: td.eventId } : {}), ...(td.parentToolCallId ? { parentToolCallId: td.parentToolCallId } : {}) };
|
|
20
20
|
}
|
|
21
|
+
if (t === "text_end") {
|
|
22
|
+
if (typeof e.content !== "string" || e.content.length === 0)
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
type: "text_end",
|
|
26
|
+
content: e.content,
|
|
27
|
+
...(typeof e.eventId === "string" ? { eventId: e.eventId } : {}),
|
|
28
|
+
...(typeof e.parentToolCallId === "string" ? { parentToolCallId: e.parentToolCallId } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
21
31
|
if (t === "reasoning_delta") {
|
|
22
32
|
const rd = e;
|
|
23
33
|
return { type: "reasoning_delta", delta: redactSecrets(rd.delta), ...(rd.eventId ? { eventId: rd.eventId } : {}), ...(rd.parentToolCallId ? { parentToolCallId: rd.parentToolCallId } : {}) };
|
package/dist/fleet-client.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 向前合并不会双计 —— 只延迟,不丢量)。
|
|
13
13
|
* - 全部 fire-and-forget + swallow-guarded:fleet 面是运维旁路,绝不影响任务面;网络错误只 warn 一次/连败类。
|
|
14
14
|
*/
|
|
15
|
-
import { UsageReport } from "@sema-agent/
|
|
15
|
+
import { UsageReport } from "@sema-agent/settings-schema/fleet";
|
|
16
16
|
/** Bound on distinct principal×model keys per window — a runaway/multi-tenant burst collapses into
|
|
17
17
|
* `__other__` instead of growing unbounded (same posture as budget.ts cardinalityGuard). */
|
|
18
18
|
export declare const FLEET_USAGE_KEY_CAP = 1024;
|
package/dist/fleet-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InstanceAnnouncement, UsageReport, DEFAULT_HEARTBEAT_MS } from "@sema-agent/
|
|
1
|
+
import { InstanceAnnouncement, UsageReport, DEFAULT_HEARTBEAT_MS } from "@sema-agent/settings-schema/fleet";
|
|
2
2
|
export const FLEET_USAGE_KEY_CAP = 1024;
|
|
3
3
|
export const FLEET_CLIENT_FETCH_TIMEOUT_MS = 5000;
|
|
4
4
|
export class FleetUsageAccumulator {
|
package/dist/fleet-lease.d.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* - 409 判别靠 center 返回的结构化 code(两端常量);center 侧变更时此处同步。
|
|
24
24
|
* - 全部 swallow-guarded:lease 面绝不 fault 任务提交路径。
|
|
25
25
|
*/
|
|
26
|
-
import { QuotaLease } from "@sema-agent/
|
|
26
|
+
import { QuotaLease } from "@sema-agent/settings-schema/fleet";
|
|
27
27
|
/** 负缓存 TTL:非 lease-enforced 的 principal 多久后再问一次 center(entitlement 升级为 lease 的收敛上界)。 */
|
|
28
28
|
export declare const NOT_LEASE_TTL_MS: number;
|
|
29
29
|
/** center 判 budget exhausted 后,多久内的重复提交直接拒绝而不再打 center(防 per-request 打爆)。 */
|
package/dist/fleet-lease.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { QuotaLease, leaseExhausted, shouldRenewLease } from "@sema-agent/
|
|
1
|
+
import { QuotaLease, leaseExhausted, shouldRenewLease } from "@sema-agent/settings-schema/fleet";
|
|
2
2
|
export const NOT_LEASE_TTL_MS = 10 * 60 * 1000;
|
|
3
3
|
export const EXHAUSTED_RETRY_MS = 30 * 1000;
|
|
4
4
|
export const LEASE_PRINCIPAL_CAP = 4096;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HooksConfig } from "@sema-agent/
|
|
1
|
+
import { HooksConfig } from "@sema-agent/settings-schema/hooks";
|
|
2
2
|
import type { Hooks } from "@sema-agent/core";
|
|
3
3
|
import type { Logger } from "../observability/logger.js";
|
|
4
4
|
import type { HookNoticeObservation } from "../fleet/fleet-bus.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { hostShell, resolveHostShell } from "../plugins/host-platform.js";
|
|
3
|
-
import { HooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_EVENT_OWNER, } from "@sema-agent/
|
|
3
|
+
import { HooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_EVENT_OWNER, } from "@sema-agent/settings-schema/hooks";
|
|
4
4
|
import { boundedNoticeText } from "../fleet/fleet-bus.js";
|
|
5
5
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
6
6
|
import { redactSecrets } from "../trace/redact.js";
|