@sema-agent/server 5.14.0 → 6.0.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 +3 -0
- package/dist/bench/s1/arms.js +2 -2
- package/dist/boot/resolve-spec.js +26 -25
- package/dist/capabilities/code-review-council.js +3 -3
- package/dist/capabilities/team.js +1 -1
- package/dist/http/routes/runs.js +2 -2
- package/dist/http/routes/tasks.js +1 -1
- package/dist/http/server.js +20 -15
- package/dist/http/wire-types.d.ts +5 -10
- package/dist/leader/fanout.d.ts +2 -2
- package/dist/leader/leader.js +1 -1
- package/dist/leader/wire.js +8 -6
- package/dist/main.js +2 -2
- package/dist/plugins/remote-env-host.js +3 -51
- package/dist/run-local.js +2 -1
- package/dist/spec-fields.d.ts +3 -5
- package/dist/spec-fields.js +10 -8
- package/dist/trace/project.d.ts +1 -1
- package/package.json +2 -2
package/USAGE.md
CHANGED
|
@@ -221,6 +221,9 @@ SEMA_REGISTRY_URL=http://<config-center-host>:3100 # 启动拉 GET /api/config
|
|
|
221
221
|
SEMA_REGISTRY_TOKEN=<SERVICE_PULL_TOKEN 的值> # 取自配置控制面主机 .env;只读拉取令牌
|
|
222
222
|
SEMA_REGISTRY_DRY_RUN=true # 安全灰度:只 LOG 中心配置 vs env 推导的差异,不 apply
|
|
223
223
|
SEMA_REGISTRY_WORKER=<worker名> # 可选:拉取 /effective?worker=<名> 取该 worker 的 roster(reconciler 按 worker 注);不设=全局 roster(向后兼容)
|
|
224
|
+
FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启 fleet 上报腿(announce/heartbeat+usage 批报到中心)。
|
|
225
|
+
# 必须是可解析的 http(s) base URL——host:port 等坏形 5.13.0 起 boot 响亮拒
|
|
226
|
+
# (此前静默每拍 announce 400,worker 永不注册)。
|
|
224
227
|
```
|
|
225
228
|
- 中心**空/未发布** → `applyEffective` 回落 env + 内建 teams 并 warn `config_center_unpublished`,**不影响在跑的服务**(接了也安全)。
|
|
226
229
|
- **灰度姿势**(配置控制面 AI 建议):先 `SEMA_REGISTRY_DRY_RUN=true` 起一轮,看日志 `sema_registry_dry_run`(中心给的 models/roles/teams + 会否覆盖 default、per-model apiKeyEnv)对得上 env 再去掉该 flag 真正 apply。
|
package/dist/bench/s1/arms.js
CHANGED
|
@@ -76,8 +76,8 @@ function emptyCostBreakdown() {
|
|
|
76
76
|
* measurement was reward-hackable and never trustworthy) is ALSO non-scorable → excluded, never a withhold-credit.
|
|
77
77
|
*/
|
|
78
78
|
export function classifyRunStatus(input) {
|
|
79
|
-
if (input.status === "failed"
|
|
80
|
-
return "infra-failed";
|
|
79
|
+
if (input.status === "failed")
|
|
80
|
+
return "infra-failed"; // core 5.8.0:status "timeout" 退役(walltime 形并入 failed)
|
|
81
81
|
if (input.repairTerminal === "oracle.unprotected")
|
|
82
82
|
return "infra-failed"; // broken/un-isolated oracle → not scorable
|
|
83
83
|
return "scored";
|
|
@@ -624,7 +624,30 @@ export function createResolveSpec(ctx) {
|
|
|
624
624
|
// 该字段,叠一条 host-declared 字符串值=同键双条矛盾帧(by-key 投影抹掉权威来源)。server 只
|
|
625
625
|
// 声明自己命名空间的键;来源语义(caller vs 墙钟)由 timeoutSec 的 spec provenance + 上面的
|
|
626
626
|
// Cap 声明组合可读。
|
|
627
|
-
|
|
627
|
+
// core 5.8.0:预算族(maxCostUsd/maxTokens/degrade)从 TaskSpec 顶层迁入 limits——顶层键已删,
|
|
628
|
+
// 继续写顶层=经宽类型静默死键(E-HIGH-1 病族)。wire 面不变:body 顶层 maxCostUsd/maxTokens 照收
|
|
629
|
+
//(SDK 在发),装配目标改到 limits。budgetStreamCancel 语义照 core 默认(maxCostUsd 在场即 true)。
|
|
630
|
+
const maxCostUsd = cappedCeiling(body.maxCostUsd, config.maxTaskCostUsd);
|
|
631
|
+
const maxTokens = cappedCeiling(body.maxTokens, config.maxTaskTokens);
|
|
632
|
+
const degrade = (() => {
|
|
633
|
+
if (!config.degrade || maxCostUsd === undefined)
|
|
634
|
+
return undefined;
|
|
635
|
+
if (Array.isArray(body.images) && body.images.length > 0 && !config.degrade.toSupportsImages) {
|
|
636
|
+
// S14 (SILENT-FALLBACK P0-f): this drop was undetectable — the task keeps its vision-capable main
|
|
637
|
+
// model and near-budget it hard-fails on cost instead of degrading. Surface it.
|
|
638
|
+
metrics.inc("degrade_dropped_total", { reason: "vision_target" });
|
|
639
|
+
logger.warn("degrade_dropped", { reason: "vision_target", model: picked.model, images: body.images.length });
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
642
|
+
return { to: config.degrade.to, atCostFraction: config.degrade.atCostFraction };
|
|
643
|
+
})();
|
|
644
|
+
const merged = {
|
|
645
|
+
...(limits ?? {}),
|
|
646
|
+
...(maxCostUsd !== undefined ? { maxCostUsd } : {}),
|
|
647
|
+
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
648
|
+
...(degrade !== undefined ? { degrade } : {}),
|
|
649
|
+
};
|
|
650
|
+
return { ...(Object.keys(merged).length > 0 ? { limits: merged } : {}), ...(declarations.length > 0 ? { configOverrides: declarations } : {}) };
|
|
628
651
|
})(),
|
|
629
652
|
// design/129: the TOC/interactive posture (single-user turnkey — the shell's
|
|
630
653
|
// session lane) defaults background children to SESSION scope = CC Backgrounded semantics (a bg Agent/
|
|
@@ -632,30 +655,8 @@ export function createResolveSpec(ctx) {
|
|
|
632
655
|
// deployments keep core's "task" default (no orphans burning tokens). Caller-trusted spec field
|
|
633
656
|
// (systemPrompt tier), NEVER read from the request body. Same tenancy predicate as the wall clock above.
|
|
634
657
|
...(config.requirePrincipal !== true ? { backgroundScope: "session" } : {}),
|
|
635
|
-
// ⑤ Per-task budget gate
|
|
636
|
-
//
|
|
637
|
-
// when crossed. budgetStreamCancel defaults true when maxCostUsd is set.
|
|
638
|
-
maxCostUsd: cappedCeiling(body.maxCostUsd, config.maxTaskCostUsd),
|
|
639
|
-
maxTokens: cappedCeiling(body.maxTokens, config.maxTaskTokens),
|
|
640
|
-
// 1.40 near-budget degradation: only meaningful with a cost ceiling (the fraction is of it).
|
|
641
|
-
// When this task has one, switch to the cheaper model at atCostFraction instead of hard-failing.
|
|
642
|
-
// vision precheck (adversarial-review finding): DROP degrade for an image-carrying task when the
|
|
643
|
-
// degrade TARGET can't read images (toSupportsImages=false). The precheck only sees the picked model, not the
|
|
644
|
-
// external degrade target — so without this a runtime degrade would send the images to a text-only gateway (the
|
|
645
|
-
// opaque 400 the precheck prevents). The task keeps its vision-capable main model; near-budget it hard-fails on
|
|
646
|
-
// cost instead of image-failing. MODEL_DEGRADE_TO_VISION=true opts back in.
|
|
647
|
-
degrade: (() => {
|
|
648
|
-
if (!config.degrade || cappedCeiling(body.maxCostUsd, config.maxTaskCostUsd) === undefined)
|
|
649
|
-
return undefined;
|
|
650
|
-
if (Array.isArray(body.images) && body.images.length > 0 && !config.degrade.toSupportsImages) {
|
|
651
|
-
// S14 (SILENT-FALLBACK P0-f): this drop was undetectable — the task keeps its vision-capable main
|
|
652
|
-
// model and near-budget it hard-fails on cost instead of degrading (rationale above). Surface it.
|
|
653
|
-
metrics.inc("degrade_dropped_total", { reason: "vision_target" });
|
|
654
|
-
logger.warn("degrade_dropped", { reason: "vision_target", model: picked.model, images: body.images.length });
|
|
655
|
-
return undefined;
|
|
656
|
-
}
|
|
657
|
-
return { to: config.degrade.to, atCostFraction: config.degrade.atCostFraction };
|
|
658
|
-
})(),
|
|
658
|
+
// ⑤ Per-task budget gate + 1.40 near-budget degrade:core 5.8.0 起全部在 limits(上方 IIFE 合成),
|
|
659
|
+
// 顶层键已随 core 类型删除。
|
|
659
660
|
// Long-term memory (design/138 S1): enabled when the memory ENGINE is wired AND a scope was derived
|
|
660
661
|
// (single-user only — multi-tenant derives none, memory dark). MF-30 PAUSE (option B, per-request —
|
|
661
662
|
// clay 2026-06-27 confirmed with core): `body.memoryWrite:false` makes THIS run read-only over memory
|
|
@@ -86,7 +86,7 @@ async function runLensReviews(subRunner, repoTools, objective, signal, lenses) {
|
|
|
86
86
|
modelRole: "subagent",
|
|
87
87
|
tools: repoTools,
|
|
88
88
|
systemPrompt: lensSystem(lens),
|
|
89
|
-
limits: {
|
|
89
|
+
limits: { maxWalltimeMs: 180_000 },
|
|
90
90
|
enableBlockedReport: false,
|
|
91
91
|
signal,
|
|
92
92
|
});
|
|
@@ -113,7 +113,7 @@ async function runConvergence(subRunner, repoTools, objective, findings, opts, s
|
|
|
113
113
|
tools: repoTools, // members may verify a disputed claim against the real code (Tool-MAD)
|
|
114
114
|
maxTranscriptTokens: 8000,
|
|
115
115
|
synthesizer: { role: "arbiter", systemPrompt: ARBITER_SYSTEM },
|
|
116
|
-
limits: {
|
|
116
|
+
limits: { maxWalltimeMs: 400_000 },
|
|
117
117
|
signal, // 1.28: a parent abort now cascades into the debate members
|
|
118
118
|
});
|
|
119
119
|
// 1.32: synthesizer 两次都失败时 `conclusion` 是 `[unavailable…]` 垃圾串,与真结论无法区分。别当结论
|
|
@@ -134,7 +134,7 @@ async function runConvergence(subRunner, repoTools, objective, findings, opts, s
|
|
|
134
134
|
// The arbiter grounds/dedups/ranks — keep it on the strong `synthesize` role (= main model).
|
|
135
135
|
modelRole: "synthesize",
|
|
136
136
|
systemPrompt: ARBITER_SYSTEM,
|
|
137
|
-
limits: {
|
|
137
|
+
limits: { maxWalltimeMs: 240_000 },
|
|
138
138
|
enableBlockedReport: false,
|
|
139
139
|
signal,
|
|
140
140
|
});
|
|
@@ -68,7 +68,7 @@ export function createTeamTool(subRunner, template, opts) {
|
|
|
68
68
|
? { role: template.synthesizer.role, ...(template.synthesizer.model ? { model: template.synthesizer.model } : {}), modelRole: template.synthesizer.modelRole, systemPrompt: template.synthesizer.systemPrompt ?? "" }
|
|
69
69
|
: undefined,
|
|
70
70
|
maxTranscriptTokens: 8000,
|
|
71
|
-
limits: {
|
|
71
|
+
limits: { maxWalltimeMs: 600_000 },
|
|
72
72
|
signal: ctx.signal, // 1.28: a team abort now cascades into the member runs (review D)
|
|
73
73
|
});
|
|
74
74
|
ctx.reportUsage?.({ tokens: team.stats.tokens, turns: team.stats.turns, tasks: template.members.length * template.rounds + 1 });
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -228,7 +228,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
228
228
|
rootTaskId: taskId,
|
|
229
229
|
...fleetRunLabels(prepared.spec.objective), // BC-1: name = short objective preview (description = live-activity, set by the publisher onEvent tool_start)
|
|
230
230
|
});
|
|
231
|
-
void runInBackground(deps.runner, { ...prepared.spec, sessionId }, runStore, taskId, deps.metrics, prepared.auth?.principal, prepared.verify, prepared.cascade ? cascadeConfig(deps.config.cascadeLadder, prepared.spec.maxCostUsd) : undefined, deps.instrumentDegenerate, deps.planCacheProbe, deps.config.traceThinking, inflightRuns, preemptableRuns, steerableRuns, deps.modelUsage, deps.elicitation, // E23: per-run elicitation context (onElicit routes inbound MCP elicitations to this run's stream)
|
|
231
|
+
void runInBackground(deps.runner, { ...prepared.spec, sessionId }, runStore, taskId, deps.metrics, prepared.auth?.principal, prepared.verify, prepared.cascade ? cascadeConfig(deps.config.cascadeLadder, prepared.spec.limits?.maxCostUsd) : undefined, deps.instrumentDegenerate, deps.planCacheProbe, deps.config.traceThinking, inflightRuns, preemptableRuns, steerableRuns, deps.modelUsage, deps.elicitation, // E23: per-run elicitation context (onElicit routes inbound MCP elicitations to this run's stream)
|
|
232
232
|
gatedPrincipal(req, deps.config) ?? null, // E23: the VERIFIED principal that may answer (same source the respond gate uses — never the spoofable header)
|
|
233
233
|
captureTurnAnchor, // E18: per-turn (message eventId → leaf entryId) anchor capture
|
|
234
234
|
fleetPub, // MF-Fleet: the run-scoped fleet-row publisher (onStart/onEvent/onTerminal across all legs)
|
|
@@ -461,7 +461,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
461
461
|
// 并发腿可能先终态化(如 approval.expired),我方写打空却仍答 "cancelled" 就是响应与账本不一致。
|
|
462
462
|
// 写后复读,照实际行应答(锁一样释放了;谁先写赢谁的 errorCode)。
|
|
463
463
|
const finalRow = await rs.getRun(taskId).catch(() => undefined);
|
|
464
|
-
if (finalRow && finalRow.errorCode !== "cancelled" && (finalRow.status === "failed" || finalRow.status === "completed" || finalRow.status === "blocked"
|
|
464
|
+
if (finalRow && finalRow.errorCode !== "cancelled" && (finalRow.status === "failed" || finalRow.status === "completed" || finalRow.status === "blocked")) {
|
|
465
465
|
sendJson(res, 202, { taskId, status: finalRow.status, errorCode: finalRow.errorCode ?? null, note: `session unlocked; the run was terminalized concurrently (${finalRow.errorCode ?? finalRow.status}) before this cancel's write — reporting the actual ledger state` });
|
|
466
466
|
return;
|
|
467
467
|
}
|
|
@@ -1032,7 +1032,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
1032
1032
|
// (verify → cascade → plain)、短路语义、传参**逐字不变**;`runWithVerification` 仍是整对象
|
|
1033
1033
|
// 直传(快审 F1:cost 顶不在重建中丢失),`runCascade` 仍从操作员梯子 + 本任务预算现算配置。
|
|
1034
1034
|
const verifyLeg = (v) => runWithVerification(deps.runner, specWithSignal, v);
|
|
1035
|
-
const cascadeLeg = () => runCascade(deps.runner, specWithSignal, cascadeConfig(deps.config.cascadeLadder, prepared.spec.maxCostUsd));
|
|
1035
|
+
const cascadeLeg = () => runCascade(deps.runner, specWithSignal, cascadeConfig(deps.config.cascadeLadder, prepared.spec.limits?.maxCostUsd));
|
|
1036
1036
|
const plainLeg = () => deps.runner.runTask(specWithSignal);
|
|
1037
1037
|
result = await withPrincipal(principal, () => prepared.verify ? verifyLeg(prepared.verify) : prepared.cascade ? cascadeLeg() : plainLeg());
|
|
1038
1038
|
}
|
package/dist/http/server.js
CHANGED
|
@@ -947,23 +947,27 @@ export function createHttpServer(rawDeps) {
|
|
|
947
947
|
if (body.limits !== undefined) {
|
|
948
948
|
const l = body.limits;
|
|
949
949
|
if (typeof l !== "object" || l === null || Array.isArray(l)) {
|
|
950
|
-
sendError(res, 400, "request.field_invalid", "limits must be an object {
|
|
950
|
+
sendError(res, 400, "request.field_invalid", "limits must be an object { maxWalltimeMs?, maxOutputTokens?, maxTurns? } (positive integers)");
|
|
951
951
|
return null;
|
|
952
952
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
953
|
+
// core 5.8.0 时限重构:旧键响亮拒并点名替代(core 同姿势 config.limit_unknown_key;「勿自行兼容」是
|
|
954
|
+
// core 的公开指令)。timeoutSec(秒)→ maxWalltimeMs(毫秒,注意单位);deadline 族三 opt-out 随
|
|
955
|
+
// 机制整族退役(nudge/call-cap/graceful-finalize 已从引擎移除),无替代键。fresh fail-loud;resume
|
|
956
|
+
// 重放不过此门,normalizeLimits 对旧键 defensive DROP(存量 suspended 体降级为默认配速,方向安全)。
|
|
957
|
+
if (l.timeoutSec !== undefined) {
|
|
958
|
+
sendError(res, 400, "request.field_invalid", "limits.timeoutSec was retired in core 5.8.0 — use limits.maxWalltimeMs (milliseconds; opt-in wall clock)");
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
for (const k of ["deadlineNudge", "callCapByDeadline", "gracefulFinalize"]) {
|
|
962
|
+
if (l[k] !== undefined) {
|
|
963
|
+
sendError(res, 400, "request.field_invalid", `limits.${k} was retired in core 5.8.0 (the deadline-pacing mechanism family is gone; there is no replacement key — remove it)`);
|
|
957
964
|
return null;
|
|
958
965
|
}
|
|
959
966
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const v = body.limits[k];
|
|
965
|
-
if (v !== undefined && v !== false) {
|
|
966
|
-
sendError(res, 400, "request.field_invalid", `limits.${k} accepts only literal false (it is an opt-out; omit to keep the default-on behavior)`);
|
|
967
|
+
for (const k of ["maxWalltimeMs", "maxOutputTokens", "maxTurns"]) {
|
|
968
|
+
const v = l[k];
|
|
969
|
+
if (v !== undefined && (typeof v !== "number" || !Number.isInteger(v) || v < 1)) {
|
|
970
|
+
sendError(res, 400, "request.field_invalid", `limits.${k} must be a positive integer${k === "maxWalltimeMs" ? " (milliseconds)" : ""}`);
|
|
967
971
|
return null;
|
|
968
972
|
}
|
|
969
973
|
}
|
|
@@ -2200,10 +2204,11 @@ export function createHttpServer(rawDeps) {
|
|
|
2200
2204
|
deps.metrics?.inc("task_tokens_total", {}, result.stats.tokens);
|
|
2201
2205
|
if (result.stats.cacheHitRate !== undefined)
|
|
2202
2206
|
deps.metrics?.observe("task_cache_hit_rate", result.stats.cacheHitRate);
|
|
2203
|
-
// ⑤ budget/limit cutoffs
|
|
2204
|
-
//
|
|
2207
|
+
// ⑤ budget/limit cutoffs:count failures by their dotted code — core 5.8.0 起统一 `limits.` 前缀
|
|
2208
|
+
// (limits.max_{tokens,cost,turns,walltime}_exceeded;旧 budget.*/limit.* 双前缀同拍退役),
|
|
2209
|
+
// operator 按此对租户撞顶告警。metric 名保留 budget_exceeded_total(时序连续性;code label 自述新码)。
|
|
2205
2210
|
const code = result.errorCode;
|
|
2206
|
-
if (code &&
|
|
2211
|
+
if (code && code.startsWith("limits.")) {
|
|
2207
2212
|
deps.metrics?.inc("budget_exceeded_total", { code });
|
|
2208
2213
|
}
|
|
2209
2214
|
// Developer-mode verification gate (1.44): count the final verdict when this task was verified.
|
|
@@ -153,19 +153,14 @@ export interface TaskRequestBody {
|
|
|
153
153
|
};
|
|
154
154
|
/** design/132 (core 1.249): one-shot end-game verification nudge — opt-in by the AUTONOMY caller. */
|
|
155
155
|
finalVerification?: boolean;
|
|
156
|
-
/** [854]④ per-request
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* 每键各自被可选 env 上限封顶(TASK_TIMEOUT_MAX_SEC / TASK_MAX_OUTPUT_TOKENS_MAX / TASK_MAX_TURNS_MAX,
|
|
161
|
-
* 缺省不设=不封顶);timeoutSec 缺席时保持既有 tenancy 墙钟姿势(resolveTaskLimits,spec-fields.ts)。 */
|
|
156
|
+
/** [854]④ per-request 配速——core 5.8.0 时限重构后的键集(#124):`maxWalltimeMs`(毫秒,opt-in 墙钟)/
|
|
157
|
+
* `maxOutputTokens`/`maxTurns`,均正整数;可选运营方上限旋钮各自封顶(TASK_TIMEOUT_MAX_SEC 保**秒**义,
|
|
158
|
+
* 对毫秒键封顶 ×1000)。旧键 `timeoutSec` 与 deadline 族三 opt-out(机制退役)fresh-submit 400 响亮
|
|
159
|
+
* 点名替代;`maxWalltimeMs` 缺席时保持既有 tenancy 墙钟姿势(resolveTaskLimits,spec-fields.ts)。 */
|
|
162
160
|
limits?: {
|
|
163
|
-
|
|
161
|
+
maxWalltimeMs?: number;
|
|
164
162
|
maxOutputTokens?: number;
|
|
165
163
|
maxTurns?: number;
|
|
166
|
-
deadlineNudge?: false;
|
|
167
|
-
callCapByDeadline?: false;
|
|
168
|
-
gracefulFinalize?: false;
|
|
169
164
|
};
|
|
170
165
|
/** design/133 (core 1.251) + G1 (core 1.253): turn-boundary attachment reminders (todo / changed-files /
|
|
171
166
|
* plan-mode) + one-shot boundary notices (post-compaction background-task recap / deferred-tools delta).
|
package/dist/leader/fanout.d.ts
CHANGED
|
@@ -38,8 +38,8 @@ export interface WorkerReport {
|
|
|
38
38
|
* it up to its bound but it didn't fully complete — e.g. the overall deadline hit or the slice cap). Present
|
|
39
39
|
* ⇒ resumable: the operator/leader can escalate (more budget) or accept the belt-diff partial progress. */
|
|
40
40
|
checkpointGate?: TaskResult["checkpointGate"];
|
|
41
|
-
/** design/74 Slice 6: the worker's terminal `TaskResult.errorCode` (e.g. `
|
|
42
|
-
* resume, `
|
|
41
|
+
/** design/74 Slice 6: the worker's terminal `TaskResult.errorCode` (e.g. `limits.max_cost_exceeded` on an
|
|
42
|
+
* exhausted resume, `limits.max_turns_exceeded` — core 5.8.0 码名轴) — surfaced for /v1/leader observability. */
|
|
43
43
|
errorCode?: TaskResult["errorCode"];
|
|
44
44
|
/** LEADER-REPAIRLOOP-INTEGRATION §10.6 (THE push chokepoint): the `runRepairLoop` terminal a single-agent
|
|
45
45
|
* worker resolved to, when the repair loop ran on this worker (LEADER_REPAIR_LOOP on). It MUST ride on the
|
package/dist/leader/leader.js
CHANGED
|
@@ -108,7 +108,7 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
108
108
|
// Map the TaskResult status directly (like fanOut's own mapStatus) — a `suspended` result that the
|
|
109
109
|
// auto-resume left unresumed (a non-resource gate, e.g. human-approval) stays `suspended` so the
|
|
110
110
|
// leader's C4 surface/cancel reaction still fires; only completed/timeout pass through, else failed.
|
|
111
|
-
status: r.status === "completed" ? "completed" : r.status === "suspended" ? "suspended" :
|
|
111
|
+
status: r.status === "completed" ? "completed" : r.status === "suspended" ? "suspended" : "failed", // core 5.8.0:TaskResult 无 "timeout"(内部词表的 timeout 仍由 fanout 墙钟臂生产)
|
|
112
112
|
stats: r.stats,
|
|
113
113
|
...(r.status === "completed" ? {} : { error: r.errorMessage ?? r.blockedReason ?? `worker ${r.status}` }),
|
|
114
114
|
...(r.checkpointGate ? { checkpointGate: r.checkpointGate } : {}),
|
package/dist/leader/wire.js
CHANGED
|
@@ -134,7 +134,8 @@ export function leaderResourceConfig(env = process.env) {
|
|
|
134
134
|
leaderTimeoutMs,
|
|
135
135
|
workerMaxTurns,
|
|
136
136
|
workerBudgetUsd,
|
|
137
|
-
|
|
137
|
+
// core 5.8.0:预算四键全入 limits(顶层 maxCostUsd 删);walltime 原生毫秒不再除
|
|
138
|
+
workerLimits: { limits: { maxTurns: workerMaxTurns, maxWalltimeMs: leaderTimeoutMs, maxCostUsd: workerBudgetUsd } },
|
|
138
139
|
presignTtlSec: Math.ceil(leaderTimeoutMs / 1000) + 600, // URL/sandbox must outlive the worker (BUG1/BUG2)
|
|
139
140
|
...(resourceSuspendOn
|
|
140
141
|
? {
|
|
@@ -223,8 +224,7 @@ export function createLeaderRunner(cfg) {
|
|
|
223
224
|
.runTaskStream({
|
|
224
225
|
objective,
|
|
225
226
|
sessionId: `leader-repair-${Date.now()}-${round}`,
|
|
226
|
-
maxCostUsd: repairBudgetUsd,
|
|
227
|
-
limits: { timeoutSec: Math.max(300, Math.floor(leaderTimeoutMs / 1000 / 2)) },
|
|
227
|
+
limits: { maxCostUsd: repairBudgetUsd, maxWalltimeMs: Math.max(300_000, Math.floor(leaderTimeoutMs / 2)) },
|
|
228
228
|
})
|
|
229
229
|
.result();
|
|
230
230
|
// A non-completed repair (timeout/abort) may leave a process alive in the SAME Kata pod that would race
|
|
@@ -281,8 +281,7 @@ export function createLeaderRunner(cfg) {
|
|
|
281
281
|
.runTaskStream({
|
|
282
282
|
objective,
|
|
283
283
|
sessionId: `leader-conflict-${Date.now()}-${round}`,
|
|
284
|
-
maxCostUsd: repairBudgetUsd,
|
|
285
|
-
limits: { timeoutSec: Math.max(300, Math.floor(leaderTimeoutMs / 1000 / 2)) },
|
|
284
|
+
limits: { maxCostUsd: repairBudgetUsd, maxWalltimeMs: Math.max(300_000, Math.floor(leaderTimeoutMs / 2)) },
|
|
286
285
|
})
|
|
287
286
|
.result();
|
|
288
287
|
if (res.status !== "completed")
|
|
@@ -422,7 +421,10 @@ export function createLeaderRunner(cfg) {
|
|
|
422
421
|
resourceSpec = {
|
|
423
422
|
checkpointStore: cfg.durable.checkpointStore,
|
|
424
423
|
resourceSuspend: { scope: `_leader-resource-${sub.workerId}`, totalBudgetUsd: resourceCfg.totalBudgetUsd },
|
|
425
|
-
maxCostUsd:
|
|
424
|
+
// core 5.8.0:maxCostUsd 入 limits 后,本 spread(spec: {...workerLimits, ..., ...resourceSpec})的
|
|
425
|
+
// limits 键是**整体覆盖**——必须显式并回 workerLimits.limits(maxTurns/maxWalltimeMs),否则
|
|
426
|
+
// per-slice 覆盖会顺手把 worker 的轮数/墙钟界丢掉(5.7 时代顶层键覆盖顶层键,无此陷阱)。
|
|
427
|
+
limits: { ...workerLimits.limits, maxCostUsd: resourceCfg.sliceMaxCostUsd },
|
|
426
428
|
maxSuspends: resourceCfg.maxSuspends,
|
|
427
429
|
};
|
|
428
430
|
}
|
package/dist/main.js
CHANGED
|
@@ -228,8 +228,8 @@ async function main() {
|
|
|
228
228
|
model: pick.catalogRef,
|
|
229
229
|
handsReadOnly: true,
|
|
230
230
|
enableFork: false,
|
|
231
|
-
maxCostUsd
|
|
232
|
-
limits: { maxTurns: 10,
|
|
231
|
+
// core 5.8.0:预算四键全入 limits(顶层 maxCostUsd 删);timeoutSec(秒)→ maxWalltimeMs(毫秒,原生同单位)
|
|
232
|
+
limits: { maxTurns: 10, maxCostUsd: 0.05, maxWalltimeMs: Math.max(30_000, timeoutMs) },
|
|
233
233
|
// spec 无 toolPolicy → core `hasEffectAwareGate` 为假 → 每次 agent hook 触发一条
|
|
234
234
|
// error 级 UNGATED 警告(非致命但纯噪声)。挂已裁决的 auto-accept 基线(与 singleUserAutoAcceptBaseline
|
|
235
235
|
// 同构;hook 子代理本就 handsReadOnly + 单用户信任面,auto-accept 是既定姿态,只是让闸机制在场)。
|
|
@@ -534,27 +534,8 @@ export class RemoteHostExecutionEnv {
|
|
|
534
534
|
env: this.mergeEnv(options?.env),
|
|
535
535
|
...spawnGroupOptions(),
|
|
536
536
|
});
|
|
537
|
-
// core
|
|
538
|
-
//
|
|
539
|
-
if (options?.onSpawn && child.pid != null) {
|
|
540
|
-
const spawnedPid = child.pid;
|
|
541
|
-
try {
|
|
542
|
-
options.onSpawn({ pid: spawnedPid, kill: () => {
|
|
543
|
-
if (child.exitCode !== null || child.signalCode !== null)
|
|
544
|
-
return;
|
|
545
|
-
try {
|
|
546
|
-
killTreeHard(spawnedPid);
|
|
547
|
-
}
|
|
548
|
-
catch {
|
|
549
|
-
try {
|
|
550
|
-
child.kill("SIGKILL");
|
|
551
|
-
}
|
|
552
|
-
catch { /* noop */ }
|
|
553
|
-
}
|
|
554
|
-
} });
|
|
555
|
-
}
|
|
556
|
-
catch { /* registry 回调失败不挡 exec(core 同姿势吞) */ }
|
|
557
|
-
}
|
|
537
|
+
// core 5.8.0:cut-kill registry seam(onSpawn/onDetachAdopted)随 tool-cut 机制退役(core 零生产者,
|
|
538
|
+
// 本臂为死码删除;1.321 时代的 still-running 守卫语义随之谢幕)。
|
|
558
539
|
const finish = (r) => {
|
|
559
540
|
if (settled)
|
|
560
541
|
return;
|
|
@@ -629,11 +610,6 @@ export class RemoteHostExecutionEnv {
|
|
|
629
610
|
clearTimeout(forceSettleTimer);
|
|
630
611
|
options?.abortSignal?.removeEventListener("abort", onAbort);
|
|
631
612
|
settled = true;
|
|
632
|
-
// cut-kill registry 注销(所有权已移交 bgManager——cut 不该再杀后台 shell;core 参考实现同位)。
|
|
633
|
-
try {
|
|
634
|
-
options?.onDetachAdopted?.();
|
|
635
|
-
}
|
|
636
|
-
catch { /* noop */ }
|
|
637
613
|
resolve(ok({ stdout: seedOut, stderr: seedErr, exitCode: 0, detached: { shellId } }));
|
|
638
614
|
};
|
|
639
615
|
// Named (not inline) so onDetach can `off` them when it hands the pipes to the adopted background shell.
|
|
@@ -874,26 +850,7 @@ export class RemoteHostExecutionEnv {
|
|
|
874
850
|
throw e;
|
|
875
851
|
}
|
|
876
852
|
closeSpoolFds(); // 父进程的 fd 副本立即关(文件 size/EOF 只反映子进程写;launch FILE 形同姿势)
|
|
877
|
-
// core
|
|
878
|
-
if (options?.onSpawn && child.pid != null) {
|
|
879
|
-
const spawnedPid = child.pid;
|
|
880
|
-
try {
|
|
881
|
-
options.onSpawn({ pid: spawnedPid, kill: () => {
|
|
882
|
-
if (child.exitCode !== null || child.signalCode !== null)
|
|
883
|
-
return;
|
|
884
|
-
try {
|
|
885
|
-
killTreeHard(spawnedPid);
|
|
886
|
-
}
|
|
887
|
-
catch {
|
|
888
|
-
try {
|
|
889
|
-
child.kill("SIGKILL");
|
|
890
|
-
}
|
|
891
|
-
catch { /* noop */ }
|
|
892
|
-
}
|
|
893
|
-
} });
|
|
894
|
-
}
|
|
895
|
-
catch { /* registry 回调失败不挡 exec */ }
|
|
896
|
-
}
|
|
853
|
+
// core 5.8.0:cut-kill registry seam 退役(pipe 径同注)。
|
|
897
854
|
// 七轮复审:'error' 监听必须在任何可提前 return 的路径(下方 F5 already-aborted 查)之前装——异步
|
|
898
855
|
// spawn 失败(如 cwd 不存在)以 ChildProcess 'error' 事件发出,无监听=uncaught 直接打死 worker 进程
|
|
899
856
|
// (plain-node 最小形实证 crash;vitest/tsx 宿主会吸收,故该面无法用测试钉住,靠此排序保证)。
|
|
@@ -971,11 +928,6 @@ export class RemoteHostExecutionEnv {
|
|
|
971
928
|
if (shellId === undefined)
|
|
972
929
|
return; // 拒收(limit/无 pid)→ 继续 fg,泵照跑,close 正常 settle
|
|
973
930
|
handedOver = true; // spool 文件归 bg(dispose 面按 FILE 形清理)
|
|
974
|
-
// cut-kill registry 注销(所有权已移交 bgManager;pipe 径同位)。
|
|
975
|
-
try {
|
|
976
|
-
options?.onDetachAdopted?.();
|
|
977
|
-
}
|
|
978
|
-
catch { /* noop */ }
|
|
979
931
|
const o = outBuf.result();
|
|
980
932
|
const e = errBuf.result();
|
|
981
933
|
finish(ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode: 0, detached: { shellId } }));
|
package/dist/run-local.js
CHANGED
|
@@ -469,7 +469,8 @@ export async function runLocal(argv, deps = {}) {
|
|
|
469
469
|
// [849]→[2400] 场景层定死终验已无生产者(autonomous 退役);OR 折入形保留,与 resolveSpec 同语义。
|
|
470
470
|
...(cap.finalVerification === true ? { finalVerification: true } : {}),
|
|
471
471
|
...(mcp ? { mcp } : {}),
|
|
472
|
-
|
|
472
|
+
// core 5.8.0:timeoutSec 键退役 → maxWalltimeMs(毫秒);taskWallClockSec 仍产秒,此处换算一次。
|
|
473
|
+
...(timeoutSec !== undefined ? { limits: { maxWalltimeMs: timeoutSec * 1000 } } : {}),
|
|
473
474
|
};
|
|
474
475
|
logger.info("run_local_start", { scenario: scenarioName, model: spec.model, sessionId, exec: config.remoteExec?.provider ?? "in-process" });
|
|
475
476
|
// ── Run ONE task to completion (the sync /v1/tasks path: plain runTask, no verify/cascade). ──
|
package/dist/spec-fields.d.ts
CHANGED
|
@@ -87,7 +87,8 @@ export declare function toolNameListFromBody(raw: unknown): string[] | undefined
|
|
|
87
87
|
export declare function promptProfileFromBody(raw: unknown): "simple" | "classic" | undefined;
|
|
88
88
|
/** [854]④ — 每键的运营方上限旋钮(env,缺省不设=不封顶)。 */
|
|
89
89
|
export interface TaskLimitCaps {
|
|
90
|
-
/** TASK_TIMEOUT_MAX_SEC
|
|
90
|
+
/** TASK_TIMEOUT_MAX_SEC(env 名不动,仍按**秒**配置;core 5.8.0 起封顶对象是 limits.maxWalltimeMs,
|
|
91
|
+
* 封顶比较时 ×1000 换算——运维面零迁移)。 */ timeoutSec?: number;
|
|
91
92
|
/** TASK_MAX_OUTPUT_TOKENS_MAX */ maxOutputTokens?: number;
|
|
92
93
|
/** TASK_MAX_TURNS_MAX */ maxTurns?: number;
|
|
93
94
|
}
|
|
@@ -98,12 +99,9 @@ export interface TaskLimitCaps {
|
|
|
98
99
|
* 运营方上限旋钮 clamp(Math.min;旋钮缺省不设=不封顶)。全空 ⇒ undefined。
|
|
99
100
|
*/
|
|
100
101
|
export declare function normalizeLimits(v: unknown, caps: TaskLimitCaps): {
|
|
101
|
-
|
|
102
|
+
maxWalltimeMs?: number;
|
|
102
103
|
maxOutputTokens?: number;
|
|
103
104
|
maxTurns?: number;
|
|
104
|
-
deadlineNudge?: false;
|
|
105
|
-
callCapByDeadline?: false;
|
|
106
|
-
gracefulFinalize?: false;
|
|
107
105
|
} | undefined;
|
|
108
106
|
/**
|
|
109
107
|
* [854]④ — body.limits 与 env 墙钟的合成规则(resolveSpec 的唯一 limits 装配点;run-local 无 body 车道不经此):
|
package/dist/spec-fields.js
CHANGED
|
@@ -359,6 +359,9 @@ export function promptProfileFromBody(raw) {
|
|
|
359
359
|
export function normalizeLimits(v, caps) {
|
|
360
360
|
if (v == null || typeof v !== "object" || Array.isArray(v))
|
|
361
361
|
return undefined;
|
|
362
|
+
// core 5.8.0:键集换 maxWalltimeMs(毫秒)。旧键(timeoutSec/deadline 族三 opt-out)fresh-submit 已在
|
|
363
|
+
// HTTP 门响亮 400 点名替代;本层只走 resume 重放的持久化 body——旧键 defensive DROP(存量 suspended
|
|
364
|
+
// 体降级为默认配速,env 墙钟兜底仍在,方向安全;绝不静默换算=那是「自行兼容」,core 明令禁止)。
|
|
362
365
|
const o = v;
|
|
363
366
|
const pick = (raw, cap) => {
|
|
364
367
|
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 1)
|
|
@@ -367,14 +370,10 @@ export function normalizeLimits(v, caps) {
|
|
|
367
370
|
return cap !== undefined ? Math.min(n, cap) : n;
|
|
368
371
|
};
|
|
369
372
|
const out = {
|
|
370
|
-
|
|
373
|
+
// caps.timeoutSec 是秒(env TASK_TIMEOUT_MAX_SEC 保名保义),封顶毫秒键时 ×1000。
|
|
374
|
+
...(() => { const n = pick(o.maxWalltimeMs, caps.timeoutSec !== undefined ? caps.timeoutSec * 1000 : undefined); return n !== undefined ? { maxWalltimeMs: n } : {}; })(),
|
|
371
375
|
...(() => { const n = pick(o.maxOutputTokens, caps.maxOutputTokens); return n !== undefined ? { maxOutputTokens: n } : {}; })(),
|
|
372
376
|
...(() => { const n = pick(o.maxTurns, caps.maxTurns); return n !== undefined ? { maxTurns: n } : {}; })(),
|
|
373
|
-
// 历史复审轴B #4(1.254):deadline 族三个 **opt-out**(design/128/130,timeoutSec 在场即默认 ON;
|
|
374
|
-
// server 恒注 tenancy 墙钟 ⇒ 此前恒开且 wire 无法关)。只认 literal false(纯关闭旋钮,零扩权面)。
|
|
375
|
-
...(o.deadlineNudge === false ? { deadlineNudge: false } : {}),
|
|
376
|
-
...(o.callCapByDeadline === false ? { callCapByDeadline: false } : {}),
|
|
377
|
-
...(o.gracefulFinalize === false ? { gracefulFinalize: false } : {}),
|
|
378
377
|
};
|
|
379
378
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
380
379
|
}
|
|
@@ -389,8 +388,11 @@ export function normalizeLimits(v, caps) {
|
|
|
389
388
|
*/
|
|
390
389
|
export function resolveTaskLimits(bodyLimits, caps, envTimeoutSec, requirePrincipal, big) {
|
|
391
390
|
const requested = normalizeLimits(bodyLimits, caps);
|
|
392
|
-
|
|
393
|
-
|
|
391
|
+
// core 5.8.0:墙钟键=maxWalltimeMs(毫秒)。taskWallClockSec 仍以秒推导(tenancy 基值+env 只抬不降,
|
|
392
|
+
// 语义原样),装配处换算一次;body 显式给了 maxWalltimeMs 则直接生效(caller 意图,可低于 env 墙)。
|
|
393
|
+
const wallSec = taskWallClockSec(envTimeoutSec, requirePrincipal, big);
|
|
394
|
+
const maxWalltimeMs = requested?.maxWalltimeMs ?? (wallSec !== undefined ? wallSec * 1000 : undefined);
|
|
395
|
+
const out = { ...(requested ?? {}), ...(maxWalltimeMs !== undefined ? { maxWalltimeMs } : {}) };
|
|
394
396
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
395
397
|
}
|
|
396
398
|
//# sourceMappingURL=spec-fields.js.map
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -287,7 +287,7 @@ export declare function compactedEventData(ev: {
|
|
|
287
287
|
modelFallback?: true;
|
|
288
288
|
fallbackReason?: "window";
|
|
289
289
|
clampedRatio?: number;
|
|
290
|
-
clampReason?: "budget" | "
|
|
290
|
+
clampReason?: "budget" | "tolerance";
|
|
291
291
|
tokensAfter?: number;
|
|
292
292
|
triggerTokensBefore?: number;
|
|
293
293
|
durationMs?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.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.
|
|
57
|
+
"@sema-agent/core": "^5.8.0",
|
|
58
58
|
"@sema-agent/registry-core": "^0.14.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|