@sema-agent/server 7.41.0 → 7.42.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 CHANGED
@@ -243,6 +243,12 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
243
243
  行与编号高水位一并抹掉(同 anchors/附件/快照的级联)——会话 id 可被重新认领,残留就等于把上一位
244
244
  主人的任务标题与编号进度交给下一个化身。
245
245
  - 多实例边界:同一数据根同时只允许一个实例(BootLock 独占,第二个进程拒启;死主自愈含 SIGKILL)。
246
+ core 5.54.0 起拒启错误带**可分派 code**(`FileStoreLockError`,message 点名占用方 pid),运维按码
247
+ 三动作:`store.dir_in_use`=另一个活进程持有该目录 → 停掉它(或换数据根/迁 SQL 后端);
248
+ `store.dir_claiming`=另一进程正在接管崩溃者留下的锁 → **瞬态,稍候重试**(接管方要么完成要么
249
+ 自己也被收割);`store.lock_unreadable`=锁文件坏形(无 owner 可解析/嵌套接管残留)→ 人工处置,
250
+ message 会点名要删的文件,**不要盲删数据根下其它东西**。server 刻意不 catch 这族错(fail-fast:
251
+ 两进程共用 file 数据目录是禁止形,静默容忍比拒启更坏)。
246
252
  `DB_BACKEND=memory` 并非全无盘:workflow 相关账本仍挂数据根下——多个 memory 形引擎**不要共用**
247
253
  数据根/HOME(1.309 起并发 boot 不再拒启,但账本内容级共享仍不受支持)。
248
254
 
