@sema-agent/server 5.1.1 → 5.2.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.
@@ -668,7 +668,10 @@ export function fleetBackgroundChildPublisher(bus, log) {
668
668
  // 现状 core 只在 terminal 事件带此键(三发射点亲核)⇒ 本透传对旧 core 惰性;core 停 spawn/tick
669
669
  // 源头补上即通,tick 臂(下方 :78x)早已同姿。server 不自行以 sessionId 推导——fork lane 的
670
670
  // forkTranscriptId 与 sessionId 的等价关系归 core 所有,不在消费端烙死。
671
- bus.publishTask({ id: e.taskId, name: redactSecrets(e.description ?? e.name ?? e.taskId), ...(e.agentType ? { agentType: redactSecrets(e.agentType) } : {}), ...rowTags(m), status: "running", tokens: 0, ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) });
671
+ // [2400] FLEET-WORKFLOWS-7:[2070]③「名不铸」的第四处——spawn 双缺 description/name 时此前 `?? e.taskId`
672
+ // 拿 a* handle 冒充显示名;tick 臂只在 name 在场才发 + 总线 MERGE ⇒ 假名永久留行。缺席不铸,与其余三处同判。
673
+ const spawnName = e.description ?? e.name;
674
+ bus.publishTask({ id: e.taskId, ...(spawnName !== undefined ? { name: redactSecrets(spawnName) } : {}), ...(e.agentType ? { agentType: redactSecrets(e.agentType) } : {}), ...rowTags(m), status: "running", tokens: 0, ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) });
672
675
  dlog("bg_child_event", { kind: "spawn", taskId: e.taskId, sessionScoped: e.sessionScoped, scope: m.tenantScope, hostSessionId: m.hostSessionId ?? null, parentTaskId: m.parentTaskId ?? null });
673
676
  };
