@sema-agent/server 3.0.0 → 3.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.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +13 -9
  4. package/dist/approval-hmac.js +1 -2
  5. package/dist/bench/s1/live-deps.js +1 -1
  6. package/dist/boot/config-center.d.ts +3 -2
  7. package/dist/boot/config-center.js +33 -19
  8. package/dist/boot/resolve-spec.js +5 -5
  9. package/dist/brain.js +42 -9
  10. package/dist/config-center/apply-effective.d.ts +1 -1
  11. package/dist/config-center/apply-effective.js +4 -4
  12. package/dist/config-center/facade.d.ts +47 -0
  13. package/dist/config-center/facade.js +46 -0
  14. package/dist/config-center/http-client.d.ts +1 -1
  15. package/dist/config-center/http-client.js +6 -6
  16. package/dist/config-center/restart-signal.d.ts +11 -3
  17. package/dist/config-center/restart-signal.js +51 -7
  18. package/dist/config-center/skills-mcp.js +1 -1
  19. package/dist/config-center/types.d.ts +2 -2
  20. package/dist/config-provider.d.ts +7 -7
  21. package/dist/config-provider.js +9 -9
  22. package/dist/config.d.ts +1 -1
  23. package/dist/config.js +5 -5
  24. package/dist/http/routes/approvals-assistant.js +3 -3
  25. package/dist/http/routes/runs.js +7 -7
  26. package/dist/http/routes/sessions.js +1 -1
  27. package/dist/http/routes/trace-usage.js +2 -2
  28. package/dist/http/server.d.ts +1 -1
  29. package/dist/http/server.js +3 -3
  30. package/dist/key-resolver.d.ts +14 -0
  31. package/dist/key-resolver.js +27 -11
  32. package/dist/plugins/remote-env-host.d.ts +32 -14
  33. package/dist/plugins/remote-env-host.js +30 -10
  34. package/dist/plugins/remote-env-local-docker.d.ts +14 -0
  35. package/dist/plugins/remote-env-local-docker.js +10 -3
  36. package/dist/plugins/run-store-sql.d.ts +8 -3
  37. package/dist/plugins/run-store-sql.js +8 -3
  38. package/dist/run-local.js +2 -2
  39. package/dist/runtime-caps-resolver.d.ts +1 -1
  40. package/dist/runtime-caps-resolver.js +1 -1
  41. package/dist/runtime-governance.d.ts +1 -1
  42. package/dist/runtime-governance.js +1 -1
  43. package/dist/sema-registry.d.ts +18 -4
  44. package/dist/sema-registry.js +17 -3
  45. package/package.json +2 -2
@@ -28,8 +28,9 @@
28
28
  * (`{ ok:true, exitCode }`), never an error.
29
29
  *
30
30
  * Platforms: macOS + Linux (POSIX shell). Forward-compatible with the `remoteExec` superset: this file also
31
- * scaffolds the `local-docker` / `remote-docker` providers' factory entry points (typed stubs that throw a
32
- * clear "not yet implemented" explicit fast-follow per DUAL-MODE-DESIGN §5; NOT faked green).
31
+ * hosts the `local-docker` / `remote-docker` providers' factory entry points both now IMPLEMENTED by the
32
+ * container adapter in remote-env-local-docker.ts (the two providers differ only in whether the `-H` daemon
33
+ * endpoint is off-box and required), re-exported/wrapped here so the discriminator has one canonical site.
33
34
  */
34
35
  import { spawn } from "node:child_process";
35
36
  import os from "node:os";