@@ -16,6 +16,12 @@ export async function mergeBranches(reports, baseSha, deps) {
16
16
  let destroy;
17
17
  // Track the current stage so a THROWN error is attributed to the right phase (not always "provision").
18
18
  let phase = "diff-out";
19
+ // [4953]② declared OUTSIDE the try (same pattern as `phase`) so the catch-all can still disclose them:
20
+ // spend/repair/measure that already HAPPENED must ride the thrown-failure return too — the leader folds
21
+ // conflictResolverCostUsd into the next round's budget, and a throw doesn't un-spend it.
22
+ let conflictResolverCostUsd = 0; // 🔴 capture the bounded conflict-resolver agent's spend (was dropped → S1 TEAM-cost undercount)
23
+ let repairMeta;
24
+ let measure;
19
25
  try {
20
26
  // 1. pull each worker's diff OUT of its container (M2). A failed diff quarantines that worker, aborts merge.
21
27
  const patches = [];
@@ -39,7 +45,6 @@ export async function mergeBranches(reports, baseSha, deps) {
39
45
  // 3. apply each patch --3way (council#3: keeps conflict context; capture .rej on failure, quarantine).
40
46
  phase = "apply";
41
47
  const conflictsResolved = [];
42
- let conflictResolverCostUsd = 0; // 🔴 capture the bounded conflict-resolver agent's spend (was dropped → S1 TEAM-cost undercount)
43
48
  for (const p of patches) {
44
49
  const patchPath = `/tmp/${p.workerId}.patch`;
45
50
  await env.writeFile(patchPath, p.patch);
@@ -71,7 +76,6 @@ export async function mergeBranches(reports, baseSha, deps) {
71
76
  // Bounded integration-repair (search 2026-06-14): a clean-merge-but-failing-build used to be terminal.
72
77
  // If the sandbox supplied a `repair`, run the repair agent IN this merged tree (it edits the working tree;
73
78
  // the squash commit below captures worker patches + repair together), then re-run testCmd. Up to maxRounds.
74
- let repairMeta;
75
79
  if (test.exitCode !== 0 && integ.repair && integ.repair.maxRounds > 0) {
76
80
  let cost = 0;
77
81
  let round = 0;
@@ -97,7 +101,6 @@ export async function mergeBranches(reports, baseSha, deps) {
97
101
  // 4b. truecorrect MEASURE (design/59) — run the thorough hidden oracle IN this sandbox (E2B), where the merged
98
102
  // code already lives, NOT on the CPU-contended control plane (fixes the frontier-multi oracle spawn-timeout
99
103
  // that thinned n). Pure observation: never gates `ok`, just reports into MergeResult.measure.
100
- let measure;
101
104
  if (deps.measureCmd) {
102
105
  const mr = await env.exec(deps.measureCmd, { cwd: repo });
103
106
  measure = mr.exitCode === 0 ? { pass: true } : { pass: false, reason: (mr.stdout + " " + mr.stderr).slice(-200) };
@@ -163,8 +166,15 @@ export async function mergeBranches(reports, baseSha, deps) {
163
166
  }
164
167
  catch (e) {
165
168
  // Honour the "never throws" contract: a thrown pullWorkerDiff/provision/exec becomes a typed failure,
166
- // attributed to the stage it threw in (not a blanket "provision").
167
- return { ok: false, phase, reason: `merge aborted in ${phase}: ${e instanceof Error ? e.message : String(e)}`, quarantine: mergeable.map((r) => r.workerId) };
169
+ // attributed to the stage it threw in (not a blanket "provision"). [4953]②: spend/repair/measure that
170
+ // already happened before the throw ride this return too (same tail as every explicit failure return).
171
+ return {
172
+ ok: false, phase, reason: `merge aborted in ${phase}: ${e instanceof Error ? e.message : String(e)}`,
173
+ quarantine: mergeable.map((r) => r.workerId),
174
+ ...(measure ? { measure } : {}),
175
+ ...(repairMeta ? { repair: repairMeta } : {}),
176
+ ...(conflictResolverCostUsd > 0 ? { conflictResolverCostUsd } : {}),
177
+ };
168
178
  }
169
179
  finally {
170
180
  // council#15: destroy the sandbox on EVERY exit path. Guarded because we may bail (diff-out) or throw
@@ -133,8 +133,10 @@ export function mkRepairOracle(cfg) {
133
133
  // fork2.2 — bind the EXACT graded bytes (the diff that produced this verdict) so the caller surfaces the
134
134
  // candidate the human reviews == the bytes that were graded (not a later re-pull that may differ).
135
135
  cfg.onGraded?.({ diff: diff.patch, hash: diffHash, passed });
136
- cfg.log?.("repair_oracle_verdict", { tier, passed, flaky, retries: K });
137
- return { tier, passed, flaky, retries: K, ...(passed && !flaky ? {} : { trace: lastTrace || "oracle rejected the candidate" }) };
136
+ // [4953]① `retries` = how many re-isolations actually RAN (the cross-repo contract's wording) — the
137
+ // first-clear-fail short-circuit means this can be < K, and reporting the configured cap was a lie.
138
+ cfg.log?.("repair_oracle_verdict", { tier, passed, flaky, retries: verdicts.length });
139
+ return { tier, passed, flaky, retries: verdicts.length, ...(passed && !flaky ? {} : { trace: lastTrace || "oracle rejected the candidate" }) };
138
140
  };
139
141
  }
140
142
  //# sourceMappingURL=repair-oracle.js.map
@@ -260,10 +260,18 @@ export class RemoteAdbExecutionEnv {
260
260
  if (finished)
261
261
  break;
262
262
  const waitMs = idleMs ? Math.max(1, idleMs - (Date.now() - lastChunk)) : 30_000;
263
- await new Promise((r) => {
264
- wake = r;
265
- setTimeout(r, waitMs);
266
- });
263
+ // [4947] A-002.11 sibling: clear the losing timer — wake() firing first used to leave it armed.
264
+ let waitTimer;
265
+ try {
266
+ await new Promise((r) => {
267
+ wake = r;
268
+ waitTimer = setTimeout(r, waitMs);
269
+ });
270
+ }
271
+ finally {
272
+ if (waitTimer !== undefined)
273
+ clearTimeout(waitTimer);
274
+ }
267
275
  if (idleMs && !finished && Date.now() - lastChunk >= idleMs) {
268
276
  throw new RemoteExecutionError("timeout", `execStream idle > ${idleMs}ms (device hang / disconnected)`);
269
277
  }
@@ -1102,7 +1102,16 @@ export class RemoteHostExecutionEnv {
1102
1102
  break;
1103
1103
  // idle/liveness bound: no chunk within readTimeoutMs → typed timeout (a hung command).
1104
1104
  const waitMs = idleMs != null ? Math.max(1, idleMs - (Date.now() - lastChunk)) : 30_000;
1105
- await new Promise((r) => { wake = r; setTimeout(r, waitMs); });
1105
+ // [4947] A-002.11 sibling: the losing side of this race must be cleared — wake() firing first
1106
+ // used to leave the timer armed for up to waitMs (30s), one per drain round on long streams.
1107
+ let waitTimer;
1108
+ try {
1109
+ await new Promise((r) => { wake = r; waitTimer = setTimeout(r, waitMs); });
1110
+ }
1111
+ finally {
1112
+ if (waitTimer !== undefined)
1113
+ clearTimeout(waitTimer);
1114
+ }
1106
1115
  if (idleMs != null && !finished && Date.now() - lastChunk >= idleMs) {
1107
1116
  finished = true;
1108
1117
  kill();
@@ -350,7 +350,15 @@ export class RemoteLocalDockerExecutionEnv {
350
350
  if (finished)
351
351
  break;
352
352
  const waitMs = idleMs != null ? Math.max(1, idleMs - (Date.now() - lastChunk)) : 30_000;
353
- await new Promise((r) => { wake = r; setTimeout(r, waitMs); });
353
+ // [4947] A-002.11 sibling: clear the losing timer — wake() firing first used to leave it armed.
354
+ let waitTimer;
355
+ try {
356
+ await new Promise((r) => { wake = r; waitTimer = setTimeout(r, waitMs); });
357
+ }
358
+ finally {
359
+ if (waitTimer !== undefined)
360
+ clearTimeout(waitTimer);
361
+ }
354
362
  if (idleMs != null && !finished && Date.now() - lastChunk >= idleMs) {
355
363
  finished = true;
356
364
  try {
@@ -480,10 +480,18 @@ export class RemoteSshExecutionEnv {
480
480
  break;
481
481
  // idle/liveness bound: no chunk within readTimeoutMs → typed timeout (unreachable host)
482
482
  const waitMs = idleMs ? Math.max(1, idleMs - (Date.now() - lastChunk)) : 30_000;
483
- await new Promise((r) => {
484
- wake = r;
485
- setTimeout(r, waitMs);
486
- });
483
+ // [4947] A-002.11 sibling: clear the losing timer — wake() firing first used to leave it armed.
484
+ let waitTimer;
485
+ try {
486
+ await new Promise((r) => {
487
+ wake = r;
488
+ waitTimer = setTimeout(r, waitMs);
489
+ });
490
+ }
491
+ finally {
492
+ if (waitTimer !== undefined)
493
+ clearTimeout(waitTimer);
494
+ }
487
495
  if (idleMs && !finished && Date.now() - lastChunk >= idleMs)
488
496
  throw new RemoteExecutionError("timeout", `execStream idle > ${idleMs}ms (provider hang / unreachable host)`);
489
497
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.41.0",
3
+ "version": "7.42.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",
@@ -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": "^5.53.0",
57
+ "@sema-agent/core": "^5.54.0",
58
58
  "@sema-agent/registry-core": "^0.19.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",