@sema-agent/server 7.120.0 → 7.121.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 (86) hide show
  1. package/MIGRATION.md +75 -0
  2. package/USAGE.md +67 -20
  3. package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -2
  4. package/deploy/sema-up/chart/templates/server.yaml +2 -2
  5. package/deploy/sema-up/stack/sema-stack-cluster.yaml +1 -1
  6. package/dist/boot/config-center.d.ts +5 -7
  7. package/dist/boot/config-center.js +33 -9
  8. package/dist/boot/engine-lease.d.ts +1 -1
  9. package/dist/boot/resolve-spec.js +1 -0
  10. package/dist/boot/shutdown.d.ts +9 -10
  11. package/dist/boot/shutdown.js +54 -16
  12. package/dist/boot/stage-09-leader.d.ts +2 -9
  13. package/dist/boot/stage-10-http-server.d.ts +1 -9
  14. package/dist/boot/stage-10-http-server.js +31 -8
  15. package/dist/capabilities/composite-members.d.ts +1 -1
  16. package/dist/capabilities/composite-members.js +1 -1
  17. package/dist/config-catalog.js +11 -5
  18. package/dist/config-center/center-request.d.ts +16 -1
  19. package/dist/config-center/center-request.js +36 -5
  20. package/dist/config-types.d.ts +39 -3
  21. package/dist/config-types.js +6 -0
  22. package/dist/config.d.ts +33 -4
  23. package/dist/config.js +99 -7
  24. package/dist/fleet/drain-entry.d.ts +87 -0
  25. package/dist/fleet/drain-entry.js +35 -0
  26. package/dist/fleet/engine-signal.d.ts +144 -0
  27. package/dist/fleet/engine-signal.js +60 -0
  28. package/dist/fleet/serving-lease.d.ts +47 -0
  29. package/dist/fleet/serving-lease.js +235 -0
  30. package/dist/fleet-client.d.ts +35 -19
  31. package/dist/fleet-client.js +49 -25
  32. package/dist/http/approval-sweeps.js +3 -5
  33. package/dist/http/dispatch.js +18 -13
  34. package/dist/http/health-self-report.d.ts +60 -0
  35. package/dist/http/health-self-report.js +53 -0
  36. package/dist/http/principal-gate.d.ts +8 -1
  37. package/dist/http/principal-gate.js +1 -0
  38. package/dist/http/readiness.d.ts +25 -5
  39. package/dist/http/readiness.js +31 -6
  40. package/dist/http/resume-legs.js +1 -1
  41. package/dist/http/route-ctx.d.ts +6 -4
  42. package/dist/http/route-table.d.ts +3 -0
  43. package/dist/http/route-table.js +4 -1
  44. package/dist/http/routes/a2a-serve.d.ts +1 -1
  45. package/dist/http/routes/a2a-serve.js +5 -5
  46. package/dist/http/routes/admin-drain.d.ts +12 -31
  47. package/dist/http/routes/admin-drain.js +15 -9
  48. package/dist/http/routes/approvals-assistant.d.ts +3 -3
  49. package/dist/http/routes/approvals-assistant.js +3 -3
  50. package/dist/http/routes/capabilities.js +2 -1
  51. package/dist/http/routes/devices.js +3 -2
  52. package/dist/http/routes/leader.d.ts +1 -1
  53. package/dist/http/routes/leader.js +1 -1
  54. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  55. package/dist/http/routes/memory-consolidation.js +1 -1
  56. package/dist/http/routes/notify-wake.d.ts +1 -1
  57. package/dist/http/routes/notify-wake.js +1 -1
  58. package/dist/http/routes/rules.js +4 -3
  59. package/dist/http/routes/runs.d.ts +2 -2
  60. package/dist/http/routes/runs.js +2 -2
  61. package/dist/http/routes/side-query.d.ts +1 -1
  62. package/dist/http/routes/side-query.js +1 -1
  63. package/dist/http/routes/tasks.d.ts +2 -2
  64. package/dist/http/routes/tasks.js +3 -3
  65. package/dist/http/server.d.ts +7 -21
  66. package/dist/http/server.js +20 -1
  67. package/dist/http/wire-types.d.ts +7 -1
  68. package/dist/model-reach-scope.d.ts +15 -3
  69. package/dist/model-reach-scope.js +3 -2
  70. package/dist/model-unreachable-notice.d.ts +2 -1
  71. package/dist/model-unreachable-notice.js +1 -0
  72. package/dist/observability/fail-open.d.ts +16 -0
  73. package/dist/observability/fail-open.js +16 -0
  74. package/dist/plugins/one-time-migrations.js +1 -1
  75. package/dist/plugins/permission-rule-store-sql.d.ts +2 -1
  76. package/dist/plugins/remote-env-device.js +0 -1
  77. package/dist/request-key-closure.js +1 -1
  78. package/dist/rules-consent.d.ts +6 -2
  79. package/dist/rules-consent.js +3 -0
  80. package/dist/runs.js +1 -1
  81. package/dist/tool-approval.d.ts +7 -0
  82. package/dist/tool-approval.js +4 -1
  83. package/dist/trace/core-keyset-guard.d.ts +2 -2
  84. package/dist/trace/self-minted-wire.d.ts +64 -0
  85. package/dist/trace/self-minted-wire.js +30 -0
  86. package/package.json +2 -2
@@ -1656,7 +1656,7 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
1656
1656
  return resp;
1657
1657
  }
