@sema-agent/client-core 0.14.0 → 0.16.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/README.md +5 -3
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +38 -25
- package/dist/adapter/downstream/terminalToSdkResult.js +107 -58
- package/dist/adapter/runStream.js +12 -8
- package/dist/controlRouter.d.ts +11 -0
- package/dist/controlRouter.js +11 -0
- package/dist/detachWire.d.ts +6 -0
- package/dist/detachWire.js +6 -0
- package/dist/finalVerifyWire.d.ts +5 -0
- package/dist/finalVerifyWire.js +5 -0
- package/dist/fleet/fleetLedger.d.ts +9 -0
- package/dist/fleet/fleetLedger.js +10 -1
- package/dist/fleet/fleetProjection.d.ts +13 -1
- package/dist/fleet/fleetProjection.js +13 -1
- package/dist/fleetAgentPanelProjection.js +25 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +23 -0
- package/dist/interactiveToolsWire.d.ts +6 -1
- package/dist/interactiveToolsWire.js +6 -1
- package/dist/limitsWire.d.ts +77 -23
- package/dist/limitsWire.js +108 -35
- package/dist/model/catalogLoader.d.ts +170 -0
- package/dist/model/catalogLoader.js +382 -0
- package/dist/model/providerAuth.d.ts +155 -0
- package/dist/model/providerAuth.js +190 -0
- package/dist/model/providerPresets.d.ts +16 -0
- package/dist/notifications.d.ts +4 -0
- package/dist/notifications.js +134 -13
- package/dist/sandboxWire.d.ts +12 -1
- package/dist/sandboxWire.js +12 -1
- package/dist/scenarioWire.d.ts +12 -1
- package/dist/scenarioWire.js +12 -1
- package/dist/seatContract.js +25 -5
- package/dist/steering.js +11 -2
- package/dist/subagentContentStore.d.ts +13 -0
- package/package.json +1 -1
|
@@ -51,7 +51,20 @@ import { DEFAULT_SESSION_KEY } from './sessionSlot.js';
|
|
|
51
51
|
export const ABSENT_SETTLE_MS = 30_000;
|
|
52
52
|
/** REF-CC-045(fleet2-07):settled 条目的回收期 —— settle 后这么久仍未再被投影 ⇒ 台账整条
|
|
53
53
|
* delete,给 `fleetAgentProjectionSizeFor()` 一个上界(此前 `seen` 只增不减,长会话里每个
|
|
54
|
-
* 跑过的 bg 子代永久占一条)。settle 事件已经发过,消费端 settle 幂等,delete 对外无事件。
|
|
54
|
+
* 跑过的 bg 子代永久占一条)。settle 事件已经发过,消费端 settle 幂等,delete 对外无事件。
|
|
55
|
+
*
|
|
56
|
+
* 🔴 [2393] notif-F13 / sweep-F7(2026-08-03 全窗复审)**两处纠**(判据换轴 + 范围如实):
|
|
57
|
+
* (a) 回收判据从 `lastSeenAt` 换成 **`settledAtMs`**(settle 那一刻的钟)。上一版 ③ 缺席兜底
|
|
58
|
+
* 把条目标 `settled:true` 时**保留旧的 lastSeenAt**,紧接着 ④ 在**同一次调用**里用
|
|
59
|
+
* `nowMs - lastSeenAt > 120s` 判回收 ⇒ 面板卸载超过 120s 再挂回来时,那条行「刚被 settle
|
|
60
|
+
* 就立刻被删」,而 `prev?.settled` 正是「终态 end 只发一次」的**唯一**判据 —— 去重记号随
|
|
61
|
+
* 条目一起消失,同 taskId 的迟到终态帧会再发一次 `fleet-row`+`end`。
|
|
62
|
+
* 换轴之后「settle 后至少保留一个回收期」成为真的。
|
|
63
|
+
* (b) 回收只对**本次投影里已不在场**的条目成立(`!present.has(taskId)`)。仍在场的终态行不删:
|
|
64
|
+
* 删了它下一拍 `prev === undefined` 会**重发 end**,每拍一次,比不删坏得多。
|
|
65
|
+
* ⇒ 上界的如实表述:**不再被投影的条目会被回收**;仍被持续投影的终态行由投影集自身定界
|
|
66
|
+
* (它的大小是引擎/宿主列出来的行数,不是本台账的无界累积)。ledger 里 REF-CC-045 的问题
|
|
67
|
+
* 陈述「只增不减」覆盖的正是前半场,后半场此前被说成也已解决,那句话不成立。 */
|
|
55
68
|
const SETTLED_RETENTION_MS = ABSENT_SETTLE_MS * 4;
|
|
56
69
|
/** currentTool 判等键(POST-MERGE 行每秒重发同值,只有键变了才算变化)。 */
|
|
57
70
|
function currentToolKeyOf(ct) {
|
|
@@ -157,6 +170,8 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
157
170
|
currentToolKey: currentToolKey !== undefined ? currentToolKey : prev?.currentToolKey,
|
|
158
171
|
lastSeenAt: nowMs,
|
|
159
172
|
settled: true,
|
|
173
|
+
// notif-F13:settle 时刻只在**首次** settle 时铸(重复投影的终态行不许一路续命回收期)。
|
|
174
|
+
settledAtMs: prev?.settled ? prev.settledAtMs : nowMs,
|
|
160
175
|
});
|
|
161
176
|
continue;
|
|
162
177
|
}
|
|
@@ -195,6 +210,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
195
210
|
currentToolKey: currentToolKey !== undefined ? currentToolKey : prev?.currentToolKey,
|
|
196
211
|
lastSeenAt: nowMs,
|
|
197
212
|
settled: false,
|
|
213
|
+
settledAtMs: undefined, // 又活了 ⇒ 回收钟归零(重新 settle 时重铸)
|
|
198
214
|
});
|
|
199
215
|
}
|
|
200
216
|
// ③ 缺席兜底 —— 必须在【本次在场行全部刷完 lastSeen 之后】判,否则 footer 长时间卸载后重挂
|
|
@@ -205,12 +221,18 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
205
221
|
if (nowMs - s.lastSeenAt <= ABSENT_SETTLE_MS)
|
|
206
222
|
continue;
|
|
207
223
|
publishEngineAgentPanelEvent({ kind: 'end', taskId, isError: false });
|
|
208
|
-
seenMap.set(taskId, { ...s, settled: true });
|
|
224
|
+
seenMap.set(taskId, { ...s, settled: true, settledAtMs: nowMs }); // notif-F13:settle 时刻按**现在**铸
|
|
209
225
|
}
|
|
210
226
|
// ④ REF-CC-045(fleet2-07):settled 条目回收 —— 给台账一个上界(此前只增不减)。settle 已发过
|
|
211
227
|
// 事件,delete 对外无事件,消费端 settle 本身幂等。
|
|
228
|
+
// [2393] notif-F13/sweep-F7 两处纠(判据见 SETTLED_RETENTION_MS 头注):
|
|
229
|
+
// · 锚 `settledAtMs`(settle 那一刻)而不是 `lastSeenAt`(最后一次被投影);
|
|
230
|
+
// · 只回收**已不在场**的条目 —— 仍在场的终态行删了会每拍重发 end。
|
|
212
231
|
for (const [taskId, s] of seenMap) {
|
|
213
|
-
if (s.settled
|
|
232
|
+
if (!s.settled || present.has(taskId))
|
|
233
|
+
continue;
|
|
234
|
+
const settledAt = s.settledAtMs ?? s.lastSeenAt;
|
|
235
|
+
if (nowMs - settledAt > SETTLED_RETENTION_MS)
|
|
214
236
|
seenMap.delete(taskId);
|
|
215
237
|
}
|
|
216
238
|
if (seenMap.size === 0)
|
package/dist/index.d.ts
CHANGED
|
@@ -223,5 +223,7 @@ export * from './seatContract.js';
|
|
|
223
223
|
export * from './agentSession/contract.js';
|
|
224
224
|
export * from './agentSession/backgroundView.js';
|
|
225
225
|
export * from './model/catalog.js';
|
|
226
|
+
export * from './model/catalogLoader.js';
|
|
227
|
+
export * from './model/providerAuth.js';
|
|
226
228
|
export * from './websearch/searchProviderPresets.js';
|
|
227
229
|
export * from './env/localeGeo.js';
|
package/dist/index.js
CHANGED
|
@@ -153,6 +153,16 @@ export * from './fleetTaskDesc.js';
|
|
|
153
153
|
// 通知族(engineTaskNotification + taskNotificationErrorSupplement + hookNoticeStore 判定半场)
|
|
154
154
|
// 已并入 notifications.js,不需要新增导出行。
|
|
155
155
|
export * from './hostEnv.js';
|
|
156
|
+
// 🔴 [2393] sweep-F14(2026-08-03 全窗复审)**补理由**(裁定:留在公面,不撤回):同一条 P2「I 族」
|
|
157
|
+
// 裁定抽出的三个内部叶里,`envFlag.ts` 明确不出 barrel、`abortableSleep.ts` 明确撤回(见下方两处
|
|
158
|
+
// 说明),只有 `unrefTimer` 在公面上而没写为什么 —— 复审点名的正是这处「同级三件、两撤一留、留的
|
|
159
|
+
// 那个无理由」。理由(补记,不是事后合理化):
|
|
160
|
+
// ① 它与另两件**不同级**:另两件是纯包内实现叶(消费者全在包内,门也改吃 dist 单文件);
|
|
161
|
+
// `unrefTimer` 守的是**跨宿主可移植性不变量**(「浏览器/桌面渲染进程没有 unref 也不炸」),
|
|
162
|
+
// 而定时器是宿主自己也会建的东西 —— 三端宿主(cli/web/desktop)自建保活定时器时需要同一份
|
|
163
|
+
// 判断,让它们各写各的 `(t as any).unref?.()` 正是本函数收编掉的那五份手抄的来源。
|
|
164
|
+
// ② 它已经在 `public-export-baseline.json` 里(0.14.0 起随包发出),撤回 = 公面 BREAKING;
|
|
165
|
+
// 而①说明它本就该公开 ⇒ 没有为一个「本该公开的东西」付破坏性代价的理由。
|
|
156
166
|
export * from './unrefTimer.js';
|
|
157
167
|
// REF-CC-141 dup-02(wave1 卡B,2026-08-01):envFlag.ts 是十处 SEMA_* 布尔 env 旋钮拼写集
|
|
158
168
|
// 单源叶模块,但只被十个已在闭包里的模块值级 import 消费,不需要经 barrel 发到 npm 公面
|
|
@@ -324,6 +334,19 @@ export * from './agentSession/backgroundView.js';
|
|
|
324
334
|
// 🔴 绝不半解析:schemaVersion 超区间/任一行形状坏 ⇒ 整份弃用走兜底(端渲「内置版本(离线)」
|
|
325
335
|
// 靠 `source` + `online.reason`,所以「静默用兜底」在本层是可观测的)。
|
|
326
336
|
export * from './model/catalog.js';
|
|
337
|
+
// ── design/166(2026-08-04):目录客户端消费链 —— 候选链 loader + 供应商凭证双轨 seam ──────────
|
|
338
|
+
// 🔴 `model/catalogLoader.ts` 是 catalog.ts 的**宿主面**,不是第二个解析器:候选链遍历 +
|
|
339
|
+
// 传输硬门(https + 域白名单,**重定向目标过同一道门**)+ `catalog.sha256` 旁签 + 缓存信封,
|
|
340
|
+
// 载荷判决(schemaVersion/逐行形状/整份弃)全部委托 `resolveModelCatalog`,一行不重写。
|
|
341
|
+
// 🔴 缓存**落盘**走注入口 `CatalogCachePort`(本包 index 闭包零 Node 内建是机械门守着的硬法;
|
|
342
|
+
// tmp+rename 的原子性是实现方的契约,参考实现在 catalogLoader.ts 的类型注释里)。
|
|
343
|
+
export * from './model/catalogLoader.js';
|
|
344
|
+
// 🔴 `model/providerAuth.ts` 与 registry SSO(cloudAuth)**物理分文件、零共享代码**
|
|
345
|
+
// ([reuse-single-sso-no-parallel-auth]:它是模型供应商凭证轨,不是 sema 登录轨)。
|
|
346
|
+
// `DEVICE_AUTH_PROVIDERS` 今天是**空表**(宁空勿假:没有我们自己名下的 client_id 之前,
|
|
347
|
+
// 填别的编辑器插件的 app id = 冒用,与 §6 判 C 档出局同一条判据),因此
|
|
348
|
+
// `supportsDeviceCodeAuth` 对每一家如实返回 false、UI 不显示该选项 —— 注册一家 = 加一行。
|
|
349
|
+
export * from './model/providerAuth.js';
|
|
327
350
|
// ── 搜索 provider 目录 + 地域预选(2026-07-31)──────────────────────────────────────────────────
|
|
328
351
|
// `websearch/searchProviderPresets` = model 目录的**同形不同表**姊妹件:数据在 json、类型与查询
|
|
329
352
|
// 在 ts。它编译出来的不是模型目录而是**引擎部署 env**(`WEB_SEARCH_*`)。
|
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
* · SEMA_HEADLESS_INTERACTIVE_TOOLS 部署旋钮(settings env 车道,拼写集 = envFlagOn/envFlagOff、
|
|
20
20
|
* REF-CC-141 dup-02 单源):真值拼写 = 不 stamp(回引擎缺省判据),假值拼写 = 显式 stamp false
|
|
21
21
|
* (与缺省同效,显式垫);非法值一次性 warn + 回缺省;
|
|
22
|
-
*
|
|
22
|
+
* 🔴 [2393] sweep-F9(2026-08-03 全窗复审)**记一笔默认翻面**:dup-02 单源化把真值拼写集从
|
|
23
|
+
* `{true,1}` 扩到 `{true,1,yes,on}`(假值集同批扩到 `{false,0,no,off,none}`)。收编**前**
|
|
24
|
+
* `SEMA_HEADLESS_INTERACTIVE_TOOLS=yes` 落进 warn 分支 ⇒ `{interactiveTools:false}` = **显式 OFF**;
|
|
25
|
+
* 收编**后**它命中真值拼写 ⇒ `{}` = **不 stamp = 由引擎缺省决定**。方向说得通(用户写 yes 就是
|
|
26
|
+
* 要交互工具),但这是 headless `-p` 车道上一次真实的默认翻面,不能只活在拼写集的 diff 里。
|
|
27
|
+
* · 缺省(env 缺席/空串)= stamp false(无人值守正解);
|
|
23
28
|
* · 旧 server 白名单 fail-soft:server resolveSpec 逐字段白名单拼 TaskSpec,未知 body 字段静默
|
|
24
29
|
* 忽略(limits wire 同款勘察结论)——本字段发了白发不炸。gap-check 实拆 server 1.210.0 dist:
|
|
25
30
|
* resolveSpec(main.js:1233-)【不】读 body.interactiveTools ⇒ 需 server 半场补一行白名单,
|
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
* · SEMA_HEADLESS_INTERACTIVE_TOOLS 部署旋钮(settings env 车道,拼写集 = envFlagOn/envFlagOff、
|
|
20
20
|
* REF-CC-141 dup-02 单源):真值拼写 = 不 stamp(回引擎缺省判据),假值拼写 = 显式 stamp false
|
|
21
21
|
* (与缺省同效,显式垫);非法值一次性 warn + 回缺省;
|
|
22
|
-
*
|
|
22
|
+
* 🔴 [2393] sweep-F9(2026-08-03 全窗复审)**记一笔默认翻面**:dup-02 单源化把真值拼写集从
|
|
23
|
+
* `{true,1}` 扩到 `{true,1,yes,on}`(假值集同批扩到 `{false,0,no,off,none}`)。收编**前**
|
|
24
|
+
* `SEMA_HEADLESS_INTERACTIVE_TOOLS=yes` 落进 warn 分支 ⇒ `{interactiveTools:false}` = **显式 OFF**;
|
|
25
|
+
* 收编**后**它命中真值拼写 ⇒ `{}` = **不 stamp = 由引擎缺省决定**。方向说得通(用户写 yes 就是
|
|
26
|
+
* 要交互工具),但这是 headless `-p` 车道上一次真实的默认翻面,不能只活在拼写集的 diff 里。
|
|
27
|
+
* · 缺省(env 缺席/空串)= stamp false(无人值守正解);
|
|
23
28
|
* · 旧 server 白名单 fail-soft:server resolveSpec 逐字段白名单拼 TaskSpec,未知 body 字段静默
|
|
24
29
|
* 忽略(limits wire 同款勘察结论)——本字段发了白发不炸。gap-check 实拆 server 1.210.0 dist:
|
|
25
30
|
* resolveSpec(main.js:1233-)【不】读 body.interactiveTools ⇒ 需 server 半场补一行白名单,
|
package/dist/limitsWire.d.ts
CHANGED
|
@@ -1,28 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/
|
|
3
|
-
*
|
|
4
|
-
* The server accepts `limits:{timeoutSec?,maxOutputTokens?,maxTurns?}` since 1.196.0 (positive-integer
|
|
5
|
-
* 400 gate, board [857]) and feeds the EXISTING pacing machinery (deadlineNudge / callCapByDeadline /
|
|
6
|
-
* gracefulFinalize) — the shell simply never stamped the field. This module is the CLI face.
|
|
2
|
+
* src/limitsWire.ts — headless `-p` run-limits wire: expose the engine's TASK-LEVEL budget face on
|
|
3
|
+
* the headless `-p` path via `TaskRequest.limits`.
|
|
7
4
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* ── core 5.8.0 词表迁移(黑板 [2489],2026-08-04)────────────────────────────────────────────
|
|
6
|
+
* `limits` 是**唯一预算面,默认全空 = 无限制**。本模块拼的三个键都骑 5.8.0 的新词表:
|
|
7
|
+
* · `maxWalltimeMs`(**毫秒**;5.7 的 `limits.timeoutSec` 已删,单位一并变)
|
|
8
|
+
* · `maxTurns`
|
|
9
|
+
* · `maxTokens`(5.8.0 的**主限**——time 退出任务轴之后,token 是首选的预算旋钮)
|
|
10
|
+
* 到限语义也换了:**没有配速机器了**。5.8.0 删掉了 deadlineNudge / callCapByDeadline /
|
|
11
|
+
* gracefulFinalize 那一套(以及引擎侧 1000 轮安全网、fork 200、session-bg 30min 暗注等全部隐式
|
|
12
|
+
* 默认),走到 `maxWalltimeMs` 不再是「引擎自己减速然后优雅收尾」,而是**响亮终局**:
|
|
13
|
+
* `status:'failed'` + `errorCode:'limits.max_walltime_exceeded'`(`status:'timeout'` 这个词整体退役)。
|
|
14
|
+
* 四个新码 `limits.max_{tokens,cost,turns,walltime}_exceeded` 的下游投影见
|
|
15
|
+
* `adapter/downstream/terminalToSdkResult.ts`。
|
|
16
|
+
*
|
|
17
|
+
* ✅ **窗口期已关闭(退役批,2026-08-04)**:交付时点壳的引擎是 server 5.14.0(捆 core 5.7),它
|
|
18
|
+
* **不认识** `maxWalltimeMs`/`maxTokens`(5.7 只认 `timeoutSec`/`maxOutputTokens`/`maxTurns`,未知键
|
|
19
|
+
* 静默丢弃),所以那段时间三个键里只有 `maxTurns` 真的生效。**server 6.0.0 已捆 core 5.8.0 发车**
|
|
20
|
+
* ⇒ 新键集在班车上生效,读面的双代兜底(`adapter/downstream/terminalToSdkResult.ts`)本批一并退役。
|
|
21
|
+
* 写面从头到尾是**单一新形**,不做两代并发(同时发 `timeoutSec` + `maxWalltimeMs` 会在 5.8 引擎上撞
|
|
22
|
+
* `config.limit_unknown_key` 当场失败,那是拿窗口期的一次静默无视换班车到货后的确定性失败)。
|
|
23
|
+
*
|
|
24
|
+
* THREE FLAGS, THREE ALIGNMENT BUCKETS:
|
|
25
|
+
* · `--deadline <sec>` → limits.maxWalltimeMs(用户面仍是**秒**,拼 wire 时 ×1000)。Upstream
|
|
26
|
+
* CC 2.1.207 has NO deadline flag → SUPERSET, help copy says so.
|
|
11
27
|
* · `--max-turns <n>` → limits.maxTurns. Upstream CC 2.1.207 HAS `--max-turns` (source 199676 /
|
|
12
28
|
* 793380) → ALIGNMENT-GAP fill: the flag was already registered (main.tsx CC-inherited surface) and
|
|
13
29
|
* drove the local query loop, but the seam path executes turns ENGINE-side — this wire finally
|
|
14
30
|
* carries the cap to where the turns actually run.
|
|
31
|
+
* · `--max-tokens <n>` → limits.maxTokens(5.8.0 主推键)。Upstream CC 2.1.207 has no run-level
|
|
32
|
+
* token budget flag → SUPERSET.
|
|
15
33
|
*
|
|
16
34
|
* PRECEDENCE per knob (flag > settings > none), scenarioWire 三件套同款:
|
|
17
35
|
* a) explicit flag → strict validation, FAIL-LOUD on an invalid
|
|
18
|
-
* value (integer domain below; a mis-typed
|
|
36
|
+
* value (integer domain below; a mis-typed budget bound must not quietly run unbounded).
|
|
19
37
|
* b) settings env lane (settings.json `env` block → 1a-envseed → process.env):
|
|
20
|
-
* `SEMA_HEADLESS_DEADLINE_SEC` / `SEMA_HEADLESS_MAX_TURNS`
|
|
21
|
-
* (spec'd 静默降级 — a settings typo must never brick every
|
|
22
|
-
* don't even warn, per the batch-E triage shape. SEMA_DEBUG
|
|
38
|
+
* `SEMA_HEADLESS_DEADLINE_SEC` / `SEMA_HEADLESS_MAX_TURNS` / `SEMA_HEADLESS_MAX_TOKENS`
|
|
39
|
+
* → SILENT degrade on an invalid value (spec'd 静默降级 — a settings typo must never brick every
|
|
40
|
+
* headless run; unlike scenarioWire we don't even warn, per the batch-E triage shape. SEMA_DEBUG
|
|
41
|
+
* surfaces the drop for diagnosis).
|
|
23
42
|
* c) neither → NO stamp = today's behaviour, verbatim.
|
|
24
43
|
*
|
|
25
|
-
* DOMAINS (integer, inclusive):
|
|
44
|
+
* DOMAINS (integer, inclusive): deadline 30..86400 **秒**(→ 30_000..86_400_000 ms 上 wire)·
|
|
45
|
+
* maxTurns / maxTokens = **正整数**(上界 = JS 安全整数)。maxTurns 此前钉 1..1000,那个上界抄的是
|
|
46
|
+
* 引擎侧 1000 轮安全网;5.8.0 把该安全网整体退役了,壳再替它守一个不存在的墙就是凭空拒绝合法配置。
|
|
26
47
|
*
|
|
27
48
|
* VERSION GATE (triage risk ③ — an OLD server SILENTLY SWALLOWS unknown body fields, worse than not
|
|
28
49
|
* shipping): the self-spawned engine is release-pinned ≥1.197.0 so the dev/release lanes are safe by
|
|
@@ -31,21 +52,53 @@
|
|
|
31
52
|
* probes `${baseUrl}/health` (the engine self-describes `version` since 1.96) and prints ONE honest
|
|
32
53
|
* stderr line when version <1.196.0 — the request still goes out unchanged (warn, don't block).
|
|
33
54
|
* Unreachable /health or an unparsable version stays silent (can't confirm staleness — fail-soft).
|
|
55
|
+
*
|
|
56
|
+
* 📐 **两道地板,守的不是同一件事**(退役批补记,2026-08-04):
|
|
57
|
+
* · 第一地板 `LIMITS_WIRE_MIN_ENGINE = 1.196.0` = 「`limits` 这个**字段**存不存在」(server 1.196.0
|
|
58
|
+
* 起接受 body.limits);低于它,整个预算面被丢。
|
|
59
|
+
* · 第二地板 **server ≥ 6.0.0** = 「**新键集**认不认」。6.0.0 是第一个捆 core 5.8.0 的 server 版本,
|
|
60
|
+
* 也就是第一个认识 `maxWalltimeMs`/`maxTokens` 的版本(5.7 只认 `timeoutSec`/`maxOutputTokens`,
|
|
61
|
+
* 未知键静默丢弃 —— `config.limit_unknown_key` 那种响亮拒是 5.8.0 才有的)。所以对 1.196.0 ≤
|
|
62
|
+
* version < 6.0.0 的引擎,`maxTurns` 生效而 `maxWalltimeMs`/`maxTokens` 会被**静默无视**。
|
|
63
|
+
* 之所以过去写不出这条:/health 自报的是 server 版本,而键名代际取决于它捆的 core 版本,当时
|
|
64
|
+
* 两者没有可从 wire 读出的映射;6.0.0 这班车把映射钉死了(6.0.0 ⇒ core ≥5.8),这条地板才成立。
|
|
65
|
+
* · 本文件的运行期探针目前只对第一地板发声(warn, don't block);第二地板先落成契约事实写在这里,
|
|
66
|
+
* 要不要给它也配一条 stderr 提示是**行为面**决定,按宪法三问单独走,不在退役批里顺手加。
|
|
34
67
|
*/
|
|
35
68
|
import { type EnvLike } from './hostEnv.js';
|
|
36
69
|
/** Settings-lane env knobs (settings.json `env` block → 1a-envseed → process.env). */
|
|
37
70
|
export declare const HEADLESS_DEADLINE_ENV = "SEMA_HEADLESS_DEADLINE_SEC";
|
|
38
71
|
export declare const HEADLESS_MAX_TURNS_ENV = "SEMA_HEADLESS_MAX_TURNS";
|
|
39
|
-
|
|
72
|
+
export declare const HEADLESS_MAX_TOKENS_ENV = "SEMA_HEADLESS_MAX_TOKENS";
|
|
73
|
+
/** `--deadline` 的**用户面**整数域(秒;30s..24h)。上 wire 前 ×1000 换成 `maxWalltimeMs`。 */
|
|
40
74
|
export declare const DEADLINE_SEC_MIN = 30;
|
|
41
75
|
export declare const DEADLINE_SEC_MAX = 86400;
|
|
76
|
+
/**
|
|
77
|
+
* `maxTurns` / `maxTokens` 的整数域 = **正整数**(上界只到 JS 安全整数,再大就不是「一个精确的
|
|
78
|
+
* 轮数/token 数」而是浮点近似了 —— 那种值静默夹取比拒绝更坏)。
|
|
79
|
+
*
|
|
80
|
+
* `MAX_TURNS_MAX` 此前是 `1_000`,抄的是引擎侧的 1000 轮安全网;core 5.8.0 把那张网**整体退役**
|
|
81
|
+
* (「零配置任务现在没有任何任务级兜底」,[2489]),壳再守它 = 替一堵不存在的墙拒绝合法配置。
|
|
82
|
+
* 常量名保留(它仍然是这个域的上界,只是值换成了真上界),消费者的 help 文案请按「正整数」写。
|
|
83
|
+
*/
|
|
42
84
|
export declare const MAX_TURNS_MIN = 1;
|
|
43
|
-
export declare const MAX_TURNS_MAX
|
|
85
|
+
export declare const MAX_TURNS_MAX: number;
|
|
86
|
+
export declare const MAX_TOKENS_MIN = 1;
|
|
87
|
+
export declare const MAX_TOKENS_MAX: number;
|
|
44
88
|
/** First server version whose /v1/tasks|/v1/tasks/stream accept body.limits (board [857]). */
|
|
45
89
|
export declare const LIMITS_WIRE_MIN_ENGINE: readonly [number, number, number];
|
|
90
|
+
/**
|
|
91
|
+
* `TaskRequest.limits` 的壳侧子集(core 5.8.0 词表)。引擎那份还有
|
|
92
|
+
* `maxCostUsd`/`maxOutputTokens`/`approachNotice`/`degrade`/… —— 本模块只拼 CLI flag 暴露的三个键,
|
|
93
|
+
* 其余键由别的配置面负责,别在这里悄悄补默认值(默认全空 = 无限制,是 5.8.0 的显式设计)。
|
|
94
|
+
*/
|
|
46
95
|
export interface HeadlessLimits {
|
|
47
|
-
|
|
96
|
+
/** 墙钟预算,**毫秒**(用户面 `--deadline` 是秒)。到限 ⇒ failed + `limits.max_walltime_exceeded`。 */
|
|
97
|
+
maxWalltimeMs?: number;
|
|
98
|
+
/** 轮数预算。到限 ⇒ failed + `limits.max_turns_exceeded`。 */
|
|
48
99
|
maxTurns?: number;
|
|
100
|
+
/** token 预算(5.8.0 主限)。到限 ⇒ failed + `limits.max_tokens_exceeded`(result 带已产出正文)。 */
|
|
101
|
+
maxTokens?: number;
|
|
49
102
|
}
|
|
50
103
|
export type LimitsParseResult = {
|
|
51
104
|
ok: true;
|
|
@@ -56,22 +109,23 @@ export type LimitsParseResult = {
|
|
|
56
109
|
error: string;
|
|
57
110
|
};
|
|
58
111
|
/**
|
|
59
|
-
* Parse `--deadline` + `--max-turns` out of an argv slice. No flags ⇒ `{ok:true}`
|
|
60
|
-
* Repeated flag ⇒ LAST wins (commander convention). Missing value / non-integer /
|
|
61
|
-
* fail-LOUD parse error (never a silent default — same stance as
|
|
62
|
-
* `flaggedFlags` names the flags that EXPLICITLY contributed (the
|
|
112
|
+
* Parse `--deadline` + `--max-turns` + `--max-tokens` out of an argv slice. No flags ⇒ `{ok:true}`
|
|
113
|
+
* with no limits. Repeated flag ⇒ LAST wins (commander convention). Missing value / non-integer /
|
|
114
|
+
* out-of-domain ⇒ fail-LOUD parse error (never a silent default — same stance as
|
|
115
|
+
* `--scenario`/`--sandbox`). `flaggedFlags` names the flags that EXPLICITLY contributed (the
|
|
116
|
+
* version-gate probe trigger).
|
|
63
117
|
*/
|
|
64
118
|
export declare function parseLimitsArgv(argv: string[]): LimitsParseResult;
|
|
65
119
|
/**
|
|
66
120
|
* The settings-lane defaults. SILENT degrade on a bad value (batch-E triage shape — stricter than
|
|
67
|
-
* scenarioWire's warn-and-ignore:
|
|
68
|
-
*
|
|
121
|
+
* scenarioWire's warn-and-ignore: a settings typo must neither brick nor spam every headless run).
|
|
122
|
+
* SEMA_DEBUG surfaces the drop.
|
|
69
123
|
*/
|
|
70
124
|
export declare function headlessLimitsFromEnv(env?: EnvLike): HeadlessLimits | undefined;
|
|
71
125
|
/**
|
|
72
126
|
* Resolve the limits for a headless `-p` submit: explicit flags > settings env defaults > none
|
|
73
127
|
* (= no stamp, today's behaviour). Merge is PER-KNOB: `--deadline 300` + SEMA_HEADLESS_MAX_TURNS=50
|
|
74
|
-
* yields {
|
|
128
|
+
* yields {maxWalltimeMs:300000, maxTurns:50}. Flag parse errors propagate fail-loud; the caller exits.
|
|
75
129
|
*/
|
|
76
130
|
export declare function limitsForPrint(argv: string[], env?: EnvLike): LimitsParseResult;
|
|
77
131
|
/** True when the engine version string satisfies the limits-wire minimum (≥1.196.0). Unparsable ⇒ false. */
|
package/dist/limitsWire.js
CHANGED
|
@@ -1,28 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/
|
|
3
|
-
*
|
|
4
|
-
* The server accepts `limits:{timeoutSec?,maxOutputTokens?,maxTurns?}` since 1.196.0 (positive-integer
|
|
5
|
-
* 400 gate, board [857]) and feeds the EXISTING pacing machinery (deadlineNudge / callCapByDeadline /
|
|
6
|
-
* gracefulFinalize) — the shell simply never stamped the field. This module is the CLI face.
|
|
2
|
+
* src/limitsWire.ts — headless `-p` run-limits wire: expose the engine's TASK-LEVEL budget face on
|
|
3
|
+
* the headless `-p` path via `TaskRequest.limits`.
|
|
7
4
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* ── core 5.8.0 词表迁移(黑板 [2489],2026-08-04)────────────────────────────────────────────
|
|
6
|
+
* `limits` 是**唯一预算面,默认全空 = 无限制**。本模块拼的三个键都骑 5.8.0 的新词表:
|
|
7
|
+
* · `maxWalltimeMs`(**毫秒**;5.7 的 `limits.timeoutSec` 已删,单位一并变)
|
|
8
|
+
* · `maxTurns`
|
|
9
|
+
* · `maxTokens`(5.8.0 的**主限**——time 退出任务轴之后,token 是首选的预算旋钮)
|
|
10
|
+
* 到限语义也换了:**没有配速机器了**。5.8.0 删掉了 deadlineNudge / callCapByDeadline /
|
|
11
|
+
* gracefulFinalize 那一套(以及引擎侧 1000 轮安全网、fork 200、session-bg 30min 暗注等全部隐式
|
|
12
|
+
* 默认),走到 `maxWalltimeMs` 不再是「引擎自己减速然后优雅收尾」,而是**响亮终局**:
|
|
13
|
+
* `status:'failed'` + `errorCode:'limits.max_walltime_exceeded'`(`status:'timeout'` 这个词整体退役)。
|
|
14
|
+
* 四个新码 `limits.max_{tokens,cost,turns,walltime}_exceeded` 的下游投影见
|
|
15
|
+
* `adapter/downstream/terminalToSdkResult.ts`。
|
|
16
|
+
*
|
|
17
|
+
* ✅ **窗口期已关闭(退役批,2026-08-04)**:交付时点壳的引擎是 server 5.14.0(捆 core 5.7),它
|
|
18
|
+
* **不认识** `maxWalltimeMs`/`maxTokens`(5.7 只认 `timeoutSec`/`maxOutputTokens`/`maxTurns`,未知键
|
|
19
|
+
* 静默丢弃),所以那段时间三个键里只有 `maxTurns` 真的生效。**server 6.0.0 已捆 core 5.8.0 发车**
|
|
20
|
+
* ⇒ 新键集在班车上生效,读面的双代兜底(`adapter/downstream/terminalToSdkResult.ts`)本批一并退役。
|
|
21
|
+
* 写面从头到尾是**单一新形**,不做两代并发(同时发 `timeoutSec` + `maxWalltimeMs` 会在 5.8 引擎上撞
|
|
22
|
+
* `config.limit_unknown_key` 当场失败,那是拿窗口期的一次静默无视换班车到货后的确定性失败)。
|
|
23
|
+
*
|
|
24
|
+
* THREE FLAGS, THREE ALIGNMENT BUCKETS:
|
|
25
|
+
* · `--deadline <sec>` → limits.maxWalltimeMs(用户面仍是**秒**,拼 wire 时 ×1000)。Upstream
|
|
26
|
+
* CC 2.1.207 has NO deadline flag → SUPERSET, help copy says so.
|
|
11
27
|
* · `--max-turns <n>` → limits.maxTurns. Upstream CC 2.1.207 HAS `--max-turns` (source 199676 /
|
|
12
28
|
* 793380) → ALIGNMENT-GAP fill: the flag was already registered (main.tsx CC-inherited surface) and
|
|
13
29
|
* drove the local query loop, but the seam path executes turns ENGINE-side — this wire finally
|
|
14
30
|
* carries the cap to where the turns actually run.
|
|
31
|
+
* · `--max-tokens <n>` → limits.maxTokens(5.8.0 主推键)。Upstream CC 2.1.207 has no run-level
|
|
32
|
+
* token budget flag → SUPERSET.
|
|
15
33
|
*
|
|
16
34
|
* PRECEDENCE per knob (flag > settings > none), scenarioWire 三件套同款:
|
|
17
35
|
* a) explicit flag → strict validation, FAIL-LOUD on an invalid
|
|
18
|
-
* value (integer domain below; a mis-typed
|
|
36
|
+
* value (integer domain below; a mis-typed budget bound must not quietly run unbounded).
|
|
19
37
|
* b) settings env lane (settings.json `env` block → 1a-envseed → process.env):
|
|
20
|
-
* `SEMA_HEADLESS_DEADLINE_SEC` / `SEMA_HEADLESS_MAX_TURNS`
|
|
21
|
-
* (spec'd 静默降级 — a settings typo must never brick every
|
|
22
|
-
* don't even warn, per the batch-E triage shape. SEMA_DEBUG
|
|
38
|
+
* `SEMA_HEADLESS_DEADLINE_SEC` / `SEMA_HEADLESS_MAX_TURNS` / `SEMA_HEADLESS_MAX_TOKENS`
|
|
39
|
+
* → SILENT degrade on an invalid value (spec'd 静默降级 — a settings typo must never brick every
|
|
40
|
+
* headless run; unlike scenarioWire we don't even warn, per the batch-E triage shape. SEMA_DEBUG
|
|
41
|
+
* surfaces the drop for diagnosis).
|
|
23
42
|
* c) neither → NO stamp = today's behaviour, verbatim.
|
|
24
43
|
*
|
|
25
|
-
* DOMAINS (integer, inclusive):
|
|
44
|
+
* DOMAINS (integer, inclusive): deadline 30..86400 **秒**(→ 30_000..86_400_000 ms 上 wire)·
|
|
45
|
+
* maxTurns / maxTokens = **正整数**(上界 = JS 安全整数)。maxTurns 此前钉 1..1000,那个上界抄的是
|
|
46
|
+
* 引擎侧 1000 轮安全网;5.8.0 把该安全网整体退役了,壳再替它守一个不存在的墙就是凭空拒绝合法配置。
|
|
26
47
|
*
|
|
27
48
|
* VERSION GATE (triage risk ③ — an OLD server SILENTLY SWALLOWS unknown body fields, worse than not
|
|
28
49
|
* shipping): the self-spawned engine is release-pinned ≥1.197.0 so the dev/release lanes are safe by
|
|
@@ -31,34 +52,63 @@
|
|
|
31
52
|
* probes `${baseUrl}/health` (the engine self-describes `version` since 1.96) and prints ONE honest
|
|
32
53
|
* stderr line when version <1.196.0 — the request still goes out unchanged (warn, don't block).
|
|
33
54
|
* Unreachable /health or an unparsable version stays silent (can't confirm staleness — fail-soft).
|
|
55
|
+
*
|
|
56
|
+
* 📐 **两道地板,守的不是同一件事**(退役批补记,2026-08-04):
|
|
57
|
+
* · 第一地板 `LIMITS_WIRE_MIN_ENGINE = 1.196.0` = 「`limits` 这个**字段**存不存在」(server 1.196.0
|
|
58
|
+
* 起接受 body.limits);低于它,整个预算面被丢。
|
|
59
|
+
* · 第二地板 **server ≥ 6.0.0** = 「**新键集**认不认」。6.0.0 是第一个捆 core 5.8.0 的 server 版本,
|
|
60
|
+
* 也就是第一个认识 `maxWalltimeMs`/`maxTokens` 的版本(5.7 只认 `timeoutSec`/`maxOutputTokens`,
|
|
61
|
+
* 未知键静默丢弃 —— `config.limit_unknown_key` 那种响亮拒是 5.8.0 才有的)。所以对 1.196.0 ≤
|
|
62
|
+
* version < 6.0.0 的引擎,`maxTurns` 生效而 `maxWalltimeMs`/`maxTokens` 会被**静默无视**。
|
|
63
|
+
* 之所以过去写不出这条:/health 自报的是 server 版本,而键名代际取决于它捆的 core 版本,当时
|
|
64
|
+
* 两者没有可从 wire 读出的映射;6.0.0 这班车把映射钉死了(6.0.0 ⇒ core ≥5.8),这条地板才成立。
|
|
65
|
+
* · 本文件的运行期探针目前只对第一地板发声(warn, don't block);第二地板先落成契约事实写在这里,
|
|
66
|
+
* 要不要给它也配一条 stderr 提示是**行为面**决定,按宪法三问单独走,不在退役批里顺手加。
|
|
34
67
|
*/
|
|
35
68
|
import { hostEnv } from './hostEnv.js';
|
|
36
69
|
import { lastFlagValue } from './argvFlagValue.js';
|
|
37
70
|
/** Settings-lane env knobs (settings.json `env` block → 1a-envseed → process.env). */
|
|
38
71
|
export const HEADLESS_DEADLINE_ENV = 'SEMA_HEADLESS_DEADLINE_SEC';
|
|
39
72
|
export const HEADLESS_MAX_TURNS_ENV = 'SEMA_HEADLESS_MAX_TURNS';
|
|
40
|
-
|
|
73
|
+
export const HEADLESS_MAX_TOKENS_ENV = 'SEMA_HEADLESS_MAX_TOKENS';
|
|
74
|
+
/** `--deadline` 的**用户面**整数域(秒;30s..24h)。上 wire 前 ×1000 换成 `maxWalltimeMs`。 */
|
|
41
75
|
export const DEADLINE_SEC_MIN = 30;
|
|
42
76
|
export const DEADLINE_SEC_MAX = 86_400;
|
|
77
|
+
/**
|
|
78
|
+
* `maxTurns` / `maxTokens` 的整数域 = **正整数**(上界只到 JS 安全整数,再大就不是「一个精确的
|
|
79
|
+
* 轮数/token 数」而是浮点近似了 —— 那种值静默夹取比拒绝更坏)。
|
|
80
|
+
*
|
|
81
|
+
* `MAX_TURNS_MAX` 此前是 `1_000`,抄的是引擎侧的 1000 轮安全网;core 5.8.0 把那张网**整体退役**
|
|
82
|
+
* (「零配置任务现在没有任何任务级兜底」,[2489]),壳再守它 = 替一堵不存在的墙拒绝合法配置。
|
|
83
|
+
* 常量名保留(它仍然是这个域的上界,只是值换成了真上界),消费者的 help 文案请按「正整数」写。
|
|
84
|
+
*/
|
|
43
85
|
export const MAX_TURNS_MIN = 1;
|
|
44
|
-
export const MAX_TURNS_MAX =
|
|
86
|
+
export const MAX_TURNS_MAX = Number.MAX_SAFE_INTEGER;
|
|
87
|
+
export const MAX_TOKENS_MIN = 1;
|
|
88
|
+
export const MAX_TOKENS_MAX = Number.MAX_SAFE_INTEGER;
|
|
89
|
+
/** 用户面秒 → wire 毫秒(core 5.8.0 `limits.maxWalltimeMs` 的单位)。 */
|
|
90
|
+
const SEC_TO_MS = 1_000;
|
|
45
91
|
/** First server version whose /v1/tasks|/v1/tasks/stream accept body.limits (board [857]). */
|
|
46
92
|
export const LIMITS_WIRE_MIN_ENGINE = [1, 196, 0];
|
|
47
93
|
const DEADLINE_USAGE = `sema: --deadline expects a whole number of seconds between ${DEADLINE_SEC_MIN} and ${DEADLINE_SEC_MAX}.\n` +
|
|
48
|
-
' --deadline <sec>
|
|
49
|
-
' the
|
|
50
|
-
' (no flag) no
|
|
51
|
-
const MAX_TURNS_USAGE =
|
|
94
|
+
' --deadline <sec> wall-clock budget for this run (sema superset — no upstream equivalent):\n' +
|
|
95
|
+
' reaching it ends the run LOUDLY (failed / limits.max_walltime_exceeded)\n' +
|
|
96
|
+
' (no flag) no wall-clock budget — today\'s behaviour';
|
|
97
|
+
const MAX_TURNS_USAGE = 'sema: --max-turns expects a positive whole number of turns.\n' +
|
|
52
98
|
' --max-turns <n> maximum number of agentic turns for this run (upstream-aligned flag,\n' +
|
|
53
99
|
' enforced engine-side on the sema seam)\n' +
|
|
54
100
|
' (no flag) no turn cap — today\'s behaviour';
|
|
101
|
+
const MAX_TOKENS_USAGE = 'sema: --max-tokens expects a positive whole number of tokens.\n' +
|
|
102
|
+
' --max-tokens <n> token budget for this run (sema superset — no upstream equivalent;\n' +
|
|
103
|
+
' the engine\'s primary run-level budget knob since core 5.8.0)\n' +
|
|
104
|
+
' (no flag) no token budget — today\'s behaviour';
|
|
55
105
|
/** STRICT integer shape for explicit flag values: ASCII digits only (no sign/decimal/exponent/space). */
|
|
56
106
|
const INT_RE = /^\d+$/;
|
|
57
107
|
// `lastFlagValue` (the `--name <v>`/`--name=<v>`/repeated-flag-last-wins scan) now lives in
|
|
58
108
|
// argvFlagValue.ts (REF-CC-143/dup-07, P3 wave1 卡C-argv, 2026-08-01) — this was the fullest of five
|
|
59
109
|
// hand-rolled copies (present/raw/valueMissing three-state) and is the one the shared leaf was cribbed
|
|
60
|
-
// from verbatim; see that file's header for the shared 缺值 contract.
|
|
61
|
-
//
|
|
110
|
+
// from verbatim; see that file's header for the shared 缺值 contract. All three flags are
|
|
111
|
+
// all-positive domains, so a NEGATIVE number (which looks flag-shaped to the scanner) correctly
|
|
62
112
|
// falls into the same fail-loud usage-hint path as a genuinely missing value below.
|
|
63
113
|
/** Validate a RAW flag value against an inclusive integer domain. Returns the number or null. */
|
|
64
114
|
function parseIntInDomain(raw, min, max) {
|
|
@@ -70,11 +120,16 @@ function parseIntInDomain(raw, min, max) {
|
|
|
70
120
|
return null;
|
|
71
121
|
return n;
|
|
72
122
|
}
|
|
123
|
+
/** 三个键任一在场 ⇒ 值得 stamp(空对象绝不上 wire —— 那是「配了个空预算」不是「没配」)。 */
|
|
124
|
+
function hasAnyLimit(l) {
|
|
125
|
+
return l.maxWalltimeMs !== undefined || l.maxTurns !== undefined || l.maxTokens !== undefined;
|
|
126
|
+
}
|
|
73
127
|
/**
|
|
74
|
-
* Parse `--deadline` + `--max-turns` out of an argv slice. No flags ⇒ `{ok:true}`
|
|
75
|
-
* Repeated flag ⇒ LAST wins (commander convention). Missing value / non-integer /
|
|
76
|
-
* fail-LOUD parse error (never a silent default — same stance as
|
|
77
|
-
* `flaggedFlags` names the flags that EXPLICITLY contributed (the
|
|
128
|
+
* Parse `--deadline` + `--max-turns` + `--max-tokens` out of an argv slice. No flags ⇒ `{ok:true}`
|
|
129
|
+
* with no limits. Repeated flag ⇒ LAST wins (commander convention). Missing value / non-integer /
|
|
130
|
+
* out-of-domain ⇒ fail-LOUD parse error (never a silent default — same stance as
|
|
131
|
+
* `--scenario`/`--sandbox`). `flaggedFlags` names the flags that EXPLICITLY contributed (the
|
|
132
|
+
* version-gate probe trigger).
|
|
78
133
|
*/
|
|
79
134
|
export function parseLimitsArgv(argv) {
|
|
80
135
|
const limits = {};
|
|
@@ -84,7 +139,8 @@ export function parseLimitsArgv(argv) {
|
|
|
84
139
|
const n = dl.raw !== undefined ? parseIntInDomain(dl.raw, DEADLINE_SEC_MIN, DEADLINE_SEC_MAX) : null;
|
|
85
140
|
if (n === null)
|
|
86
141
|
return { ok: false, error: DEADLINE_USAGE };
|
|
87
|
-
|
|
142
|
+
// 用户面是秒,wire 面是毫秒(core 5.8.0 `maxWalltimeMs`)—— 换算只在这一个方向、这一个点做。
|
|
143
|
+
limits.maxWalltimeMs = n * SEC_TO_MS;
|
|
88
144
|
flaggedFlags.push('--deadline');
|
|
89
145
|
}
|
|
90
146
|
const mt = lastFlagValue(argv, '--max-turns');
|
|
@@ -95,6 +151,14 @@ export function parseLimitsArgv(argv) {
|
|
|
95
151
|
limits.maxTurns = n;
|
|
96
152
|
flaggedFlags.push('--max-turns');
|
|
97
153
|
}
|
|
154
|
+
const mk = lastFlagValue(argv, '--max-tokens');
|
|
155
|
+
if (mk.present) {
|
|
156
|
+
const n = mk.raw !== undefined ? parseIntInDomain(mk.raw, MAX_TOKENS_MIN, MAX_TOKENS_MAX) : null;
|
|
157
|
+
if (n === null)
|
|
158
|
+
return { ok: false, error: MAX_TOKENS_USAGE };
|
|
159
|
+
limits.maxTokens = n;
|
|
160
|
+
flaggedFlags.push('--max-tokens');
|
|
161
|
+
}
|
|
98
162
|
return {
|
|
99
163
|
ok: true,
|
|
100
164
|
...(flaggedFlags.length > 0 ? { limits } : {}),
|
|
@@ -103,8 +167,8 @@ export function parseLimitsArgv(argv) {
|
|
|
103
167
|
}
|
|
104
168
|
/**
|
|
105
169
|
* The settings-lane defaults. SILENT degrade on a bad value (batch-E triage shape — stricter than
|
|
106
|
-
* scenarioWire's warn-and-ignore:
|
|
107
|
-
*
|
|
170
|
+
* scenarioWire's warn-and-ignore: a settings typo must neither brick nor spam every headless run).
|
|
171
|
+
* SEMA_DEBUG surfaces the drop.
|
|
108
172
|
*/
|
|
109
173
|
export function headlessLimitsFromEnv(env = hostEnv()) {
|
|
110
174
|
const out = {};
|
|
@@ -112,7 +176,7 @@ export function headlessLimitsFromEnv(env = hostEnv()) {
|
|
|
112
176
|
if (dl) {
|
|
113
177
|
const n = parseIntInDomain(dl, DEADLINE_SEC_MIN, DEADLINE_SEC_MAX);
|
|
114
178
|
if (n !== null)
|
|
115
|
-
out.
|
|
179
|
+
out.maxWalltimeMs = n * SEC_TO_MS; // env 面同样是秒(变量名带 _SEC),换算同一口
|
|
116
180
|
else if (env.SEMA_DEBUG) {
|
|
117
181
|
// eslint-disable-next-line no-console
|
|
118
182
|
console.error(`[sema] ${HEADLESS_DEADLINE_ENV}="${dl}" is not an integer in ${DEADLINE_SEC_MIN}..${DEADLINE_SEC_MAX} — ignoring the settings default`);
|
|
@@ -125,15 +189,25 @@ export function headlessLimitsFromEnv(env = hostEnv()) {
|
|
|
125
189
|
out.maxTurns = n;
|
|
126
190
|
else if (env.SEMA_DEBUG) {
|
|
127
191
|
// eslint-disable-next-line no-console
|
|
128
|
-
console.error(`[sema] ${HEADLESS_MAX_TURNS_ENV}="${mt}" is not
|
|
192
|
+
console.error(`[sema] ${HEADLESS_MAX_TURNS_ENV}="${mt}" is not a positive integer — ignoring the settings default`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const mk = env[HEADLESS_MAX_TOKENS_ENV]?.trim();
|
|
196
|
+
if (mk) {
|
|
197
|
+
const n = parseIntInDomain(mk, MAX_TOKENS_MIN, MAX_TOKENS_MAX);
|
|
198
|
+
if (n !== null)
|
|
199
|
+
out.maxTokens = n;
|
|
200
|
+
else if (env.SEMA_DEBUG) {
|
|
201
|
+
// eslint-disable-next-line no-console
|
|
202
|
+
console.error(`[sema] ${HEADLESS_MAX_TOKENS_ENV}="${mk}" is not a positive integer — ignoring the settings default`);
|
|
129
203
|
}
|
|
130
204
|
}
|
|
131
|
-
return out
|
|
205
|
+
return hasAnyLimit(out) ? out : undefined;
|
|
132
206
|
}
|
|
133
207
|
/**
|
|
134
208
|
* Resolve the limits for a headless `-p` submit: explicit flags > settings env defaults > none
|
|
135
209
|
* (= no stamp, today's behaviour). Merge is PER-KNOB: `--deadline 300` + SEMA_HEADLESS_MAX_TURNS=50
|
|
136
|
-
* yields {
|
|
210
|
+
* yields {maxWalltimeMs:300000, maxTurns:50}. Flag parse errors propagate fail-loud; the caller exits.
|
|
137
211
|
*/
|
|
138
212
|
export function limitsForPrint(argv, env = hostEnv()) {
|
|
139
213
|
const parsed = parseLimitsArgv(argv);
|
|
@@ -141,8 +215,7 @@ export function limitsForPrint(argv, env = hostEnv()) {
|
|
|
141
215
|
return parsed;
|
|
142
216
|
const fromEnv = headlessLimitsFromEnv(env);
|
|
143
217
|
const merged = { ...(fromEnv ?? {}), ...(parsed.limits ?? {}) };
|
|
144
|
-
|
|
145
|
-
return { ok: true, ...(any ? { limits: merged } : {}), flaggedFlags: parsed.flaggedFlags };
|
|
218
|
+
return { ok: true, ...(hasAnyLimit(merged) ? { limits: merged } : {}), flaggedFlags: parsed.flaggedFlags };
|
|
146
219
|
}
|
|
147
220
|
/** True when the engine version string satisfies the limits-wire minimum (≥1.196.0). Unparsable ⇒ false. */
|
|
148
221
|
export function versionSupportsLimits(v) {
|
|
@@ -180,7 +253,7 @@ export async function engineLimitsSupportWarning(baseUrl, flaggedFlags, opts) {
|
|
|
180
253
|
// No version field = pre-1.96 engine — definitely older than 1.196.0, warn honestly.
|
|
181
254
|
if (versionSupportsLimits(version))
|
|
182
255
|
return undefined;
|
|
183
|
-
const flags = flaggedFlags.length > 0 ? flaggedFlags.join('/') : '--deadline/--max-turns';
|
|
256
|
+
const flags = flaggedFlags.length > 0 ? flaggedFlags.join('/') : '--deadline/--max-turns/--max-tokens';
|
|
184
257
|
return `[sema] engine ${version ?? '(unversioned, pre-1.96)'} does not support ${flags} (needs ≥1.196.0); the flag will be ignored by the engine`;
|
|
185
258
|
}
|
|
186
259
|
catch {
|