@sema-agent/server 6.5.0 → 6.6.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.
@@ -280,6 +280,15 @@ export function createResolveSpec(ctx) {
280
280
  if (rawAddDirs !== undefined && !cwdHonored(config)) {
281
281
  logger.warn("task_additional_directories_ignored", { honored: false, sessionId: auth?.sessionId ?? null });
282
282
  }
283
+ // core 5.11.0([2625] Added①,[2597] P0 案三决③A):`additionalReadDirectories` —— 只读围栏拓宽。
284
+ // 与 additionalDirectories 同门(host 单用户车道)、同形校验(parseAdditionalDirectories:绝对路径
285
+ // 数组 fail-loud)、同「忽略必留声」纪律;差异只在语义:只进 READ 面(classify 读边界 auto-allow +
286
+ // read_file/grep 等 containment),写面照旧拒——读授权永不静默变写授权(core 侧保证,我们只透传)。
287
+ const rawAddReadDirs = body.additionalReadDirectories;
288
+ const additionalReadDirectories = cwdHonored(config) ? parseAdditionalDirectories(rawAddReadDirs) : undefined;
289
+ if (rawAddReadDirs !== undefined && !cwdHonored(config)) {
290
+ logger.warn("task_additional_read_directories_ignored", { honored: false, sessionId: auth?.sessionId ?? null });
291
+ }
283
292
  // body.model (explicit /model picker or run --model) WINS over settings.model (the config default); both gate
284
293
  // against the catalog allow-list via resolveTaskModel (a settings.model not in the catalog is ignored, never injected).
285
294
  // 🔴 TOB 档位联跑曾 FAIL(坐标级):the gate must see the TIER-EXPANDED catalog — expandTiers
@@ -320,7 +329,7 @@ export function createResolveSpec(ctx) {
320
329
  metrics.inc("images_omitted_total", { model: picked.model });
321
330
  logger.warn("images_omitted_no_vision_model", { model: picked.model, count: body.images.length });
322
331
  }
323
- return { taskHooks, additionalDirectories, wireCatalog, picked };
332
+ return { taskHooks, additionalDirectories, additionalReadDirectories, wireCatalog, picked };
324
333
  };
325
334
  /** 阶段③(anchors):吃 请求体 + auth,吐 两个历史锚解析后的 entryId(resumeAt / rewindFilesTo —— eventId→entryId
326
335
  * 过 anchor store,未知锚在此 4xx 而不是交给 core)与 142-S4 项目登记簿种子 s4DefaultScopes。 */
