@sema-agent/server 5.12.0 → 5.13.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.
@@ -103,6 +103,18 @@ export class FleetUsageAccumulator {
103
103
  return this.cells.size;
104
104
  }
105
105
  }
106
+ /** #123 F1 披露半场(§M):center 的拒绝响应体是操作员唯一的自诊线索(E2E #121:web 端明说了
107
+ * 「address must be a parseable http(s) base URL」而 server 日志只有 `announce 400`)——摘要进错误
108
+ * 信息(cap 200 字符);读体失败不改变结果面(照旧只有状态码)。 */
109
+ async function rejectionSnippet(res) {
110
+ try {
111
+ const text = (await res.text()).slice(0, 200).trim();
112
+ return text ? ` — center said: ${text}` : "";
113
+ }
114
+ catch {
115
+ return "";
116
+ }
117
+ }
106
118
  /**
107
119
  * Start the worker-side fleet loops. Caller has already gated on "center configured ∧ advertise address set"
108
120
  * (startFleetClientFromEnv below) — this function assumes a complete opts.
@@ -131,7 +143,7 @@ export function startFleetClient(opts) {
131
143
  // 半开连接钉住;心跳侧每 tick 叠一个在飞请求。
132
144
  const res = await f(`${base}/api/fleet/instances`, { method: "POST", headers, body: JSON.stringify(body), signal: AbortSignal.timeout(FLEET_CLIENT_FETCH_TIMEOUT_MS) });
133
145
  if (!res.ok)
134
- throw new Error(`announce ${res.status}`);
146
+ throw new Error(`announce ${res.status}${await rejectionSnippet(res)}`);
135
147
  if (failStreak > 0)
136
148
  opts.logger?.info("fleet_announce_recovered", { after: failStreak });
137
149
  failStreak = 0;
@@ -156,7 +168,7 @@ export function startFleetClient(opts) {
156
168
  try {
157
169
  const res = await f(`${base}/api/fleet/usage`, { method: "POST", headers, body: JSON.stringify({ reports: [report] }), signal: AbortSignal.timeout(FLEET_CLIENT_FETCH_TIMEOUT_MS) });
158
170
  if (!res.ok)
159
- throw new Error(`usage ${res.status}`);
171
+ throw new Error(`usage ${res.status}${await rejectionSnippet(res)}`);
160
172
  }
161
173
  catch (err) {
162
174
  // Delivery failed → the window was never recorded center-side; carry the amounts forward (no loss, no double).
@@ -196,6 +208,19 @@ export function startFleetClientFromEnv(cfg, opts) {
196
208
  const address = opts.advertiseAddress ?? process.env.FLEET_ADVERTISE_ADDRESS;
197
209
  if (!cc?.baseUrl || !cc.token || !address)
198
210
  return undefined;
211
+ // #123 F1(E2E #121 立案):旋钮**设了但坏形** ≠ 没设——center 端要求可解析的 http(s) base URL,
212
+ // 坏形(如缺 scheme 的 host:port)此前静默通过,启动后 fleet 腿每拍 announce 400、worker 永不注册。
213
+ // env fail-loud 族判据([2443] A1 同族):这里前置响亮拒,部署在起点就失败可见,而不是带病运行。
214
+ let parsed;
215
+ try {
216
+ parsed = new URL(address);
217
+ }
218
+ catch {
219
+ parsed = undefined;
220
+ }
221
+ if (parsed === undefined || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
222
+ throw new Error(`FLEET_ADVERTISE_ADDRESS must be a parseable http(s) base URL (e.g. "http://10.0.0.5:8090"); got "${address}" — the fleet center rejects any other shape on every announce`);
223
+ }
199
224
  if (!cc.worker) {
200
225
  opts.logger?.warn("fleet_client_no_worker_scope", { note: "FLEET_ADVERTISE_ADDRESS set but SEMA_REGISTRY_WORKER missing — announce needs the worker (dispatch key); fleet client not started" });
201
226
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "5.12.0",
3
+ "version": "5.13.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": "^5.1.0",
71
+ "@sema-agent/sdk": "^5.2.0",
72
72
  "@types/libsodium-wrappers": "^0.7.14",
73
73
  "@types/node": "22.10.2",
74
74
  "@types/pg": "^8.20.0",