@sema-agent/server 3.7.0 → 3.8.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/dist/budget.js CHANGED
@@ -139,6 +139,12 @@ export function createTracer(metrics, costQuota, modelUsage, fleetUsage, fleetLe
139
139
  if (principal)
140
140
  costQuota?.add(principal, e.costMicroUsd);
141
141
  }
142
+ else if (e.costMicroUsd === undefined) {
143
+ // D5:cost 整键缺席 = 未知(RB-368)——计数供 /metrics/summary 的 costUnknown/unpricedCalls
144
+ // 信号(costUsd 在 unpriced 部署恒 0,没有这个信号消费端会读成「零花费」)。显式 0 =
145
+ // declared free,走上面 truthy 为假的静默臂,不计入 unpriced。
146
+ metrics.inc("brain_calls_unpriced_total", { model: modelId(e.model) });
147
+ }
142
148
  // ┌─ #59 裁定 / RB-368 结案([2052]① cli 报案 → [2073] 立案 → [2076] 工单挂 core → [2083] 到货) ─┐
143
149
  // 病灶报案:下面三处曾是 `e.costMicroUsd ?? 0`(账本 + fleetUsage + fleetLease),把「成本未知」记成
144
150
  // 「免费」,与 `pricingConfigured=false` 的部署组合 ⇒ 全部账目假 0。
