@sema-agent/server 7.0.0 → 7.1.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.
@@ -812,7 +812,13 @@ export function createResolveSpec(ctx) {
812
812
  // per-task across suspend is a larger, separate decision that must cover the approval baseline too — tracked
813
813
  // for center (adversarial-review HIGH-1), deliberately NOT a governance-only partial freeze here (that would
814
814
  // be an inconsistent false-safety: approval would still be live).
815
- const governedBase = applyRuntimeGovernance(spec, { autonomy: config.autonomy, commandPolicy: config.commandPolicy });
815
+ // #153([2713] 分单②):MANUAL_MODE_SHELL_GATE 在此无条件施加( autonomy 同拍)——部署旋钮不再
816
+ // 依赖客户端 settings 在场性/模式臂/host lane(三重条件曾让 print/headless 腿与沙箱 lane 全静默失效)。
817
+ const governedBase = applyRuntimeGovernance(spec, {
818
+ autonomy: config.autonomy,
819
+ commandPolicy: config.commandPolicy,
820
+ ...(config.manualModeShellGate ? { manualModeShellGate: config.manualModeShellGate } : {}),
821
+ });
816
822
  // Fold the client's per-request settings stamp onto the governed spec, TIGHTEN-ONLY (deny-wins) — AFTER
817
823
  // the approval baseline + operator governance, so the order is deployment ⊇ operator ⊇ client (each can only
818
824
  // narrow). tightenTaskSpec THROWS if the client settings would LOOSEN a stricter baseline → a 422 client error
@@ -890,10 +896,8 @@ export function createResolveSpec(ctx) {
890
896
  // (deny 恒赢 —— 豁免/acceptDirs 越不过;集合取舍 core 成文,server 只透传 config 旋钮:
891
897
  // 缺省 = core RECOMMENDED_SENSITIVE_PATTERNS,SENSITIVE_WRITE_PATTERNS 显式替换/off 关闭)。
892
898
  ...(config.sensitiveWritePatterns.length > 0 ? { sensitivePatterns: config.sensitiveWritePatterns } : {}),
893
- // [1557]§四 opt-in(cli[1555]②「Bash echo > file 绕写门」缺口的部署侧补丁):MANUAL_MODE_SHELL_GATE
894
- // 未设置时 config.manualModeShellGate 缺席,本行不传字段——deriveSettingsPolicy 的 default/
895
- // auto/acceptEdits 分支照旧不碰 TaskSpec.shellGate,零行为变化。
896
- ...(config.manualModeShellGate ? { shellGate: config.manualModeShellGate } : {}),
899
+ // [1557]§四 shellGate 旧家已随 #153 搬到 applyRuntimeGovernance(governance 层无条件施加,
900
+ // 见上方 governedBase)——wiring 不再承运该旋钮,FsWriteGateWiring.shellGate 字段一并拆除。
897
901
  // [841]① / core 1.294 exemption seam:gate 产 ask 前查同一只 approval_exemption 店(与 decide
898
902
  // remember="session" 的授予、ask 政策层探针同店同 canonical toolName 键空间)——「本会话不再询问」
899
903
  // 对 fs-write 门同 turn 即时生效(父+继承子任务,ask 路径不再重入)。has() reject ⇒ core 按
@@ -86,15 +86,23 @@ export declare function compileCommandPolicy(rules: CommandRule[] | undefined):
86
86
  */
87
87
  export declare function autonomyOverrides(autonomy: Autonomy | undefined): Partial<TaskSpec>;
88
88
  /**
89
- * Apply the operator's runtime governance (autonomy + commandPolicy) onto a base `TaskSpec`, TIGHTEN-ONLY, in a
90
- * SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy` override (combined onto the
91
- * baseline by `tightenTaskSpec` — TRAP #1), and autonomy expands to `handsReadOnly` / `shellGate` overrides
92
- * (TRAP #2). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any override would LOOSEN a stricter base safety
93
- * field — surfacing a misconfiguration loudly instead of silently weakening the gate. Returns `base` untouched
94
- * when there is nothing to apply (no autonomy override and no commandPolicy).
89
+ * Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate) onto a base
90
+ * `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy`
91
+ * override (combined onto the baseline by `tightenTaskSpec` — TRAP #1), and autonomy expands to
92
+ * `handsReadOnly` / `shellGate` overrides (TRAP #2). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any
93
+ * override would LOOSEN a stricter base safety field — surfacing a misconfiguration loudly instead of silently
94
+ * weakening the gate. Returns `base` untouched when there is nothing to apply.
95
+ *
96
+ * #153([2713] server 分单②):`MANUAL_MODE_SHELL_GATE` 从 settings 折叠搬到这里**无条件施加**。旧家
97
+ * (task-settings deriveSettingsPolicy 的 default/auto/acceptEdits 三臂)让一个**部署级 tighten-only 旋钮
98
+ * 的生死由客户端表态在场性决定**:body 无 settings/permissionMode(cli print/headless 腿按设计不 stamp)
99
+ * ⇒ 旋钮静默失效(clay 实机 Monitor 案「门根本没铸」);bypassPermissions 一句话掀掉部署闸;非 host lane
100
+ * fsWriteGate wiring 不建 ⇒ 沙箱 lane 全失效。governance 层与 autonomy 同拍施加后三者全闭。rank 合成:
101
+ * autonomy 派生值与旋钮取大(ask→"always" 压过 "classify"),再对 base 只升不降(防 tightenTaskSpec throw)。
95
102
  */
96
103
  export declare function applyRuntimeGovernance(base: TaskSpec, governance: {
97
104
  autonomy?: Autonomy;
98
105
  commandPolicy?: CommandRule[];
106
+ manualModeShellGate?: "always" | "classify";
99
107
  }): TaskSpec;
100
108
  //# sourceMappingURL=runtime-governance.d.ts.map
@@ -204,19 +204,40 @@ export function autonomyOverrides(autonomy) {
204
204
  return {};
205
205
  }
206
206
  }
207
+ /** core `tighten-task-spec` 的 SHELL_GATE_RANK 镜像(单一属主:#153 搬家后本文件是 server 侧唯一副本;
208
+ * task-settings 的旧副本随 settings 折叠臂一并拆除)。tightenTaskSpec 的契约是「override 自身必须收紧」
209
+ * 而非「取两者较严」——低于 base 的 override 直接 throw,所以施加前要按 rank 只升不降。 */
210
+ const SHELL_GATE_RANK = { off: 0, classify: 1, always: 2 };
207
211
  /**
208
- * Apply the operator's runtime governance (autonomy + commandPolicy) onto a base `TaskSpec`, TIGHTEN-ONLY, in a
209
- * SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy` override (combined onto the
210
- * baseline by `tightenTaskSpec` — TRAP #1), and autonomy expands to `handsReadOnly` / `shellGate` overrides
211
- * (TRAP #2). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any override would LOOSEN a stricter base safety
212
- * field — surfacing a misconfiguration loudly instead of silently weakening the gate. Returns `base` untouched
213
- * when there is nothing to apply (no autonomy override and no commandPolicy).
212
+ * Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate) onto a base
213
+ * `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy`
214
+ * override (combined onto the baseline by `tightenTaskSpec` — TRAP #1), and autonomy expands to
215
+ * `handsReadOnly` / `shellGate` overrides (TRAP #2). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any
216
+ * override would LOOSEN a stricter base safety field — surfacing a misconfiguration loudly instead of silently
217
+ * weakening the gate. Returns `base` untouched when there is nothing to apply.
218
+ *
219
+ * #153([2713] server 分单②):`MANUAL_MODE_SHELL_GATE` 从 settings 折叠搬到这里**无条件施加**。旧家
220
+ * (task-settings deriveSettingsPolicy 的 default/auto/acceptEdits 三臂)让一个**部署级 tighten-only 旋钮
221
+ * 的生死由客户端表态在场性决定**:body 无 settings/permissionMode(cli print/headless 腿按设计不 stamp)
222
+ * ⇒ 旋钮静默失效(clay 实机 Monitor 案「门根本没铸」);bypassPermissions 一句话掀掉部署闸;非 host lane
223
+ * fsWriteGate wiring 不建 ⇒ 沙箱 lane 全失效。governance 层与 autonomy 同拍施加后三者全闭。rank 合成:
224
+ * autonomy 派生值与旋钮取大(ask→"always" 压过 "classify"),再对 base 只升不降(防 tightenTaskSpec throw)。
214
225
  */
215
226
  export function applyRuntimeGovernance(base, governance) {
216
227
  const overrides = { ...autonomyOverrides(governance.autonomy) };
217
228
  const coarse = compileCommandPolicy(governance.commandPolicy);
218
229
  if (coarse)
219
230
  overrides.toolPolicy = coarse;
231
+ if (governance.manualModeShellGate !== undefined) {
232
+ const candidate = overrides.shellGate !== undefined && SHELL_GATE_RANK[overrides.shellGate] >= SHELL_GATE_RANK[governance.manualModeShellGate]
233
+ ? overrides.shellGate
234
+ : governance.manualModeShellGate;
235
+ // base 已更严(如 resume 重放时 base 带 always)⇒ 省略,让 base 原样保留(省略=行为等价,不 throw)。
236
+ if (SHELL_GATE_RANK[candidate] >= SHELL_GATE_RANK[base.shellGate ?? "off"])
237
+ overrides.shellGate = candidate;
238
+ else
239
+ delete overrides.shellGate;
240
+ }
220
241
  if (overrides.toolPolicy === undefined && overrides.handsReadOnly === undefined && overrides.shellGate === undefined) {
221
242
  return base; // nothing to govern → avoid a needless tightenTaskSpec pass
222
243
  }
@@ -181,19 +181,21 @@ export interface FsWriteGateWiring {
181
181
  * now closed). Source = config `SENSITIVE_WRITE_PATTERNS` (unset ⇒ core RECOMMENDED_SENSITIVE_PATTERNS; the
182
182
  * set curation is core's — the server passes through). Absent/empty ⇒ no sensitive policy (pre-1.295 shape). */
183
183
  sensitivePatterns?: string[];
184
- /** [1557]§四 opt-in(cli[1555]②「echo -n … > file 绕写门」缺口的部署侧补丁,core[1556] 建议):
185
- * CC manual 系模式(default/auto/acceptEdits — 见 {@link deriveSettingsPolicy})額外把
186
- * `TaskSpec.shellGate` 收紧到这个值,让 Bash 走同一 fs-write 审批链(core `bashReversibilityProbe`
187
- * 在 `"classify"` 下只拦构造性不可逆命令——含 shell 重定向 `>`/`>>` 等,纯读命令白名单自动放行)。
188
- * **Absent(缺省)= 零行为变化**(config `MANUAL_MODE_SHELL_GATE` 未设置时不传本字段,pre-此批行为
189
- * byte-for-byte;这是个 tighten-only opt-in,不是默认开启——运营方需要显式选择开启)。 */
190
- shellGate?: "always" | "classify";
191
184
  }
192
185
  export declare function deriveSettingsPolicy(settings: ParsedTaskSettings, gate?: FsWriteGateWiring, workflowGate?: WorkflowGateWiring): {
193
186
  toolPolicy?: ToolPolicy;
194
187
  handsReadOnly?: boolean;
195
188
  enablePlanMode?: boolean;
196
- shellGate?: "always" | "classify";
197
189
  };
190
+ /**
191
+ * Apply a client's parsed per-request settings onto a base `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link
192
+ * tightenTaskSpec} pass (so the permission override composes deny-wins onto the baseline — TRAP #1 — and
193
+ * `handsReadOnly`/`onAsk` can only narrow). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any override would
194
+ * LOOSEN a stricter base — surfaced by the caller as a 4xx (a client settings stamp that tries to widen is a
195
+ * client error, never a silent weakening). `model`/`outputStyle` are applied directly (model is the caller's
196
+ * choice WITHIN the catalog — the caller gates it before calling; outputStyle appends to the system prompt).
197
+ *
198
+ * Returns `base` untouched when the parsed settings project nothing onto the spec.
199
+ */
198
200
  export declare function applyTaskSettings(base: TaskSpec, settings: ParsedTaskSettings, gate?: FsWriteGateWiring, workflowGate?: WorkflowGateWiring): TaskSpec;
199
201
  //# sourceMappingURL=task-settings.d.ts.map
@@ -426,18 +426,16 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
426
426
  return { toolPolicy: compose(createWorkflowPlanDenyPolicy()), handsReadOnly: true, enablePlanMode: true };
427
427
  case "acceptEdits": {
428
428
  const p = compose(fsWriteGate(true), createWorkflowAskPolicy(workflowGate?.isExempt));
429
- return { ...(p ? { toolPolicy: p } : {}), ...(gate?.shellGate ? { shellGate: gate.shellGate } : {}) };
429
+ return p ? { toolPolicy: p } : {};
430
430
  }
431
431
  case "default":
432
432
  case "auto": {
433
433
  // (historical note kept: the pre-[816] impl fell through to base-only here — with no gate to attach,
434
434
  // acceptEdits-as-loosen could not be honored. The [820] factory inverts the frame: default now ADDS an ask
435
435
  // gate and acceptEdits ADDS a narrower one, both tighten-safe under the deny-wins fold.)
436
- // [1557]§四: gate.shellGate opt-in(absent = pre-此批行为 byte-for-byte)rides alongside the fs-write ask
437
- // gate on the SAME "CC manual" modes — closes the "echo > file bypasses the write gate" hole (cli[1555]②)
438
- // when the deployment explicitly opts in (config MANUAL_MODE_SHELL_GATE).
436
+ // ([1557]§四 shellGate 便车已随 #153 governance 层——见 FsWriteGateWiring 尾注。)
439
437
  const p = compose(fsWriteGate(false), createWorkflowAskPolicy(workflowGate?.isExempt));
440
- return { ...(p ? { toolPolicy: p } : {}), ...(gate?.shellGate ? { shellGate: gate.shellGate } : {}) };
438
+ return p ? { toolPolicy: p } : {};
441
439
  }
442
440
  case "bypassPermissions":
443
441
  case undefined:
@@ -454,27 +452,17 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
454
452
  *
455
453
  * Returns `base` untouched when the parsed settings project nothing onto the spec.
456
454
  */
457
- // [1557]§四: core's tightenTaskSpec THROWS TaskSpecTightenError when an override's shellGate ranks LOWER than
458
- // base's (its contract is "the override must itself tighten", not "take the stricter of the two" — unlike
459
- // handsReadOnly, which ORs). Red-first repro: tightenTaskSpec({shellGate:"always"}, {shellGate:"classify"})
460
- // throws — so a deployment that layers autonomy=ask (→"always") UNDER this opt-in (→"classify") would 422
461
- // EVERY request on that mode. Mirrors core's own SHELL_GATE_RANK (tighten-task-spec.js) — only pass the
462
- // override through when it would not be a downgrade; otherwise the baseline already covers it (tightenTaskSpec's
463
- // base-only branch keeps `base.shellGate` unchanged either way — omitting is behaviorally identical, never a
464
- // silent weakening, just avoids the throw).
465
- const SHELL_GATE_RANK = { off: 0, classify: 1, always: 2 };
455
+ // ([1557]§四 SHELL_GATE_RANK 副本与 shellGateSafe 守卫已随 #153 runtime-governance ——
456
+ // settings 折叠不再触碰 TaskSpec.shellGate,base governance 层施加的值原样透传。)
466
457
  export function applyTaskSettings(base, settings, gate, workflowGate) {
467
- const { toolPolicy, handsReadOnly, enablePlanMode, shellGate } = deriveSettingsPolicy(settings, gate, workflowGate);
468
- const shellGateSafe = shellGate !== undefined && SHELL_GATE_RANK[shellGate] >= SHELL_GATE_RANK[base.shellGate ?? "off"] ? shellGate : undefined;
458
+ const { toolPolicy, handsReadOnly, enablePlanMode } = deriveSettingsPolicy(settings, gate, workflowGate);
469
459
  let next = base;
470
- if (toolPolicy !== undefined || handsReadOnly !== undefined || shellGateSafe !== undefined) {
460
+ if (toolPolicy !== undefined || handsReadOnly !== undefined) {
471
461
  const overrides = {};
472
462
  if (toolPolicy !== undefined)
473
463
  overrides.toolPolicy = toolPolicy;
474
464
  if (handsReadOnly !== undefined)
475
465
  overrides.handsReadOnly = handsReadOnly;
476
- if (shellGateSafe !== undefined)
477
- overrides.shellGate = shellGateSafe; // [1557]§四 opt-in; rank-guarded above
478
466
  next = tightenTaskSpec(next, overrides); // deny-wins compose + THROWS on loosen (fail-loud)
479
467
  }
480
468
  // R4: enablePlanMode (mount present_plan) is ADDITIVE — it widens NOTHING (a read-only plan workflow), so it rides
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.0.0",
3
+ "version": "7.1.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",
@@ -68,7 +68,7 @@
68
68
  "sharp": "^0.35.3"
69
69
  },
70
70
  "devDependencies": {
71
- "@sema-agent/sdk": "^6.5.0",
71
+ "@sema-agent/sdk": "^6.6.0",
72
72
  "@types/libsodium-wrappers": "^0.7.14",
73
73
  "@types/node": "22.10.2",
74
74
  "@types/pg": "^8.20.0",