@@ -1647,19 +1648,38 @@ export function hostExecutionEnvFactory(config = {}) {
1647
1648
  // ─────────────────────────────── remoteExec superset: docker providers ───────────────────────────────
1648
1649
  //
1649
1650
  // DUAL-MODE-DESIGN §5 defines a `remoteExec.provider` superset. `host` (above) is implemented; `local-docker`
1650
- // is NOW IMPLEMENTED (the isolation lane) it lives in its own module (remote-env-local-docker.ts) and is
1651
- // re-exported here so the discriminator entry point + the historical import site stay stable. `remote-docker`
1652
- // (a REMOTE docker daemon over TLS/SSH) is still an explicit fast-follow stub — it throws a clear "not yet
1653
- // implemented" so selecting it fails loud at wiring time instead of silently degrading isolation. NOT faked green.
1651
+ // (the isolation lane) and `remote-docker` (that same container lane aimed at a REMOTE daemon) are BOTH
1652
+ // implemented by remote-env-local-docker.ts and re-exported / wrapped here so the discriminator entry points +
1653
+ // the historical import sites stay stable.
1654
1654
  // `local-docker` (the isolation lane): real implementation moved to remote-env-local-docker.ts. Re-export the
1655
1655
  // factory + config from here so existing wiring (`import { localDockerExecutionEnvFactory } from "./remote-env-host"`)
1656
1656
  // keeps working and the provider discriminator has one canonical entry point.
1657
1657
  export { localDockerExecutionEnvFactory, RemoteLocalDockerExecutionEnv } from "./remote-env-local-docker.js";
1658
+ import { localDockerExecutionEnvFactory } from "./remote-env-local-docker.js";
1658
1659
  /**
1659
- * `remote-docker` factory entry point wired but an explicit TODO (DUAL-MODE-DESIGN §5 fast-follow). Throws
1660
- * at factory-BUILD time so selecting it before it exists fails loud rather than degrading isolation silently.
1660
+ * `ExecutionEnvFactory` for the TOC `remote-docker` backend (DUAL-MODE-DESIGN §5): a per-task container on
1661
+ * SOMEONE ELSE'S docker daemon (offload the operator's box stays free; isolation:true, suspendable:false).
1662
+ * Same lifetime contract as local-docker: unconnected/lazy, one container per task, `destroy()` = `docker rm -f`.
1663
+ *
1664
+ * 🔴 **Endpoint credentials are the docker CLI's own** — this adapter deliberately carries none. The docker CLI
1665
+ * reads `DOCKER_CERT_PATH` + `DOCKER_TLS_VERIFY` from the worker's environment (inherited by every spawn) for a
1666
+ * `tcp://` + TLS daemon, and uses `ssh-agent` / `~/.ssh/config` for an `ssh://` one. That keeps key material out
1667
+ * of this process's config objects entirely. Consequence to know: `tcp://…:2376` WITHOUT those env vars fails
1668
+ * loud at connect (the daemon refuses plaintext) — it does not silently downgrade; a plaintext `tcp://…:2375`
1669
+ * daemon is unauthenticated and remains the operator's decision, exactly as in the registry-core schema.
1670
+ * (The registry-core `remote-docker` arm additionally models `tlsCertEnv`/`tlsKeyEnv`/`tlsCaEnv`/`sshKeyEnv` =
1671
+ * env-NAMEs the control plane resolves to PEM BYTES. Materializing those bytes to short-lived 0600 files and
1672
+ * passing `--tlsverify --tlscert/--tlskey/--tlscacert` is the remaining config-surface follow-up, together with
1673
+ * the `provider:"remote-docker"` arm on `AppConfig.remoteExec` — see the note on that union in config-types.ts.)
1674
+ *
1675
+ * Throws at factory-BUILD time on a missing/blank endpoint: a "remote" lane with no endpoint would otherwise
1676
+ * quietly become the LOCAL daemon — an isolation-topology surprise, so it fails loud instead.
1661
1677
  */
1662
- export function remoteDockerExecutionEnvFactory(_config) {
1663
- throw new RemoteExecutionError("unsupported", "remoteExec provider 'remote-docker' is not yet implemented (DUAL-MODE-DESIGN §5 fast-follow) — use 'host', 'local-docker', or 'e2b' until it ships");
1678
+ export function remoteDockerExecutionEnvFactory(config, deps = {}) {
1679
+ const dockerHost = config.dockerHost?.trim();
1680
+ if (!dockerHost) {
1681
+ throw new RemoteExecutionError("connect_failed", "remoteExec provider 'remote-docker' requires a non-empty dockerHost (the remote daemon endpoint, e.g. tcp://host:2376 or ssh://user@host) — use provider 'local-docker' for THIS machine's daemon");
1682
+ }
1683
+ return localDockerExecutionEnvFactory({ ...config, dockerHost, providerLabel: "remote-docker" }, deps);
1664
1684
  }
1665
1685
  //# sourceMappingURL=remote-env-host.js.map
@@ -17,8 +17,20 @@ export interface LocalDockerEnvConfig {
17
17
  /**
18
18
  * DOCKER_HOST override for the docker CLI (e.g. a non-default socket). Passed as `-H <value>` on EVERY docker
19
19
  * invocation (not the process env) so it can't leak / drift. Absent = the daemon DOCKER_HOST points at.
20
+ *
21
+ * Pointing this OFF-BOX (`tcp://host:2376`, `ssh://user@host`) is exactly the DUAL-MODE-DESIGN §5
22
+ * **`remote-docker`** provider — see {@link remoteDockerExecutionEnvFactory} (remote-env-host.ts), which is
23
+ * this adapter with the endpoint required. Nothing else about the class is host-local: the workspace is
24
+ * `mkdir`'d INSIDE the container (never a bind-mount of a host path) and bytes move by `docker cp` /
25
+ * `docker exec cat`, which stream over the daemon API.
20
26
  */
21
27
  dockerHost?: string;
28
+ /**
29
+ * Provider NAME reported on the {@link WorkspaceHandle}. Default `"local-docker"`; the `remote-docker` arm
30
+ * passes `"remote-docker"` so a handle never mislabels which lane it is (the CLASS is shared — `remote-docker`
31
+ * IS this adapter aimed at a remote daemon). Cosmetic/observability only: it selects no behavior.
32
+ */
33
+ providerLabel?: string;
22
34
  /** Container memory limit (docker `--memory`, e.g. "2g"). Absent = no limit. */
23
35
  memory?: string;
24
36
  /** Container CPU limit (docker `--cpus`, e.g. 1.5). Absent = no limit. */
@@ -65,6 +77,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
65
77
  private readonly spawnFn;
66
78
  /** Stable container NAME (also our handle id). Created on connect; targeted by every docker exec/cp/rm. */
67
79
  private readonly containerName;
80
+ /** Provider name put on the WorkspaceHandle ("local-docker" | "remote-docker"). Observability only. */
81
+ private readonly providerName;
68
82
  /** Set once the container is up + the workspace dir created. */
69
83
  private containerId?;
70
84
  private handle?;
@@ -66,6 +66,8 @@ export class RemoteLocalDockerExecutionEnv {
66
66
  spawnFn;
67
67
  /** Stable container NAME (also our handle id). Created on connect; targeted by every docker exec/cp/rm. */
68
68
  containerName;
69
+ /** Provider name put on the WorkspaceHandle ("local-docker" | "remote-docker"). Observability only. */
70
+ providerName;
69
71
  /** Set once the container is up + the workspace dir created. */
70
72
  containerId;
71
73
  handle;
@@ -92,6 +94,7 @@ export class RemoteLocalDockerExecutionEnv {
92
94
  const id = config.id ?? randomBytes(6).toString("hex");
93
95
  const suffix = randomBytes(4).toString("hex"); // always-unique tail so a reused id never collides
94
96
  this.containerName = `sema-docker-${sanitizeId(id)}-${suffix}`;
97
+ this.providerName = config.providerLabel?.trim() || PROVIDER;
95
98
  }
96
99
  resolve(p) {
97
100
  return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
@@ -165,7 +168,7 @@ export class RemoteLocalDockerExecutionEnv {
165
168
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
166
169
  }
167
170
  this.containerId = run.value.stdout.trim() || this.containerName;
168
- this.handle = { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
171
+ this.handle = { sandboxId: this.containerName, provider: this.providerName, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
169
172
  if (config?.secrets?.length) {
170
173
  // Secrets are injected per-command via cfg.env (resolved by the control plane), not at connect — record only.
171
174
  }
@@ -197,7 +200,7 @@ export class RemoteLocalDockerExecutionEnv {
197
200
  // re-run `docker run --name <same>` → name conflict → the failure path rm -f's the very container we just
198
201
  // re-attached to (destroying the running workspace). The name IS our exec/handle target.
199
202
  this.containerId = this.containerName;
200
- this.handle ??= { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
203
+ this.handle ??= { sandboxId: this.containerName, provider: this.providerName, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
201
204
  return ok(this.handle);
202
205
  }
203
206
  return { ok: false, error: new RemoteExecutionError("connect_failed", `container '${this.containerName}' is not running (gone or stopped) — no managed snapshot to resume`) };
@@ -609,7 +612,11 @@ export class RemoteLocalDockerExecutionEnv {
609
612
  return;
610
613
  this.containerShellProbed = true;
611
614
  try {
612
- const r = await this.docker(this.withHostFlag(["exec", this.containerName, "/bin/sh", "-c", "command -v bash"]), { timeoutMs: this.cfg.controlTimeoutMs });
615
+ // 🔴 NO withHostFlag here — `docker()` already prepends it. A second `-H` is not a harmless repeat: the
616
+ // docker CLI rejects the whole command (`invalid argument … specify only one -H`), so on every
617
+ // REMOTE-daemon container (`dockerHost` set — always, on the `remote-docker` arm) the probe used to fail
618
+ // and silently degrade the user shell to /bin/sh for a reason unrelated to the image.
619
+ const r = await this.docker(["exec", this.containerName, "/bin/sh", "-c", "command -v bash"], { timeoutMs: this.cfg.controlTimeoutMs });
613
620
  if (r.ok && r.value.exitCode === 0 && r.value.stdout.trim().length > 0)
614
621
  this.containerShell = "bash";
615
622
  }
@@ -126,9 +126,14 @@ export declare class SqlRunStore {
126
126
  * suspend, an INTENTIONAL durable pause" (design/76 §2.5) — the service MUST park it the same way, else the run is
127
127
  * terminalized + its lock released and the plan_review/dry_run_review resume can never run (it gets
128
128
  * taskId=undefined and drives the model unprotected on an unlocked session). CAS on `running`/`needs_review`
129
- * (reaper-revert guard, mirrors setSuspended). 🔴 The store SQL here is exercised by mocked unit tests only — a
130
- * real-TiDB integration test of the park→resume→claim invariant is a required-before-GA TODO (the lesson from the
131
- * P2 lease-SQL + this needs_review-park bug: mocked tests hide store-SQL defects).
129
+ * (reaper-revert guard, mirrors setSuspended). The park→resume→claim invariant is pinned on BOTH REAL engines by
130
+ * `test/run-store-reaper-integration.test.ts` (`inv#1` row ownership across park→resume→handover, `inv#2` two
131
+ * replicas racing one session exactly one claim wins, `inv#3`/`inv#3'` stale-claim takeover vs fresh/parked
132
+ * claims), closing the former required-before-GA gap. Keep it that way: the P2 lease-SQL bug and this
133
+ * needs_review-park bug were both born from store SQL that mocked unit tests pass — measured, not asserted, when
134
+ * the invariant set was built: an upserting (non-exclusive) claim reddened all four real-DB cases while all 111
135
+ * mocked run-store unit tests stayed green. A mock cannot observe a unique-key claim race, a rolled-back loser,
136
+ * or a multi-table reaper DELETE.
132
137
  */
133
138
  setNeedsReview(taskId: string): Promise<void>;
134
139
  /**
@@ -206,9 +206,14 @@ export class SqlRunStore {
206
206
  * suspend, an INTENTIONAL durable pause" (design/76 §2.5) — the service MUST park it the same way, else the run is
207
207
  * terminalized + its lock released and the plan_review/dry_run_review resume can never run (it gets
208
208
  * taskId=undefined and drives the model unprotected on an unlocked session). CAS on `running`/`needs_review`
209
- * (reaper-revert guard, mirrors setSuspended). 🔴 The store SQL here is exercised by mocked unit tests only — a
210
- * real-TiDB integration test of the park→resume→claim invariant is a required-before-GA TODO (the lesson from the
211
- * P2 lease-SQL + this needs_review-park bug: mocked tests hide store-SQL defects).
209
+ * (reaper-revert guard, mirrors setSuspended). The park→resume→claim invariant is pinned on BOTH REAL engines by
210
+ * `test/run-store-reaper-integration.test.ts` (`inv#1` row ownership across park→resume→handover, `inv#2` two
211
+ * replicas racing one session exactly one claim wins, `inv#3`/`inv#3'` stale-claim takeover vs fresh/parked
212
+ * claims), closing the former required-before-GA gap. Keep it that way: the P2 lease-SQL bug and this
213
+ * needs_review-park bug were both born from store SQL that mocked unit tests pass — measured, not asserted, when
214
+ * the invariant set was built: an upserting (non-exclusive) claim reddened all four real-DB cases while all 111
215
+ * mocked run-store unit tests stayed green. A mock cannot observe a unique-key claim race, a rolled-back loser,
216
+ * or a multi-table reaper DELETE.
212
217
  */
213
218
  async setNeedsReview(taskId) {
214
219
  await this.db.query(this.q("UPDATE task_run SET status = 'needs_review', updated_at = ? WHERE task_id = ? AND status IN ('running','needs_review')", "UPDATE task_run SET status = 'needs_review', updated_at = $1 WHERE task_id = $2 AND status IN ('running','needs_review')"), [new Date(), taskId]);
package/dist/run-local.js CHANGED
@@ -33,7 +33,7 @@ import { taskWallClockSec } from "./task-workflow.js";
33
33
  import { validatePromptsDomain, applyCatalogToSource, CORE_ENGINE_VERSION } from "./capabilities/center-prompts.js";
34
34
  import { loadConfig, logConfigDiagnostics } from "./config.js";
35
35
  import { createConfigProvider } from "./config-provider.js";
36
- import { applyEffective, resolveMcpServers, mcpForScenario } from "./sema-registry.js";
36
+ import { applyEffective, resolveMcpServers, mcpForScenario } from "./config-center/facade.js";
37
37
  import { hostExecutionEnvFactory } from "./plugins/remote-env-host.js";
38
38
  import { makeLoadProjectMemory, makeProbeInstructionSources } from "./project-memory.js";
39
39
  import { loadSkills } from "./capabilities/skills.js";
@@ -272,7 +272,7 @@ export async function runLocal(argv, deps = {}) {
272
272
  return 2;
273
273
  }
274
274
  const sessionStore = fileBackend.sessionStore;
275
- const keyResolver = createKeyResolver(config.modelApiKeyEnv);
275
+ const keyResolver = createKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys); // [2006] 后续:三参全链(sealed→env→undefined),与 boot 正形同源——二参形曾让 sealed 模型静默落网关 key
276
276
  // Host exec lane: run on THIS machine, in a FIXED workspace (CC-like) — the cwd by default (or --workspace /
277
277
  // a config.d workdir). The agent's file edits PERSIST there + are user-visible; the dir is the user's OWN and
278
278
  // is NEVER deleted (workspaceDir mode). One env per task; the Runner owns its lifetime.
@@ -28,7 +28,7 @@
28
28
  */
29
29
  import type { RuntimeCaps } from "@sema-agent/core";
30
30
  import type { EntitlementRuntimeCaps } from "@sema-agent/registry-core";
31
- import { fetchPrincipalCaps, type ExecutionRuling } from "./sema-registry.js";
31
+ import { fetchPrincipalCaps, type ExecutionRuling } from "./config-center/facade.js";
32
32
  import type { ScenarioRuling } from "./capabilities/scenarios.js";
33
33
  export interface EntitlementsResolverOpts {
34
34
  baseUrl: string;
@@ -1,4 +1,4 @@
1
- import { fetchPrincipalCaps } from "./sema-registry.js";
1
+ import { fetchPrincipalCaps } from "./config-center/facade.js";
2
2
  import { HttpError } from "./security.js";
3
3
  import { createLogger } from "./observability/logger.js";
4
4
  const defaultLogger = createLogger();
@@ -51,7 +51,7 @@ export type Autonomy = "read-only" | "ask" | "plan" | "auto";
51
51
  export declare const COARSE_SHELL_TOOLS_MIRROR: readonly string[];
52
52
  /** Validate `commandPolicy` rules — the shape (array of {command, decision}), the `command` (a bare argv[0] name,
53
53
  * see {@link VALID_COMMAND_NAME}), AND the `decision` enum. Returns the list of human-readable errors (empty =
54
- * OK). Called at config-apply (sema-registry.ts `applyRuntimeHot`) so a malformed rule that BYPASSED the
54
+ * OK). Called at config-apply (config-center/facade.ts `applyRuntimeHot`) so a malformed rule that BYPASSED the
55
55
  * registry schema (a hand-edited config.d / a non-conformant publish) is rejected LOUDLY rather than
56
56
  * silently mis-compiling — review MEDIUM: an out-of-enum `decision` (e.g. "DENY") would otherwise fall through
57
57
  * {@link compileCommandPolicy}'s `decision==="deny"?…:"ask"` to ASK, silently WEAKENING an intended deny. */
@@ -108,7 +108,7 @@ const VALID_COMMAND_NAME = /^[A-Za-z0-9._+-]+$/;
108
108
  const VALID_DECISIONS = new Set(["allow", "ask", "deny"]);
109
109
  /** Validate `commandPolicy` rules — the shape (array of {command, decision}), the `command` (a bare argv[0] name,
110
110
  * see {@link VALID_COMMAND_NAME}), AND the `decision` enum. Returns the list of human-readable errors (empty =
111
- * OK). Called at config-apply (sema-registry.ts `applyRuntimeHot`) so a malformed rule that BYPASSED the
111
+ * OK). Called at config-apply (config-center/facade.ts `applyRuntimeHot`) so a malformed rule that BYPASSED the
112
112
  * registry schema (a hand-edited config.d / a non-conformant publish) is rejected LOUDLY rather than
113
113
  * silently mis-compiling — review MEDIUM: an out-of-enum `decision` (e.g. "DENY") would otherwise fall through
114
114
  * {@link compileCommandPolicy}'s `decision==="deny"?…:"ask"` to ASK, silently WEAKENING an intended deny. */
@@ -10,9 +10,23 @@
10
10
  * (core 1.45), so each model/cascade-rung authenticates with its own upstream key.
11
11
  *
12
12
  * Per-model `baseUrl` IS transported (a catalog model may live on a different gateway;
13
- * core brain honors model.baseUrl, absent = "" = boot-env endpoint). TODO remaining: hot-reload
14
- * of models/roles needs Runner support — today teams hot-reload via the registry, while a models/roles
15
- * change is logged as "restart to apply".
13
+ * core brain honors model.baseUrl, absent = "" = boot-env endpoint).
14
+ *
15
+ * Hot-reload status (复审 2026-07-29 P1-11 — 亲读判定,取代此处旧的 "models/roles are restart-to-apply"
16
+ * TODO, which went stale when `mutateInPlace` landed):
17
+ * - models/roles/roster/teams/projects/autonomy — **HOT**. `applyEffective` mutates `config.models`/
18
+ * `config.roles` IN PLACE, and core's Runner reads `this.deps.models/roles` per task off that very
19
+ * reference. Both brains re-resolve `model.baseUrl || config.baseUrl` inside `buildRequest()` on every
20
+ * call (core 2.1.0 `brain/openai.js` + `brain/anthropic.js`), so a moved gateway takes effect on the
21
+ * next task with no restart.
22
+ * - the same plane under a **tier-frozen** Runner — deferred, not hot: core expands a PRIVATE catalog copy
23
+ * at construction, so `main` defers the whole plane and the `models-tiers` restart slice signals.
24
+ * - **`degrade-route`** — the one genuinely boot-frozen catalog consumer left: reactive degrade
25
+ * (`MODEL_DEGRADE_REACTIVE`) bakes the target Model + its gateway + its key into the brain composition
26
+ * at boot. It cannot be hot-applied without rebuilding the brain, so it is registered on the existing
27
+ * restartRequired /health contract instead (see `restart-signal.ts`). Fail-loud beats serving a stale
28
+ * gateway silently on the rate-limit path.
29
+ * - skills/mcp/scenarios/runtime-gates — restart-to-apply by construction (baked into the boot wiring).
16
30
  *
17
31
  * (design/158 A13, internal-lossless) This module is now a FACADE: the implementation lives in
18
32
  * `src/config-center/` split by responsibility group (HTTP client / EffectiveConfig application /
@@ -21,7 +35,7 @@
21
35
  */
22
36
  export { fetchEffective, fetchPrincipalCaps, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./config-center/http-client.js";
23
37
  export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, runtimeHasActiveGate, } from "./config-center/apply-effective.js";
24
- export { restartReasons, planeHasActiveTiers, modelPlaneChanged, type RestartSlice, type RestartSignal, } from "./config-center/restart-signal.js";
38
+ export { restartReasons, planeHasActiveTiers, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type RestartSignal, } from "./config-center/restart-signal.js";
25
39
  export { applyCenterSkills, resolveMcpServers, mcpForScenario } from "./config-center/skills-mcp.js";
26
40
  export type { CenterSkillManifest, CenterMcpServer, EffectiveConfig, ExecutionRuling, SessionMirrorRuling, } from "./config-center/types.js";
27
41
  //# sourceMappingURL=sema-registry.d.ts.map
@@ -10,9 +10,23 @@
10
10
  * (core 1.45), so each model/cascade-rung authenticates with its own upstream key.
11
11
  *
12
12
  * Per-model `baseUrl` IS transported (a catalog model may live on a different gateway;
13
- * core brain honors model.baseUrl, absent = "" = boot-env endpoint). TODO remaining: hot-reload
14
- * of models/roles needs Runner support — today teams hot-reload via the registry, while a models/roles
15
- * change is logged as "restart to apply".
13
+ * core brain honors model.baseUrl, absent = "" = boot-env endpoint).
14
+ *
15
+ * Hot-reload status (复审 2026-07-29 P1-11 — 亲读判定,取代此处旧的 "models/roles are restart-to-apply"
16
+ * TODO, which went stale when `mutateInPlace` landed):
17
+ * - models/roles/roster/teams/projects/autonomy — **HOT**. `applyEffective` mutates `config.models`/
18
+ * `config.roles` IN PLACE, and core's Runner reads `this.deps.models/roles` per task off that very
19
+ * reference. Both brains re-resolve `model.baseUrl || config.baseUrl` inside `buildRequest()` on every
20
+ * call (core 2.1.0 `brain/openai.js` + `brain/anthropic.js`), so a moved gateway takes effect on the
21
+ * next task with no restart.
22
+ * - the same plane under a **tier-frozen** Runner — deferred, not hot: core expands a PRIVATE catalog copy
23
+ * at construction, so `main` defers the whole plane and the `models-tiers` restart slice signals.
24
+ * - **`degrade-route`** — the one genuinely boot-frozen catalog consumer left: reactive degrade
25
+ * (`MODEL_DEGRADE_REACTIVE`) bakes the target Model + its gateway + its key into the brain composition
26
+ * at boot. It cannot be hot-applied without rebuilding the brain, so it is registered on the existing
27
+ * restartRequired /health contract instead (see `restart-signal.ts`). Fail-loud beats serving a stale
28
+ * gateway silently on the rate-limit path.
29
+ * - skills/mcp/scenarios/runtime-gates — restart-to-apply by construction (baked into the boot wiring).
16
30
  *
17
31
  * (design/158 A13, internal-lossless) This module is now a FACADE: the implementation lives in
18
32
  * `src/config-center/` split by responsibility group (HTTP client / EffectiveConfig application /
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "3.0.0",
3
+ "version": "3.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",
@@ -67,7 +67,7 @@
67
67
  "sharp": "^0.35.3"
68
68
  },
69
69
  "devDependencies": {
70
- "@sema-agent/sdk": "^0.0.46",
70
+ "@sema-agent/sdk": "^1.0.0",
71
71
  "@types/libsodium-wrappers": "^0.7.14",
72
72
  "@types/node": "22.10.2",
73
73
  "@types/pg": "^8.20.0",