@tansr/serve 0.6.1 → 0.8.1

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 (5) hide show
  1. package/NOTICE +59 -0
  2. package/README.md +131 -11
  3. package/dist/index.d.ts +2929 -1763
  4. package/dist/index.js +18318 -11253
  5. package/package.json +5 -3
package/NOTICE ADDED
@@ -0,0 +1,59 @@
1
+ NOTICE — @tansr/serve
2
+ =====================
3
+
4
+ Generated by `pnpm release:verify-notice --write --package server` (tansr FX-C-29, inline-declaration rule).
5
+ Do not edit by hand: `pnpm release:verify-notice --check` regenerates this file from the esbuild metafile and fails
6
+ on any difference. Every third-party package compiled into the shipped bundle is listed below, no more and no less;
7
+ each must carry an allow-listed license. Humans review the license column; the machine keeps the table honest.
8
+
9
+ Artifacts covered: dist/index.js
10
+ Runtime dependencies resolved from the consumer's node_modules (declared, not bundled): zod
11
+ Bundled third-party packages: 2
12
+
13
+ BUNDLED THIRD-PARTY PACKAGES
14
+ ----------------------------
15
+ undici 8.10.0 | MIT
16
+ zod-to-json-schema 3.25.2 | ISC
17
+
18
+ LICENSE TEXTS
19
+ -------------
20
+
21
+ --- undici 8.10.0 (MIT) ---
22
+ MIT License
23
+
24
+ Copyright (c) Matteo Collina and Undici contributors
25
+
26
+ Permission is hereby granted, free of charge, to any person obtaining a copy
27
+ of this software and associated documentation files (the "Software"), to deal
28
+ in the Software without restriction, including without limitation the rights
29
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
30
+ copies of the Software, and to permit persons to whom the Software is
31
+ furnished to do so, subject to the following conditions:
32
+
33
+ The above copyright notice and this permission notice shall be included in all
34
+ copies or substantial portions of the Software.
35
+
36
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
37
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
38
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
39
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
40
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
41
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
42
+ SOFTWARE.
43
+
44
+ --- zod-to-json-schema 3.25.2 (ISC) ---
45
+ ISC License
46
+
47
+ Copyright (c) 2020, Stefan Terdell
48
+
49
+ Permission to use, copy, modify, and/or distribute this software for any
50
+ purpose with or without fee is hereby granted, provided that the above
51
+ copyright notice and this permission notice appear in all copies.
52
+
53
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
54
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
55
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
56
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
57
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
58
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
59
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -32,6 +32,24 @@ npm install @tansr/serve
32
32
  (`@tansr/protocol`、`@tansr/i18n`、`@tansr/kernel`、`@tansr/providers`、
33
33
  `@tansr/sdk`)已编译内联,恒不外泄安装面。
34
34
 
35
+ ## 系统媒体工具 / System media tools
36
+
37
+ 图像生成、视频生成、语音转文字和文字转语音均由内核系统工具执行,工具名为
38
+ `ImageGen`、`VideoGen`、`SpeechToText`、`TextToSpeech`。平台内置会话工厂根据应用能力
39
+ 装配它们,并使用 SDK 的平台提供方调用授权模型;`capabilities.platform` 表示平台服务
40
+ 授权,不表示独立的“平台工具”类别。平台令牌、模型权限、配额、计费及运行时权限继续生效。
41
+
42
+ Android/iOS 经 `/v2` 会话连接时,模型与系统媒体工具运行在开发者的 serve 进程中,
43
+ 手机负责提交输入和展示真实工具事件/媒体产物,不因此取得服务端文件或终端命令权限。
44
+ 用户主动录音转写、朗读使用既有音频直连端点;它们与模型调用媒体工具并存,不自动进入聊天历史。
45
+ SDK 进程内集成仍须通过 `tools.builtin` 显式选择四媒体;这与 serve 平台工厂按应用授权装配的入口不同。
46
+
47
+ Image generation, video generation, transcription, and speech synthesis are built-in system tools.
48
+ The platform session factory assembles the authorized tools with platform-backed providers.
49
+ `capabilities.platform` retains its service-authorization meaning. On Android and iOS, these tools
50
+ run in the serve process; the mobile client renders their events and artifacts. User-triggered audio
51
+ operations use the separate direct endpoints and do not fabricate model tool events or chat history.
52
+
35
53
  ## 集成骨架