1658
1658
  export const RUNS_ROUTES = [
1659
- { id: "runs-submit", path: "/v1/runs", billable: true, methods: ["POST"] },
1659
+ { id: "runs-submit", path: "/v1/runs", billable: "submit", methods: ["POST"] },
1660
1660
  { id: "run-steer", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/steer$/, label: "/v1/runs/:id/steer", methods: ["POST"] },
1661
1661
  { id: "run-interrupt", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/interrupt$/, label: "/v1/runs/:id/interrupt", methods: ["POST"] },
1662
1662
  { id: "run-detach", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/detach$/, label: "/v1/runs/:id/detach", methods: ["POST"] },
@@ -1669,7 +1669,7 @@ export const RUN_VERBS_ROUTES = [
1669
1669
  { id: "run-subagent-output", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/output$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["GET"] },
1670
1670
  { id: "run-subagent-stream", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/stream$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["GET"] },
1671
1671
  { id: "run-subagent-steer", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/steer$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["POST"] },
1672
- { id: "run-subagent-resume", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/resume$/, label: "/v1/runs/:id/subagents/:sub/*", billable: true, methods: ["POST"] },
1672
+ { id: "run-subagent-resume", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/resume$/, label: "/v1/runs/:id/subagents/:sub/*", billable: "resume", methods: ["POST"] },
1673
1673
  { id: "run-task-output", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/tasks\/(?<tid>[^/]+)\/output$/, label: "/v1/runs/:id/tasks/:tid/*", methods: ["GET"] },
1674
1674
  { id: "run-task-stop", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/tasks\/(?<tid>[^/]+)\/stop$/, label: "/v1/runs/:id/tasks/:tid/*", methods: ["POST"] },
1675
1675
  { id: "elicitation-respond", pattern: /^\/v1\/elicitations\/(?<id>[^/]+)\/respond$/, label: "/v1/elicitations/:id/respond", methods: ["POST"] },
@@ -17,7 +17,7 @@ export declare function handleSideQuery(req: IncomingMessage, res: ServerRespons
17
17
  export declare const SIDE_QUERY_ROUTES: readonly [{
18
18
  readonly id: "side-query";
19
19
  readonly path: "/v1/side-query";
20
- readonly billable: true;
20
+ readonly billable: "submit";
21
21
  readonly methods: readonly ["POST"];
22
22
  }];
23
23
  /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
@@ -133,6 +133,6 @@ export async function handleSideQuery(req, res, match, ctx) {
133
133
  }
134
134
  }
135
135
  export const SIDE_QUERY_ROUTES = [
136
- { id: "side-query", path: "/v1/side-query", billable: true, methods: ["POST"] },
136
+ { id: "side-query", path: "/v1/side-query", billable: "submit", methods: ["POST"] },
137
137
  ];
138
138
  //# sourceMappingURL=side-query.js.map
@@ -25,12 +25,12 @@ export declare function handleTasks(req: IncomingMessage, res: ServerResponse, m
25
25
  export declare const TASKS_ROUTES: readonly [{
26
26
  readonly id: "tasks-submit";
27
27
  readonly path: "/v1/tasks";
28
- readonly billable: true;
28
+ readonly billable: "submit";
29
29
  readonly methods: readonly ["POST"];
30
30
  }, {
31
31
  readonly id: "tasks-submit-stream";
32
32
  readonly path: "/v1/tasks/stream";
33
- readonly billable: true;
33
+ readonly billable: "submit";
34
34
  readonly methods: readonly ["POST"];
35
35
  }];
36
36
  /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
@@ -639,7 +639,7 @@ export async function handleTasks(req, res, match, ctx) {
639
639
  });
640
640
  }
641
641
  const withApproval = () => approvalCtx && deps.toolApproval ? deps.toolApproval.runWithContext(approvalCtx, withQuestion) : withQuestion();
642
- await instructionLeg.run(prepared.spec.sessionId, () => withModelReachScope(prepared.spec.sessionId, () => deps.sendUserFile
642
+ await instructionLeg.run(prepared.spec.sessionId, () => withModelReachScope({ sessionId: prepared.spec.sessionId, taskId: durableTaskId }, () => deps.sendUserFile
643
643
  ? deps.sendUserFile.runWithContext({
644
644
  taskId: askTaskId,
645
645
  emit: async (f) => {
@@ -901,7 +901,7 @@ export async function handleTasks(req, res, match, ctx) {
901
901
  }
902
902
  }
903
903
  export const TASKS_ROUTES = [
904
- { id: "tasks-submit", path: "/v1/tasks", billable: true, methods: ["POST"] },
905
- { id: "tasks-submit-stream", path: "/v1/tasks/stream", billable: true, methods: ["POST"] },
904
+ { id: "tasks-submit", path: "/v1/tasks", billable: "submit", methods: ["POST"] },
905
+ { id: "tasks-submit-stream", path: "/v1/tasks/stream", billable: "submit", methods: ["POST"] },
906
906
  ];
907
907
  //# sourceMappingURL=tasks.js.map
@@ -649,8 +649,6 @@ export interface ServiceGovernanceDeps {
649
649
  * 「诊断说亮、位说暗」。缺席(桩/一次性 CLI)⇒ 读面如实答 `deployment_incapable`。 */
650
650
  autoModeFace?: import("../auto-mode-face.js").AutoModeFaceDeps;
651
651
  }
652
- /** **部署自述面**:只被 /health、/v1/capabilities 与提交前置门读的部署事实(多为 live getter,
653
- * 不是快照——/health 必须回答「此刻」)。 */
654
652
  export interface ServiceDeploymentDeps {
655
653
  /** Stable per-process id recorded on runs (audit / stale recovery). */
656
654
  instanceId?: string;
@@ -687,15 +685,10 @@ export interface ServiceDeploymentDeps {
687
685
  * (= 引擎附着租约集的大小)。消费方是 `boot/engine-lease.ts` 的自退判据;与 `inflight` 同族,
688
686
  * 同一个共享盒子,不另开第二条 main↔server 的通道。
689
687
  */
690
- drainState?: {
691
- draining: boolean;
692
- since?: number;
693
- reason?: string;
694
- inflight?: () => number;
695
- lastActivityAt?: () => number;
696
- attachedStreams?: () => number;
697
- lastAttachEndedAt?: () => number;
698
- };
688
+ drainState?: import("../fleet/drain-entry.js").DrainState;
689
+ /** 编排器片 2 件 3:服务租约此刻的读数(`fleet/serving-lease.ts` 的客户端交出;引擎信号铸点与计费准入判同读)。缺席 = 没装租约客户端
690
+ * (非 fleet 成员 / 没接中心 / 测试夹具)⇒ `none`(与没有租约的引擎逐字同)。 */
691
+ servingLease?: () => import("../fleet/engine-signal.js").ServingLeaseView;
699
692
  /** boot ready 门(b):false = 布防(`CONFIG_REQUIRE_ROSTER=true`)且首次 effective pull 尚未落 roster
700
693
  * (worker 只有占位模型)。就绪原因 `model_roster_pending`(`http/readiness.ts`):计费提交 503 + `/readyz` 503 +
701
694
  * /health 加性 `ready:false`。absent = 恒 ready(未布防 / 非 registry 部署),现有部署零影响。config-center 的
@@ -722,6 +715,9 @@ export interface ServiceDeploymentDeps {
722
715
  * (`boot/stage-10-http-server.ts` → `modelRefusals: () => deployment.modelRefusalsState?.()`)—— 一处铸、两出口。缺席 / 返回 undefined =
723
716
  * 没有可报的拒或没有配置管道 ⇒ `/health` 形状逐字不变(additive)。 */
724
717
  modelRefusalsState?: () => import("@sema-agent/settings-schema").ModelRefusalsSignal | undefined;
718
+ /** `/health.configHandoff`(只上 `/health`):在场 ⇔ 部署声明了耐久接力 ∧ 应用着的那一代没被确认(config-center 接力账的 `configHandoffState()`;合成律
719
+ * `fleet/engine-signal.ts` 的 `buildConfigHandoff`)。缺席 / 返回 undefined = 有接力、没有中心或本地腿 ⇒ 键缺席。 */
720
+ configHandoffState?: () => import("../fleet/engine-signal.js").ConfigHandoff | undefined;
725
721
  /** [ref] 追加件:`POST /v1/admin/config/refresh` 的实现面 —— 触发**一次既有**的 refresh 拍(在飞则 join)。
726
722
  * 缺席 / 回 `"not-wired"` = 本部署没有配置管道 ⇒ 该口诚实 4xx(绝不吞成 200)。 */
727
723
  configRefreshNow?: () => Promise<"triggered" | "not-wired" | "failed">;
@@ -739,16 +735,6 @@ export interface ServiceDeploymentDeps {
739
735
  mcpRevocationState?: () => {
740
736
  revoked: string[];
741
737
  } | undefined;
742
- /** codex R18: the persistent "retained WITHOUT a next-boot handoff" state — a candidate whose restart signal is
743
- * withheld because no restart-surviving handoff exists yet (durable-declared LKG publication incomplete, or a
744
- * boot skills debt not yet proven repairable). Surfaced on /health (`modelPlaneDeferred` — the key keeps its birth
745
- * name: it was born for the model-plane deferral, which retired with the `models-tiers` slice in S-668 波 0) so
746
- * the stuck state outlives the one-shot warn (the etag advances → later ticks 304). */
747
- planeDeferredState?: () => {
748
- version: number;
749
- since: number;
750
- blocked?: string[];
751
- } | undefined;
752
738
  /** B1(鲁棒性批3 设计件):后台**缓存**探针的读座——/health 绝不逐请求打 DB(center/k8s 高频面),
753
739
  * main.ts 只在 SQL 后端(backend.kind !== "local")挂环(createStoreLiveProbe,STORE_PROBE_INTERVAL_MS)。
754
740
  * 缺席(local/memory/探针关)= /health 形状不变;接线 = `storeProbe{live,ageMs,error?}` 恒在
@@ -31,6 +31,23 @@ export { routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDis
31
31
  export { MAX_SKILL_CONTENT_CHARS, MAX_SYSTEM_PROMPT_CHARS, MAX_OUTPUT_SCHEMA_CHARS, validateUserSkills };
32
32
  import { explicitOperatorOk, isOperator } from "./principal-gate.js";
33
33
  export { explicitOperatorOk, isOperator };
34
+ class DrainYieldingPreemptBook extends Map {
35
+ drainingNow;
36
+ constructor(drainingNow) {
37
+ super();
38
+ this.drainingNow = drainingNow;
39
+ }
40
+ set(taskId, ctrl) {
41
+ super.set(taskId, ctrl);
42
+ if (this.drainingNow())
43
+ ctrl.abort();
44
+ return this;
45
+ }
46
+ yieldAll() {
47
+ for (const ctrl of this.values())
48
+ ctrl.abort();
49
+ }
50
+ }
34
51
  export function flattenServiceDeps(deps) {
35
52
  const { stores, coordinators, seams, observability, governance, deployment, knobs, ...flat } = deps;
36
53
  if (!(stores || coordinators || seams || observability || governance || deployment || knobs))
@@ -44,7 +61,7 @@ export function createHttpServer(rawDeps) {
44
61
  }
45
62
  const idemCache = new IdempotencyCache();
46
63
  const inflightRuns = new Map();
47
- const preemptableRuns = new Map();
64
+ const preemptableRuns = new DrainYieldingPreemptBook(() => deps.drainState?.draining === true);
48
65
  const steerableRuns = new Map();
49
66
  const wakeParkMints = new Map();
50
67
  const counters = { uncountedBillableInflight: 0, admittedInflight: 0 };
@@ -58,6 +75,8 @@ export function createHttpServer(rawDeps) {
58
75
  };
59
76
  if (deps.drainState)
60
77
  deps.drainState.lastActivityAt = () => lastBillableActivityAt;
78
+ if (deps.drainState)
79
+ deps.drainState.yieldInflight = () => preemptableRuns.yieldAll();
61
80
  if (deps.hookWakeBus) {
62
81
  deps.hookWakeBus.deliver = async (sessionId, text) => {
63
82
  try {
@@ -6,7 +6,7 @@
6
6
  * cut: ONLY TaskRequestBody (its field types are inline or core imports — no other server.ts-local satellites);
7
7
  * sibling request/response shapes can migrate here later on the same pattern.
8
8
  */
9
- import type { A2aServerSpec, AgentDefinition, ApprovalReach, McpServerSpec, PermissionMode, SkillSpec } from "@sema-agent/core";
9
+ import type { A2aServerSpec, AgentDefinition, ApprovalReach, McpServerSpec, PermissionMode, SkillSpec, TaskSpec } from "@sema-agent/core";
10
10
  import type { wiringManifestFaceEventData } from "../trace/project.js";
11
11
  import type { buildSyncSubmitBody } from "./sync-submit-body.js";
12
12
  export interface TaskRequestBody {
@@ -63,6 +63,12 @@ export interface TaskRequestBody {
63
63
  * question (raised on the board). Rejected fail-loud when `systemPrompt` is an already-assembled prompt
64
64
  * (anchors detected) — that assembler arm cannot mount the rider (silent drop would belie the capability bit). */
65
65
  appendSystemPrompt?: string;
66
+ /** 7.121.0:输出风格的**每轮一句**(引擎 `TaskSpec.outputStyle`,core 7.39.0 起):原样投给引擎,引擎在每个附件周期用自己的标记帧陈述
67
+ * `"<name> output style is active. <turnReminder>"`(`turnReminder` 缺席 ⇒ 引擎的缺省句),回声为 `steering_injected` `source:"output_style"`。
68
+ * **一个事实一个座位**:风格的**正文**(指南全文)走 `settings.outputStyle`(折进 `appendSystemPrompt` 那一块),这里只放名字与每轮那一句;
69
+ * 只在非缺省风格时声明。值形的属主是引擎(`{ name, turnReminder? }` 恰两键、`name` 非空且 ≤ `OUTPUT_STYLE_NAME_MAX`、`turnReminder` 在场即非空串):
70
+ * 本服务**不另判**,坏值在 prepare 被引擎拒 `config.output_style_invalid`(不会被静默忽略)。能力位 `outputStyle`。 */
71
+ outputStyle?: TaskSpec["outputStyle"];
66
72
  /** Explicit model selection (CLI `/model` picker + `run --model <ref>`). An INTENT by catalog
67
73
  * name (e.g. "deepseek-pro") — resolveSpec gates it against the SAME allow-list as an inline `@name` mention
68
74
  * (config.models keys), so it picks WHICH configured model but can never inject one. Wins over an inline
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * 两层,一只 `AsyncLocalStorage`:
5
5
  * · **腿层**(`withModelReachScope`,三条带用户面投递口的 run 腿各包一次:流式 `POST /v1/tasks/stream`、后台 `POST /v1/runs`、续跑):
6
- * 带着这条腿的 `sessionId`(通告归属键)与本腿已经说过的目标集合(去重单位 = 每腿每目标一次);
6
+ * 带着这条腿的两枚身份 —— `sessionId`(通告归属键)与 `taskId`(这条腿的 run 资源 id,= 本腿交给引擎的 `spec.taskId`;7.121.0)——
7
+ * 与本腿已经说过的目标集合(去重单位 = 每腿每目标一次);
7
8
  * · **调用层**(`createModelReachBrain` 每次模型调用包一次):一只可变的 {@link ModelCallReach},出网观察者
8
9
  * (`outbound-dispatcher.ts` 的 undici 诊断通道订阅)把这次调用发出的每一条请求的**结局**记在上面 —— 最后一条是
9
10
  * 「连上了、有了回答」还是「建连就失败了」。
@@ -33,6 +34,12 @@ export interface ModelCallReach {
33
34
  /** 一条 run 腿的作用域。 */
34
35
  export interface ModelReachLegScope {
35
36
  readonly sessionId: string;
37
+ /**
38
+ * 7.121.0:这条腿的 **run 资源 id** —— 与本腿交给引擎的 `spec.taskId` 同值(流式腿 = `X-Task-Id` / `meta.taskId`,后台腿 = 提交回执的
39
+ * `taskId`,续跑腿 = 被续的那条 run 行 id)。缺席 = 这条腿没有 run 资源(没有 run 店的部署上的流式腿)。不是引擎的 run 身份
40
+ * (`TaskResult.runId`,每次 prepare 一铸)—— 那一枚在模型调用这一层读不到。
41
+ */
42
+ readonly taskId?: string;
36
43
  /** 本腿已经通告过的目标(`targetHost`)—— 去重单位 = 每腿每目标一次。 */
37
44
  readonly announced: Set<string>;
38
45
  }
@@ -43,9 +50,14 @@ export interface ModelReachStore {
43
50
  export declare const modelReachContext: AsyncLocalStorage<ModelReachStore>;
44
51
  /**
45
52
  * 腿层入口:每条腿都开**自己的**作用域 —— `sessionId` 缺席(无会话腿)⇒ 空作用域(没有归属键就没有用户面可投;也不继承外层腿的
46
- * 归属键 —— codex r1 旁注:修前缺席即原样执行、会沿用外层腿)。嵌套时内层覆盖外层 —— 归属跟着「这一轮是谁的腿」走。
53
+ * 归属键 —— codex r1 旁注:修前缺席即原样执行、会沿用外层腿)。嵌套时内层覆盖外层 —— 归属跟着「这一轮是谁的腿」走,两枚身份一起走
54
+ * (内层没有 run 资源就是没有,不借外层的)。两键都是**必填形参**(值可 `undefined`):三个调用点各自说清这条腿有没有 run 资源,
55
+ * 漏传一枚 = 编译红,不会静默变成「没有」。
47
56
  */
48
- export declare function withModelReachScope<T>(sessionId: string | undefined, fn: () => T): T;
57
+ export declare function withModelReachScope<T>(leg: {
58
+ readonly sessionId: string | undefined;
59
+ readonly taskId: string | undefined;
60
+ }, fn: () => T): T;
49
61
  /** 调用层入口(`createModelReachBrain` 用):在当前腿作用域(可能缺席)之上开一只新的调用观察。 */
50
62
  export declare function withModelCallReach<T>(call: ModelCallReach, fn: () => T): T;
51
63
  /** 观察者读口:当前异步上下文里的模型调用观察(作用域外 ⇒ `undefined`)。 */
@@ -1,7 +1,8 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  export const modelReachContext = new AsyncLocalStorage();
3
- export function withModelReachScope(sessionId, fn) {
4
- return modelReachContext.run(sessionId === undefined || sessionId.length === 0 ? {} : { leg: { sessionId, announced: new Set() } }, fn);
3
+ export function withModelReachScope(leg, fn) {
4
+ const { sessionId, taskId } = leg;
5
+ return modelReachContext.run(sessionId === undefined || sessionId.length === 0 ? {} : { leg: { sessionId, ...(taskId !== undefined && taskId.length > 0 ? { taskId } : {}), announced: new Set() } }, fn);
5
6
  }
6
7
  export function withModelCallReach(call, fn) {
7
8
  const outer = modelReachContext.getStore();
@@ -20,7 +20,8 @@
20
20
  * 没有归属键 ⇒ 不铸(运维日志 `outbound_connect_failed` 照有)。腿里的**辅助**调用(WebFetch 摘要器、压实、提示建议、分类器 …)与
21
21
  * 主循环**同一条规则**:本帧说的是「这个模型端点够不着」,不是「这条 run 失败了」(措辞如此,不暗示终态);不设豁免名单 —— codex r1 / r2
22
22
  * 两轮证明豁免名单结构上开放(core 自己发起的辅助调用本仓包不到)。按 **run 终态**归属是另一种设计,候裁(收车档 ⑤)。去重单位 = **每腿每目标一次**(同一条腿里
23
- * 重试 / 子代对同一个网关再失败,不再说一遍)。受众 user;detail = `{ code, targetHost, proxyInUse?, remedy, sessionId }` ——
23
+ * 重试 / 子代对同一个网关再失败,不再说一遍)。受众 user;detail = `{ code, targetHost, proxyInUse?, remedy, sessionId, taskId? }` ——
24
+ * `taskId`(7.121.0)= 这条腿的 run 资源 id,腿没有 run 资源时缺席;引擎的 run 身份 `runId` 在这一层读不到,本码不带 ——
24
25
  * `proxyInUse` 只在这一跳走了代理时在场,且只是**变量名**(代理地址是基础设施细节,不上租户 wire,与 `OutboundProxyError` 的
25
26
  * F3 裁定同一条)。`targetHost`(模型端点的 `host:port`)对 user 受众是**新披露的部署事实**(F7 裁定:它是部署自己声明的模型端点,
26
27
  * 不是基础设施秘密,可以上;代理地址仍不上)—— 不要读成「终局句里本就有」:代理形下终局句只带码与 `via outbound proxy (from <变量>)`、不含任何主机地址,
@@ -17,6 +17,7 @@ function announceModelUnreachable(leg, failure, router = defaultEngineNoticeRout
17
17
  ...(failure.proxyInUse !== null ? { proxyInUse: failure.proxyInUse } : {}),
18
18
  remedy: MODEL_UNREACHABLE_REMEDY,
19
19
  sessionId: leg.sessionId,
20
+ ...(leg.taskId !== undefined ? { taskId: leg.taskId } : {}),
20
21
  },
21
22
  sessionId: leg.sessionId,
22
23
  });
@@ -398,6 +398,22 @@ export declare const FAIL_OPEN_TAGS: {
398
398
  readonly cls: "P-DEBT";
399
399
  readonly note: "7.120.0(`config-lkg.ts` 的 `landedPathOf`,`lkgReadDenyRows` 每次铸部署姿态座席时调;detail = `segment=<那一段的路径>`):`CONFIG_LKG_PATH` 覆写路径上有一段**目录在、却解不开**(悬空符号链接 / 链接环 / 读不了的目录:`realpath` 失败而 `lstat` 不说「不存在」)⇒ 这一段之下 LKG 真正会落在哪说不出来,读拒行只剩声明的拼写那一行(照旧在),真实落点那一行缺席。段**不存在**(`ENOENT` / `ENOTDIR`:还没建出来)是常态,不进这一臂;末段(文件本身)是悬空链接也不进(落盘的 rename 换掉的是那条目录项,文件就落在声明的拼写上)。归 P 的理由:这一行守的是 LKG 文件(含凭据),链接之后通了、LKG 写到链接目标下,模型按目标路径读得到它,直到重启重铸座席。归 DEBT 的理由:引擎没有「锚定单文件」的读拒行、座席只在 Runner 构造时铸一次,本服务说不出将来的落点;拿文件名在任意深度拒会把用户仓里同名文件一并挡掉。计数非零 = 去看 detail 点名的那一段(链接指向哪、卷挂了没有),修好后重启。";
400
400
  };
401
+ readonly "server.fleet.serving-lease-unconfirmed": {
402
+ readonly cls: "P-DEBT";
403
+ readonly note: "7.121.0 修复轮(`fleet/serving-lease.ts` 的 `renewOnce`,租约生效时每一次续租;detail = `worker=<w> generation=<g> why=<不可达句 | verdict=unknown>`):中心这一次**没有确认**本实例该不该服务 —— 不可达 / 超时 / 非 2xx / 答复读不成判决,或答 `unknown`(此刻没有本实例的生命周期对象)。按设计**什么都不改**(规则 2 第三臂):到期之前照常 `granted`,过期后 `expired` 照常接单,直到过期满 `FLEET_LEASE_OUTAGE_MAX_MS` 才拒新工作。放行的最坏后果 = 一只其实已被取代的实例在中心失联期间多服务至多「租约剩余 + `T_max`」(中心的代际下限持久,恢复后第一次续租即拒);不放行的代价是中心抖一下整队停服。归 P 类:围栏轴,最坏后果是作业完整性(被取代的实例多服务),不是难看;归 DEBT:容忍规则本身(`T_max` 的取值与去抖)待裁定,收口条件 = 那次裁定落地。计数持续非零 = 中心长时间没确认本实例,去看 detail 的 why(连不上 / 中心不认识这只实例)。";
404
+ };
405
+ readonly "server.fleet.serving-lease-capability-absent": {
406
+ readonly cls: "P-DEBT";
407
+ readonly note: "7.121.0 修复轮(`fleet/serving-lease.ts` 的 `noteCenterCapabilities`;fleet 成员每一次中心能力读数;detail = `worker=<w> generation=<g> was=<enabled|none>`):中心答复体读得出、却**没有宣告** `servingLease`(老中心 `{ ok: true }` / 降级中心 / 位撤掉)⇒ 规则 1:租约不生效,`leaseState: none`,零续租、零 503 —— 这只 fleet 成员此刻**不受围栏**。答复体读不出不进这一臂(那是「没有读数」,上一次的位与钟原样,`fleet-client.ts` `capabilityReadingOf`);已被拒的实例不进(拒是黏的,位消失不复活)。放行的最坏后果 = 中心不执行围栏期间,一只被取代的实例照常服务;不放行的代价是老中心 / 降级中心下所有成员在第一次续租就失败。归 P 类:围栏轴,成员此刻不受围栏;归 DEBT:能力门控是契约规则(中心不宣告 = 不执行),收口条件 = 租约容忍规则的那次裁定(含「位从有到无再到有」的去抖)落地。计数非零 = 这台中心没宣告租约能力。";
408
+ };
409
+ readonly "server.fleet.announce-reply-unreadable": {
410
+ readonly cls: "P-DEBT";
411
+ readonly note: "7.121.0 修复验证轮(`fleet-client.ts` 的公告环,fleet 成员每一拍心跳 2xx 答复;detail = `why=<body|not-an-object|invalid-capabilities> leaseState=<此刻的租约态>`):答复不是能力读数(体读不出 / 不是对象 / `capabilities` 键在场而形坏 —— settings-schema 9.0.0 `readCapabilityReading` 的 `unreadable`,外加本仓的 `body`)⇒ 当作没有这一拍答复,租约的状态与钟原样。**一律**计数(不论租约生效与否):租约还没生效(`leaseState=none`)时这只成员**一直不受围栏**,而修前这一形零计数(中心每拍答 `<html>` ⇒ 成员恒 none、零痕迹);租约已生效时计数说明中心的答复在坏、围栏靠的是上一次读数。归 P 类:围栏轴;归 DEBT:本进程判不出中心为什么答坏体,收口条件 = 中心契约 / 监控(中心侧对心跳答复形的执法)落地。每段连拍另有一行 warn `fleet_announce_reply_unreadable`(带 `why`)。";
412
+ };
413
+ readonly "server.fleet.denied-marker-unpersisted": {
414
+ readonly cls: "P-DEBT";
415
+ readonly note: "7.121.0(`fleet/serving-lease.ts` 的 `enterDenied`,中心以正面证据拒了本实例的服务租约那一拍;detail = `worker=<w> generation=<g>`):拒要落成数据根下一只文件(重启后起步即排空),而这一次**没落下** —— 没有数据根,或写失败(盘满 / 只读挂载 / 目录不在)。本进程照样被拒、照样当拍排空、到期自停;放行的是**重启之后**:同代际重启的实例不记得被拒,在第一次续租得到答复之前会接新工作(中心宣告了能力位时,第一次续租就再被拒一次 —— 中心的代际下限是持久的;中心那时不可达则一直接单到过期满 `FLEET_LEASE_OUTAGE_MAX_MS`)。归 P 类:这是围栏的记忆;归 DEBT:进程内无法把「记不住」变成 fail-closed(起步不接单直到第一次答复,是对租约不可达容忍规则的改动,另案)。denied 行的 `sticky` 同拍说 `process-only`,不声称跨重启记得。计数非零 = 这台机器的数据根写不进,或部署没有数据根。";
416
+ };
401
417
  };
402
418
  /** 词表键推导的闭集类型——未登记的 tag 传不进 {@link recordFailOpen}(编译期拒)。 */
403
419
  export type FailOpenTag = keyof typeof FAIL_OPEN_TAGS;
@@ -371,6 +371,22 @@ export const FAIL_OPEN_TAGS = {
371
371
  cls: "P-DEBT",
372
372
  note: "7.120.0(`config-lkg.ts` 的 `landedPathOf`,`lkgReadDenyRows` 每次铸部署姿态座席时调;detail = `segment=<那一段的路径>`):`CONFIG_LKG_PATH` 覆写路径上有一段**目录在、却解不开**(悬空符号链接 / 链接环 / 读不了的目录:`realpath` 失败而 `lstat` 不说「不存在」)⇒ 这一段之下 LKG 真正会落在哪说不出来,读拒行只剩声明的拼写那一行(照旧在),真实落点那一行缺席。段**不存在**(`ENOENT` / `ENOTDIR`:还没建出来)是常态,不进这一臂;末段(文件本身)是悬空链接也不进(落盘的 rename 换掉的是那条目录项,文件就落在声明的拼写上)。归 P 的理由:这一行守的是 LKG 文件(含凭据),链接之后通了、LKG 写到链接目标下,模型按目标路径读得到它,直到重启重铸座席。归 DEBT 的理由:引擎没有「锚定单文件」的读拒行、座席只在 Runner 构造时铸一次,本服务说不出将来的落点;拿文件名在任意深度拒会把用户仓里同名文件一并挡掉。计数非零 = 去看 detail 点名的那一段(链接指向哪、卷挂了没有),修好后重启。",
373
373
  },
374
+ "server.fleet.serving-lease-unconfirmed": {
375
+ cls: "P-DEBT",
376
+ note: "7.121.0 修复轮(`fleet/serving-lease.ts` 的 `renewOnce`,租约生效时每一次续租;detail = `worker=<w> generation=<g> why=<不可达句 | verdict=unknown>`):中心这一次**没有确认**本实例该不该服务 —— 不可达 / 超时 / 非 2xx / 答复读不成判决,或答 `unknown`(此刻没有本实例的生命周期对象)。按设计**什么都不改**(规则 2 第三臂):到期之前照常 `granted`,过期后 `expired` 照常接单,直到过期满 `FLEET_LEASE_OUTAGE_MAX_MS` 才拒新工作。放行的最坏后果 = 一只其实已被取代的实例在中心失联期间多服务至多「租约剩余 + `T_max`」(中心的代际下限持久,恢复后第一次续租即拒);不放行的代价是中心抖一下整队停服。归 P 类:围栏轴,最坏后果是作业完整性(被取代的实例多服务),不是难看;归 DEBT:容忍规则本身(`T_max` 的取值与去抖)待裁定,收口条件 = 那次裁定落地。计数持续非零 = 中心长时间没确认本实例,去看 detail 的 why(连不上 / 中心不认识这只实例)。",
377
+ },
378
+ "server.fleet.serving-lease-capability-absent": {
379
+ cls: "P-DEBT",
380
+ note: "7.121.0 修复轮(`fleet/serving-lease.ts` 的 `noteCenterCapabilities`;fleet 成员每一次中心能力读数;detail = `worker=<w> generation=<g> was=<enabled|none>`):中心答复体读得出、却**没有宣告** `servingLease`(老中心 `{ ok: true }` / 降级中心 / 位撤掉)⇒ 规则 1:租约不生效,`leaseState: none`,零续租、零 503 —— 这只 fleet 成员此刻**不受围栏**。答复体读不出不进这一臂(那是「没有读数」,上一次的位与钟原样,`fleet-client.ts` `capabilityReadingOf`);已被拒的实例不进(拒是黏的,位消失不复活)。放行的最坏后果 = 中心不执行围栏期间,一只被取代的实例照常服务;不放行的代价是老中心 / 降级中心下所有成员在第一次续租就失败。归 P 类:围栏轴,成员此刻不受围栏;归 DEBT:能力门控是契约规则(中心不宣告 = 不执行),收口条件 = 租约容忍规则的那次裁定(含「位从有到无再到有」的去抖)落地。计数非零 = 这台中心没宣告租约能力。",
381
+ },
382
+ "server.fleet.announce-reply-unreadable": {
383
+ cls: "P-DEBT",
384
+ note: "7.121.0 修复验证轮(`fleet-client.ts` 的公告环,fleet 成员每一拍心跳 2xx 答复;detail = `why=<body|not-an-object|invalid-capabilities> leaseState=<此刻的租约态>`):答复不是能力读数(体读不出 / 不是对象 / `capabilities` 键在场而形坏 —— settings-schema 9.0.0 `readCapabilityReading` 的 `unreadable`,外加本仓的 `body`)⇒ 当作没有这一拍答复,租约的状态与钟原样。**一律**计数(不论租约生效与否):租约还没生效(`leaseState=none`)时这只成员**一直不受围栏**,而修前这一形零计数(中心每拍答 `<html>` ⇒ 成员恒 none、零痕迹);租约已生效时计数说明中心的答复在坏、围栏靠的是上一次读数。归 P 类:围栏轴;归 DEBT:本进程判不出中心为什么答坏体,收口条件 = 中心契约 / 监控(中心侧对心跳答复形的执法)落地。每段连拍另有一行 warn `fleet_announce_reply_unreadable`(带 `why`)。",
385
+ },
386
+ "server.fleet.denied-marker-unpersisted": {
387
+ cls: "P-DEBT",
388
+ note: "7.121.0(`fleet/serving-lease.ts` 的 `enterDenied`,中心以正面证据拒了本实例的服务租约那一拍;detail = `worker=<w> generation=<g>`):拒要落成数据根下一只文件(重启后起步即排空),而这一次**没落下** —— 没有数据根,或写失败(盘满 / 只读挂载 / 目录不在)。本进程照样被拒、照样当拍排空、到期自停;放行的是**重启之后**:同代际重启的实例不记得被拒,在第一次续租得到答复之前会接新工作(中心宣告了能力位时,第一次续租就再被拒一次 —— 中心的代际下限是持久的;中心那时不可达则一直接单到过期满 `FLEET_LEASE_OUTAGE_MAX_MS`)。归 P 类:这是围栏的记忆;归 DEBT:进程内无法把「记不住」变成 fail-closed(起步不接单直到第一次答复,是对租约不可达容忍规则的改动,另案)。denied 行的 `sticky` 同拍说 `process-only`,不声称跨重启记得。计数非零 = 这台机器的数据根写不进,或部署没有数据根。",
389
+ },
374
390
  };
375
391
  export function failOpenTagForDroppedFrame(frameType) {
376
392
  switch (frameType) {
@@ -617,7 +617,7 @@ function removedRuleRowNote(kind) {
617
617
  case "rule":
618
618
  return "a persisted permission rule whose project scope root is not a complete absolute path was removed by a one-time migration: the current engine drops such a row on every read (it protected nothing) and refuses any write naming that scope, so it could not be revoked through DELETE /v1/rules; re-add it with the project's full path if it is still wanted";
619
619
  case "revoked-rule":
620
- return "a persisted permission rule whose project scope root is not a complete absolute path, and which had already been revoked (every approval behind it is removed by a tombstone in the same bucket), was removed by a one-time migration: it granted nothing before and grants nothing now — the revocation stands, no action needed";
620
+ return "a persisted permission rule row whose project scope root is not a complete absolute path, and which had no live approval left (every approval behind it was already removed, or it never had one), was removed by a one-time migration: the row took effect nowhere before and takes effect nowhere now — no action needed";
621
621
  case "tombstone":
622
622
  return "a revocation record (tombstone) whose project scope root is not a complete absolute path was removed by a one-time migration: it can only cover rules with that same scope, which were removed with it or were never there, so nothing it revoked comes back — no action needed";
623
623
  default: {
@@ -417,7 +417,8 @@ export declare const RULE_REAP_BATCH = 500;
417
417
  *
418
418
  * `kind` 是闭集(启动日志按它穷尽分句,`one-time-migrations.ts` `logOneTimeMigrations`):
419
419
  * · `rule` —— 仍有活 add 的规则行(引擎读侧每跑一次丢它并告知;它从未生效,要的话按项目的完整路径重写);
420
- * · `revoked-rule` —— 每个 add 都已被同桶墓碑撤掉的规则行(判活用 core 的 `applyTombstones`):它什么都不授予,人的撤销照旧成立,无需处置;
420
+ * · `revoked-rule` —— 没有活 add 的规则行(判活用 core 的 `applyTombstones`:每个 add 都已被同桶墓碑撤掉,或这一行本来就没有 add):它在任何一侧都已不生效
421
+ * (allow / deny / ask 同判),无需处置;逐行 warn 的句子只说这个事实,不依赖桶里有没有墓碑、也不依赖态;
421
422
  * · `tombstone` —— 一条撤销记录。它只盖得住同 (behavior, 文本, scope) 的规则 ⇒ 那些规则的 scope 与它同样不被收,随它一起删了或本来就不在,
422
423
  * 删它不会让任何被撤销的东西回来。
423
424
  */
@@ -5,7 +5,6 @@ export const DEVICE_PROVIDER = "device";
5
5
  const ok = (value) => ({ ok: true, value });
6
6
  const STREAM_PENDING_MAX_BYTES = 2 * 1024 * 1024;
7
7
  const STREAM_PENDING_MAX_ENTRIES = 8192;
8
- const VERIFY_FIRST = "may have already executed on the target; verify its effect before re-running";
9
8
  export const DEVICE_OUTCOME_TO_FILE_ERROR = {
10
9
  aborted: "aborted",
11
10
  already_exists: "already_exists",
@@ -23,7 +23,7 @@ const TASK_BODY_DECLARED_KEYS = [
23
23
  "compaction", "compactionModel", "council", "cwd", "debate", "deferTools", "deviceId", "disableSkills", "enableFork",
24
24
  "excludeAllTools", "excludeTools", "fileHistory",
25
25
  "finalVerification", "forwardSubagentEvents", "images", "inheritEnv", "interactiveTools", "jobId", "limits", "maxCostUsd",
26
- "maxTokens", "mcpServers", "memory", "memoryCapture", "memoryWrite", "model", "objective", "oneShot", "outputRetries",
26
+ "maxTokens", "mcpServers", "memory", "memoryCapture", "memoryWrite", "model", "objective", "oneShot", "outputRetries", "outputStyle",
27
27
  "outputSchema", "permissionMode", "projectId", "promptProfile", "reasoningEffort", "requireExistingSession",
28
28
  "resilience", "restoreFiles", "resumeAt", "resumeAtMode", "retainBackgroundProcesses", "retainSubagentSessions",
29
29
  "rewindFiles", "rewindFilesTo", "sandboxImageProfile", "scenario", "scratchpadDir", "selfOrchestration",
@@ -510,9 +510,13 @@ export type DurableRuleScope = Exclude<RuleScope, {
510
510
  * 客户端会无限重试)、引擎读侧丢(撤销口删不动),一条规则在两侧是两种命运。存量的那种行由一次性迁移 M11 删
511
511
  * (`plugins/one-time-migrations.ts` 登记表)。 */
512
512
  export declare function parseRuleScope(text: string): DurableRuleScope | undefined;
513
- /** 引擎不收的 scope(或 cc-import 层 root)的**唯一一句**拒句:写 / 撤 / 列举过滤三个路由口、cc-import 预览的层 root、local-import 的成员拒因
514
- * `scope_unreadable` 都出站这一只(各口的码照旧各自的),不在读点各抄一份。 */
513
+ /** 引擎不收的 scope 的**唯一一句**拒句:写 / 撤 / 列举过滤三个路由口、local-import 的成员拒因 `scope_unreadable` 都出站这一只
514
+ * (各口的码照旧各自的),不在读点各抄一份。cc-import 预览的层 root 判据是同一只(`isValidDurableScope`),但那份体里没有 scope 字段,
515
+ * 拒句另由 {@link buildCcImportLayerRootRefusal} 铸。 */
515
516
  export declare const RULE_SCOPE_REFUSAL_SENTENCE = "scope must be 'global' or 'project:<root>' with an absolute root";
517
+ /** cc-import 预览里第 `layerIndex` 层的 root 不是引擎收的绝对路径时的拒句 —— 点名这份体里真有的字段与层号(体里没有 scope 字段,
518
+ * 不回 {@link RULE_SCOPE_REFUSAL_SENTENCE})。判据与上面三个入口是同一只谓词,所以「absolute」是同一个词;句子只此一处铸,路由不手拼。 */
519
+ export declare function buildCcImportLayerRootRefusal(layerIndex: number): string;
516
520
  /** 一条规则在 wire 上的行 = 一个逻辑 (rule, scope) 对([ref] v2 §6 F5)。
517
521
  * `adds` **如实上 wire**、不折叠成单值:同一条规则可以被批准过多次(不同 dot、不同来源、不同时刻),
518
522
  * 折成「一个 addedAt」会让「这条规则是我导入的还是我点过卡的」在治理面上不可分。 */
@@ -61,6 +61,9 @@ export function parseRuleScope(text) {
61
61
  return isValidDurableScope(scope) ? scope : undefined;
62
62
  }
63
63
  export const RULE_SCOPE_REFUSAL_SENTENCE = "scope must be 'global' or 'project:<root>' with an absolute root";
64
+ export function buildCcImportLayerRootRefusal(layerIndex) {
65
+ return `layers[${layerIndex}].root must be an absolute path`;
66
+ }
64
67
  export function sameWireRuleIdentity(row, identity) {
65
68
  return row.behavior === identity.behavior && row.rule === identity.rule && row.scope === identity.scope;
66
69
  }
package/dist/runs.js CHANGED
@@ -423,7 +423,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
423
423
  await append("suggestions", { suggestions: suggestions.map((s) => redactSecrets(s)) });
424
424
  }
425
425
  };
426
- await instructionLeg.run(spec.sessionId, () => withModelReachScope(spec.sessionId, () => withPrincipal(principal, () => {
426
+ await instructionLeg.run(spec.sessionId, () => withModelReachScope({ sessionId: spec.sessionId, taskId }, () => withPrincipal(principal, () => {
427
427
  const withElicit = elicitation
428
428
  ? () => elicitation.runWithContext({ taskId, owner: elicitOwner ?? owner, emit: elicitEmit, abortSignal: cancelCtrl.signal }, runBody)
429
429
  : runBody;
@@ -1341,6 +1341,13 @@ export declare class ToolApprovalCoordinator {
1341
1341
  * 流没了,值相等即目标集;极端复用形下多转的那条也只是从流内卡变 durable 卡,方向保守。
1342
1342
  * 返回触发条数(0 = 无流内未决 ask,调用方照旧 abort)。幂等:已结算条目跳过,重复调用无害。 */
1343
1343
  parkOpenAsksForTask(wireTaskId: string): number;
1344
+ /**
1345
+ * 编排器片 2 件 2 —— 下面 `drainForShutdown` 的第 ① 步,提前到**排空进入**那一拍(`boot/shutdown.ts` 的唯一排空入口调):锁存
1346
+ * {@link shutdownDraining},本进程握着的每一张未决卡按断连同路改路耐久停驻(core `onAsk` 答 "unavailable" 的合同 —— 有 park 设施则停驻、
1347
+ * 没有则无头自动拒,fail-closed);之后**登记**的卡在登记那一拍同路(`askBroadcast` 读同一位)。幂等(再调只是再扫一遍未决表)。
1348
+ * 让出放在进入那一拍而不是硬停那一拍:活卡在排空窗里没有人能答到一只正在下线的副本上,早一拍停驻 = 早一拍能在替补上赎回。
1349
+ */
1350
+ beginDrain(): void;
1344
1351
  /**
1345
1352
  * 🔴 **S-929 —— 有序关停的审批面收尾(一只;`boot/shutdown.ts` 的 `hardShutdown` 起点调,SIGTERM / SIGINT / SIGHUP 三条停止路同一处)。**
1346
1353
  *
@@ -707,11 +707,14 @@ export class ToolApprovalCoordinator {
707
707
  }
708
708
  return n;
709
709
  }
710
- async drainForShutdown(deadlineMs) {
710
+ beginDrain() {
711
711
  this.shutdownDraining = true;
712
712
  for (const e of [...this.pending.values()])
713
713
  if (e.settledOutcome === undefined)
714
714
  e.forceParkNow();
715
+ }
716
+ async drainForShutdown(deadlineMs) {
717
+ this.beginDrain();
715
718
  while ((this.asksInFlight > 0 || this.pending.size > 0 || this.streams.size > 0 || this.parksAwaitingCommit.size > 0) && Date.now() < deadlineMs) {
716
719
  await new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_POLL_MS));
717
720
  }
@@ -148,8 +148,8 @@ type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCal
148
148
  type _GuardParkArmQueue = AssertAllKeysHandled<Exclude<ParkArmKeys, ParkArmQueueProjected | ParkArmQueueExcluded>>;
149
149
  type _GuardParkArmQueueReverse = AssertAllKeysHandled<Exclude<ParkArmQueueProjected | ParkArmQueueExcluded, ParkArmKeys>>;
150
150
  type _NoDoubleBookParkArmQueue = AssertAllKeysHandled<Extract<ParkArmQueueProjected, ParkArmQueueExcluded>>;
151
- type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "approvalReach" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "permissionMode" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory" | "userInvokedSkills" | "allowedTools";
152
- type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence" | "outputStyle";
151
+ type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "approvalReach" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "permissionMode" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory" | "userInvokedSkills" | "outputStyle" | "allowedTools";
152
+ type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence";
153
153
  type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
154
154
  type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
155
155
  type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveWriteFace" | "effectiveMemoryScopes" | "stats" | "editedFiles" | "responseModel" | "compaction";
@@ -169,6 +169,70 @@ export declare const SELF_MINTED_WIRE: {
169
169
  };
170
170
  };
171
171
  };
172
+ /** 7.121.0:`GET /health` 自报位 `auth.kinds[]` 的成员(验过的身份来源闭集;这台部署的装配能回的那几个)。 */
173
+ readonly healthAuthKind: {
174
+ readonly at: "GET /health auth.kinds[]";
175
+ readonly members: {
176
+ service_token: {
177
+ attr: string;
178
+ since: string;
179
+ };
180
+ sso: {
181
+ attr: string;
182
+ since: string;
183
+ };
184
+ direct_door_jwt: {
185
+ attr: string;
186
+ since: string;
187
+ };
188
+ };
189
+ };
190
+ /** 7.121.0:`GET /health` 自报位 `auth.identityAttach`。 */
191
+ readonly healthIdentityAttach: {
192
+ readonly at: "GET /health auth.identityAttach";
193
+ readonly members: {
194
+ available: {
195
+ attr: string;
196
+ since: string;
197
+ };
198
+ unavailable: {
199
+ attr: string;
200
+ since: string;
201
+ };
202
+ };
203
+ };
204
+ /** 7.121.0:`GET /health` 自报位 `operator`。 */
205
+ readonly healthOperator: {
206
+ readonly at: "GET /health operator";
207
+ readonly members: {
208
+ present: {
209
+ attr: string;
210
+ since: string;
211
+ };
212
+ absent: {
213
+ attr: string;
214
+ since: string;
215
+ };
216
+ };
217
+ };
218
+ /** 7.121.0:`GET /health` 自报位 `center.sessionsVisible`(两个布尔 + 一个词;成员按 JSON 字面登记)。 */
219
+ readonly healthSessionsVisible: {
220
+ readonly at: "GET /health center.sessionsVisible";
221
+ readonly members: {
222
+ true: {
223
+ attr: string;
224
+ since: string;
225
+ };
226
+ false: {
227
+ attr: string;
228
+ since: string;
229
+ };
230
+ unknown: {
231
+ attr: string;
232
+ since: string;
233
+ };
234
+ };
235
+ };
172
236
  /** 本仓在出口对 core 值做的变换(值变、键不变)。 */
173
237
  readonly wireValueRule: {
174
238
  readonly at: "出口值变换(<端点>#<键> → 变换)";
@@ -60,6 +60,36 @@ export const SELF_MINTED_WIRE = {
60
60
  shutdown_before_park: { attr: "-", since: "7.116.0" },
61
61
  },
62
62
  },
63
+ healthAuthKind: {
64
+ at: "GET /health auth.kinds[]",
65
+ members: {
66
+ service_token: { attr: "-", since: "7.121.0" },
67
+ sso: { attr: "-", since: "7.121.0" },
68
+ direct_door_jwt: { attr: "-", since: "7.121.0" },
69
+ },
70
+ },
71
+ healthIdentityAttach: {
72
+ at: "GET /health auth.identityAttach",
73
+ members: {
74
+ available: { attr: "-", since: "7.121.0" },
75
+ unavailable: { attr: "-", since: "7.121.0" },
76
+ },
77
+ },
78
+ healthOperator: {
79
+ at: "GET /health operator",
80
+ members: {
81
+ present: { attr: "-", since: "7.121.0" },
82
+ absent: { attr: "-", since: "7.121.0" },
83
+ },
84
+ },
85
+ healthSessionsVisible: {
86
+ at: "GET /health center.sessionsVisible",
87
+ members: {
88
+ true: { attr: "-", since: "7.121.0" },
89
+ false: { attr: "-", since: "7.121.0" },
90
+ unknown: { attr: "-", since: "7.121.0" },
91
+ },
92
+ },
63
93
  wireValueRule: {
64
94
  at: "出口值变换(<端点>#<键> → 变换)",
65
95
  members: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.120.0",
3
+ "version": "7.121.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",
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "@sema-agent/core": "7.46.0",
68
- "@sema-agent/settings-schema": "8.0.0",
68
+ "@sema-agent/settings-schema": "9.0.0",
69
69
  "e2b": "^2.28.0",
70
70
  "libsodium-wrappers": "^0.8.4",
71
71
  "mysql2": "^3.22.4",