@sema-agent/server 7.101.0 → 7.102.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.
- package/MIGRATION.md +23 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +58 -4
- package/dist/approval-card.d.ts +35 -2
- package/dist/approval-card.js +19 -1
- package/dist/background-session-reap.d.ts +55 -0
- package/dist/background-session-reap.js +90 -0
- package/dist/boot/config-center.d.ts +16 -4
- package/dist/boot/config-center.js +44 -15
- package/dist/boot/exit-boundary.js +17 -0
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/runtime-caps.js +3 -2
- package/dist/boot/session-faces.js +3 -4
- package/dist/boot/shutdown.d.ts +2 -1
- package/dist/boot/shutdown.js +33 -12
- package/dist/boot/stage-07-capability-layer.d.ts +1 -1
- package/dist/boot/stage-07-capability-layer.js +6 -4
- package/dist/boot/stage-08-reapers.d.ts +1 -1
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stage-10-http-server.js +9 -3
- package/dist/capabilities/builtin-tools.d.ts +14 -1
- package/dist/capabilities/builtin-tools.js +3 -0
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capabilities/scenarios.js +3 -3
- package/dist/center-credential-renewer.d.ts +76 -0
- package/dist/center-credential-renewer.js +359 -0
- package/dist/center-credential.d.ts +76 -22
- package/dist/center-credential.js +119 -43
- package/dist/config-catalog.js +9 -4
- package/dist/config-center/apply-effective.js +5 -12
- package/dist/config-center/center-request.d.ts +13 -0
- package/dist/config-center/center-request.js +23 -3
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/http-client.js +2 -5
- package/dist/config-center/read-warnings.d.ts +19 -48
- package/dist/config-center/read-warnings.js +7 -49
- package/dist/config-center/restart-signal.d.ts +8 -11
- package/dist/config-center/types.d.ts +4 -2
- package/dist/config-provider.js +1 -48
- package/dist/config-types.d.ts +77 -5
- package/dist/config-types.js +5 -0
- package/dist/config.js +49 -13
- package/dist/device-store.d.ts +1 -1
- package/dist/fleet/fleet-bus.d.ts +30 -4
- package/dist/fleet/fleet-bus.js +20 -4
- package/dist/fleet/fleet-reconciler.d.ts +29 -6
- package/dist/fleet/fleet-reconciler.js +109 -79
- package/dist/fleet-client.d.ts +21 -7
- package/dist/fleet-client.js +41 -12
- package/dist/fleet-lease.d.ts +1 -1
- package/dist/http/approval-sweeps.js +5 -20
- package/dist/http/dispatch.js +20 -14
- package/dist/http/readiness.d.ts +87 -0
- package/dist/http/readiness.js +54 -0
- package/dist/http/resume-legs.js +5 -4
- package/dist/http/route-table.d.ts +7 -0
- package/dist/http/route-table.js +6 -0
- package/dist/http/routes/a2a-serve.js +6 -14
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/background.d.ts +80 -0
- package/dist/http/routes/background.js +94 -0
- package/dist/http/routes/capabilities.js +2 -0
- package/dist/http/server.d.ts +21 -8
- package/dist/http/server.js +2 -2
- package/dist/model-entry-refusal.d.ts +11 -10
- package/dist/model-entry-refusal.js +13 -10
- package/dist/observability/fail-open.d.ts +6 -2
- package/dist/observability/fail-open.js +6 -2
- package/dist/parked-decide.d.ts +11 -5
- package/dist/parked-decide.js +4 -1
- package/dist/parked-revive-surface.d.ts +47 -0
- package/dist/parked-revive-surface.js +20 -0
- package/dist/plugins/background-shell-support.d.ts +20 -1
- package/dist/plugins/background-shell-support.js +26 -0
- package/dist/plugins/host-platform.d.ts +11 -1
- package/dist/plugins/host-platform.js +11 -0
- package/dist/plugins/remote-env-host.d.ts +14 -1
- package/dist/plugins/remote-env-host.js +18 -5
- package/dist/tool-approval.d.ts +39 -1
- package/dist/tool-approval.js +37 -8
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/package.json +3 -3
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-739 / S-740(车OE,7.102.0):**就绪判的唯一属主** —— 一只谓词 {@link readinessOf},全部读点经它:
|
|
3
|
+
*
|
|
4
|
+
* ① `GET /readyz`(`dispatch.ts`,凭据门之前,与 `/health` 同位):200 `{ready:true}` / 503 `{ready:false, reasons}` ——
|
|
5
|
+
* 给**摘流**语义的探针(k8s readinessProbe / LB 健康检查);**重启**语义的探针(Docker HEALTHCHECK、k8s livenessProbe)
|
|
6
|
+
* 留在 `/health`:中心宕机时整队副本同卡 roster 未落,指 `/readyz` 就会一起重启打转(对抗复审 #6);
|
|
7
|
+
* ② `GET /health` 的 `ready` / `readyReason` / `reasons` 三键({@link healthReadinessKeys});
|
|
8
|
+
* ③ 计费提交门的 503(`dispatch.ts` 的 billable 门、`routes/a2a-serve.ts` 的 `message/send`、
|
|
9
|
+
* `approval-sweeps.ts` 的 PARKED 赎回席 —— 三处同调 {@link billableRefusalOf},拒绝体只此一处铸);
|
|
10
|
+
* ④ fleet 心跳 `InstanceAnnouncement.ready` / `draining`(`fleet-client.ts`;装配层接
|
|
11
|
+
* `readinessOf(deployment)` —— 与 HTTP 面读的是同一个 deps 组)。
|
|
12
|
+
*
|
|
13
|
+
* 修前是四份手抄:`/health` 读 `modelReady`、计费门两道各读一只、a2a 与赎回席各抄一遍,心跳读
|
|
14
|
+
* `!isDraining()`(不含 roster)⇒ roster 未落时心跳说「就绪」而计费提交在 503,中心按心跳把它当可路由。
|
|
15
|
+
*
|
|
16
|
+
* **reasons 闭集 = `draining | model_roster_pending`,刻意不含 storeLive**(web-admin [ref] 要、[ref] 裁):
|
|
17
|
+
* DB 抖动是**共模**失效 —— 一次库抖若进就绪门,整队副本会在同一拍被 k8s / LB 摘出路由,比「打到一台库连不上
|
|
18
|
+
* 的副本」更坏。`storeLive:false` 留在 `/health` 体位 + 既有告警键,摘不摘流由读体的编排器裁(披露不代裁)。
|
|
19
|
+
* 加一个 reason = 改 {@link READINESS_REASONS} ⇒ 下面两只按 reason 穷举的表(`READY_REASON_PHRASE` 的
|
|
20
|
+
* 映射型 / `billableRefusalOf` 的 switch)当场编译红,逼你给出它的 `/health` 句与计费门拒绝体。
|
|
21
|
+
*
|
|
22
|
+
* 模块形:纯叶(只值 import `send.ts`)—— `fleet-client.ts` / `boot/` 都会读它,不许长出回环。
|
|
23
|
+
*/
|
|
24
|
+
import type { ServerResponse } from "node:http";
|
|
25
|
+
/**
|
|
26
|
+
* 就绪原因闭集,**数组序 = 优先序**:`/health.readyReason` 取首条的句、计费门取首条的拒绝码。
|
|
27
|
+
* draining 在前 —— 一台正在下线的副本「去找替补」(Retry-After 15)比「稍后重试本机」(5)更对。
|
|
28
|
+
*/
|
|
29
|
+
export declare const READINESS_REASONS: readonly ["draining", "model_roster_pending"];
|
|
30
|
+
export type ReadinessReason = (typeof READINESS_REASONS)[number];
|
|
31
|
+
/** 就绪判。未就绪时 `reasons` 非空(类型上就是非空元组 —— 空数组的未就绪判构造不出来)、按
|
|
32
|
+
* {@link READINESS_REASONS} 序、无重复。 */
|
|
33
|
+
export type ReadinessVerdict = {
|
|
34
|
+
readonly ready: true;
|
|
35
|
+
} | {
|
|
36
|
+
readonly ready: false;
|
|
37
|
+
readonly reasons: readonly [ReadinessReason, ...ReadinessReason[]];
|
|
38
|
+
};
|
|
39
|
+
export type NotReadyVerdict = Extract<ReadinessVerdict, {
|
|
40
|
+
ready: false;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* 谓词的输入 —— `ServiceDeps` 上的同名两键(`modelReady` / `drainState`),HTTP 面与心跳读的是**同一个对象**。
|
|
44
|
+
* 两键缺席 = 该输入恒就绪(纯 env 部署没有 roster 可等;嵌入宿主 / 测试夹具没有 drain 面)。
|
|
45
|
+
*/
|
|
46
|
+
export interface ReadinessInputs {
|
|
47
|
+
/** boot ready 门(b):false = 布防且 roster 未落(`CONFIG_REQUIRE_ROSTER=true`)。 */
|
|
48
|
+
readonly modelReady?: () => boolean;
|
|
49
|
+
/** 收尾链翻转的共享 drain 态。 */
|
|
50
|
+
readonly drainState?: {
|
|
51
|
+
readonly draining: boolean;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** **唯一**的就绪谓词(活判,每次调用现读输入 —— 不是快照)。 */
|
|
55
|
+
export declare function readinessOf(deps: ReadinessInputs): ReadinessVerdict;
|
|
56
|
+
/**
|
|
57
|
+
* `/health` 的就绪三键(additive:就绪 ⇒ 空对象,`/health` 正常形状逐字不变)。
|
|
58
|
+
* `reasons` = 与 `/readyz` 逐字同一份闭集词;`readyReason` = 首条的人读句(旧消费端读的就是它)。
|
|
59
|
+
*/
|
|
60
|
+
export declare function healthReadinessKeys(v: ReadinessVerdict): Record<string, unknown>;
|
|
61
|
+
/** `/readyz` 两形:200 `{ready:true}` / 503 `{ready:false, reasons}` + `Retry-After: 5`。体只这两键(不带身份字段:
|
|
62
|
+
* 就绪探针给 k8s readinessProbe / Traefik / LB 摘流用,不是 `/health` 的替身;`drainReason` 等运维自由文本也不上这一面)。 */
|
|
63
|
+
export declare function sendReadyz(res: ServerResponse, v: ReadinessVerdict): void;
|
|
64
|
+
/** 计费提交门的拒绝(首条 reason 定码)。`retryAfterSec` 由发送形(`sendBillableRefusal`)镜像成头;交回
|
|
65
|
+
* `{status, body}` 的赎回席只取体(它的路由刻意不带头,见 `approval-sweeps.ts` 该臂注)。 */
|
|
66
|
+
export interface BillableRefusal {
|
|
67
|
+
readonly status: 503;
|
|
68
|
+
readonly body: {
|
|
69
|
+
readonly error: string;
|
|
70
|
+
readonly errorCode: string;
|
|
71
|
+
readonly message: string;
|
|
72
|
+
readonly reason?: string;
|
|
73
|
+
};
|
|
74
|
+
readonly retryAfterSec: 15 | 5;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 计费提交门的拒绝体 —— **唯一铸点**(`error` 字面 `"draining"` / `"model_roster_pending"` 与 `message` 是冻结的
|
|
78
|
+
* wire 契约:SDK / 壳的 pre-stream 重试按它们判型;冻结在 `test/api-error-text-freeze.test.ts` 的
|
|
79
|
+
* `FROZEN_RESULT_BODY` 与 `test/api-notice-text-freeze.test.ts` §2)。`drainReason` = `POST /v1/admin/drain`
|
|
80
|
+
* 声明的停机因由([ref],additive 键 `reason`;未声明 = 键缺席)。
|
|
81
|
+
*/
|
|
82
|
+
export declare function billableRefusalOf(v: NotReadyVerdict, drainReason: string | undefined): BillableRefusal;
|
|
83
|
+
/** 发送形:`Retry-After` + 体(`dispatch.ts` 的 billable 门与 a2a `message/send` 同调)。走 `sendError` —— errorCode
|
|
84
|
+
* 是它的必填位置参(对抗复审 #3:修前这里是 `sendJson(res, r.status, r.body)`,与 leader 口的豁免行逐字同文,
|
|
85
|
+
* 在 error-code-key-gate 上借了别人的豁免过门);体键序与修前逐字同(`error, errorCode, message, reason?`)。 */
|
|
86
|
+
export declare function sendBillableRefusal(res: ServerResponse, refusal: BillableRefusal): void;
|
|
87
|
+
//# sourceMappingURL=readiness.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { sendJson, sendError } from "./send.js";
|
|
2
|
+
export const READINESS_REASONS = ["draining", "model_roster_pending"];
|
|
3
|
+
export function readinessOf(deps) {
|
|
4
|
+
const [first, ...rest] = READINESS_REASONS.filter((r) => holds(r, deps));
|
|
5
|
+
return first === undefined ? { ready: true } : { ready: false, reasons: [first, ...rest] };
|
|
6
|
+
}
|
|
7
|
+
function holds(reason, deps) {
|
|
8
|
+
switch (reason) {
|
|
9
|
+
case "draining":
|
|
10
|
+
return deps.drainState?.draining === true;
|
|
11
|
+
case "model_roster_pending":
|
|
12
|
+
return deps.modelReady !== undefined && !deps.modelReady();
|
|
13
|
+
default: {
|
|
14
|
+
const unhandled = reason;
|
|
15
|
+
throw new Error(`readiness reason without a predicate arm: ${String(unhandled)}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const READY_REASON_PHRASE = {
|
|
20
|
+
draining: "draining for shutdown or upgrade",
|
|
21
|
+
model_roster_pending: "model roster pending (first effective-config pull)",
|
|
22
|
+
};
|
|
23
|
+
export function healthReadinessKeys(v) {
|
|
24
|
+
if (v.ready)
|
|
25
|
+
return {};
|
|
26
|
+
return { ready: false, readyReason: READY_REASON_PHRASE[v.reasons[0]], reasons: [...v.reasons] };
|
|
27
|
+
}
|
|
28
|
+
export function sendReadyz(res, v) {
|
|
29
|
+
if (v.ready) {
|
|
30
|
+
sendJson(res, 200, { ready: true });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
res.setHeader("retry-after", "5");
|
|
34
|
+
sendJson(res, 503, { ready: false, reasons: [...v.reasons] });
|
|
35
|
+
}
|
|
36
|
+
export function billableRefusalOf(v, drainReason) {
|
|
37
|
+
const primary = v.reasons[0];
|
|
38
|
+
switch (primary) {
|
|
39
|
+
case "draining":
|
|
40
|
+
return { status: 503, body: { error: "draining", errorCode: "draining", message: "this instance is draining for shutdown/upgrade — retry against the replacement instance", ...(drainReason ? { reason: drainReason } : {}) }, retryAfterSec: 15 };
|
|
41
|
+
case "model_roster_pending":
|
|
42
|
+
return { status: 503, body: { error: "model_roster_pending", errorCode: "state.model_roster_pending", message: "this worker has no model yet (waiting for the first effective-config pull to land the roster) — retry shortly" }, retryAfterSec: 5 };
|
|
43
|
+
default: {
|
|
44
|
+
const unhandled = primary;
|
|
45
|
+
throw new Error(`readiness reason without a billable refusal: ${String(unhandled)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function sendBillableRefusal(res, refusal) {
|
|
50
|
+
const { error, errorCode, ...extra } = refusal.body;
|
|
51
|
+
res.setHeader("retry-after", String(refusal.retryAfterSec));
|
|
52
|
+
sendError(res, refusal.status, errorCode, error, extra);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=readiness.js.map
|
package/dist/http/resume-legs.js
CHANGED
|
@@ -236,10 +236,10 @@ export function createResumeLegs(ctx) {
|
|
|
236
236
|
const ownerLeaseAdmittedHere = req !== undefined;
|
|
237
237
|
if (req !== undefined) {
|
|
238
238
|
const parkedAgentStore = deps.backgroundAgentStore;
|
|
239
|
-
const
|
|
239
|
+
const parkedSurface = deps.parkedReviveSurface;
|
|
240
240
|
const parkWarn = (event, fields) => deps.logger?.warn?.(event, fields);
|
|
241
241
|
const origin = await findParkOrigin({
|
|
242
|
-
...(parkedAgentStore !== undefined &&
|
|
242
|
+
...(parkedAgentStore !== undefined && parkedSurface !== undefined ? { agentStore: parkedAgentStore } : {}),
|
|
243
243
|
...(deps.workflowAgentSessionIndex !== undefined && deps.workflowRunStore !== undefined
|
|
244
244
|
? { workflowIndex: deps.workflowAgentSessionIndex, workflowRunStore: deps.workflowRunStore }
|
|
245
245
|
: {}),
|
|
@@ -252,12 +252,13 @@ export function createResumeLegs(ctx) {
|
|
|
252
252
|
...(answer !== undefined ? { answer } : {}),
|
|
253
253
|
...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
|
|
254
254
|
};
|
|
255
|
-
if (origin?.kind === "background" && parkedAgentStore !== undefined &&
|
|
255
|
+
if (origin?.kind === "background" && parkedAgentStore !== undefined && parkedSurface !== undefined) {
|
|
256
256
|
const parked = await withPrincipal(decodeCheckpointScope(cp.scope), () => decideParkedAgent({
|
|
257
257
|
agentStore: parkedAgentStore,
|
|
258
258
|
checkpointStore: cs,
|
|
259
259
|
registry: defaultTaskRegistry,
|
|
260
|
-
|
|
260
|
+
reviveSurface: parkedSurface,
|
|
261
|
+
...(deps.sessionStorage?.ownerOf ? { sessionOwnerOf: deps.sessionStorage.ownerOf.bind(deps.sessionStorage) } : {}),
|
|
261
262
|
...(deps.parkedKnownAgentTypes ? { knownAgentTypes: deps.parkedKnownAgentTypes } : {}),
|
|
262
263
|
...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
|
|
263
264
|
...(deps.logger ? { warn: parkWarn } : {}),
|
|
@@ -83,6 +83,13 @@ export declare const ROUTES: readonly RouteRow[];
|
|
|
83
83
|
* 「门前行不许出现在门后行之后」由 `test/route-order-freeze.test.ts` 钉住。 */
|
|
84
84
|
export declare const PRE_CREDENTIAL_ROUTES: readonly RouteRow[];
|
|
85
85
|
export declare const POST_CREDENTIAL_ROUTES: readonly RouteRow[];
|
|
86
|
+
/**
|
|
87
|
+
* **探针路径**(k8s / 编排器 / 抓取器的常态高频采样):不计 admission inflight、不推活性时钟(`http/server.ts`
|
|
88
|
+
* 的 `noteActivity`)、不入附着租约集 —— 常态采样噪音不许把 SIGHUP 空闲窗 / 引擎租约的「无人使用」判据永远撑开。
|
|
89
|
+
* 两处消费(`dispatch.ts` 的 admission 门、`server.ts` 的活性时钟)读这一只;S-739 前是两份手抄
|
|
90
|
+
* (`url === "/health" || url.startsWith("/metrics")`),加 `/readyz` 时漏一份 = k8s 每 5-10s 一探就让副本永不空闲。
|
|
91
|
+
*/
|
|
92
|
+
export declare function isProbePath(url: string): boolean;
|
|
86
93
|
/**
|
|
87
94
|
* 全表 = 流水线四行 + **每一行 `ROUTES` 自己的 `decls`**,按行序摊平。
|
|
88
95
|
*
|
package/dist/http/route-table.js
CHANGED
|
@@ -29,6 +29,7 @@ import { handleApprovalsAssistant, APPROVALS_ASSISTANT_ROUTES } from "./routes/a
|
|
|
29
29
|
import { handleRuns, handleRunVerbs, RUNS_ROUTES, RUN_VERBS_ROUTES } from "./routes/runs.js";
|
|
30
30
|
import { handleRunMemoryCapture, RUN_MEMORY_CAPTURE_ROUTES } from "./routes/run-memory-capture.js";
|
|
31
31
|
import { handleMcpReconnect, MCP_RECONNECT_ROUTES } from "./routes/mcp-reconnect.js";
|
|
32
|
+
import { handleBackground, BACKGROUND_ROUTES } from "./routes/background.js";
|
|
32
33
|
import { handleSideQuery, SIDE_QUERY_ROUTES } from "./routes/side-query.js";
|
|
33
34
|
import { handleTasks, TASKS_ROUTES } from "./routes/tasks.js";
|
|
34
35
|
import { handleLeader, LEADER_ROUTES } from "./routes/leader.js";
|
|
@@ -50,6 +51,7 @@ export const ROUTES = [
|
|
|
50
51
|
row({ handler: handleRunVerbs, decls: RUN_VERBS_ROUTES, position: "post-credential", label: "run-verbs" }),
|
|
51
52
|
row({ handler: handleRunMemoryCapture, decls: RUN_MEMORY_CAPTURE_ROUTES, position: "post-credential", label: "run-memory-capture" }),
|
|
52
53
|
row({ handler: handleMcpReconnect, decls: MCP_RECONNECT_ROUTES, position: "post-credential", label: "mcp-reconnect" }),
|
|
54
|
+
row({ handler: handleBackground, decls: BACKGROUND_ROUTES, position: "post-credential", label: "background" }),
|
|
53
55
|
row({ handler: handleLeader, decls: LEADER_ROUTES, position: "post-credential", label: "leader", enabled: (deps) => Boolean(deps.leaderEndpoint) }),
|
|
54
56
|
row({ handler: handleImages, decls: IMAGES_ROUTES, position: "post-credential", label: "images" }),
|
|
55
57
|
row({ handler: handleApprovalsAssistant, decls: APPROVALS_ASSISTANT_ROUTES, position: "post-credential", label: "approvals-assistant", enabled: (deps) => Boolean(deps.checkpointStore) }),
|
|
@@ -80,10 +82,14 @@ export const PRE_CREDENTIAL_ROUTES = ROUTES.filter((r) => r.position === "pre-cr
|
|
|
80
82
|
export const POST_CREDENTIAL_ROUTES = ROUTES.filter((r) => r.position === "post-credential");
|
|
81
83
|
const PIPELINE_ROUTES = [
|
|
82
84
|
{ id: "health", path: "/health", methods: ["GET"] },
|
|
85
|
+
{ id: "readyz", path: "/readyz", methods: ["GET"] },
|
|
83
86
|
{ id: "metrics", path: "/metrics", methods: ["GET"] },
|
|
84
87
|
{ id: "metrics-summary", path: "/metrics/summary", methods: ["GET"] },
|
|
85
88
|
{ id: "metrics-plan-cache", path: "/metrics/plan-cache", methods: ["GET"] },
|
|
86
89
|
];
|
|
90
|
+
export function isProbePath(url) {
|
|
91
|
+
return url === "/health" || url === "/readyz" || url.startsWith("/metrics");
|
|
92
|
+
}
|
|
87
93
|
export const ROUTE_DECLARATIONS = [...PIPELINE_ROUTES, ...ROUTES.flatMap((r) => r.decls)];
|
|
88
94
|
function declMatches(d, url) {
|
|
89
95
|
if ("path" in d)
|
|
@@ -6,6 +6,7 @@ import { isParkedRunStatus, isTerminalRunStatus } from "../../plugins/store-cont
|
|
|
6
6
|
import { HttpError } from "../../security.js";
|
|
7
7
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
8
8
|
import { sendJson, sendError } from "../send.js";
|
|
9
|
+
import { readinessOf, billableRefusalOf, sendBillableRefusal } from "../readiness.js";
|
|
9
10
|
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
10
11
|
import { assertNever } from "../route-ctx.js";
|
|
11
12
|
import { createDurableRun } from "./runs.js";
|
|
@@ -169,20 +170,11 @@ function rpcErrorFromTypedFailure(err, skillId) {
|
|
|
169
170
|
}
|
|
170
171
|
function submitUnavailable(res, ctx) {
|
|
171
172
|
const { deps } = ctx;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
});
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
if (deps.modelReady && !deps.modelReady()) {
|
|
181
|
-
res.setHeader("retry-after", "5");
|
|
182
|
-
sendError(res, 503, "state.model_roster_pending", "model_roster_pending", { message: "this worker has no model yet (waiting for the first effective-config pull to land the roster) — retry shortly" });
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
return false;
|
|
173
|
+
const verdict = readinessOf(deps);
|
|
174
|
+
if (verdict.ready)
|
|
175
|
+
return false;
|
|
176
|
+
sendBillableRefusal(res, billableRefusalOf(verdict, deps.drainState?.reason));
|
|
177
|
+
return true;
|
|
186
178
|
}
|
|
187
179
|
function readBlocking(params) {
|
|
188
180
|
if (params.configuration === undefined)
|
|
@@ -289,6 +289,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
289
289
|
...(s.hasBidiControls === true ? { hasBidiControls: true } : {}),
|
|
290
290
|
...(s.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
291
291
|
...(s.mandated === true ? { mandated: true } : {}),
|
|
292
|
+
...(s.mandate !== undefined ? { mandate: s.mandate } : {}),
|
|
292
293
|
...(s.denialLimitFallback !== undefined ? { denialLimitFallback: s.denialLimitFallback } : {}),
|
|
293
294
|
...(s.origin !== undefined ? { origin: s.origin } : {}),
|
|
294
295
|
...(s.ruleStoreUnreadable !== undefined ? { ruleStoreUnreadable: s.ruleStoreUnreadable } : {}),
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-745(core 7.32.0 #1107 的 server 半场;wire 形 [ref] / [ref] / [ref] 定)—— **会话后台任务的两只读口**:
|
|
3
|
+
*
|
|
4
|
+
* `GET /v1/sessions/:id/background` → 200 `{ rows: [{ id, description?, type, status, createdAt, sessionScoped, retained? }] }`
|
|
5
|
+
* `POST /v1/sessions/:id/background/stop` body `{ includeRetained: boolean }` → 200 `{ receipts: [{ id, outcome }] }`
|
|
6
|
+
*
|
|
7
|
+
* 消费方:cli L-694 ①(删前核登记)/ L-686(退出三选一:「Exit and stop tasks」= `includeRetained:true`,`sema kill` =
|
|
8
|
+
* `includeRetained:false`,两臂都等受据再退)/ client-core CC-194 B 半(对账谓词)。老 server 404 ⇒ 壳只出 Stay / 退出两臂。
|
|
9
|
+
*
|
|
10
|
+
* ── 读口:逐字投 core `list()` 行 ─────────────────────────────────────────────────────────────────
|
|
11
|
+
* 读的是**进程内**注册表(`defaultTaskRegistry.list`),访问元组 = 会话自己的 `{ owner: sid, sessionId: sid, scope }`
|
|
12
|
+
* (scope = 会话行属主 `owner ?? "default"`,与 core 注册铸值同源)⇒ 可见集完全由 core 的 `canAccess` 判(fail-closed:
|
|
13
|
+
* 缺轴即拒;本会话的会话驻留行、task id 即会话 id 的 run 行、本会话发起的 workflow、根会话是本会话的后台 agent)。
|
|
14
|
+
* 键逐个处置(下面的 {@link ROW_KEY_DISPOSITION},core 给行加键 ⇒ 编译红):`task_id` 改名 `id`;`retained` **真才带**
|
|
15
|
+
* (core:缺席 ⇒ 这一行在 `includeRetained:false` 的硬面上会停);`description` 是模型写的描述、缺席时是启动时的命令文本 ——
|
|
16
|
+
* 内容族 ⇒ `redactSecrets` + 截 {@link DESCRIPTION_MAX}(显示串;core 不另存原始命令)。多副本部署:只列**本副本**上的行。
|
|
17
|
+
*
|
|
18
|
+
* ── 停止口:会话级收敛同一只函数 ────────────────────────────────────────────────────────────────
|
|
19
|
+
* `reapSessionBackgroundHard`(`background-session-reap.ts`,与 `DELETE /v1/sessions/:id` 同一只):软面(会话的后台 agent /
|
|
20
|
+
* workflow 一并结算;`includeRetained:true` 时 retain 行也先收到 SIGTERM)→ 等一次 `HOST_BG_KILL_GRACE_MS` → 硬面(车OL O1 / O2);
|
|
21
|
+
* `includeRetained` **没有缺省**(core 同一条:调用方说明这是哪一种停),缺席 / 非布尔 ⇒ 400。
|
|
22
|
+
* 受据只含 shell 行(`background_bash` / `monitor`),`outcome` 四词逐字透传:`killed` / `already_gone` / `retained_skipped` /
|
|
23
|
+
* `no_pgid`(= 没有同步硬收落地、已派普通 kill,「可能还在跑」—— 远端 / 沙箱车道的常态,不是失败)。受据在 200 之前已齐(`await`
|
|
24
|
+
* 收敛函数;有后台行时应答慢一次宽限)。
|
|
25
|
+
*
|
|
26
|
+
* ── 鉴权与门 ────────────────────────────────────────────────────────────────────────────────────
|
|
27
|
+
* 与 `POST /v1/sessions/:id/mcp/reconnect` 同一套:会话属主面缺席 ⇒ 501;未知会话与他人会话**同码同串** 404(无存在性
|
|
28
|
+
* oracle);显式 operator 可读可停任何会话(监督动作)。零模型工作 ⇒ 不进 billable、不吃 drain 503;停止口是 mutating ⇒
|
|
29
|
+
* `rateLimited`。能力位 `background: { listFace, exitFaces }` 与两口的 501 从同一个谓词推导(会话属主面在场)。
|
|
30
|
+
*
|
|
31
|
+
* 🔴 **停止口在无凭据写门内**(`credentialGated: ["POST"]`;车OL F1 = 7.102.0 合并复审 gpt-6 腿 [high]):门的两个合取项它都中
|
|
32
|
+
* —— ① 授权的唯一输入是 principal 头(属主判 `runOwnerOk` 与 operator 臂 `explicitOperatorOk` 读的都是 `gatedPrincipal`,
|
|
33
|
+
* 无 service 凭证的部署上它收调用方自报的头);② 不可撤销、爆炸半径跨租户(知道会话 id + 自报它的属主 ⇒ 停掉那个会话的后台
|
|
34
|
+
* 进程,`includeRetained:true` 连显式 retain 的活服务一起停)。于是无 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` 的部署上它与
|
|
35
|
+
* 其余改写门同码同句 503 `auth.service_token_required`(在路由之前,`dispatch.ts` 那一道),同一只会话级收敛函数的另一个调用点
|
|
36
|
+
* `DELETE /v1/sessions/:id` 早在同一形下被 `isDestructiveSessionWrite` 拒。读口 `GET …/background` 不进:它有属主门兜底、只读、
|
|
37
|
+
* 只列本会话,与 `GET /v1/rules` 同判。已知同族缺口(不在本口单独打补丁):能力位 `exitFaces` 不含「凭据就绪」这一项,见契约
|
|
38
|
+
* 附录 B.1 `memoryCompliance` 行的同族说明。
|
|
39
|
+
*/
|
|
40
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
41
|
+
import { type TaskRegistry, type BackgroundShellHardKillReceipt } from "@sema-agent/core";
|
|
42
|
+
import { type RouteCtx, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
|
|
43
|
+
/** `description` 的显示截断(模型描述通常一句话;回落的命令文本可能是一整段脚本)。 */
|
|
44
|
+
export declare const DESCRIPTION_MAX = 500;
|
|
45
|
+
/** core `list()` 的行型(从唯一的消费位取,不抄结构)。 */
|
|
46
|
+
type RegistryListRow = ReturnType<TaskRegistry["list"]>[number];
|
|
47
|
+
/** 读口的一行(白名单显式构造;缺席的可选键不铸)。 */
|
|
48
|
+
export interface SessionBackgroundRow {
|
|
49
|
+
id: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
type: RegistryListRow["type"];
|
|
52
|
+
status: RegistryListRow["status"];
|
|
53
|
+
createdAt: number;
|
|
54
|
+
sessionScoped: boolean;
|
|
55
|
+
retained?: true;
|
|
56
|
+
}
|
|
57
|
+
/** 停止口的一条受据(`outcome` 逐字透传 core 闭集)。 */
|
|
58
|
+
export interface SessionBackgroundReceipt {
|
|
59
|
+
id: string;
|
|
60
|
+
outcome: BackgroundShellHardKillReceipt["outcome"];
|
|
61
|
+
}
|
|
62
|
+
/** 能力位与两口 501 的**同一个**谓词:会话属主面在场(入站 owner 门 fail-closed,缺它两口都 501)。 */
|
|
63
|
+
export declare function sessionBackgroundFacesAvailable(deps: Pick<RouteCtx["deps"], "sessionStorage">): boolean;
|
|
64
|
+
export declare function handleBackground(req: IncomingMessage, res: ServerResponse, match: RouteMatch<BackgroundRouteId>, ctx: RouteCtx): Promise<void>;
|
|
65
|
+
export declare const BACKGROUND_ROUTES: readonly [{
|
|
66
|
+
readonly id: "session-background-list";
|
|
67
|
+
readonly pattern: RegExp;
|
|
68
|
+
readonly label: "/v1/sessions/:id/background";
|
|
69
|
+
readonly methods: readonly ["GET"];
|
|
70
|
+
}, {
|
|
71
|
+
readonly id: "session-background-stop";
|
|
72
|
+
readonly pattern: RegExp;
|
|
73
|
+
readonly label: "/v1/sessions/:id/background/stop";
|
|
74
|
+
readonly credentialGated: readonly ["POST"];
|
|
75
|
+
readonly methods: readonly ["POST"];
|
|
76
|
+
}];
|
|
77
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
78
|
+
export type BackgroundRouteId = RouteIdsOf<typeof BACKGROUND_ROUTES>;
|
|
79
|
+
export {};
|
|
80
|
+
//# sourceMappingURL=background.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defaultTaskRegistry } from "@sema-agent/core";
|
|
3
|
+
import { reapSessionBackgroundHard } from "../../background-session-reap.js";
|
|
4
|
+
import { redactSecrets } from "../../trace/redact.js";
|
|
5
|
+
import { sendJson, sendError } from "../send.js";
|
|
6
|
+
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
7
|
+
import {} from "../route-ctx.js";
|
|
8
|
+
import { assertNever } from "../route-ctx.js";
|
|
9
|
+
const LIST_LIMIT = 500;
|
|
10
|
+
export const DESCRIPTION_MAX = 500;
|
|
11
|
+
const ROW_KEY_DISPOSITION = {
|
|
12
|
+
task_id: "keep",
|
|
13
|
+
type: "keep",
|
|
14
|
+
status: "keep",
|
|
15
|
+
createdAt: "keep",
|
|
16
|
+
sessionScoped: "keep",
|
|
17
|
+
retained: "keep",
|
|
18
|
+
description: "keep",
|
|
19
|
+
toolUseId: "strip",
|
|
20
|
+
updatedAt: "strip",
|
|
21
|
+
};
|
|
22
|
+
void ROW_KEY_DISPOSITION;
|
|
23
|
+
function projectRow(r) {
|
|
24
|
+
return {
|
|
25
|
+
id: r.task_id,
|
|
26
|
+
...(r.description !== undefined ? { description: redactSecrets(r.description).slice(0, DESCRIPTION_MAX) } : {}),
|
|
27
|
+
type: r.type,
|
|
28
|
+
status: r.status,
|
|
29
|
+
createdAt: r.createdAt,
|
|
30
|
+
sessionScoped: r.sessionScoped,
|
|
31
|
+
...(r.retained === true ? { retained: true } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const StopBodySchema = z.object({ includeRetained: z.boolean() }).strict();
|
|
35
|
+
export function sessionBackgroundFacesAvailable(deps) {
|
|
36
|
+
return Boolean(deps.sessionStorage?.ownerOf);
|
|
37
|
+
}
|
|
38
|
+
export async function handleBackground(req, res, match, ctx) {
|
|
39
|
+
const { deps } = ctx;
|
|
40
|
+
const id = match.decl.id;
|
|
41
|
+
if (id === "session-background-stop" && ctx.helpers.rateLimited(req, res))
|
|
42
|
+
return;
|
|
43
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
44
|
+
if (deps.config.requirePrincipal && principal === undefined) {
|
|
45
|
+
sendPrincipalRefusal(res, req, deps.config);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let includeRetained = false;
|
|
49
|
+
if (id === "session-background-stop") {
|
|
50
|
+
const parsed = StopBodySchema.safeParse((await ctx.helpers.readJson(req)) ?? {});
|
|
51
|
+
if (!parsed.success) {
|
|
52
|
+
sendError(res, 400, "request.body_shape", "body must be { includeRetained: boolean } — required, no default: true = also stop the background processes this session was asked to keep running (explicit retain), false = keep them (they are reported as retained_skipped)");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
includeRetained = parsed.data.includeRetained;
|
|
56
|
+
}
|
|
57
|
+
const sessionId = ctx.helpers.safeDecode(match.params.sessionId);
|
|
58
|
+
if (sessionId === null) {
|
|
59
|
+
sendError(res, 400, "request.path_malformed", "session id segment is not valid percent-encoding");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!sessionBackgroundFacesAvailable(deps)) {
|
|
63
|
+
sendError(res, 501, "capability.session_ownership_required", "the session background faces require the session-ownership face (sessionStorage.ownerOf)");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const owner = await deps.sessionStorage.ownerOf(sessionId);
|
|
67
|
+
if (owner === undefined) {
|
|
68
|
+
sendError(res, 404, "not_found.session", "session not found");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const isOperator = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
72
|
+
if (!isOperator && !ctx.helpers.runOwnerOk(req, res, owner, "session"))
|
|
73
|
+
return;
|
|
74
|
+
const scope = owner ?? "default";
|
|
75
|
+
switch (id) {
|
|
76
|
+
case "session-background-list": {
|
|
77
|
+
const rows = defaultTaskRegistry.list({ owner: sessionId, sessionId, scope }, { limit: LIST_LIMIT }).map(projectRow);
|
|
78
|
+
sendJson(res, 200, { rows });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
case "session-background-stop": {
|
|
82
|
+
const { receipts } = await reapSessionBackgroundHard(sessionId, scope, { includeRetained, reason: includeRetained ? "session_background_stop_all" : "session_background_stop" }, deps.logger);
|
|
83
|
+
sendJson(res, 200, { receipts: receipts.map((r) => ({ id: r.taskId, outcome: r.outcome })) });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
return assertNever(id);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export const BACKGROUND_ROUTES = [
|
|
91
|
+
{ id: "session-background-list", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/background$/, label: "/v1/sessions/:id/background", methods: ["GET"] },
|
|
92
|
+
{ id: "session-background-stop", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/background\/stop$/, label: "/v1/sessions/:id/background/stop", credentialGated: ["POST"], methods: ["POST"] },
|
|
93
|
+
];
|
|
94
|
+
//# sourceMappingURL=background.js.map
|
|
@@ -25,6 +25,7 @@ import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
|
25
25
|
import { judgeAutoModeArming } from "../../auto-mode-face.js";
|
|
26
26
|
import { coercePermissionModeStrict, SETTINGS_PERMISSION_MODE_WORDS } from "../../task-settings.js";
|
|
27
27
|
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
28
|
+
import { sessionBackgroundFacesAvailable } from "./background.js";
|
|
28
29
|
function routePairingOf(deps, name, m) {
|
|
29
30
|
if (isSealedKeyPoison(deps.config.modelApiKeys?.[name]))
|
|
30
31
|
return "unknown";
|
|
@@ -92,6 +93,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
92
93
|
sessionBackgroundable: Boolean(deps.runStore),
|
|
93
94
|
runMemoryCaptureOptOut: Boolean(deps.runStore),
|
|
94
95
|
mcpReconnect: Boolean(deps.runStore) && Boolean(deps.sessionStorage?.ownerOf),
|
|
96
|
+
background: { listFace: sessionBackgroundFacesAvailable(deps), exitFaces: sessionBackgroundFacesAvailable(deps) },
|
|
95
97
|
sharedMemory: Boolean(deps.sharedMemoryStore && deps.orgMemoryDirectory),
|
|
96
98
|
adoption: Boolean(deps.backend?.adoptionLog) && deps.config.operatorPrincipals.length > 0,
|
|
97
99
|
memoryBundle: Boolean(deps.memoryBundleExport && deps.memoryBundleImport) && deps.config.operatorPrincipals.length > 0,
|
package/dist/http/server.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { IncomingMessage } from "node:http";
|
|
|
3
3
|
import { type Runner, type TaskSpec, type TaskResult, type WorkflowRunStore, type MemoryEntry, type MemoryExportBundle, type MemoryImportReport, type EntryProvenanceAccount, type MemoryErasureAttestation, type MemoryEntryOrigin, type OriginClearanceRow, type SessionMemoryStatus, type ConsolidationRunReceipt, type ConsolidationDriverRunRow, type SideQuerySpec, type SideQueryResult, type Brain, type EngineNotice } from "@sema-agent/core";
|
|
4
4
|
import type { TaskRequestBody } from "./wire-types.js";
|
|
5
5
|
import type { ServiceConfig } from "../config-types.js";
|
|
6
|
-
import { type
|
|
6
|
+
import { type SessionMirrorRuling } from "../config-center/facade.js";
|
|
7
|
+
import type { RestartSignal } from "@sema-agent/settings-schema/fleet";
|
|
7
8
|
import { type OwnerAwareSessionStore } from "../security.js";
|
|
8
9
|
import type { RunStore, ImageIndex, ImageBake, CheckpointStoreFull, ResumeAnchorStore, ApprovalExemptionStore, ApprovalNonceStore, ServiceSessionPolicyStore, ServiceFileHistoryStore, ToolResultStoreFull, StoreBackend } from "../plugins/store-backend.js";
|
|
9
10
|
import type { RuleConsentLane } from "../rules-consent.js";
|
|
@@ -549,7 +550,9 @@ export interface ServiceSeamDeps {
|
|
|
549
550
|
};
|
|
550
551
|
};
|
|
551
552
|
}) => void;
|
|
552
|
-
|
|
553
|
+
/** #1110(core 7.32.0):parked 赎回面工厂(按行解析驱动哪只 Agent 工具 + 同出一张列表的继承座;属主 `parked-revive-surface.ts`)。
|
|
554
|
+
* 缺席 = 这台部署没有 park 底座(checkpointStore / backgroundAgentStore 任一缺席),bg 赎回臂不接线。 */
|
|
555
|
+
parkedReviveSurface?: import("../parked-revive-surface.js").ParkedReviveSurfaceFactory;
|
|
553
556
|
/** 可选第三件:revive args 的 `subagent_type` roster 成员集([ref] Q1 成员性规则)。当前生产装配
|
|
554
557
|
* **恒缺席**(boot 内建 default 无自定义 agents roster,per-request 的 spec.agents 不进 boot 工具)
|
|
555
558
|
* = 恒省略 subagent_type,typed 子代按 general-purpose 形复活(保真度 caveat 已报 core,[ref]
|
|
@@ -562,7 +565,7 @@ export interface ServiceSeamDeps {
|
|
|
562
565
|
* `rebuildInheritedGate` 头注。 */
|
|
563
566
|
parkedReviveInheritedGate?: (row: import("@sema-agent/core").BackgroundAgentRecord, identity: {
|
|
564
567
|
principal: string | undefined;
|
|
565
|
-
}) => Promise<
|
|
568
|
+
}) => Promise<import("../boot/parked-revive-gate.js").RebuiltInheritedGate | undefined>;
|
|
566
569
|
}
|
|
567
570
|
/** **可观测面**:日志/指标/累加器。全体 fail-open——观测失败绝不改变业务面行为。 */
|
|
568
571
|
export interface ServiceObservabilityDeps {
|
|
@@ -640,7 +643,8 @@ export interface ServiceDeploymentDeps {
|
|
|
640
643
|
* respond/subagent) stay open so the runs we are waiting on can still finish/interact.
|
|
641
644
|
* • `inflight` is ASSIGNED BY createServer (a live union of this replica's in-flight legs: durable bg/resume
|
|
642
645
|
* `inflightRuns` + live sync/resume streams `steerableRuns`) — main.ts polls it to know when drain is done.
|
|
643
|
-
* • /health carries `draining:true`
|
|
646
|
+
* • /health carries `draining:true` + `version` (build self-description); draining is a readiness reason
|
|
647
|
+
* (`http/readiness.ts`) ⇒ `/readyz` 503 + `/health` `ready:false` (the k8s readiness 摘流信号).
|
|
644
648
|
* • [ref]:`attachedStreams` 同样 ASSIGNED BY createServer —— 本副本当前打开着的 SSE 流条数
|
|
645
649
|
* (= 引擎附着租约集的大小)。消费方是 `boot/engine-lease.ts` 的自退判据;与 `inflight` 同族,
|
|
646
650
|
* 同一个共享盒子,不另开第二条 main↔server 的通道。
|
|
@@ -654,21 +658,28 @@ export interface ServiceDeploymentDeps {
|
|
|
654
658
|
attachedStreams?: () => number;
|
|
655
659
|
lastAttachEndedAt?: () => number;
|
|
656
660
|
};
|
|
657
|
-
/** boot ready 门(b):false =
|
|
658
|
-
* (worker 只有占位模型)
|
|
659
|
-
*
|
|
661
|
+
/** boot ready 门(b):false = 布防(`CONFIG_REQUIRE_ROSTER=true`)且首次 effective pull 尚未落 roster
|
|
662
|
+
* (worker 只有占位模型)。就绪原因 `model_roster_pending`(`http/readiness.ts`):计费提交 503 + `/readyz` 503 +
|
|
663
|
+
* /health 加性 `ready:false`。absent = 恒 ready(未布防 / 非 registry 部署),现有部署零影响。config-center 的
|
|
664
|
+
* markRosterLanded 在落模型后翻 true(one-way latch)。 */
|
|
660
665
|
modelReady?: () => boolean;
|
|
661
666
|
/** Sema-registry restart signal: the refresh loop sets this when a pull carries restart-to-apply
|
|
662
667
|
* changes (skills/mcp/a2a/scenarios/… baked at boot — the `RestartSlice` closed set) that DIFFER from this process's boot config.
|
|
663
668
|
* Surfaced at GET /health as `restartRequired` + `restart{reasons,version,since}` so the orchestrator can
|
|
664
669
|
* auto rolling-restart the worker. Change-detected (not presence) — absent when nothing baked-at-boot
|
|
665
|
-
* changed, so polling it never causes a restart loop.
|
|
670
|
+
* changed, so polling it never causes a restart loop. S-741:the fleet heartbeat reads THIS getter too
|
|
671
|
+
* (`boot/stage-10-http-server.ts` → `restart: () => deployment.restartState?.()`), so `/health.restart` and the
|
|
672
|
+
* announcement's `restart` are the same object — one mint point, two exits. */
|
|
666
673
|
restartState?: () => RestartSignal | undefined;
|
|
667
674
|
/** [ref] 世代账([ref] §2):本副本的配置收敛状态 —— `/health` 取三键
|
|
668
675
|
* (configTargetVersion / configAppliedVersion / configApplyStaleMs),`GET /v1/diagnostics/wiring` 取整份
|
|
669
676
|
* (按组的 appliedVersion / deferredKeys+铸因 / lastRejected 消毒形)。缺席 = 这个进程没有配置管道
|
|
670
677
|
* (纯 env 部署 / 测试夹具)⇒ 两个面的形状逐字不变(additive)。 */
|
|
671
678
|
configApplyState?: () => import("../config-center/apply-ledger.js").ConfigApplyLedgerView | undefined;
|
|
679
|
+
/** S-698:中心凭证健康位(`/health.centerCredential`)—— 凭证来源的健康态 + 续签器状态(`center-credential-renewer.ts`
|
|
680
|
+
* `buildCenterCredentialHealthView` 铸形,只有闭集词 / 时刻 / 计数,不带路径与令牌)。缺席 / 返回 undefined = 健康或没有中心
|
|
681
|
+
* ⇒ `/health` 形状逐字不变(additive)。 */
|
|
682
|
+
centerCredentialState?: () => import("../center-credential-renewer.js").CenterCredentialHealthView | undefined;
|
|
672
683
|
/** [ref] 追加件:`POST /v1/admin/config/refresh` 的实现面 —— 触发**一次既有**的 refresh 拍(在飞则 join)。
|
|
673
684
|
* 缺席 / 回 `"not-wired"` = 本部署没有配置管道 ⇒ 该口诚实 4xx(绝不吞成 200)。 */
|
|
674
685
|
configRefreshNow?: () => Promise<"triggered" | "not-wired" | "failed">;
|
|
@@ -798,6 +809,8 @@ export declare function flattenServiceDeps(deps: ServiceDeps): FlatServiceDeps;
|
|
|
798
809
|
/**
|
|
799
810
|
* Managed-Agents-style HTTP/SSE service surface.
|
|
800
811
|
* GET /health
|
|
812
|
+
* GET /readyz → readiness probe: 200 {ready:true} / 503 {ready:false, reasons} (S-739; same predicate
|
|
813
|
+
* as /health.ready, the billable-submit 503s and the fleet heartbeat — http/readiness.ts)
|
|
801
814
|
* POST /v1/tasks → run to completion, TaskResult JSON (synchronous)
|
|
802
815
|
* POST /v1/tasks/stream → SSE of TaskEvent, live tokens (synchronous, same instance)
|
|
803
816
|
* POST /v1/runs → async: 202 {taskId, sessionId, status}, runs in background (S1)
|
package/dist/http/server.js
CHANGED
|
@@ -24,7 +24,7 @@ import { createRequestGuards } from "./request-guards.js";
|
|
|
24
24
|
import { createAdmission, MAX_SKILL_CONTENT_CHARS, MAX_SYSTEM_PROMPT_CHARS, MAX_OUTPUT_SCHEMA_CHARS, validateUserSkills } from "./admission.js";
|
|
25
25
|
import { createResumeLegs } from "./resume-legs.js";
|
|
26
26
|
import { createDispatch, hasServiceAuth } from "./dispatch.js";
|
|
27
|
-
import { routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath } from "./route-table.js";
|
|
27
|
+
import { routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, isProbePath } from "./route-table.js";
|
|
28
28
|
import { createApprovalSweeps } from "./approval-sweeps.js";
|
|
29
29
|
export { routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, hasServiceAuth };
|
|
30
30
|
export { MAX_SKILL_CONTENT_CHARS, MAX_SYSTEM_PROMPT_CHARS, MAX_OUTPUT_SCHEMA_CHARS, validateUserSkills };
|
|
@@ -49,7 +49,7 @@ export function createHttpServer(rawDeps) {
|
|
|
49
49
|
deps.drainState.inflight = () => new Set([...inflightRuns.keys(), ...steerableRuns.keys()]).size + counters.uncountedBillableInflight + counters.admittedInflight + (deps.leaderEndpoint?.inflight?.() ?? 0);
|
|
50
50
|
let lastBillableActivityAt = performance.now();
|
|
51
51
|
const noteActivity = (url) => {
|
|
52
|
-
if (url
|
|
52
|
+
if (isProbePath(url))
|
|
53
53
|
return;
|
|
54
54
|
lastBillableActivityAt = performance.now();
|
|
55
55
|
};
|
|
@@ -8,7 +8,7 @@ export declare const MODEL_ENTRY_REFUSAL_CODES: {
|
|
|
8
8
|
export type ModelEntryRefusalCode = (typeof MODEL_ENTRY_REFUSAL_CODES)[ModelEntryRefusalReason];
|
|
9
9
|
/** 「这张表没有缺省模型」的机读码(S-705;boot 拒启与热路径模型面组拒同一个码)。 */
|
|
10
10
|
export declare const DEFAULT_MODEL_MISSING_CODE = "config.default_model_missing";
|
|
11
|
-
/** 🔴 S-712「被拒的名字被档位 / 别名增广铸到了别的条目上(
|
|
11
|
+
/** 🔴 S-712「被拒的名字被档位 / 别名增广铸到了别的条目上(或被丢身份载体形不对、读不出名字,无从证明没有)」的机读码
|
|
12
12
|
* (不变量 `refused-names-not-minted`;热路径 / boot 首次 apply / dry-run 同一个码)。 */
|
|
13
13
|
export declare const REFUSED_NAME_MINTED_CODE = "config.model_refused_name_minted";
|
|
14
14
|
/**
|
|
@@ -47,20 +47,21 @@ export interface CatalogEntryLike {
|
|
|
47
47
|
* 坐标,不取它的输出(输出会补缺省、剥扩展键,那是读腿的事)。
|
|
48
48
|
*/
|
|
49
49
|
export declare function judgeCatalogEntry(m: CatalogEntryLike, ctx: CatalogEntryVerdictCtx): ModelEntryRefusal | undefined;
|
|
50
|
-
/** 一批已启用条目的判决:可用的(保序)、不可用的(按 catalog name)
|
|
50
|
+
/** 一批已启用条目的判决:可用的(保序)、不可用的(按 catalog name)、以及形不对的被丢身份载体行(坐标)。 */
|
|
51
51
|
export interface CatalogEntriesVerdict<M extends CatalogEntryLike> {
|
|
52
52
|
readonly usable: readonly M[];
|
|
53
53
|
readonly refused: Readonly<Record<string, ModelEntryRefusal>>;
|
|
54
|
-
/** S-712
|
|
55
|
-
*
|
|
56
|
-
|
|
54
|
+
/** S-712:被丢身份**载体形不对**的行坐标(`models.3`;载体不是列表时是一个固定标记)。读腿只为有名的被丢条目铸载体行
|
|
55
|
+
* (`read-warnings.ts` `DroppedModelIdentity`,`name` 必在),所以这一形只来自绕过读腿的输入被改过(盘上 LKG 被手改 / 手写
|
|
56
|
+
* effective)。非空 ⇒ 读不出的名字无从证明它不被增广铸出 ⇒ 不变量 `refused-names-not-minted` 不成立 ⇒ 模型面组拒(fail-closed)。 */
|
|
57
|
+
readonly malformed: readonly string[];
|
|
57
58
|
}
|
|
58
59
|
/**
|
|
59
60
|
* 批量形(模型面 stage 与 dry-run 共用这一只 —— 报数与真 apply 永不撕裂)。
|
|
60
61
|
* S-712:`dropped` = 读腿交出的被丢条目身份(`EffectiveConfig.models.droppedIdentities`)—— 它们与逐条判据判出的 `entry_invalid`
|
|
61
|
-
* 是**同一件事**(条目不合 `ModelEntry` 形)
|
|
62
|
-
* `
|
|
63
|
-
*
|
|
62
|
+
* 是**同一件事**(条目不合 `ModelEntry` 形),只是在第一道边界就被丢了;每一行铸一块墓碑(拒因 provider 词空:告警身份不带它)。
|
|
63
|
+
* 入参按 `unknown` 判形:载体可能来自盘上 LKG(本仓写的,但文件可被改)。**规则一条**:载体要么缺席,要么是一列带字符串 `name`
|
|
64
|
+
* 的行;其余一律「载体形不对」进 `malformed`(fail-closed)—— 读腿自己从不铸这种形。
|
|
64
65
|
* 与一条可用 / 已判不可用条目同名时不覆盖(可用条目仍可用;同名撞车由不变量 `refused-names-not-minted` 响亮拒)。
|
|
65
66
|
*/
|
|
66
67
|
export declare function judgeCatalogEntries<M extends CatalogEntryLike>(enabled: readonly M[], ctx: CatalogEntryVerdictCtx, dropped?: unknown): CatalogEntriesVerdict<M>;
|
|
@@ -125,6 +126,6 @@ export interface RefusedNameView {
|
|
|
125
126
|
export declare function mintedRefusedNames(view: RefusedNameView): MintedRefusedName[];
|
|
126
127
|
/** 不变量 `refused-names-not-minted` 的拒因句(`undefined` = 成立)。warn 与世代账 / 拒因同一句;逐对明细另走 {@link mintedRefusedNames}。 */
|
|
127
128
|
export declare function refusedNameMintedRefusal(view: RefusedNameView): string | undefined;
|
|
128
|
-
/**
|
|
129
|
-
export declare function
|
|
129
|
+
/** 被丢身份载体形不对的拒因句(同一不变量同一个码:读不出的名字无从证明它不被增广铸出)。 */
|
|
130
|
+
export declare function malformedDroppedCarrierRefusal(entries: readonly string[]): string;
|
|
130
131
|
//# sourceMappingURL=model-entry-refusal.d.ts.map
|