36
54
 
37
55
  ```ts
@@ -115,9 +133,16 @@ const v2: AgentSessionsOptions = {
115
133
  `reason?`(contract-v0.21 / RFC-SC-1)= 终局帧 `TerminalReason` 原值,单帧区分
116
134
  `client_gone`(订阅者离场被策略止损)/ `internal_error`(内核故障)/ 用户中断
117
135
  `aborted_*` 等,接收方按「已知值专项 + 未知值兜底」消费;
118
- - **签名**:`secret` 在场时携 `x-tansr-signature: sha256=<hex>`
119
- (`TURN_END_NOTIFY_SIGNATURE_HEADER`),对原始请求体全文 HMAC-SHA256——
120
- 接收端先验签再消费;
136
+ - **签名**:`secret` 在场时携 `x-tansr-signature: v2=<hex>, v1=<hex>`
137
+ (`TURN_END_NOTIFY_SIGNATURE_HEADER`;FX-C-43 双签一版,`signatureVersions`
138
+ 缺省 `['v2','v1']`):`v2` = protocol `WEBHOOK_SIGNATURE_V2` canonical(随行
139
+ `x-tansr-timestamp` / `x-tansr-nonce`),`v1` = 对原始请求体全文 HMAC-SHA256——
140
+ 接收端先验签再消费(tansrd 缺省只收 v2);
141
+ - **CLI 形态两键(FX-C-09/续)**:`tansr serve --v2` 以 `TANSR_SERVE_NOTIFY_URL`
142
+ (绝对 http(s) URL)/ `TANSR_SERVE_NOTIFY_SECRET` 两 env 接同一缝(见下文旋钮表
143
+ `notify` 组)——**无 URL = 不出站**;secret 单独在场忽略并告警;启动期 stdout 一行
144
+ 只打 URL 的 origin 与「签名在场 / 缺席」,secret 与路径 / 查询串恒不打印;未开 `--v2`
145
+ 时两键列入「仅对 /v2 宿主生效」告警;
121
146
  - **投递纪律**:fire-and-forget 恒不阻断会话主链;2xx 即成功,否则指数退避
122
147
  重试(缺省 1+2 次,`V2_LIMITS.notifyMaxRetries/notifyTimeoutMs/notifyBackoffMs`
123
148
  可覆写);终败走 `onDeliveryFailure` 结构化通报;服务器优雅关闭期恒不出站;
@@ -221,6 +246,15 @@ process.on('SIGINT', shutdown);
221
246
  Ctrl+C(SIGINT)收束,Linux/容器上两枚都会到;
222
247
  - **与 `onTurnEndNotify` 的关系**:优雅关闭期出站通知恒不发(见上文投递纪律),
223
248
  客户端靠回连 `Last-Event-ID` / history / resume 追赶。
249
+ - **进程级兜底(十王修案 FX-C-02)**:引擎恒不替你挂 `unhandledRejection` / `uncaughtException`
250
+ 监听,但导出 `installProcessGuards({ logger, onFatal, drain?, drainTimeoutMs? })`(返回卸载函数)
251
+ 供宿主入口**安装一次**:任何逃逸到进程级的拒绝 / 异常恒记一条结构化事件
252
+ `process.unhandled_rejection` / `process.uncaught_exception{ origin, message, stack(≤ 2 KB), policy }`
253
+ (旧形 logger 回落 i18n 文案行);`onFatal:'drain-exit'` = 记完 → `drain()`(有界,缺省 10 s)→
254
+ `exit(1)`(多租常驻进程缺省:`tansr serve` / serve-v2 参考宿主 / tansrd);`'log'` = 记完继续
255
+ (单用户入口缺省:acp / mcp serve / headless / serve-demo)。env `TANSR_SERVE_ON_UNHANDLED=log|drain-exit`
256
+ 经 `resolveServeRuntimeOptions(env).onUnhandled` 翻转;路由层错误边界(SC-01)与建会临界区
257
+ (FX-C-01)是第一道,这是最后一道——不是替代品。
224
258
 
225
259
  完整可跑示例见仓内 `examples/serve-demo/server.ts`(接线封装在 `examples/serve-demo/runtime.ts`)。
226
260
 
@@ -255,10 +289,69 @@ const server = await startServer({
255
289
  纪律(doc/118 §八 G5/G9 代拍):**任一变量缺席/空串即不落键 → 未设 = 库缺省,行为零漂移**;
256
290
  非法值(非整数、越界、枚举外)进 `warnings` 由宿主一行告警后忽略,恒不拒启;`applied` 列出生效
257
291
  的变量名,`appliedRuntimeEnvByGroup(runtime, 'governance', 'v2')` 可判某些组是否在场(CLI `tansr serve`
258
- /v1 面,治理五键与分片码在场即提示无效)。全表 23 键(env 库选项 缺省 → 建议值)见
259
- `deploy/serve-v2/README.md` §三 `deploy/serve-v2/.env.example`;分组:`admission`(4)/ `http`(1)/
260
- `observability`(1)/ `ring`(1)/ `governance`(5)/ `v2`(1,分片码)/ `upstream`(1)/ `session`(4)/
261
- `sse`(3)/ `log`(1)。不在表内(语义归各自入口):`TANSR_SERVE_TOKEN`(/v1 Bearer)、
292
+ 缺省是 /v1 面,治理五键与分片码在场即提示无效;`tansr serve --v2` / `TANSR_SERVE_V2=1` 起挂 /v2 单运维方形
293
+ ——同一 Bearer token + `x-tansr-end-user` 头分域、store `<sessionsDir>/agent-v2/`、`cwdPolicy` = 启动 cwd 子树,
294
+ 此时这些键有消费点;FX-C-46,doc/98 §十 2026-09-08 行)。建议值与部署口径见 `deploy/serve-v2/README.md` §三 与
295
+ `deploy/serve-v2/.env.example`;全表由 `runtime-options.ts``SERVE_RUNTIME_ENV` **表生成**如下(FX-C-41,
296
+ doc/130 R-12 生成律:`pnpm env:example` 再生、`pnpm docs:check` 幂等门;同一张表还生成
297
+ `packages/server/.env.example`,`pnpm env:check` 锁住;`process` 组的 `TANSR_SERVE_ON_UNHANDLED` →
298
+ `installProcessGuards({ onFatal })`,见上节「进程级兜底」;`factory` 组三键 →
299
+ `createAgentSessionFactory({ ...runtime.factory })` 顶层键,语义见表后一段):
300
+
301
+ <!-- env:begin -->
302
+ <!-- 由 packages/server/src/runtime-options.ts 的 SERVE_RUNTIME_ENV 表生成(FX-C-41):`pnpm env:example` 再生,`pnpm docs:check` 幂等门;手改本区段会被门打回 -->
303
+
304
+ 全表 **30 键**(键序 = 表声明序;缺席 / 空串 = 库缺省,非法值忽略并告警):
305
+
306
+ | 变量 | 组 | 目标选项 | 形态 | 约束 | 单位 |
307
+ |---|---|---|---|---|---|
308
+ | `TANSR_SERVE_MAX_ACTIVE_SESSIONS` | `admission` | `admission.maxActiveSessions` | int | integer >= 1 | — |
309
+ | `TANSR_SERVE_MAX_SSE_CONNECTIONS` | `admission` | `admission.maxSseConnections` | int | integer >= 1 | — |
310
+ | `TANSR_SERVE_MAX_INFLIGHT_BODY_BYTES` | `admission` | `admission.maxInflightBodyBytes` | int | integer >= 1 | bytes |
311
+ | `TANSR_SERVE_ELD_THRESHOLD_MS` | `admission` | `admission.eventLoopDelayP99Ms` | int | integer >= 1 | ms |
312
+ | `TANSR_SERVE_REQUEST_TIMEOUT_MS` | `http` | `http.requestTimeout` | int | integer >= 0 | ms |
313
+ | `TANSR_SERVE_EXPOSE_OBSERVABILITY` | `observability` | `observability.expose` | bool | 1\|true\|yes\|on / 0\|false\|no\|off | — |
314
+ | `TANSR_SERVE_EXPOSE_METRICS` | `observability` | `observability.exposeMetrics` | bool | 1\|true\|yes\|on / 0\|false\|no\|off | — |
315
+ | `TANSR_SERVE_EVENT_BUFFER_MAX_BYTES` | `ring` | `eventBufferMaxBytes` | int | integer >= 1 | bytes |
316
+ | `TANSR_SERVE_ORPHAN_GRACE_MS` | `governance` | `v2.governance.orphanGraceMs` | int | integer >= 0 | ms |
317
+ | `TANSR_SERVE_IDLE_AFTER_GONE_MS` | `governance` | `v2.governance.idleAfterGoneMs` | int | integer >= 0 | ms |
318
+ | `TANSR_SERVE_MAX_RETAINED_SESSIONS` | `governance` | `v2.governance.maxRetainedSessions` | int | integer >= 0 | — |
319
+ | `TANSR_SERVE_MAX_SESSION_LIFETIME_MS` | `governance` | `v2.governance.maxSessionLifetimeMs` | int | integer >= 1 | ms |
320
+ | `TANSR_SERVE_MAX_TURNS_PER_SESSION` | `governance` | `v2.governance.maxTurnsPerSession` | int | integer >= 1 | — |
321
+ | `TANSR_SERVE_SESSION_SHARD` | `v2` | `v2.sessionIdShard` | int | integer in [0, 255] | — |
322
+ | `TANSR_SERVE_NOTIFY_URL` | `notify` | `v2.onTurnEndNotify.url` | string | absolute http(s) URL | — |
323
+ | `TANSR_SERVE_NOTIFY_SECRET` | `notify` | `v2.onTurnEndNotify.secret` | **secret** | non-empty string; value never logged; requires TANSR_SERVE_NOTIFY_URL | — |
324
+ | `TANSR_SERVE_UPSTREAM_MAX_INFLIGHT` | `upstream` | `governor.bulkhead.maxConcurrent` | int | integer >= 1 | — |
325
+ | `TANSR_SERVE_UPSTREAM_CONNECT_TIMEOUT_MS` | `session` | `session.timeouts.connectTimeoutMs` | int | integer >= 1 | ms |
326
+ | `TANSR_SERVE_UPSTREAM_IDLE_TIMEOUT_MS` | `session` | `session.timeouts.idleTimeoutMs` | int | integer >= 1 | ms |
327
+ | `TANSR_SERVE_UPSTREAM_TOTAL_TIMEOUT_MS` | `session` | `session.timeouts.totalTimeoutMs` | int | integer >= 1 | ms |
328
+ | `TANSR_SERVE_TURN_WALL_CLOCK_MS` | `session` | `session.maxTurnWallClockMs` | int | integer >= 1 | ms |
329
+ | `TANSR_SERVE_SSE_MAX_BUFFER_BYTES` | `sse` | `sse.maxBufferBytes` | int | integer >= 1 | bytes |
330
+ | `TANSR_SERVE_SSE_SLOW_POLICY` | `sse` | `sse.onSlowSubscriber` | enum | disconnect\|drop-oldest | — |
331
+ | `TANSR_SERVE_SSE_RETRY_MS` | `sse` | `sse.retryMs` | int | integer >= 0 | ms |
332
+ | `TANSR_SERVE_LOG_FORMAT` | `log` | `logger.log` | enum | json\|text | — |
333
+ | `TANSR_SERVE_ON_UNHANDLED` | `process` | `processGuards.onFatal` | enum | log\|drain-exit | — |
334
+ | `TANSR_SERVE_READY_STREAM` | `process` | `readyFrame` | enum | stdout\|stderr\|none | — |
335
+ | `TANSR_SERVE_CWD_ON_RESUME` | `factory` | `factory.cwdOnResume` | enum | current\|stored | — |
336
+ | `TANSR_SERVE_SESSION_MAX_AGE_DAYS` | `factory` | `factory.sessions.retention.maxAgeDays` | int | integer >= 1 | days |
337
+ | `TANSR_SERVE_SESSION_MAX_PER_END_USER` | `factory` | `factory.sessions.retention.maxPerEndUser` | int | integer >= 1 | — |
338
+
339
+ 分组:`admission`(4) / `http`(1) / `observability`(2) / `ring`(1) / `governance`(5) / `v2`(1) / `notify`(2) / `upstream`(1) / `session`(4) / `sse`(3) / `log`(1) / `process`(2) / `factory`(3)。同表生成的部署样例:`packages/server/.env.example`。
340
+ <!-- env:end -->
341
+
342
+ `factory` 组三键语义(表内只列形态与约束):`TANSR_SERVE_CWD_ON_RESUME=current|stored` → `cwdOnResume`,
343
+ resume 时存储 cwd ≠ 本次的处置——配了 `cwdPolicy` 的宿主缺省 `stored`(存储值重过策略闸,
344
+ 策略拒 / 目录已失 → 409 `cwd_unavailable{detail.reason}`),未配缺省 `current`;`current` = 一键回退,十王修案 FX-C-08;
345
+ `TANSR_SERVE_SESSION_MAX_AGE_DAYS` / `TANSR_SERVE_SESSION_MAX_PER_END_USER` → `sessions.retention.{maxAgeDays,maxPerEndUser}`,
346
+ 会话保留期(十王修案 FX-C-09 / C2-04):**两者皆缺席 = 零删除 = 现状永存**;任一在场即启用治理扫描第 ⑤ 段「store 保留」——
347
+ 每 sweep 周期经工厂 `sweepRetention` 触发、实扫间隔缺省 1 h,按 store `meta.updatedAt` 删超龄(`maxAgeDays` 缺席按 30 d 兜底,
348
+ 与 api 会话租约窗同值;**M-05**:平台内置形装配过的 endUser 域以其 bundle `governance.sessionRetentionDays`(控制台 org 治理配置)
349
+ 覆写该域超龄阈值,缺键域沿兜底;**落盘(FX-C-09 收官)**:该天数随建档写入会话 `meta.json` 的 `retentionDays`(resume 再装配后对账),
350
+ 扫描逐会话优先按它判龄,serve 重启后零回退——收官前建的无键旧会话仍按兜底)/ 每 endUser 超数最旧的会话记录(附件 / 同居快照 / 独立根快照同删;在册活跃会话恒不删),
351
+ 每轮有删除即记 `session.retention_swept{scanned,deleted,failures,byReason}`;程序面另有 `maxBytes` / `sweepIntervalMs`
352
+ 与手动 `build.sweepSessionRetention({ force:true })`。
353
+
354
+ 不在表内(语义归各自入口):`TANSR_SERVE_TOKEN`(/v1 Bearer)、
262
355
  `TANSR_SERVE_DRAIN_TIMEOUT_MS`(drain 超时,缺省 30 s)、`TANSR_SERVE_V1_RETENTION_MS` /
263
356
  `TANSR_SERVE_V1_MAX_RETAINED`(SC-18)、`TANSR_SERVE_MCP_CONNECTIONS`(SC-31;缺省 1、上限 16。
264
357
  **SC-44 起只是进程级兜底层**:`mcpServers.<name>.pool.connections` 逐台键 > 程序面 `pool` 选项 > 本 env
@@ -292,7 +385,15 @@ NetworkPolicy);`deploy/serve/` 是 CLI `tansr serve` /v1「一容器一智能体
292
385
  (缺省 30 s,到点 `interrupt()`,轮末 store 提交照走)→ flush 落盘 → 停监听 ≤ 5 s + 连接收口 ≤ 5 s →
293
386
  宿主 `governor.close()` → exit 0;t0+45 s 未退出 → SIGKILL。宽限恒 ≥ drain 超时 + 15 s。
294
387
  - **观测端点暴露**:容器绑 `0.0.0.0` 时须 `TANSR_SERVE_EXPOSE_OBSERVABILITY=1`(参考镜像已设),探针
295
- 与抓取器走内网直连副本;反代恒不把 `/metrics` `/healthz` `/readyz` 转到公网。
388
+ 与抓取器走内网直连副本;反代恒不把 `/metrics` `/healthz` `/readyz` 转到公网。十王修案 FX-C-42(O-5):
389
+ `TANSR_SERVE_EXPOSE_METRICS` 单独控 `/metrics`(优先于总开关);CLI `tansr serve` 入口把 `/healthz` `/readyz`
390
+ 钉为**恒开**(探针面无机密,父进程 tansrd 池 / K8s 探活不受 `EXPOSE_OBSERVABILITY=0` 影响),库形态仍按
391
+ `exposeHealth` / `exposeMetrics` / `expose` 三选项自定。
392
+ - **进程间就绪帧**(FX-C-42;S2-04 / 候拍 ㉑):listening 后引擎恒额外写一行 `TANSR_READY {"v":1,"url":"http://127.0.0.1:1234","pid":4242,"readyz":"/readyz"}`
393
+ 到 stdout——它**不是日志**(不经 `ServeLogger`、不受 `TANSR_SERVE_LOG_FORMAT` 影响、无 ts / level),是父子进程
394
+ 协议(tansrd 池 `readyFrom` 第 ① 形;② `server.listening` JSON 行 / ③ 文案行为兜底,旧 serve 兼容一版)。
395
+ 日志采集器按行严格 JSON 解析时 `TANSR_SERVE_READY_STREAM=stderr` 改道,嵌入式宿主 `readyFrame:'none'` 或
396
+ 传写函数自定;`formatReadyFrame` / `parseReadyFrame` 是帧的唯一形态源(两端同笔)。
296
397
  - **容量估算**(本机现状档不外推;doc/118 §一-1.1):单进程 ≈2500 活跃 SSE 连接时 ELD p99 ≈190 ms、
297
398
  5000 连接 ≈250 ms;≈35 KiB 堆/会话(全装配链);企业档参考帽 `maxActiveSessions 5000` /
298
399
  `maxSseConnections 10000` / `maxInflightBodyBytes 256 MiB` / ELD p99 200 ms(G5,仅文档)。
@@ -432,10 +533,14 @@ serve 层:`tansr_serve_http_requests_total{method,route,status}`(route 恒模板
432
533
  `tansr_serve_event_loop_delay_ms{stat=p50|p99|max}`(两次采集之间的窗口)、
433
534
  `process_resident_memory_bytes`、`tansr_serve_webhook_deliveries_total{result}`、
434
535
  `tansr_serve_store_commit_errors_total` / `tansr_serve_store_create_errors_total`、
435
- `tansr_serve_token_mint_failures_total`。
536
+ `tansr_serve_token_mint_failures_total`、`tansr_serve_token_invalidations_total{reason}`(FX-C-15:上游 401
537
+ 裁定令牌失效,按信封 `detail.reason` 分键;每次伴随失效 + 重铸 + 重试恰一次)。
436
538
 
437
539
  纪律:指标标签**恒不含** sessionId / endUserId / 令牌 / 消息内容(高基数与凭据禁令);
438
- 未知路径一律 `route="other"`。同进程起多台 `startServer` 且共享缺省注册表时,gauge 族由最后
540
+ 未知路径一律 `route="other"`。`/v2` `route` 模板与路由分发同出一表 `V2_ROUTE_TABLE`(导出;21 端点 ×
541
+ `{ method, action(模板), pattern, endpoint }`,FX-C-45)——此前观测层手写 7 动作,compact / checkpoints 全族 /
542
+ cwd / audio 九条路径落 `other`;今 `other` 只对域内 404 出现(锁卷 `observability-route-coverage.test.ts`)。
543
+ 同进程起多台 `startServer` 且共享缺省注册表时,gauge 族由最后
439
544
  采集者覆写、counter 族累加——需按实例隔离读数时各传 `observability.metrics: createMetricsRegistry()`。
440
545
 
441
546
  ### 结构化日志与 request-id
@@ -498,7 +603,16 @@ build.upstreamStats?.(); // { tokenMinter: { inflight, queued, mints,
498
603
  - **铸令牌闸 / 抖动 / 负缓存**:500 endUser 同时首连时 `/v1/app-tokens` 在飞峰值 = 闸值(此前 500);
499
604
  同批铸出的令牌不再同刻进入刷新窗;某 endUser 铸造失败后窗内不再出网(直接回同一错),
500
605
  刷新失败但旧令牌仍在时效内直接沿用旧令牌;`warm(endUserIds)` 可在启动期预热。
501
- - **bundle 缓存**:键 `(apiBaseUrl, appId)`,TTL = min(平台 `max-age`, `bundleCacheTtlMs`);过期后
606
+ - **令牌失效反馈(十王修案 FX-C-15 / R-02)**:平台对携 `x-tansr-app-token` 的请求(模型 exchange / bundle /
607
+ 系统工具的平台提供方)回 `401 { error: { code: 'unauthorized', detail: { reason } } }` 时,刷新 fetch 在**同一请求内**
608
+ `minter.invalidate`(仅当缓存现值仍是本次所携令牌)→ 重铸 → 重发**恰一次**;重试仍 401 则原样交回(每请求至多
609
+ 一次重铸,恒不循环)。`reason === 'check_unavailable'`(平台校验依赖瞬态不可用)不失效、不重铸;其余码 /
610
+ 403 / 无信封亦不触发。此前吊销令牌后 serve 拿旧令牌 ≈ 55 min 恒 401。计数:`stats().invalidations{reason}`
611
+ (只计真正逐出)+ 指标 `tansr_serve_token_invalidations_total{reason}`(按 401 裁定计;自实现 minter 缺
612
+ `invalidate` 时只计不重试)。失效码集合 `TOKEN_INVALIDATING_CODES = ['unauthorized']`(导出;M-13 起改由
613
+ openapi `x-invalidates-token` 生成到 sdk,serve import)。
614
+ - **bundle 缓存**:键 `(apiBaseUrl, appId, scope, featuresFingerprint)`(FX-C-16:径别 `app_user` | `owner` + 客户端
615
+ features 声明指纹;serve 恒令牌径,宿主自有 owner 径消费方共享同一实例亦不串档),TTL = min(平台 `max-age`, `bundleCacheTtlMs`);过期后
502
616
  `If-None-Match` 条件重拉(304 只续期);features(heartbeat)按 endUser 每 TTL 一次而非每会话。
503
617
  建会话上游往返:冷 endUser 3 → 1(仅铸令牌;首次 +1 config),热 endUser 2 → 0。
504
618
  **一致性 SLA**:控制台改模型授权/别名后在网关生效的延迟上界 = `bundleCacheTtlMs`(缺省 60 s);
@@ -518,3 +632,9 @@ build.upstreamStats?.(); // { tokenMinter: { inflight, queued, mints,
518
632
  - **G3 平台限流上报**:平台现行限流键绑 app key(`/t1/exchange` 120/min)+ 源 IP(600/min);网关形态
519
633
  下全部 endUser 共用一把 key、一个出口 IP,本节的闸/缓存只能削峰、不能抬顶——`upstream_unavailable`
520
634
  持续出现且 `Retry-After` 对齐到分钟整点,即触顶信号,应上报平台侧调档(总卷 G3 / C 路 D-1)。
635
+
636
+ ## 应用系统提示词与移动端(AP-SP)
637
+
638
+ 平台内置形的 `platform.system` 为宿主业务段;未设置时使用平台应用 `systemPrompt`。默认 `fallback` 下显式值(含 `[]`)替换平台段;平台 `systemPromptPolicy=prepend` 时保留平台段在前,再拼宿主段,`[]` 也不会移除平台段。`platform.systemAppend` 追加宿主工具使用指南,不触发覆盖。P 与 S 是同一 system 层输入,拼接不是权限或冲突裁决机制。
639
+
640
+ Android/iOS 经 `/v2` 连接本服务,用户 `prompt` 不是 system 字段;配置放在平台或 Node 宿主,客户端不传 appkey。平台保存正文或策略后,已有会话在下一轮开始前按 ETag 验证配置,绕过 60 秒装配缓存;会话历史继续保留,正在执行的一轮(含工具多步)使用该轮提示词快照。宿主 S/A 仍在建会时固定;恢复历史不阻止下一轮刷新。刷新失败会以 `application_prompt_refresh_failed` 和 `turn.aborted` 中止本轮,不带旧配置发起模型调用;刷新默认最多等待 30 秒,可取消后重试。工具、权限、模型和计价等其他配置仍沿原装配生命周期。示例及完整矩阵见 [serve-demo](../../examples/serve-demo/README.md) 与 [提示词指南](https://docs.tansr.com/sdk/system-prompts/)。此变更尚需正式发布包含 AP-SP 的版本。