674
677
  const handleTick = (e, m) => {
@@ -18,12 +18,18 @@ export const ACTIVE_RUN_CONFLICT_BASE_TEXT = "session already has an active run
18
18
  /** gate.kind → 它的那一个 resume 入口(sessionId/taskId 寻址,无秘密)。 */
19
19
  export function resumeEntryForGate(kind, ids) {
20
20
  switch (kind) {
21
+ // tool_approval / policy_ask 不是 core 现役 kind(wire 集=WIRE_GATE_KINDS 六个,type 门双向等值)——
22
+ // 这两臂是**耐久历史行容忍**([2373] D 类):旧 core 铸的 checkpoint 行可能带旧 kind 名,店里读出来
23
+ // 仍要给出真出路;不是对上游新帧的兼容臂,别当 [2354] 清理对象误删。
21
24
  case "tool_approval":
22
25
  case "human":
23
26
  case "irreversible_ask":
24
27
  case "policy_ask":
25
28
  return `/v1/approvals/${ids.sessionId}/decide`;
26
29
  case "plan_review":
30
+ // needs_review 与 plan_review 同入口(TASKS-RUNS-8 附注,[2400] 审计补臂):routes/tasks.ts 的
31
+ // needs_review park 注明文「resumable via /plan_review」——此前缺臂 ⇒ 409 体 pendingGate 丢指引。
32
+ case "needs_review":
27
33
  return `/v1/assistant/tasks/${ids.taskId}/plan_review`;
28
34
  case "resource_limit":
29
35
  return `/v1/assistant/tasks/${ids.taskId}/resume`;
@@ -508,8 +508,10 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
508
508
  // verify-reject): an unbound decide can consume a gate that was SWAPPED after the operator's GET (see
509
509
  // gate A, approve lands on gate B) — tolerable as a one-shot legacy quirk, NOT as the mint of a
510
510
  // SESSION-PERSISTENT auto-approval for a tool the operator never saw. remember is a NEW contract, so
511
- // it can demand the strict calling shape (the BFF already receives boundCallId/checkpointToken on the
512
- // pending row). Plain decide without remember keeps the legacy unbound fallback untouched.
511
+ // it can demand the strict calling shape the BFF receives boundCallId on the pending row
512
+ // (HITL-7 注纠,[2400] 审计:checkpointToken 从不上 pending 行,listPending SELECT token 列;
513
+ // 下面的门是「或」关系,boundCallId 单独即满足,所以旧措辞没造成真故障)。
514
+ // Plain decide without remember keeps the legacy unbound fallback untouched.
513
515
  if (!body.checkpointToken && !body.boundCallId) {
514
516
  sendError(res, 400, "remember_requires_binding", "remember requires the decision binding (echo checkpointToken and/or boundCallId from the pending approval)");
515
517
  return;
@@ -127,12 +127,13 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
127
127
  subagentResume: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
128
128
  // TaskRequest.settings (client per-request SemaSettings stamp) → projected into the engine TIGHTEN-ONLY
129
129
  // (deny-wins, after the deployment + operator baseline). v1 wires permissions + permissionMode (default/
130
- // acceptEdits/plan — `bypassPermissions` is NEVER honored from settings) + model + outputStyle. `env` is
131
- // DEFERRED (TaskSpec deliberately has no per-task env injection) advertised here so the client never
132
- // silently loses a setting (it can warn the user / withhold those fields).
130
+ // acceptEdits/plan — `bypassPermissions` is NEVER honored from settings) + model + outputStyle.
131
+ // `env`([2400] X-8):与真消费点同谓词——resolve-spec setSessionShellEnv 只在 cwdHonored
132
+ // (单用户 host lane)注入 shell env;其余 lane 收到即警告忽略。此前硬编码 false 是「TaskSpec
133
+ // per-task env 注入」时代的陈旧断言(R-survey shellEnv seam 落地后失真=说没有但其实有,活能力被藏)。
133
134
  // `hooks`(hook-runner 阶段一):TRUE 当本部署会真跑 hook 命令 = 单用户闸(hook 命令跑在
134
135
  // worker host,class ② 能力授予;resolveSpec 的 taskHooks 装配同一谓词)。多租户 = false(收到警告忽略)。
135
- taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: false, hooks: deps.config.requirePrincipal !== true },
136
+ taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: cwdHonored(deps.config), hooks: deps.config.requirePrincipal !== true },
136
137
  // [1476] R1 / [1478] R2: TaskRequest.appendSystemPrompt is accepted top-level → TaskSpec.appendSystemPrompt
137
138
  // (first-class lane for the shell's product-knowledge block). A shell probes THIS flag: false/absent ⇒ fall
138
139
  // back to the settings.outputStyle ride-along (older servers fold that into the same spec field).
@@ -542,7 +542,13 @@ async function handleRunsBody(req, res, url, ctx, miss) {
542
542
  try {
543
543
  body = (await readJson(req));
544
544
  }
545
- catch {
545
+ catch (e) {
546
+ // TASKS-RUNS-5([2400] 审计):readJson 的 HttpError(413 超限)原样透传——裸 catch 折成
547
+ // 400 invalid_json 会让 9 MiB 合法 JSON 被报「JSON 非法」(compact 腿是既有对照组正确形)。
548
+ if (e instanceof HttpError) {
549
+ sendError(res, e.status, httpErrorCode(e.status, e.code), e.message);
550
+ return;
551
+ }
546
552
  sendError(res, 400, "request.invalid_json", "invalid JSON body");
547
553
  return;
548
554
  }
@@ -856,7 +862,11 @@ async function handleRunsBody(req, res, url, ctx, miss) {
856
862
  try {
857
863
  body = (await readJson(req));
858
864
  }
859
- catch {
865
+ catch (e) {
866
+ if (e instanceof HttpError) {
867
+ sendError(res, e.status, httpErrorCode(e.status, e.code), e.message);
868
+ return;
869
+ } // TASKS-RUNS-5 同族
860
870
  sendError(res, 400, "request.invalid_json", "invalid JSON body");
861
871
  return;
862
872
  }
@@ -1187,7 +1197,11 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
1187
1197
  try {
1188
1198
  body = (await readJson(req));
1189
1199
  }
1190
- catch {
1200
+ catch (e) {
1201
+ if (e instanceof HttpError) {
1202
+ sendError(res, e.status, httpErrorCode(e.status, e.code), e.message);
1203
+ return;
1204
+ } // TASKS-RUNS-5 同族
1191
1205
  sendError(res, 400, "request.invalid_json", "invalid JSON body");
1192
1206
  return;
1193
1207
  }
@@ -159,17 +159,30 @@ export interface TaskRequestBody {
159
159
  timeoutSec?: number;
160
160
  maxOutputTokens?: number;
161
161
  maxTurns?: number;
162
+ deadlineNudge?: false;
163
+ callCapByDeadline?: false;
164
+ gracefulFinalize?: false;
162
165
  };
163
166
  /** design/133 (core 1.251) + G1 (core 1.253): turn-boundary attachment reminders (todo / changed-files /
164
- * plan-mode) + one-shot boundary notices (post-compaction background-task recap / deferred-tools delta). */
167
+ * plan-mode) + one-shot boundary notices (post-compaction background-task recap / deferred-tools delta).
168
+ * [2400] CAPS-OPS-13:本类型经 src/index.ts 导出给 embedder——键集必须与真验收面(spec-fields.ts
169
+ * normalizeAttachments 10 键 / normalizeLimits 6 键)同源;此前少 8 键 ⇒ agentListing:false(唯一能关
170
+ * core 默认开列表注入的通道)与三个 deadline opt-out 在类型面失踪。agentListing/skillsListing 是
171
+ * boolean 透传(core DEFAULT-ON,explicit false 才关);budgetUsd/mcpInstructions 只认 literal true;
172
+ * limits 三个 opt-out 只认 literal false(normalizer 同判)。 */
165
173
  attachments?: {
166
174
  todoReminder?: boolean;
175
+ todoReminderMode?: "baseline" | "off";
167
176
  changedFiles?: boolean | {
168
177
  maxFiles?: number;
169
178
  };
170
179
  planModeReminder?: boolean;
180
+ budgetUsd?: true;
171
181
  backgroundTasks?: boolean;
172
182
  toolsDelta?: boolean;
183
+ mcpInstructions?: true;
184
+ agentListing?: boolean;
185
+ skillsListing?: boolean;
173
186
  };
174
187
  /** C1 (core 1.219, subagent viewing pane): opt-in — forward a delegated child's live CONTENT events
175
188
  * (text_delta/reasoning_delta/tool_start/tool_end, each stamped `parentToolCallId`) onto this run's stream/durable
package/dist/runs.js CHANGED
@@ -697,8 +697,10 @@ promptManifests) {
697
697
  }
698
698
  // E12 (shell-host contract): after a COMPLETED run, pull core's opt-in post-completion prompt suggestions (a fire-and-forget
699
699
  // pass in core; suggestions() never rejects + resolves to [] when off / not completed / empty). UNTRUSTED model
700
- // text for the shell UI ONLY → redact + persist as a `suggestions` event so the shell reads it off the events tail
701
- // (like model_usage); NEVER re-fed to a model. Gated on a COMPLETED terminal a suspended/parked/failed leg has
700
+ // text for the shell UI ONLY → redact + persist as a `suggestions` event on the durable ledger; NEVER re-fed to a
701
+ // model. ⚠️ 读法(TASKS-RUNS-1 注漂移修,[2400] 审计):它落账在 setTerminal **之后**,而 live tail
702
+ // (sse-log)在终态时有意**不投递**这一帧(见 sse-log.ts 注)——消费契约是 `await result()` 后从
703
+ // `GET /v1/runs/:id` 的 RunRecord.suggestions 读,**不是**从 events tail 等它。 Gated on a COMPLETED terminal — a suspended/parked/failed leg has
702
704
  // none (core also returns [] for a non-completed task; the explicit status gate avoids the needless call + pins the
703
705
  // completed-only contract rather than depending on core's guard).
704
706
  if (spec.suggestNextPrompts && reached?.kind === "done" && reached.result.status === "completed") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "5.1.1",
3
+ "version": "5.2.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",