@@ -374,7 +383,7 @@ export function createResolveSpec(ctx) {
374
383
  /** 阶段④(spec 字面量):吃 请求体 + auth + `opts.leg` + 前三阶段的全部产出,吐**尚未过治理层**的 TaskSpec
375
384
  * 字面量 —— 部署方拥有的 body→TaskSpec 映射本体(compactionModel 的 fresh 腿 400 也在这一段)。 */
376
385
  const assembleSpecLiteral = (body, auth, opts, parts) => {
377
- const { scenarioName, cap, centerDecls, acceptedAppend, parsedSettings, attachmentNotice, taskHooks, additionalDirectories, wireCatalog, picked, resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes, } = parts;
386
+ const { scenarioName, cap, centerDecls, acceptedAppend, parsedSettings, attachmentNotice, taskHooks, additionalDirectories, additionalReadDirectories, wireCatalog, picked, resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes, } = parts;
378
387
  const spec = {
379
388
  // D-1:附件告知随 objective 进 durable 流(只有名字/mime/尺寸——**内容永不进流**,这正是
380
389
  // 「拼进 objective 是伪方案」的账要划清的线;文件名已消毒为安全字符集,无注入面)。
@@ -464,6 +473,8 @@ export function createResolveSpec(ctx) {
464
473
  // design/119 (CC --add-dir): extra host dirs the FILE tools may access (core canonicalizes each into the
465
474
  // containment allowlist + lists them in the `# Environment` block). Single-user host lane only (gated above).
466
475
  additionalDirectories,
476
+ // core 5.11.0:只读围栏(见上方 parse 段注)。缺席=字节等价([2625] 自查③)。
477
+ additionalReadDirectories,
467
478
  // EnterPlanMode (core 1.167): MODEL-DRIVEN plan — `enablePlanMode:true` alone (writable
468
479
  // start, NO handsReadOnly) auto-mounts `enter_plan_mode`/`present_plan` so the model can self-enter read-only
469
480
  // plan AT ANY TIME it judges a task needs planning. 🆕 DEFAULT ON (clay: resident; PLAN_MODE_ENABLED=false opts
@@ -691,6 +691,24 @@ export function createHttpServer(rawDeps) {
691
691
  return null;
692
692
  }
693
693
  }
694
+ // core 5.11.0:additionalReadDirectories(只读围栏)—— 与 additionalDirectories 同形 fail-loud
695
+ // (绝对路径数组、无 `..`、同条数上限);语义差异(只进 READ 面)在 core,形校验在此对齐。
696
+ const addReadDirsRaw = body.additionalReadDirectories;
697
+ if (addReadDirsRaw !== undefined) {
698
+ if (!Array.isArray(addReadDirsRaw)) {
699
+ sendError(res, 400, "request.field_invalid", "additionalReadDirectories must be an array of absolute host paths");
700
+ return null;
701
+ }
702
+ if (addReadDirsRaw.length > MAX_ADDITIONAL_DIRS) {
703
+ sendError(res, 400, "request.field_invalid", `additionalReadDirectories must have at most ${MAX_ADDITIONAL_DIRS} entries`);
704
+ return null;
705
+ }
706
+ const badRead = addReadDirsRaw.find((d) => !isValidCwd(d));
707
+ if (badRead !== undefined) {
708
+ sendError(res, 400, "request.field_invalid", "each additionalReadDirectories entry must be a non-empty absolute host path (no '..' segments)");
709
+ return null;
710
+ }
711
+ }
694
712
  // body.settings (client SemaSettings stamp) — fail-loud shape/size caps on submit, matching the sibling
695
713
  // fields' posture (parseTaskSettings is also defensive on every path incl. resume; this 400s a bad submit early
696
714
  // rather than silently coercing — adversarial review). The semantic projection (tighten-only) happens in resolveSpec.
@@ -131,7 +131,11 @@ export declare function leaderResourceConfig(env?: NodeJS.ProcessEnv): {
131
131
  /** design/74 Slice 6: opt-in (LEADER_RESOURCE_SUSPEND=true) resource-slice suspend for workers. When set, a
132
132
  * worker SUSPENDS at the per-slice window (`sliceMaxCostUsd`) instead of failing at the budget, and the
133
133
  * leader auto-resumes (resumeWithVerification) until the TOTAL (`totalBudgetUsd` = workerBudgetUsd) is spent
134
- * — a budget overrun becomes a resumable pause, not a worker failure. `maxSuspends` is sized to the slice
134
+ * — a budget overrun becomes a resumable pause, not a worker failure. ⚠️ 语义更新(core 5.11.0 B3,
135
+ * 2026-08-06):`maxSuspends` 现在只数「连续无进展」park(批准+执行后链基归零),不再是总 park 数上限
136
+ * ——下面按预算切片总数放大的 sizing 公式因此从「必要」变「保守冗余」(合法的资源续跑链永不触 cap,
137
+ * cap 只剩防空转环职责);公式保留是无害的(上限只会更松不误杀),别再按「总数预算」理解它。
138
+ * 原注:`maxSuspends` is sized to the slice
135
139
  * count (+margin) so the suspend-loop cap doesn't fire before the budget is legitimately spent. */
136
140
  resourceSuspend?: {
137
141
  totalBudgetUsd: number;
@@ -382,18 +382,19 @@ export class ToolApprovalCoordinator {
382
382
  // [1559]三:emit 完成 vs TTL/abort/respond 三选一落定——显式 race,不让挂死的 emit 独占执行权。
383
383
  await Promise.race([emitAllP, allowedP]);
384
384
  const settled = await allowedP;
385
- // Completion breadcrumb (card dismiss) — fire-and-forget to every target that actually got the OPEN frame
386
- // (aliveCtxs a ctx filtered out at entry as already-dead never saw `tool_approval` in the first place, so
387
- // it has nothing to dismiss), so a slow/hung emit never delays returning to core AND every connection in
388
- // the set learns the card resolved elsewhere([1559]四「其余连接收 resolved/dismissed 通知」的直接落点——
389
- // 单连接场景退化为原来的一次 emit)。**per-ctx 链在该 ctx 自己的 openP 之后**(F2 修的落点)——
390
- // `openByCtx.get(c)!` 永不 reject(emitOne 内部已吸收),所以不需要额外 `.catch` 才能继续链下去;
391
- // `.then(() => c.emit(...))` 包一层是必须的:该 ctx 的 complete emit 若同步 throw,要先转成
392
- // rejection 才能被后面的 `.catch` 接住,否则会在此处同步抛出、跳过 catch。
385
+ // Completion breadcrumb (card dismiss) — fire-and-forget to every target that actually got the OPEN frame,
386
+ // so a slow/hung emit never delays returning to core AND every connection that saw the card learns it
387
+ // resolved elsewhere([1559]四「其余连接收 resolved/dismissed 通知」的直接落点——单连接场景退化为
388
+ // 原来的一次 emit)。**per-ctx 链在该 ctx 自己的 openP 之后且看它的布尔结果**([2557] 批3 F2 D1 修,
389
+ // 2026-08-06):emitOne 失败是 resolve(false) 不是 reject,旧形 `.then(() => emit(complete))`
390
+ // resolve 值全放行 ⇒ open 没送达的连接(瞬断型 emit 目标)也收 `tool_approval_complete` = 孤儿
391
+ // close,注释与代码曾不符(红先=wire-pairing-approval D1,修后翻正)。`delivered` 门后:
392
+ // `.then((delivered) => delivered ? c.emit(...) : undefined)` —— complete emit 若同步 throw,
393
+ // `.then` 回调内抛出转成 rejection 由尾部 `.catch` 吸收(链形不变)。
393
394
  for (const c of aliveCtxs) {
394
395
  void openByCtx
395
396
  .get(c)
396
- .then(() => c.emit({ type: "tool_approval_complete", approvalId: id, outcome: lastOutcome }))
397
+ .then((delivered) => (delivered ? c.emit({ type: "tool_approval_complete", approvalId: id, outcome: lastOutcome }) : undefined))
397
398
  .catch(() => undefined);
398
399
  }
399
400
  // emit 全灭 = 卡从未送达任何人(≠人拒绝/TTL 走人)——同属「无活人可达」类 ⇒ "unavailable" 交 durable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "6.5.0",
3
+ "version": "6.6.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.10.0",
57
+ "@sema-agent/core": "^5.11.0",
58
58
  "@sema-agent/registry-core": "^0.14.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",
@@ -68,7 +68,7 @@
68
68
  "sharp": "^0.35.3"
69
69
  },
70
70
  "devDependencies": {
71
- "@sema-agent/sdk": "^6.3.0",
71
+ "@sema-agent/sdk": "^6.4.0",
72
72
  "@types/libsodium-wrappers": "^0.7.14",
73
73
  "@types/node": "22.10.2",
74
74
  "@types/pg": "^8.20.0",