@@ -167,10 +173,10 @@ export function createTracer(metrics, costQuota, modelUsage, fleetUsage, fleetLe
167
173
  // 渲染成「未知」而非「免费」;第二级 = 行级缺席(本改动),沿账本→durable `model_usage`→
168
174
  // `aggregateModelUsage`→`TaskStats.modelUsage` 回声→usage-analytics 的 `estimated` 标记,一路
169
175
  // 保持缺席(聚合面的**未知传染**语义见 trace/project.ts)。
170
- // · **一处没接住,如实记账**:fleet 批报腿(下面 fleetUsage)——registry-core
171
- // `UsageEntry.costMicroUsd` 是 `z.number()` **必填**,wire 上表达不了缺席。故批报入参虽然
172
- // 如实不带键,accumulator 侧仍只能把未知折成「不计入已知和」(见 fleet-client.ts 的旁注),
173
- // center 看到的仍是低估而非「未知」。要真治需 registry-core 契约改 optional + center 消费面同改。
176
+ // · fleet 批报腿(下面 fleetUsage)registry-core 0.11.0/[2103] 起**同样接住了**:wire 有
177
+ // 缺席位(UsageEntry.costMicroUsd optional),accumulator cell 级传染透传缺席,center 聚合面
178
+ // 「下界 + costUnknown」——两层语义与本处的关系见 fleet-client.ts 的旁注(3.7.0 前这里曾写
179
+ // 「wire 表达不了缺席、center 只见低估」,已被该车推翻;doc-rot 2026-07-30)。
174
180
  // └──────────────────────────────────────────────────────────────────────────────────────────────┘
175
181
  // E8 (shell-host): per-task × per-model usage for the `TaskStats.modelUsage` echo. `record` is a NO-OP unless
176
182
  // e.taskId is a REGISTERED top-level run (the leak fence — sub-task/throwaway taskIds are never registered, so
@@ -109,7 +109,7 @@ export interface FleetTaskRow {
109
109
  path: string;
110
110
  edits: number;
111
111
  }>;
112
- /** [2070]①/[2080] 裁定(server 半场,3.5.4):**产出这一行的车道**。
112
+ /** [2070]①/[2080] 裁定(server 半场,3.6.0):**产出这一行的车道**。
113
113
  * - 缺席 = BCE 车道(`fleetBackgroundChildPublisher`)的 a\* 与 wa\* 真行,或顶层 run 行 —— 单源,常态。
114
114
  * - `"run-leg"` = 由每腿的 `fleetRunPublisher` 从 `task_progress` tick 铸的**复合 id 行**
115
115
  * (`${runId} ${childTaskId}`)。此形只在**没有 BCE 真行**的前台 delegation 上保留(同步腿委派的
@@ -157,7 +157,7 @@ export function fleetRunPublisher(bus, run) {
157
157
  return typeof t === "number" ? t : undefined;
158
158
  };
159
159
  /**
160
- * 🔴 [2070]①/[2080] 让位判据(run-leg 侧半场,server 3.5.4)。
160
+ * 🔴 [2070]①/[2080] 让位判据(run-leg 侧半场,server 3.6.0)。
161
161
  *
162
162
  * 病灶(cli [2070]① 实测):本车道给**每一条**子代 tick 铸复合 id 行 `${runId} ${childTaskId}`,而
163
163
  * BCE 车道(`fleetBackgroundChildPublisher`)对同一个 agent 已有 `a*`/`wa*` 稳定 handle 真行 ⇒ 同屏两行
@@ -318,6 +318,15 @@ export function fleetRunPublisher(bus, run) {
318
318
  candidateCids.push(viaToolCall);
319
319
  const liveCids = candidateCids.filter((c) => children.has(c));
320
320
  const hadRow = liveCids.length > 0;
321
+ // 🔴 F1(2026-07-30 复审):无活行时先查 BCE claim——claim 在场 = 该 agent 的全部帧归 BCE 车道
322
+ // (run-leg 的 tick 臂已全程让位,children 恒空),终态臂同样让位:合成复合 id 瞬态终帧会让
323
+ // sourceLane 文档承诺废除的「同 agent 双 id 域帧」在终态那一拍回潮(且合成帧不带 sourceLane)。
324
+ // 返回 true=已被处理(BCE 车道自己发 a* 终帧);无 claim 的 bash 无 tick 形走下方原语义。
325
+ if (!hadRow) {
326
+ const claimed = [taskId, altId].some((t) => t !== undefined && bus.backgroundChildLaneRow(run.scope, t) !== undefined);
327
+ if (claimed)
328
+ return true;
329
+ }
321
330
  // 都没活行 = bash 无 tick 形:按首候选合成瞬态终帧(建即离,诚实且无害)。
322
331
  // [2070]③:合成帧**不铸 name** —— 这条「瞬态」帧会被壳的终态留存池渲 60s,顶上去的 a\* handle
323
332
  // 在那 60s 里就是用户看到的名字。父链接/租户/会话三位照发(它们是归属事实,不是显示名)。
@@ -796,7 +805,7 @@ export function fleetBackgroundChildPublisher(bus, log) {
796
805
  handleSpawn(e);
797
806
  return;
798
807
  }
799
- const m = meta.get(e.taskId) ?? { sessionScoped: e.sessionScoped, tenantScope: "default", spawnedAt: Date.now() }; // scope domain map holds on the lazily-minted path too([1338]④ 首见起表)
808
+ const m = meta.get(e.taskId) ?? { sessionScoped: e.sessionScoped, tenantScope: e.scope ?? "default", spawnedAt: Date.now() }; // scope domain map holds on the lazily-minted path too([1338]④ 首见起表);F2(2026-07-30 复审):lazy-mint 曾硬编码 "default",scoped 部署下 claim 落错租户键 ⇒ 让位失效退回双生行
800
809
  if (!meta.has(e.taskId)) {
801
810
  remember(e.taskId, m); // tick/terminal without a seen spawn (restart/eviction edge) — row minted lazily
802
811
  dlog("bg_child_event", { kind: `${e.kind}-lazy-mint`, taskId: e.taskId, sessionScoped: e.sessionScoped });
@@ -54,7 +54,10 @@ export const SKIPPED_ENTRY_TYPES = [
54
54
  ];
55
55
  /** 单次评估送进模型的 transcript 字符上限。超出保留**尾部**(最近的对话对"条件是否已达成"更相关)。 */
56
56
  export const BRANCH_TRANSCRIPT_MAX_CHARS = 60_000;
57
- /** content(string | 片段数组)→ 可读文本。只取 text 片段;image/工具结果等非文本片段不进 transcript。 */
57
+ /** content(string | 片段数组)→ 可读文本。只取 text 片段;image/工具结果等非文本片段不进 transcript。
58
+ * 已登记的残余窄形(F5,2026-07-30 复审):core `toNormalizedTextBlock` 会把混进 content 的非法块
59
+ * 文本化成 `[normalized] {json}` 送模型,证据面这里丢弃 —— 模型可见/证据缺席方向的缺口,但触发
60
+ * 前提是非标准块混进 custom content(上游产生面不可达);若上游真出现该形,闭合钉/评估者误拦会先暴露。 */
58
61
  function contentText(c) {
59
62
  const parts = [];
60
63
  if (typeof c === "string")
@@ -34,6 +34,9 @@ export interface MetricsSummary {
34
34
  tokensTotal: number;
35
35
  costUsd: number;
36
36
  costUsdByModel: Record<string, number>;
37
+ /** true ⇒ 窗口内有 cost 未知的 brain.call(unpriced 部署),costUsd/costUsdByModel 是下界(D5)。 */
38
+ costUnknown: boolean;
39
+ unpricedCalls: number;
37
40
  taskDurationAvgSec: number | null;
38
41
  brainFirstTokenAvgMs: number | null;
39
42
  brainCallAvgMs: number | null;
@@ -163,6 +163,11 @@ export class Metrics {
163
163
  tokensTotal: ctotal("task_tokens_total"),
164
164
  costUsd: ctotal("model_cost_micro_usd_total") / 1e6,
165
165
  costUsdByModel: Object.fromEntries(Object.entries(cby("model_cost_micro_usd_total", "model")).map(([k, v]) => [k, v / 1e6])),
166
+ // D5([2122] 挂账,additive v1):unpriced 部署下 costUsd 恒 0 而消费端读成「零花费」。
167
+ // costUnknown=true ⇒ costUsd/costUsdByModel 是**下界**(RB-368 缺席=未知语义;显式 0 = declared
168
+ // free 不计入 unpriced)。计数点在 budget.ts 的 brain.call 臂。
169
+ costUnknown: ctotal("brain_calls_unpriced_total") > 0,
170
+ unpricedCalls: ctotal("brain_calls_unpriced_total"),
166
171
  taskDurationAvgSec: havg("task_duration_seconds"),
167
172
  brainFirstTokenAvgMs: havg("brain_first_token_ms"),
168
173
  brainCallAvgMs: havg("brain_call_latency_ms"),
@@ -70,6 +70,12 @@ export function aggregateModelUsage(events) {
70
70
  continue;
71
71
  for (const [model, d] of Object.entries(usage)) {
72
72
  any = true;
73
+ // F3(2026-07-30 复审):null/非对象行的守卫必须在任何取值**之前**——原来它排在 `d.inputTokens`
74
+ // 之后,对唯一能判别的形(null)先抛,守卫不可达;坏行按整行 0 折(#59 同口径),cost 记未知。
75
+ if (d === null || typeof d !== "object") {
76
+ costUnknown.add(model);
77
+ continue;
78
+ }
73
79
  const cur = total[model] ?? { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 };
74
80
  cur.inputTokens += num(d.inputTokens);
75
81
  cur.outputTokens += num(d.outputTokens);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
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",
@@ -46,6 +46,7 @@
46
46
  "dev:bake-runner": "tsx src/bake-runner/main.ts",
47
47
  "test": "vitest run",
48
48
  "test:db-integration": "vitest run integration",
49
+ "perf:baseline": "tsx scripts/perf-baseline.mjs",
49
50
  "build:binary": "bun build --compile src/main.ts --outfile dist/sema-server",
50
51
  "build:binary:linux-x64": "bun build --compile --target=bun-linux-x64 src/main.ts --outfile dist/sema-server-linux-x64",
51
52
  "build:binary:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/main.ts --outfile dist/sema-server-darwin-arm64",