@sema-agent/server 7.52.0 → 7.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/USAGE.md CHANGED
@@ -884,6 +884,13 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
884
884
  > 且**状态变更与审计行同一个事务**(不存在"解冻了但账上没有")。
885
885
  > - `GET /v1/ops/retention/audit?domain=&limit=&before=` —— 审计读面(keyset 分页,游标形同名册面)。
886
886
  > - 能力位 `capabilities.retention` = `{mode, maxAgeDays}`(lane 开着)或 `null`(关着)。
887
+ > - **子代转录(placed 分区)的留存天数没有独立旋钮座**(S-03/#292②,设计判定 2026-08-30):placed 转录的
888
+ > 删除权**独家**归联合 reap(`reapDurableAgents`:行赢删才 release 转录;上面这条 lane 的候选谓词显式排除
889
+ > placed,见 `plugins/retention-store-sql.ts` 第五腿)——生效视界 = `BG_AGENT_RETENTION_MS`(缺省 7 天,
890
+ > 毫秒形,同时是 agent 行的留存旋钮;分区孤儿腿的 `olderThanMs` 同源)。core 的
891
+ > `SUBAGENT_TRANSCRIPT_RETENTION_DAYS_DEFAULT`(30 天)是共享词表常量,**本服务未按它跑**;天数形专用
892
+ > 旋钮要等 core 在 reap policy 开出转录独立年龄座后再铸(届时名 `SUBAGENT_TRANSCRIPT_RETENTION_DAYS`)。
893
+ > 生效值上 boot 日志:`wiring_static.subagentTranscriptRetentionMs`。
887
894
  > - `MEMORY_CONSOLIDATION_DRIVER=on|off`(默认 `off`):记忆**折叠(consolidation)阀门**。一个从未折叠过的
888
895
  > 记忆库只涨不折,检索质量随库龄衰减;开这根旋钮就给 worker 装上两个 operator 口,让人在自己选的节奏上
889
896
  > 跑折叠。⚠️ **闭集两词,别的一律拒启**——本旋钮是新的,不继承 `MEMORY_ENGINE` 的 `true/1/yes` 放宽:
@@ -1124,12 +1131,16 @@ CC 逐字节形的提醒帧:`<total_tokens>N tokens left</total_tokens>`(**缺
1124
1131
  - **分辨依据**(消费端/日志过滤):该帧内文**恒以 ` tokens left` 结尾**;`Infinite` 常量臂(`infinite`
1125
1132
  档)是唯一例外,单独放行——它不带数字、不依赖上限,且只在显式 `true` 下可达。
1126
1133
 
1127
- ### 9.2 部署级 token 治理窗(USAGE_WINDOWS,6.1.0 起)
1134
+ ### 9.2 部署级治理窗(USAGE_WINDOWS,6.1.0 起;$ ceiling=S-18/core 7.0.0 起)
1128
1135
 
1129
- 跨任务的滑动 token 预算(core `usageWindows`),缺省不设=关。单键 JSON 数组:
1136
+ 跨任务的滑动预算(core `usageWindows`),缺省不设=关。单键 JSON 数组;每窗两只**独立** ceiling
1137
+ (`maxTokens` token 数 / `maxCostUsd` 绝对美元)——each optional、**至少其一**(双缺拒启),
1138
+ whichever fills first 即耗尽,缺席的 ceiling 不参与(非 0、非 ∞):
1130
1139
 
1131
1140
  ```bash
1132
1141
  USAGE_WINDOWS='[{"windowMs":18000000,"maxTokens":5000000,"anchor":"first-use"}]' # 5h 窗 500 万 token
1142
+ USAGE_WINDOWS='[{"windowMs":604800000,"maxCostUsd":25,"anchor":"rolling"}]' # 7d 滑动窗 $25(纯 $ 窗合法)
1143
+ USAGE_WINDOWS='[{"windowMs":18000000,"maxTokens":5000000,"maxCostUsd":10,"anchor":"first-use"}]' # 双 ceiling:先满者停
1133
1144
  # anchor:"first-use"=首次消费开桶、到期重开;"rolling"=滑动窗(最近 windowMs 内的消费之和)
1134
1145
  ```
1135
1146
 
@@ -1140,8 +1151,13 @@ USAGE_WINDOWS='[{"windowMs":18000000,"maxTokens":5000000,"anchor":"first-use"}]'
1140
1151
  任务撞窗时,基建合格(checkpoint+durable approval)⇒ suspend(reason `usage_window`)等窗放行,
1141
1152
  不合格 ⇒ 响亮失败同码落终态记录。
1142
1153
  - 计量覆盖:主任务、具名/委托 subagent、hook 内部任务全计(部署真花销都进窗)。
1143
- - 坏形 `USAGE_WINDOWS` 拒启(fail-loud 点名第几项);**改窗形≈重开账**(first-use 桶按 windowMs
1144
- 配对,改了窗长老桶下次记账即弃)。
1154
+ - **$ (S-18,core 7.0.0 起)**:`maxCostUsd` 要求 run **可计价**——模型既无 pricing 条目也无
1155
+ `Model.cost` 声明时,cost 不是 0 而是**不存在**,$ 窗对它不可求值 ⇒ 任务门口拒
1156
+ (`config.usage_window_unpriced`),绝不记 0 让天花板静默失效。MIXED 声明(部署里同时有 $ 窗与纯
1157
+ token 窗)下共享账本行统一携带 cost;全部署零 $ 窗时账本行与此前逐字节同形。
1158
+ - 坏形 `USAGE_WINDOWS` 拒启(fail-loud 点名第几项);窗对象是**闭集**四键 {windowMs, maxTokens?,
1159
+ maxCostUsd?, anchor}——未知键(含拼错的 `maxCostUSD`)拒启点名,绝不静默降级成 token-only 治理;
1160
+ **改窗形≈重开账**(first-use 桶按 windowMs 配对,改了窗长老桶下次记账即弃)。
1145
1161
  - 存储:SQL 后端=跨副本一致(`usage_window` 表,行锁守恒);local=文件;纯内存 dev=进程内。
1146
1162
 
1147
1163
  ### 9.3 沙箱寿命声明(6.1.0 起)
@@ -34,11 +34,13 @@ export function createDeviceLane(ctx) {
34
34
  ...(knobs?.deliveryTimeoutMs != null ? { dispatchTimeoutMs: knobs.deliveryTimeoutMs } : {}),
35
35
  ...(knobs?.reconnectGraceMs != null ? { reconnectGraceMs: knobs.reconnectGraceMs } : {}),
36
36
  ...(knobs?.maxInflightPerDevice != null ? { maxInflightPerDevice: knobs.maxInflightPerDevice } : {}),
37
+ ...(knobs?.maxPreAuthPerRemote !== undefined ? { maxPreAuthConnectionsPerRemote: knobs.maxPreAuthPerRemote } : {}),
37
38
  ...(knobs?.supersede ? { supersede: knobs.supersede } : {}),
38
39
  });
39
40
  logger.info("device_lane_enabled", {
40
41
  replicaId,
41
42
  maxInflightPerDevice: knobs?.maxInflightPerDevice ?? 4,
43
+ maxPreAuthPerRemote: knobs?.maxPreAuthPerRemote ?? 8,
42
44
  crossReplicaEnrollRateLimit: ctx.rateGate !== undefined,
43
45
  });
44
46
  return { store, enrollment, hub };
@@ -625,6 +625,11 @@ export interface ServiceConfigFlat {
625
625
  execTimeoutMs?: number;
626
626
  /** `DEVICE_MAX_INFLIGHT_PER_DEVICE`(§11-O3 clay 已裁 = 4)。 */
627
627
  maxInflightPerDevice?: number;
628
+ /** `DEVICE_MAX_PREAUTH_PER_REMOTE`(默认 8;字面 `off` = 显式停用 per-remote 层)= 未鉴权
629
+ * 握手的 per-remote 并发帽(S-04/A-075.52:帽键是裸 remoteAddress——XFF 反代 / 公司 NAT 部署
630
+ * 形会把全员折叠成单键,per-remote 层退化成共享预算而非公平层,`off` + 全局帽 64 是诚实形)。
631
+ * 坏值拒启(#210 A 档),词表与拒因见 config.ts `parseDevicePreAuthPerRemoteEnv`。 */
632
+ maxPreAuthPerRemote?: number | "off";
628
633
  /** `DEVICE_SUPERSEDE`(默认 `stale_only`)= 同 deviceId 二连的顶替策略(§5.2)。 */
629
634
  supersede?: "stale_only" | "deny";
630
635
  } | {
@@ -1141,20 +1146,30 @@ export interface ServiceConfigFlat {
1141
1146
  * Running rows are never reaped (that is the orphan-grace sweep's job). Default 30 days, floor 1m.
1142
1147
  * `WORKFLOW_RUN_RETENTION_MS`. */
1143
1148
  workflowRunRetentionMs: number;
1144
- /** design/166-T1 — core 5.8.0 部署级 token 治理窗(`RunnerDeps.usageWindows`):滑动窗内跨任务
1145
- * token 预算,key=principal(缺席=GLOBAL 一口锅,per-key 各一份额度——混流部署语义见 USAGE.md)。
1146
- * `USAGE_WINDOWS` 单键 JSON 数组(每项 {windowMs,maxTokens,anchor:"first-use"|"rolling"});
1147
- * 缺省未设=关(零行为变化)。fail-loud:坏 JSON/空数组/非正整数/未知 anchor boot throw
1148
- * (parseUsageWindows)。窗形变更≈重开账(first-use 桶按 windowMs 配对,老桶下次 charge 即弃)。 */
1149
+ /** design/166-T1 — core 5.8.0 部署级治理窗(`RunnerDeps.usageWindows`):滑动窗内跨任务预算,
1150
+ * key=principal(缺席=GLOBAL 一口锅,per-key 各一份额度——混流部署语义见 USAGE.md)。
1151
+ * `USAGE_WINDOWS` 单键 JSON 数组(每项 {windowMs, maxTokens?, maxCostUsd?, anchor});
1152
+ * 缺省未设=关(零行为变化)。S-18(core 7.0.0):`maxCostUsd` 是与 maxTokens **对等**的第二只
1153
+ * ceiling(绝对美元;whichever fills first 即耗尽;缺席的 ceiling 不参与——非 0、非 )——两者
1154
+ * each optional on its own、**at least one required**(双缺=parseUsageWindows 响亮拒)。fail-loud:
1155
+ * 坏 JSON/空数组/坏值/未知 anchor → boot throw(parseUsageWindows)。窗形变更≈重开账(first-use
1156
+ * 桶按 windowMs 配对,老桶下次 charge 即弃)。$ 窗要求 run 可计价:无价目模型撞 $ 窗=core 门口拒
1157
+ * (`config.usage_window_unpriced`),USAGE.md §9.2。 */
1149
1158
  usageWindows?: readonly {
1150
1159
  windowMs: number;
1151
- maxTokens: number;
1160
+ maxTokens?: number;
1161
+ maxCostUsd?: number;
1152
1162
  anchor: "first-use" | "rolling";
1153
1163
  }[];
1154
1164
  /** core 1.364 durable background agents([1503] 提货单③):reaper 经 `TaskRegistry.reapDurableAgents`
1155
1165
  * (联合 reap——条件删赢了才 release 转录 session;裸 store.reap 会 strand 转录)按 scope 清 TERMINAL
1156
1166
  * `a*` 行,老于此值的删。SQL twins 走 DISTINCT-scope 枚举;local FileBackgroundAgentStore 单租户
1157
- * scope="default"。Default 7 days, floor 1m。`BG_AGENT_RETENTION_MS`。 */
1167
+ * scope="default"。Default 7 days, floor 1m。`BG_AGENT_RETENTION_MS`。
1168
+ * 🔴 S-03(#292②,设计判定 2026-08-30):这根旋钮**同时是子代转录(placed 分区)的生效留存视界**——
1169
+ * 联合 reap 行赢删才 release 转录,分区孤儿腿 `olderThanMs` 同源(core `reapDurableAgentsLane` 亲核),
1170
+ * retention lane 对 placed 显式让权(retention-store-sql.ts 第五腿)。core 词表常量
1171
+ * `SUBAGENT_TRANSCRIPT_RETENTION_DAYS_DEFAULT`(30d)在 core dist 零消费,本服务未按它跑;天数形
1172
+ * 专用旋钮无 core policy 座,候 core 扩座后再铸(生效值已投 wiring_static 日志)。 */
1158
1173
  backgroundAgentRetentionMs: number;
1159
1174
  /** 同批:stale-running 翻转窗——running 行的写者心跳(core 60s)断更超过此值 = 宿主进程死,行翻
1160
1175
  * failed/stoppedBy=system(诚实终态,跨实例读面永不虚构存活)。core 契约要求 ≥10min(60s 心跳的
package/dist/config.d.ts CHANGED
@@ -15,11 +15,19 @@ export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config
15
15
  export declare function parseAutonomy(raw: string | undefined): Autonomy | undefined;
16
16
  /** design/166-T1 — parse `USAGE_WINDOWS`(单键 JSON 数组;逐窗散键在窗数可变时组合爆炸,JSON 是唯一
17
17
  * 封闭形)。unset/空串 → undefined(关)。fail-loud 族(numEnv 同纪律):坏 JSON/非数组/空数组/
18
- * 非正有限整数/未知 anchor 全部 boot throw 点名——「设了但无意义」静默关掉=部署方以为有治理实际没有。
19
- * server 严于 core(core 允 maxTokens=0=恒拒锅;这里要求正整数,0 配置显然是笔误不是意图)。 */
18
+ * 坏值/未知 anchor 全部 boot throw 点名——「设了但无意义」静默关掉=部署方以为有治理实际没有。
19
+ * S-18(core 7.0.0 `UsageWindow.maxCostUsd`,dist/core/usage-window-store.d.ts:44-68 亲读):
20
+ * `maxTokens` 与 `maxCostUsd` 是两只**独立** ceiling——each optional on its own、**at least one
21
+ * required**(双缺=拒:a window with neither admits nothing it can govern),whichever fills first
22
+ * 即耗尽;缺席的 ceiling 不参与(非 0、非 ∞)。修前这里的白名单重铸只认三键,$ 键配了也被静默
23
+ * 剥掉——正是本函数要消灭的那族病自己。
24
+ * server 严于 core(core 允两 ceiling 取 0=恒满锁死窗;这里都要求正数,0 配置显然是笔误不是意图);
25
+ * `maxCostUsd` 另提前执行 core 的 micro-USD 门(×1e6 仍有限——超界 ceiling 在账本单位里读作 ∞,
26
+ * 「看起来被治理、实际什么都不拦」,core 在门口拒,这里拒在 boot)。 */
20
27
  export declare function parseUsageWindows(raw: string | undefined): readonly {
21
28
  windowMs: number;
22
- maxTokens: number;
29
+ maxTokens?: number;
30
+ maxCostUsd?: number;
23
31
  anchor: "first-use" | "rolling";
24
32
  }[] | undefined;
25
33
  /** Numeric env with validation: a non-numeric value FAILS at startup instead of silently becoming `NaN`
package/dist/config.js CHANGED
@@ -38,18 +38,38 @@ export function parseUsageWindows(raw) {
38
38
  parsed = JSON.parse(v);
39
39
  }
40
40
  catch (e) {
41
- throw new Error(`env USAGE_WINDOWS is not valid JSON: ${String(e)} — expected e.g. [{"windowMs":18000000,"maxTokens":5000000,"anchor":"first-use"}]`);
41
+ throw new Error(`env USAGE_WINDOWS is not valid JSON: ${String(e)} — expected e.g. [{"windowMs":18000000,"maxTokens":5000000,"anchor":"first-use"}] or [{"windowMs":604800000,"maxCostUsd":25,"anchor":"rolling"}]`);
42
42
  }
43
43
  if (!Array.isArray(parsed) || parsed.length === 0) {
44
- throw new Error(`env USAGE_WINDOWS must be a NON-EMPTY JSON array of {windowMs,maxTokens,anchor} (an empty array is a governance no-op nobody chose — unset the env to turn windows off)`);
44
+ throw new Error(`env USAGE_WINDOWS must be a NON-EMPTY JSON array of {windowMs, maxTokens?, maxCostUsd?, anchor} (an empty array is a governance no-op nobody chose — unset the env to turn windows off)`);
45
45
  }
46
46
  const out = parsed.map((w, i) => {
47
47
  const o = w;
48
48
  const posInt = (n) => typeof n === "number" && Number.isInteger(n) && n > 0;
49
- if (typeof w !== "object" || w === null || !posInt(o.windowMs) || !posInt(o.maxTokens) || (o.anchor !== "first-use" && o.anchor !== "rolling")) {
50
- throw new Error(`env USAGE_WINDOWS[${i}] is invalid: expected {windowMs: positive int ms, maxTokens: positive int, anchor: "first-use"|"rolling"} (got ${JSON.stringify(w)})`);
49
+ const posUsd = (n) => typeof n === "number" && Number.isFinite(n) && n > 0 && Number.isFinite(n * 1e6);
50
+ if (typeof w !== "object" || w === null || !posInt(o.windowMs) || (o.anchor !== "first-use" && o.anchor !== "rolling")) {
51
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: expected {windowMs: positive int ms, maxTokens?: positive int, maxCostUsd?: positive USD, anchor: "first-use"|"rolling"} (got ${JSON.stringify(w)})`);
51
52
  }
52
- return Object.freeze({ windowMs: o.windowMs, maxTokens: o.maxTokens, anchor: o.anchor });
53
+ for (const k of Object.keys(w)) {
54
+ if (k !== "windowMs" && k !== "maxTokens" && k !== "maxCostUsd" && k !== "anchor") {
55
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: unknown key \`${k}\` — the window shape is the CLOSED set {windowMs, maxTokens?, maxCostUsd?, anchor}; a misspelled ceiling (e.g. \`maxCostUSD\`) must not silently leave the deployment ungoverned (got ${JSON.stringify(w)})`);
56
+ }
57
+ }
58
+ if (o.maxTokens === undefined && o.maxCostUsd === undefined) {
59
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: at least one ceiling is required — \`maxTokens\` (tokens) and/or \`maxCostUsd\` (absolute USD); a window with neither admits nothing it can govern (got ${JSON.stringify(w)})`);
60
+ }
61
+ if (o.maxTokens !== undefined && !posInt(o.maxTokens)) {
62
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: \`maxTokens\` (when present) must be a positive integer (got ${JSON.stringify(w)})`);
63
+ }
64
+ if (o.maxCostUsd !== undefined && !posUsd(o.maxCostUsd)) {
65
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: \`maxCostUsd\` (when present) must be a positive finite USD amount that stays finite in micro-USD (× 1e6) (got ${JSON.stringify(w)})`);
66
+ }
67
+ return Object.freeze({
68
+ windowMs: o.windowMs,
69
+ ...(o.maxTokens !== undefined ? { maxTokens: o.maxTokens } : {}),
70
+ ...(o.maxCostUsd !== undefined ? { maxCostUsd: o.maxCostUsd } : {}),
71
+ anchor: o.anchor,
72
+ });
53
73
  });
54
74
  return Object.freeze(out);
55
75
  }
@@ -252,6 +272,26 @@ function optNumEnvStrict(name) {
252
272
  return undefined;
253
273
  return parseNumOrFail(name, raw);
254
274
  }
275
+ function parseDevicePreAuthPerRemoteEnv() {
276
+ const raw = process.env.DEVICE_MAX_PREAUTH_PER_REMOTE;
277
+ if (raw === undefined || raw === "")
278
+ return undefined;
279
+ if (raw === "off")
280
+ return "off";
281
+ if (!/^[1-9]\d*$/.test(raw)) {
282
+ throw new Error(`env DEVICE_MAX_PREAUTH_PER_REMOTE=${JSON.stringify(raw)} must be a positive integer or the literal "off" — ` +
283
+ `a malformed cap would otherwise run as the default (8) while the operator believes their value is in force ` +
284
+ `(#210: deployment knobs never fall back silently). "0" is not a valid cap (it would refuse every device handshake); ` +
285
+ `use "off" to disable the per-remote layer explicitly (reverse-proxy / NAT deployments collapse source IPs, A-075.52).`);
286
+ }
287
+ const n = Number(raw);
288
+ if (!Number.isSafeInteger(n)) {
289
+ throw new Error(`env DEVICE_MAX_PREAUTH_PER_REMOTE=${raw.length > 24 ? `${raw.slice(0, 24)}…(${raw.length} digits)` : raw} overflows the safe-integer range — ` +
290
+ `Number() would read it as ${n}, and a cap of Infinity silently disables the per-remote layer without taking the explicit "off" arm. ` +
291
+ `Use a value within Number.MAX_SAFE_INTEGER, or the literal "off".`);
292
+ }
293
+ return n;
294
+ }
255
295
  function parseFileHistoryRetention() {
256
296
  const raw = process.env.FILE_HISTORY_RETENTION_KEEP;
257
297
  if (raw === undefined || raw === "")
@@ -1434,6 +1474,7 @@ function parseOrchestrationDomain(ctx) {
1434
1474
  ...((n) => (n !== undefined ? { reconnectGraceMs: n } : {}))(optNumEnvStrict("DEVICE_RECONNECT_GRACE_MS")),
1435
1475
  ...((n) => (n !== undefined ? { execTimeoutMs: n } : {}))(optNumEnvStrict("DEVICE_EXEC_TIMEOUT_MS")),
1436
1476
  ...((n) => (n !== undefined ? { maxInflightPerDevice: n } : {}))(optNumEnvStrict("DEVICE_MAX_INFLIGHT_PER_DEVICE")),
1477
+ ...((n) => (n !== undefined ? { maxPreAuthPerRemote: n } : {}))(parseDevicePreAuthPerRemoteEnv()),
1437
1478
  ...(process.env.DEVICE_SUPERSEDE ? { supersede: enumEnv("DEVICE_SUPERSEDE", "stale_only", ["stale_only", "deny"]) } : {}),
1438
1479
  }
1439
1480
  :
@@ -151,8 +151,10 @@ export interface DeviceWsHubOptions {
151
151
  chunkBufferMaxBytes?: number;
152
152
  /** codex R1-F3:本副本**未鉴权**连接的并发帽(超额在 upgrade 阶段就 503 拒)。 */
153
153
  maxPreAuthConnections?: number;
154
- /** codex R1-F3:单来源地址的未鉴权连接并发帽。 */
155
- maxPreAuthConnectionsPerRemote?: number;
154
+ /** codex R1-F3:单来源地址的未鉴权连接并发帽。S-04(A-075.52):字面 `off` = 显式停用该层——
155
+ * 帽键是裸 remoteAddress,XFF 反代 / 公司 NAT 部署形把全员折叠成单键,这层从公平层退化成
156
+ * 「全员共享一份预算」;停层后全局帽(`maxPreAuthConnections`)照常兜底。 */
157
+ maxPreAuthConnectionsPerRemote?: number | "off";
156
158
  }
157
159
  export interface DeviceWsHub {
158
160
  /** 把 `GET /v1/device/ws` 的 upgrade 处理器挂到裸 `http.Server` 上。 */
@@ -735,9 +735,15 @@ export function createDeviceWsHub(options) {
735
735
  if (draining || closed)
736
736
  return refuseUpgrade(socket, 503);
737
737
  const remoteKey = req.socket.remoteAddress ?? "unknown";
738
- if (preAuthCount >= maxPreAuthConnections || (preAuthByRemote.get(remoteKey) ?? 0) >= maxPreAuthConnectionsPerRemote) {
739
- metrics?.inc("device_preauth_refused_total");
740
- logger?.warn("device_preauth_cap_hit", { remoteKey, preAuthCount, cap: maxPreAuthConnections });
738
+ const perRemoteCount = preAuthByRemote.get(remoteKey) ?? 0;
739
+ const globalHit = preAuthCount >= maxPreAuthConnections;
740
+ const perRemoteHit = maxPreAuthConnectionsPerRemote !== "off" && perRemoteCount >= maxPreAuthConnectionsPerRemote;
741
+ if (globalHit || perRemoteHit) {
742
+ const arm = globalHit ? (perRemoteHit ? "both" : "global") : "per_remote";
743
+ metrics?.inc("device_preauth_refused_total", { arm });
744
+ logger?.warn("device_preauth_cap_hit", {
745
+ remoteKey, arm, preAuthCount, cap: maxPreAuthConnections, perRemoteCount, perRemoteCap: maxPreAuthConnectionsPerRemote,
746
+ });
741
747
  return refuseUpgrade(socket, 503);
742
748
  }
743
749
  preAuthCount += 1;
@@ -180,7 +180,10 @@ export interface FleetWorkflowRow {
180
180
  /** MF-Fleet additive(cli [1726] 二①):**已启动**(非排队中)的 agent 数 —— CC 2.1.220 `⚠ Large workflow`
181
181
  * 规模告警的分母。判别用 core 导出的 `deriveAgentDisplayStatus`(`startedAt` 未置 ⇒ 显示态 `queued`),
182
182
  * **不自己重写谓词**:两端各写一遍必然漂移(本仓刚在治理面镜像上吃过同款)。
183
- * ADDITIVE / tolerate-absent:缺席时消费方可退回 `doneCount + failedCount` 这个**诚实下界**(告警偏早)。 */
183
+ * ADDITIVE / tolerate-absent:缺席时消费方可退回 `doneCount + failedCount` 这个**诚实下界**(告警偏早)。
184
+ * 2.1.250 复核(S-11,语料=cc-decoded/cli250.js):告警函数 220↔250 逐字同形——agents 轴比的是
185
+ * `scheduledAgents`,`started` 是 token/agent **投影的分母**(`started>0 ? totalTokens/started : 70k`);
186
+ * 阈值 25/1.5e6/70k 与 `CLAUDE_CODE_WORKFLOW_SIZE_WARNING_*` 覆盖名两版一致。 */
184
187
  startedCount?: number;
185
188
  elapsedMs?: number;
186
189
  tokens?: number;
@@ -48,7 +48,14 @@ export declare function hookMatcherMatches(matcher: string | undefined, value: s
48
48
  * 求值:名部走 permission matcher(交替/正则同 matcher 字段),括号内 arg-模式(`*` 通配)匹配工具输入的
49
49
  * 主字符串(Bash=command,文件工具=file_path/path)。CC 行为逐条目:不匹配 → 跳过该条目(记日志);
50
50
  * 非工具事件(payload 无 tool_name)→ "cannot be evaluated" 跳过;解析失败 → 跳过。返回:
51
- * true=跑 / "not_matched" / "unevaluable"(调用方分别记账;跳过永不静默)。 */
51
+ * true=跑 / "not_matched" / "unevaluable"(调用方分别记账;跳过永不静默)。
52
+ *
53
+ * 2.1.250 复核(S-11,2026-08-30;语料=sema-internal `blueprints/cc-decoded/cli250.js`):求值器与 220
54
+ * 逐字同构——五种工具事件之外「cannot be evaluated for non-tool event」跳过、「Skipping hook due to if
55
+ * condition … not matching」跳过,两串日志逐字在场;括号内容经工具 `preparePermissionMatcher` 求值,与
56
+ * 本实现同向。差异登记(不改行为):①250 证据里名部=**规范化后等值比对**(`vd(toolName)!==o`),
57
+ * 「交替/正则」是 matcher 字段的文法——本实现名部走 hookMatcherMatches 属**超集**(alternation 也认),
58
+ * 如实登记不收窄;②250 的 `if` 已扩到 prompt/agent/http/mcp_tool 条目(本 runner 只翻 command,不涉)。 */
52
59
  export declare function hookIfMatches(cond: string, payload: unknown): true | "not_matched" | "unevaluable";
53
60
  /** createTaskHooks 的装配上下文(payload 的 CC-verbatim 字段来源)。 */
54
61
  export interface HookRunnerContext {
@@ -51,7 +51,9 @@ export interface RouteHelpers {
51
51
  usageWindowDenied(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
52
52
  safeDecode(seg: string): string | null;
53
53
  isFleetWide(req: IncomingMessage): boolean;
54
- runOwnerOk(req: IncomingMessage, res: ServerResponse, owner: string | null): boolean;
54
+ /** 属主门。`notFoundForm` not-yours 臂的 404 拼法:缺省 `"run"`(not_found.run 长文,#262);session
55
+ * 资源面传 `"session"`(not_found.session + "session not found")与其 unknown 臂同码同串(G52 [5778])。 */
56
+ runOwnerOk(req: IncomingMessage, res: ServerResponse, owner: string | null, notFoundForm?: "run" | "session"): boolean;
55
57
  sessionOwnerScope(req: IncomingMessage): {
56
58
  fleetWide: boolean;
57
59
  gateOwner: string | null;
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { sendJson, sendError } from "../send.js";
3
- import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
3
+ import { buildOriginFencedBundle, readOriginAware } from "../../memory-origin-fence.js";
4
4
  import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
5
5
  export const MEMORY_BUNDLE_EXPORT_PATH = "/v1/memory/export";
6
6
  export const MEMORY_BUNDLE_IMPORT_PATH = "/v1/memory/import";
@@ -94,8 +94,7 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
94
94
  }
95
95
  try {
96
96
  const bundle = await engineExport(parsed.data.scopes);
97
- const fence = buildOriginFence(bundle.entries, readOriginAware(new URL(req.url ?? "", "http://x").searchParams));
98
- sendJson(res, 200, fence.withheldOriginTagged !== undefined ? { ...bundle, entries: fence.entries, withheldOriginTagged: fence.withheldOriginTagged } : bundle);
97
+ sendJson(res, 200, buildOriginFencedBundle(bundle, readOriginAware(new URL(req.url ?? "", "http://x").searchParams)));
99
98
  }
100
99
  catch (err) {
101
100
  if (!sentClassifiedFailure(res, ctx, "export", err))
@@ -430,7 +430,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
430
430
  sendError(res, 404, "not_found.session", "session not found");
431
431
  return;
432
432
  }
433
- if (!runOwnerOk(req, res, audit.owner))
433
+ if (!runOwnerOk(req, res, audit.owner, "session"))
434
434
  return;
435
435
  const faces = deps.memoryCompliance;
436
436
  if (faces === undefined) {
@@ -2027,7 +2027,7 @@ export function createHttpServer(rawDeps) {
2027
2027
  const gateOwner = fleetWide ? null : (gatedPrincipal(req, deps.config) || null);
2028
2028
  return { fleetWide, gateOwner };
2029
2029
  }
2030
- function runOwnerOk(req, res, owner) {
2030
+ function runOwnerOk(req, res, owner, notFoundForm = "run") {
2031
2031
  const principal = gatedPrincipal(req, deps.config);
2032
2032
  if (deps.config.requirePrincipal && !principal) {
2033
2033
  sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
@@ -2035,7 +2035,10 @@ export function createHttpServer(rawDeps) {
2035
2035
  }
2036
2036
  const legacyShellOwner = !deps.config.requirePrincipal && owner === "anon:shell-live" && (principal === undefined || principal === null);
2037
2037
  if (!legacyShellOwner && owner !== null && principal !== owner) {
2038
- sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE);
2038
+ if (notFoundForm === "session")
2039
+ sendError(res, 404, "not_found.session", "session not found");
2040
+ else
2041
+ sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE);
2039
2042
  return false;
2040
2043
  }
2041
2044
  return true;
package/dist/main.js CHANGED
@@ -555,6 +555,7 @@ async function main() {
555
555
  posture: staticWiring.interaction.posture,
556
556
  streamApproval: streamApprovalGate.active ? "active" : streamApprovalGate.reason,
557
557
  subagentTranscripts: staticWiring.fleet.subagentTranscripts,
558
+ subagentTranscriptRetentionMs: config.backgroundAgentRetentionMs,
558
559
  });
559
560
  const stores = {
560
561
  runStore,
@@ -1,42 +1,14 @@
1
- /**
2
- * design/383 S-6 / S-50 §三(S-49 F3)—— 带 `origin` 标的记忆条目在 wire 读口上的 **fence-or-refuse**。
3
- *
4
- * ## 病(S-6 禁形)
5
- * 记忆条目的 `origin` 标(design/336 provenance 标记族)是 additive 键。旧 SDK / 旧壳的 strict/strip parse 会
6
- * **静默剥掉**它 ⇒ 一条「外源已标」的条目摊平成无标文本,消费端再把它当洁净内容引用/回写 = 出处被洗掉。
7
- * core 无法在 wire 上强制(它不认识客户端),383 §3.4 显式授权 server 裁三点,S-50 §三裁定如下。
8
- *
9
- * ## 三点裁定(逐字)
10
- * 1. **判定点 = 端点级能力位,不猜客户端版本**:server 无 per-client 版本可见性。凡投影带标条目的读口,
11
- * **缺省不投带标条目**;消费方以查询参数 `originAware=1` 声明「我认识 origin 键族」后才放行。参数缺席 =
12
- * 老客户端假定成立 ⇒ fence。
13
- * 2. **粒度 = 整条目扣留,不做字段降级**:字段级(剥 origin 只给文本)恰是被禁的摊平。整条目扣留 + 计数披露
14
- * (`refusedUntrusted` 计数拒的既有先例形):响应带 `withheldOriginTagged: <n>`(additive,**只在 n>0 时铸**——
15
- * 只记真),消费端机读得出「有 n 条因我没声明能力而被围栏」。
16
- * 3. **能力位形 = 每请求查询参数,不是每客户端注册**:注册制引入一张会陈旧的表;查询参数让「谁认识键族」由
17
- * 当次调用自证,零状态。SDK 新版缺省带 `originAware=1`;旧 SDK 不带 ⇒ 结构性 fence。
18
- *
19
- * ## 判「带标」的谓词
20
- * 条目对象上 **own** `origin` 键且值非 `undefined`。不读 `origin` 的内部形(那是 core 的开放判别式),只判在场。
21
- *
22
- * ## 射程(S-50 §三.1 点名的读口,本仓现状三处)
23
- * · `GET /v1/memory/export?scope=`(memory-policy.ts,属主自助条目读,分页/全量两腿)
24
- * · `GET /v1/memory/origin/external?scopes=`(memory-origin.ts,外源标记人面 —— 它的每条都带标,缺省下
25
- * 全部扣留 + 计数:operator 的工具必须声明能力位才看得见,这正是「围栏」的意思,不是 bug)
26
- * · `POST /v1/memory/export`(memory-bundle.ts,治理携出包 —— 包经 SDK 落盘再 import,旧 SDK 剥标后 import
27
- * 会把无标条目当洁净内容收进另一台 = 跨库洗出处;所以包也围栏,计数键落在包顶层)
28
- * 不围栏的:`GET /v1/memory/entries/:id/provenance`(单条出处账,消费它的人就是来读 origin 的,且响应根不是
29
- * 条目)、`GET /v1/memory/origin/clearances`(`buildClearanceRowWire` 深白名单投影,已是显式形)。
30
- *
31
- * ## 读面行为收窄 = L3(S-50 §三.3 成文)
32
- * 存量消费者若依赖读到带标条目(现状 = origin 键族尚未在这些读口上稳定出现;真实射程 = 蒸馏产物到货后的新条目),
33
- * 从本版起须带 `originAware=1`。判据入 383 片3 判据面。
34
- */
1
+ import { type MemoryExportBundle } from "@sema-agent/core";
35
2
  /** 查询参数名与合法值(闭集:只认字面 `1`;`true`/`yes` 不认 —— 能力位不做宽松解析)。 */
36
3
  export declare const ORIGIN_AWARE_QUERY = "originAware";
37
4
  /** 从 `URLSearchParams` 读能力位:`originAware=1` ⇒ true;缺席/其它值 ⇒ false(老客户端假定)。 */
38
5
  export declare function readOriginAware(params: URLSearchParams): boolean;
39
- /** 「这条条目带 origin 标吗」—— own 键在场且非 undefined。纯谓词,不读 origin 的形。 */
6
+ /** 「这条条目带 origin 标吗」—— 载体三形(G53 [5778] + codex R1-high 后齐):
7
+ * ① 顶层 `origin`(listExternal 投影行)—— own 键在场且非 undefined 即带标(不读形;typed null/坏形也算:
8
+ * core 读取器对 null 会抛,围栏对读不出的载体只许扣留不许放行);
9
+ * ② `frontmatter.origin`(core `MemoryEntry`,138 S3)—— 同 own 键判;
10
+ * ③ `frontmatter.extra` 的 origin 悬置形块 —— 单源直调 core 出口的 `committedOriginOf`(typed 缺席时读
11
+ * extraOriginCarriers[0];退化形记 at:0 也算载体,过近似=安全方向),server 不自铸解析。 */
40
12
  export declare function isOriginTagged(entry: unknown): boolean;
41
13
  /**
42
14
  * 围栏(`build*`:纯数据)。`originAware=true` ⇒ 原数组同一引用、无计数键(零行为差);否则扣留带标条目,
@@ -47,4 +19,17 @@ export declare function buildOriginFence<T>(entries: readonly T[], originAware:
47
19
  entries: readonly T[];
48
20
  withheldOriginTagged?: number;
49
21
  };
22
+ /**
23
+ * 治理携出包的围栏(`build*`:纯数据)。`originAware=true` ∨ 零扣留 ⇒ **原对象同一引用**(逐字节旧形,
24
+ * integrity 一个字不动);否则:扣留带标条目 + 顶层 `withheldOriginTagged`(只记真),并按扣留后内容重铸
25
+ * integrity —— `entryCount`=kept 数、`revs`=kept 的 id→rev(core 校验器要求 revs 基数≡条目 id 集,
26
+ * 只重算 entryCount/sectionHashes 而留旧 revs 同样过不了自校验)、`sectionHashes.entries`=kept 的
27
+ * canonical 哈希;meta/governance/residuals 三段内容未动 ⇒ 三段哈希原样。
28
+ * governance 行(challenges/lineage)**不**随条目扣留:它们不携 origin 键(无标可剥,S-6 的摊平病对它们
29
+ * 不成立),扣了反而是静默改写治理史;import 侧对超出 entries 的引用按 referentialOrphans 具名报告,
30
+ * 诚实方向一致。
31
+ */
32
+ export declare function buildOriginFencedBundle(bundle: MemoryExportBundle, originAware: boolean): MemoryExportBundle & {
33
+ withheldOriginTagged?: number;
34
+ };
50
35
  //# sourceMappingURL=memory-origin-fence.d.ts.map
@@ -1,9 +1,27 @@
1
+ import { createHash } from "node:crypto";
2
+ import { committedOriginOf } from "@sema-agent/core";
1
3
  export const ORIGIN_AWARE_QUERY = "originAware";
2
4
  export function readOriginAware(params) {
3
5
  return params.get(ORIGIN_AWARE_QUERY) === "1";
4
6
  }
5
7
  export function isOriginTagged(entry) {
6
- return typeof entry === "object" && entry !== null && Object.prototype.hasOwnProperty.call(entry, "origin") && entry.origin !== undefined;
8
+ if (typeof entry !== "object" || entry === null)
9
+ return false;
10
+ if (Object.prototype.hasOwnProperty.call(entry, "origin") && entry.origin !== undefined)
11
+ return true;
12
+ const fm = entry.frontmatter;
13
+ if (typeof fm !== "object" || fm === null)
14
+ return false;
15
+ if (Object.prototype.hasOwnProperty.call(fm, "origin") && fm.origin !== undefined)
16
+ return true;
17
+ const extra = fm.extra;
18
+ if (extra === undefined)
19
+ return false;
20
+ if (!Array.isArray(extra))
21
+ return true;
22
+ if (!extra.every((l) => typeof l === "string"))
23
+ return true;
24
+ return committedOriginOf({ extra }) !== undefined;
7
25
  }
8
26
  export function buildOriginFence(entries, originAware) {
9
27
  if (originAware)
@@ -12,4 +30,37 @@ export function buildOriginFence(entries, originAware) {
12
30
  const withheld = entries.length - kept.length;
13
31
  return withheld > 0 ? { entries: kept, withheldOriginTagged: withheld } : { entries: kept };
14
32
  }
33
+ function sha256Line(text) {
34
+ return createHash("sha256").update(`${text}\n`, "utf8").digest("hex");
35
+ }
36
+ function canonicalJsonStringify(value) {
37
+ if (Array.isArray(value))
38
+ return `[${value.map((v) => (v === undefined || typeof v === "function" || typeof v === "symbol" ? "null" : canonicalJsonStringify(v))).join(",")}]`;
39
+ if (value !== null && typeof value === "object") {
40
+ const rec = value;
41
+ const keys = Object.keys(rec)
42
+ .filter((k) => rec[k] !== undefined)
43
+ .sort();
44
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJsonStringify(rec[k])}`).join(",")}}`;
45
+ }
46
+ return JSON.stringify(value);
47
+ }
48
+ export function buildOriginFencedBundle(bundle, originAware) {
49
+ if (originAware)
50
+ return bundle;
51
+ const kept = bundle.entries.filter((e) => !isOriginTagged(e));
52
+ const withheld = bundle.entries.length - kept.length;
53
+ if (withheld === 0)
54
+ return bundle;
55
+ return {
56
+ ...bundle,
57
+ entries: kept,
58
+ integrity: {
59
+ entryCount: kept.length,
60
+ revs: Object.fromEntries(kept.map((e) => [e.id, e.rev])),
61
+ sectionHashes: { ...bundle.integrity.sectionHashes, entries: sha256Line(canonicalJsonStringify(kept)) },
62
+ },
63
+ withheldOriginTagged: withheld,
64
+ };
65
+ }
15
66
  //# sourceMappingURL=memory-origin-fence.js.map
package/dist/run-local.js CHANGED
@@ -5,7 +5,7 @@ import { realpathSync } from "node:fs";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { loadRemoteExec } from "@sema-agent/settings-schema/node";
8
- import { Runner, uuidv7, parseModelMention, AdoptionError, FileStorageBackend, NodeExecutionEnv, NodeLspManager, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore } from "@sema-agent/core";
8
+ import { Runner, uuidv7, parseModelMention, AdoptionError, FileStorageBackend, NodeExecutionEnv, NodeLspManager, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, defaultTaskRegistry } from "@sema-agent/core";
9
9
  import { hasOperatorGateIntent } from "./approval.js";
10
10
  import { createBrain } from "./brain.js";
11
11
  import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
@@ -378,9 +378,9 @@ export async function runLocal(argv, deps = {}) {
378
378
  promptSource,
379
379
  executionEnvFactory,
380
380
  lspManager,
381
- backgroundAgentStore: undefined,
382
- mailboxStore: undefined,
383
- rosterStore: undefined,
381
+ backgroundAgentStore: fileBackend.backgroundAgentStore,
382
+ mailboxStore: fileBackend.mailboxStore,
383
+ rosterStore: fileBackend.rosterStore,
384
384
  deploymentHooks: {
385
385
  ...createPermissionDeniedMeter(metrics),
386
386
  ...(config.hooksTimeoutMs !== undefined ? { timeoutMs: config.hooksTimeoutMs } : {}),
@@ -416,6 +416,7 @@ export async function runLocal(argv, deps = {}) {
416
416
  const scenarioDeps = {
417
417
  runner, subRunner, handslessSubRunner, model: "default", skills, repoClient, metrics, logger,
418
418
  ...(webSearch ? { webSearch } : {}),
419
+ backgroundAgentStore: fileBackend.backgroundAgentStore,
419
420
  taskListStoreFor: taskListLane.storeFor,
420
421
  brandIdentity: true,
421
422
  };
@@ -473,6 +474,27 @@ export async function runLocal(argv, deps = {}) {
473
474
  ...(Object.keys(limits).length > 0 ? { limits } : {}),
474
475
  }, config, workspaceDir);
475
476
  logger.info("run_local_start", { scenario: scenarioName, model: spec.model, sessionId, exec: config.remoteExec?.provider ?? "in-process" });
477
+ const settleSessionBackground = async () => {
478
+ const reaped = defaultTaskRegistry.reapSessionBackground(sessionId, scope);
479
+ if (reaped === 0)
480
+ return;
481
+ const deadline = Date.now() + 5_000;
482
+ try {
483
+ for (;;) {
484
+ const running = (await fileBackend.backgroundAgentStore.listByScope(scope, { status: "running" })).length;
485
+ if (running === 0)
486
+ return;
487
+ if (Date.now() >= deadline) {
488
+ logger.warn("run_local_bg_settle_timeout", { reaped, stillRunning: running });
489
+ return;
490
+ }
491
+ await new Promise((r) => setTimeout(r, 25));
492
+ }
493
+ }
494
+ catch (err) {
495
+ logger.warn("run_local_bg_settle_readback_failed", { reaped, err: err instanceof Error ? err.message : String(err) });
496
+ }
497
+ };
476
498
  let result;
477
499
  let exitCode = 0;
478
500
  const taskRunner = pickHandsRunner(cap.hands, { full: runner, handsless: handslessRunner });
@@ -481,6 +503,7 @@ export async function runLocal(argv, deps = {}) {
481
503
  }
482
504
  catch (err) {
483
505
  printErr(`task crashed: ${err instanceof Error ? err.message : String(err)}`);
506
+ await settleSessionBackground();
484
507
  runner.sessions.dispose();
485
508
  subRunner.sessions.dispose();
486
509
  void lspManager?.dispose();
@@ -509,6 +532,7 @@ export async function runLocal(argv, deps = {}) {
509
532
  logger.warn("memory_sync_failed", { reason: "post-task", err: String(err) });
510
533
  }
511
534
  }
535
+ await settleSessionBackground();
512
536
  runner.sessions.dispose();
513
537
  subRunner.sessions.dispose();
514
538
  void lspManager?.dispose();
@@ -18,11 +18,11 @@
18
18
  import type { TaskNotificationPayload, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, TaskEvent, TraceEvent, MailboxMessage, MailboxStore } from "@sema-agent/core";
19
19
  /** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。 */
20
20
  type AssertAllKeysHandled<T extends never> = T;
21
- type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode";
21
+ type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure";
22
22
  type NotificationExcluded = "peer";
23
23
  type _GuardNotification = AssertAllKeysHandled<Exclude<keyof TaskNotificationPayload, NotificationProjected | NotificationExcluded>>;
24
24
  type BgNotifProjected = "taskId" | "sessionId" | "seq" | "status" | "summary" | "stoppedBy" | "resumable" | "recentSteps" | "editedFiles" | "usage" | "transcriptId" | "rootSessionId" | "parentTaskId" | "parentToolCallId" | "completionId";
25
- type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "description" | "agentType" | "name" | "currentAction" | "currentTool" | "parentSessionId" | "startedAt" | "workflowRunId" | "progressTaskId" | "progressParentTaskId";
25
+ type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "description" | "agentType" | "name" | "currentAction" | "currentTool" | "parentSessionId" | "startedAt" | "workflowRunId" | "progressTaskId" | "progressParentTaskId" | "model";
26
26
  type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, BgNotifProjected | BgNotifExcluded>>;
27
27
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
28
28
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
@@ -165,6 +165,7 @@ export declare function taskProgressEventData(ev: {
165
165
  workflowRunId?: string;
166
166
  workflowAgentLabel?: string;
167
167
  seq?: number;
168
+ model?: string;
168
169
  }): Record<string, unknown>;
169
170
  /** The `task_notification` payload (core 1.202 design/115 P2: a background task finished — this event is the
170
171
  * LEDGER/display projection of the OBSERVED terminal state, emitted the moment core observes it. It does NOT
@@ -209,6 +210,10 @@ export declare function taskNotificationEventData(ev: {
209
210
  completionId?: string;
210
211
  error?: string;
211
212
  errorCode?: string;
213
+ apiFailure?: {
214
+ status?: number;
215
+ requestId?: string;
216
+ };
212
217
  };
213
218
  eventId?: string;
214
219
  parentToolCallId?: string;
@@ -106,6 +106,7 @@ export function taskProgressEventData(ev) {
106
106
  ...(typeof ev.taskType === "string" && ev.taskType.length > 0 ? { taskType: ev.taskType } : {}),
107
107
  ...(ev.parentTaskId ? { parentTaskId: ev.parentTaskId } : {}),
108
108
  ...(ev.name ? { name: redactSecrets(ev.name) } : {}),
109
+ ...(typeof ev.model === "string" && ev.model.length > 0 ? { model: ev.model } : {}),
109
110
  ...(ev.status !== undefined ? { status: ev.status } : {}),
110
111
  ...(ev.usage ? { usage: ev.usage } : {}),
111
112
  ...(ev.currentAction ? { currentAction: redactSecrets(ev.currentAction) } : {}),
@@ -129,6 +130,14 @@ export function taskNotificationEventData(ev) {
129
130
  ...(n.completionId !== undefined ? { completionId: n.completionId } : {}),
130
131
  ...(n.error !== undefined ? { error: redactSecrets(n.error) } : {}),
131
132
  ...(n.errorCode !== undefined ? { errorCode: n.errorCode } : {}),
133
+ ...(n.apiFailure !== undefined
134
+ ? {
135
+ apiFailure: {
136
+ ...(typeof n.apiFailure.status === "number" ? { status: n.apiFailure.status } : {}),
137
+ ...(typeof n.apiFailure.requestId === "string" ? { requestId: n.apiFailure.requestId } : {}),
138
+ },
139
+ }
140
+ : {}),
132
141
  ...(n.source !== undefined ? { source: redactSecrets(n.source) } : {}),
133
142
  ...(n.exitCode !== undefined ? { exitCode: n.exitCode } : {}),
134
143
  ...(n.partial !== undefined ? { partial: n.partial } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.52.0",
3
+ "version": "7.52.1",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -54,7 +54,7 @@
54
54
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
55
55
  },
56
56
  "dependencies": {
57
- "@sema-agent/core": "7.0.0",
57
+ "@sema-agent/core": "7.0.1",
58
58
  "@sema-agent/settings-schema": "1.0.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",
@@ -69,7 +69,7 @@
69
69
  "sharp": "^0.35.3"
70
70
  },
71
71
  "devDependencies": {
72
- "@sema-agent/sdk": "7.2.0",
72
+ "@sema-agent/sdk": "7.3.0",
73
73
  "@types/libsodium-wrappers": "^0.7.14",
74
74
  "@types/node": "22.10.2",
75
75
  "@types/pg": "^8.20.0",