@sema-agent/server 7.119.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.
- package/MIGRATION.md +129 -0
- package/USAGE.md +72 -23
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -2
- package/deploy/sema-up/chart/templates/server.yaml +2 -2
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +1 -1
- package/dist/boot/config-center.d.ts +5 -7
- package/dist/boot/config-center.js +37 -13
- package/dist/boot/engine-lease.d.ts +1 -1
- package/dist/boot/execution-env.js +3 -2
- package/dist/boot/fatal-exit.d.ts +7 -1
- package/dist/boot/fatal-exit.js +2 -1
- package/dist/boot/resolve-spec.js +1 -0
- package/dist/boot/runner-deps.d.ts +4 -3
- package/dist/boot/runner-deps.js +7 -3
- package/dist/boot/shutdown.d.ts +9 -10
- package/dist/boot/shutdown.js +54 -16
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-09-leader.d.ts +2 -9
- package/dist/boot/stage-10-http-server.d.ts +1 -9
- package/dist/boot/stage-10-http-server.js +31 -8
- package/dist/capabilities/builtin-tools.d.ts +31 -8
- package/dist/capabilities/builtin-tools.js +22 -1
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/scenarios.d.ts +8 -6
- package/dist/capabilities/scenarios.js +3 -3
- package/dist/capabilities/send-user-file-tool.d.ts +6 -3
- package/dist/capabilities/send-user-file-tool.js +1 -1
- package/dist/config-catalog.js +13 -7
- package/dist/config-center/center-request.d.ts +16 -1
- package/dist/config-center/center-request.js +36 -5
- package/dist/config-lkg.d.ts +33 -5
- package/dist/config-lkg.js +55 -11
- package/dist/config-types.d.ts +46 -3
- package/dist/config-types.js +6 -0
- package/dist/config.d.ts +33 -4
- package/dist/config.js +107 -15
- package/dist/deployment-governance.d.ts +3 -1
- package/dist/deployment-governance.js +3 -1
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +4 -0
- package/dist/device-ws-hub.js +17 -9
- package/dist/device-ws-protocol.d.ts +15 -0
- package/dist/fleet/drain-entry.d.ts +87 -0
- package/dist/fleet/drain-entry.js +35 -0
- package/dist/fleet/engine-signal.d.ts +144 -0
- package/dist/fleet/engine-signal.js +60 -0
- package/dist/fleet/serving-lease.d.ts +47 -0
- package/dist/fleet/serving-lease.js +235 -0
- package/dist/fleet-client.d.ts +35 -19
- package/dist/fleet-client.js +49 -25
- package/dist/http/approval-sweeps.js +3 -5
- package/dist/http/dispatch.js +18 -13
- package/dist/http/health-self-report.d.ts +60 -0
- package/dist/http/health-self-report.js +53 -0
- package/dist/http/principal-gate.d.ts +8 -1
- package/dist/http/principal-gate.js +1 -0
- package/dist/http/readiness.d.ts +25 -5
- package/dist/http/readiness.js +31 -6
- package/dist/http/resume-legs.js +5 -1
- package/dist/http/route-ctx.d.ts +6 -4
- package/dist/http/route-table.d.ts +3 -0
- package/dist/http/route-table.js +4 -1
- package/dist/http/routes/a2a-serve.d.ts +1 -1
- package/dist/http/routes/a2a-serve.js +5 -5
- package/dist/http/routes/admin-drain.d.ts +12 -31
- package/dist/http/routes/admin-drain.js +15 -9
- package/dist/http/routes/approvals-assistant.d.ts +3 -3
- package/dist/http/routes/approvals-assistant.js +3 -3
- package/dist/http/routes/capabilities.js +3 -1
- package/dist/http/routes/devices.js +6 -5
- package/dist/http/routes/leader.d.ts +1 -1
- package/dist/http/routes/leader.js +1 -1
- package/dist/http/routes/memory-consolidation.d.ts +1 -1
- package/dist/http/routes/memory-consolidation.js +1 -1
- package/dist/http/routes/notify-wake.d.ts +1 -1
- package/dist/http/routes/notify-wake.js +1 -1
- package/dist/http/routes/rules.js +14 -6
- package/dist/http/routes/runs.d.ts +2 -2
- package/dist/http/routes/runs.js +2 -2
- package/dist/http/routes/side-query.d.ts +1 -1
- package/dist/http/routes/side-query.js +1 -1
- package/dist/http/routes/tasks.d.ts +2 -2
- package/dist/http/routes/tasks.js +3 -3
- package/dist/http/server.d.ts +7 -21
- package/dist/http/server.js +20 -1
- package/dist/http/wire-types.d.ts +7 -1
- package/dist/list-entry-display.d.ts +14 -0
- package/dist/list-entry-display.js +14 -0
- package/dist/model-reach-scope.d.ts +15 -3
- package/dist/model-reach-scope.js +3 -2
- package/dist/model-unreachable-notice.d.ts +2 -1
- package/dist/model-unreachable-notice.js +1 -0
- package/dist/namespaced-rule-name.js +2 -6
- package/dist/observability/fail-open.d.ts +20 -0
- package/dist/observability/fail-open.js +20 -0
- package/dist/operator-ask.d.ts +1 -1
- package/dist/plugins/one-time-migrations.d.ts +8 -0
- package/dist/plugins/one-time-migrations.js +72 -3
- package/dist/plugins/permission-rule-store-file.js +2 -6
- package/dist/plugins/permission-rule-store-sql.d.ts +65 -6
- package/dist/plugins/permission-rule-store-sql.js +166 -59
- package/dist/plugins/remote-env-device.d.ts +12 -1
- package/dist/plugins/remote-env-device.js +13 -6
- package/dist/plugins/scheduler-support.d.ts +8 -4
- package/dist/plugins/scheduler-support.js +5 -5
- package/dist/request-key-closure.js +1 -1
- package/dist/rule-entry-list.d.ts +0 -1
- package/dist/rule-entry-list.js +4 -9
- package/dist/rules-consent.d.ts +16 -3
- package/dist/rules-consent.js +8 -4
- package/dist/run-local.js +2 -0
- package/dist/runs.js +1 -1
- package/dist/sema-config-dir.d.ts +19 -0
- package/dist/sema-config-dir.js +33 -0
- package/dist/session-policy-wire.js +5 -4
- package/dist/spec-fields.d.ts +5 -4
- package/dist/task-settings.d.ts +3 -3
- package/dist/tool-approval.d.ts +7 -0
- package/dist/tool-approval.js +4 -1
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/engine-notice-wire.d.ts +6 -4
- package/dist/trace/engine-notice-wire.js +16 -9
- package/dist/trace/self-minted-wire.d.ts +78 -0
- package/dist/trace/self-minted-wire.js +32 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +56 -14
- package/package.json +3 -3
|
@@ -1,40 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* 编排壳在发停机信号(SIGTERM)**之前**先调本口显式声明停机因由;server 记入 `drainState.reason`,
|
|
4
|
-
* draining 期间由 `/health`(`drainReason`)与 503 draining 体(`reason`)透出。未声明 = 两面键缺席
|
|
5
|
-
* (additive,不编数)——升级/缩容/驱逐在消费端因此可判别,而不是一律读成「实例没了」。
|
|
2
|
+
* `POST /v1/admin/drain` —— 排空口(编排器长远稿片 2 件 2;契约体 = settings-schema 8.1.0 `DrainRequest`,回执 = `DrainReceipt`)。
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* (
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `/health` 在 `server.ts` 的鉴权门**之前**、无鉴权、缺省全网卡监听,USAGE 三处白纸黑字把它定为「无需
|
|
19
|
-
* 鉴权」—— 任何能连到这个端口的人都读得到这段**运维手打的自由文本**。
|
|
20
|
-
* **裁定:行为不改。** 判据三条:① `/health` 免鉴权是既成契约,而它正是这条信息的设计消费者(k8s
|
|
21
|
-
* probe / LB / 编排器带不了 token,`drainReason` 放这儿的全部理由就是给它们看);② 值的内容完全由
|
|
22
|
-
* operator 自选,且窗口只在 draining 期;③ 同一免鉴权体上早已有 `dataRoot` 绝对路径 / `pid` / `port` /
|
|
23
|
-
* `instanceId` / `configHash` / `storeProbe`,单独收紧本键不改变那面的暴露量级。
|
|
24
|
-
* (那条另立的兄弟件 —— `storeProbe.error` 把**原始异常 message 未截断**塞出去、DSN 片段可随之外泄 ——
|
|
25
|
-
* 已在 [ref] 修掉:免鉴权面上它改成闭集词,原文只走日志轴。两件的判据不同故不合并:本键的值由 operator
|
|
26
|
-
* 自选且窗口只在 draining 期,`storeProbe.error` 的值则来自不受控的驱动异常。)
|
|
27
|
-
* ⇒ 真正的缺口是**告诫缺席**:此前路由头注、400 文案、CHANGELOG、wire 契约四处都只说「透出」,没有一处
|
|
28
|
-
* 说「其中一面免鉴权」,操作员要拼出「我写的字是公开的」得自己合并两处文档去推理。本批把它写进**写口
|
|
29
|
-
* 现场**(400 文案 + 本注 + USAGE 端点条目),因为那是操作员唯一会读到的地方。
|
|
30
|
-
* 词表化(把自由文本换成受限枚举)= 行为变更,**不在本批**:它会让「rolling upgrade to 7.36.0」这类真正
|
|
31
|
-
* 有用的因由说不出来,取舍要属主拍。
|
|
4
|
+
* 一只入口的 HTTP 车道(Docker 车道 / 中心直调 / 人手形):体经契约读器 `readDrainRequest`(一处判形),身份四元经
|
|
5
|
+
* `fleet/drain-entry.ts` 的 `drainIdentityMismatches`(一处判身份;本引擎的 scope = `config.fleetScope`,env `FLEET_SCOPE` 的唯一读数),
|
|
6
|
+
* 进入排空经 `drainState.enter`(收尾链装的唯一入口)。
|
|
7
|
+
* · 门序 = operator 面的既有口径(`routes/retention-ops.ts` 逐字):身份 → 授权 → 验型;billable=false,**刻意不吃排空 503**
|
|
8
|
+
* (排空中的重放必须能拿到 `already_draining` 回执,吃了就自锁)。
|
|
9
|
+
* · 体坏 ⇒ 400 `request.field_invalid`(零状态变化);身份不符 ⇒ 409 `conflict.fleet_identity`(零状态变化;拒句点名不符的字段,不回显值);
|
|
10
|
+
* 没有收尾链(嵌入宿主 / 测试夹具)⇒ 501 `capability.drain_state_required`。
|
|
11
|
+
* · 首次 ⇒ 200 回执 `state:"draining"`;同一 `actionId`(或别的动作)再来 ⇒ 200 `already_draining`,回显第一次的 `since` / `deadlineAt`,不重置。
|
|
12
|
+
* · 人手形:`actionId` 自取(推荐 `manual:<uuid>`),身份照样四元比对 —— 非 fleet 成员不带 `expectedGeneration`(缺席对缺席);
|
|
13
|
+
* `instanceId` 在场才比(从本机 `/health` 读);不带 `scope` = 点名缺省 scope(多租引擎要带它自己的 `FLEET_SCOPE`)。
|
|
14
|
+
* 修前形(7.35.0 起的 `{reason}` 停机因由声明:只记一句话、不进排空)随契约体退役 —— 零别名;那一形的请求体读不成 `DrainRequest` ⇒ 400。
|
|
32
15
|
*/
|
|
33
16
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
34
17
|
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
35
18
|
export declare const ADMIN_DRAIN_PATH = "/v1/admin/drain";
|
|
36
|
-
/** 上限与 approvals 的 reason 族同量级(消费面同为人读短句;非同一常量——两面语义无关,独立演化)。 */
|
|
37
|
-
export declare const MAX_DRAIN_REASON_CHARS = 256;
|
|
38
19
|
export declare function handleAdminDrain(req: IncomingMessage, res: ServerResponse, match: RouteMatch<AdminDrainRouteId>, ctx: RouteCtx): Promise<void>;
|
|
39
20
|
export declare const ADMIN_DRAIN_ROUTES: readonly [{
|
|
40
21
|
readonly id: "admin-drain";
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { readDrainRequest } from "@sema-agent/settings-schema/fleet";
|
|
1
2
|
import { sendJson, sendError } from "../send.js";
|
|
2
3
|
import { gatedPrincipal, explicitOperatorOk, sendOperatorPrincipalsRequired, sendPrincipalRefusal } from "../principal-gate.js";
|
|
3
4
|
import * as facts from "../../capability-facts.js";
|
|
5
|
+
import { fleetScopeOf } from "../../config-types.js";
|
|
6
|
+
import { drainIdentityMismatches } from "../../fleet/drain-entry.js";
|
|
4
7
|
import { assertNever } from "../route-ctx.js";
|
|
5
8
|
export const ADMIN_DRAIN_PATH = "/v1/admin/drain";
|
|
6
|
-
export const MAX_DRAIN_REASON_CHARS = 256;
|
|
7
9
|
const OPERATOR_ONLY_MESSAGE = "drain declaration is operator-only: the caller principal is not in OPERATOR_PRINCIPALS";
|
|
8
10
|
export async function handleAdminDrain(req, res, match, ctx) {
|
|
9
11
|
const { deps } = ctx;
|
|
@@ -22,18 +24,22 @@ export async function handleAdminDrain(req, res, match, ctx) {
|
|
|
22
24
|
sendError(res, 403, "auth.operator_only", OPERATOR_ONLY_MESSAGE);
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
sendError(res, 400, "request.field_invalid", `the drain reason is surfaced verbatim on the UNAUTHENTICATED /health endpoint (and in 503 draining bodies), so anyone who can reach this port can read it — do not put ticket ids, hostnames, or internal identifiers in it. body must be { reason: string }: a non-empty string of at most ${MAX_DRAIN_REASON_CHARS} characters`);
|
|
27
|
+
const request = readDrainRequest(await ctx.helpers.readJson(req));
|
|
28
|
+
if (request === undefined) {
|
|
29
|
+
sendError(res, 400, "request.field_invalid", "the drain request must be { actionId, worker, deadlineAt, scope?, expectedGeneration?, instanceId?, term? }: actionId a non-empty idempotency key, worker this engine's worker name, deadlineAt epoch milliseconds, expectedGeneration a canonical decimal string (\"7\") — nothing was changed");
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const enter = deps.drainState?.enter;
|
|
33
|
+
if (enter === undefined) {
|
|
34
|
+
sendError(res, 501, "capability.drain_state_required", "this deployment has no drain entry wired (embedded in another process?) — there is no drain for this request to enter");
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
|
-
deps.
|
|
36
|
-
|
|
37
|
+
const mismatched = drainIdentityMismatches(request, { scope: deps.config.fleetScope, worker: deps.config.configCenter?.worker, generation: deps.config.fleetGeneration, instanceId: deps.instanceId });
|
|
38
|
+
if (mismatched.length > 0) {
|
|
39
|
+
sendError(res, 409, "conflict.fleet_identity", `the drain request names a different instance than this engine (mismatched: ${mismatched.join(", ")}) — nothing was changed; compare with this engine's GET /health (worker comes from SEMA_REGISTRY_WORKER, generation from FLEET_GENERATION, scope from FLEET_SCOPE — unset, and a request without scope, mean "${fleetScopeOf(undefined)}")`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
sendJson(res, 200, enter({ actionId: request.actionId, deadlineAt: request.deadlineAt }));
|
|
37
43
|
return;
|
|
38
44
|
}
|
|
39
45
|
default:
|
|
@@ -183,17 +183,17 @@ export declare const APPROVALS_ASSISTANT_ROUTES: readonly [{
|
|
|
183
183
|
readonly id: "assistant-task-resume";
|
|
184
184
|
readonly pattern: RegExp;
|
|
185
185
|
readonly label: "/v1/assistant/tasks/:id/resume";
|
|
186
|
-
readonly billable:
|
|
186
|
+
readonly billable: "resume";
|
|
187
187
|
readonly methods: readonly ["POST"];
|
|
188
188
|
}, {
|
|
189
189
|
readonly id: "assistant-task-plan-review";
|
|
190
190
|
readonly pattern: RegExp;
|
|
191
191
|
readonly label: "/v1/assistant/tasks/:id/plan_review";
|
|
192
|
-
readonly billable:
|
|
192
|
+
readonly billable: "hitl-answer";
|
|
193
193
|
readonly methods: readonly ["POST"];
|
|
194
194
|
}, {
|
|
195
195
|
readonly prefix: "/v1/approvals";
|
|
196
|
-
readonly billable:
|
|
196
|
+
readonly billable: "hitl-answer";
|
|
197
197
|
}];
|
|
198
198
|
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
199
199
|
export type ApprovalsAssistantRouteId = RouteIdsOf<typeof APPROVALS_ASSISTANT_ROUTES>;
|
|
@@ -917,9 +917,9 @@ export const APPROVALS_ASSISTANT_ROUTES = [
|
|
|
917
917
|
{ id: "approval-exemptions-list", pattern: /^\/v1\/approvals\/(?<sessionId>[^/]+)\/exemptions(?:\/(?<tool>[^/]+))?$/, label: "/v1/approvals/:id/exemptions", methods: ["GET"] },
|
|
918
918
|
{ id: "approval-exemption-delete", pattern: /^\/v1\/approvals\/(?<sessionId>[^/]+)\/exemptions(?:\/(?<tool>[^/]+))?$/, label: "/v1/approvals/:id/exemptions", methods: ["DELETE"] },
|
|
919
919
|
{ id: "assistant-task-preempt", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/preempt$/, label: "/v1/assistant/tasks/:id/preempt", methods: ["POST"] },
|
|
920
|
-
{ id: "assistant-task-resume", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/resume$/, label: "/v1/assistant/tasks/:id/resume", billable:
|
|
921
|
-
{ id: "assistant-task-plan-review", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/plan_review$/, label: "/v1/assistant/tasks/:id/plan_review", billable:
|
|
922
|
-
{ prefix: "/v1/approvals", billable:
|
|
920
|
+
{ id: "assistant-task-resume", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/resume$/, label: "/v1/assistant/tasks/:id/resume", billable: "resume", methods: ["POST"] },
|
|
921
|
+
{ id: "assistant-task-plan-review", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/plan_review$/, label: "/v1/assistant/tasks/:id/plan_review", billable: "hitl-answer", methods: ["POST"] },
|
|
922
|
+
{ prefix: "/v1/approvals", billable: "hitl-answer" },
|
|
923
923
|
];
|
|
924
924
|
const DECISION_DOOR_KEY_TABLE = deriveDecisionDoorKeyTable(DECISION_DOOR_FAMILY, APPROVALS_ASSISTANT_ROUTES);
|
|
925
925
|
//# sourceMappingURL=approvals-assistant.js.map
|
|
@@ -79,7 +79,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
79
79
|
: false,
|
|
80
80
|
sessionEvents: facts.sessionEventStream(deps).available,
|
|
81
81
|
fleet: facts.fleetStream(deps).available
|
|
82
|
-
? { stream: true, sessionScope: true, observe: true, resume: "snapshot", bgNotifyFailClosed: true, steerPriority: "engine", hookFailureNotice: true }
|
|
82
|
+
? { stream: true, sessionScope: true, observe: true, resume: "snapshot", bgNotifyFailClosed: true, steerPriority: "engine", hookFailureNotice: true, leaseClient: true, generationEcho: true }
|
|
83
83
|
: false,
|
|
84
84
|
workspace: facts.workspaceBrowse().available,
|
|
85
85
|
leader: facts.leaderLane(deps).available,
|
|
@@ -130,6 +130,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
130
130
|
readFace: deps.config.readFace ?? null,
|
|
131
131
|
writeProtection: projectWriteProtectionCapability(deps.writeProtection),
|
|
132
132
|
appendSystemPrompt: true,
|
|
133
|
+
outputStyle: true,
|
|
133
134
|
compactionModel: true,
|
|
134
135
|
subagentOutput: facts.subagentOutput(deps).available,
|
|
135
136
|
subagentStream: facts.subagentOutput(deps).available,
|
|
@@ -161,6 +162,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
161
162
|
approvalDecisionNote: true,
|
|
162
163
|
approvalDecidedBy: true,
|
|
163
164
|
permissionModes: [...PERMISSION_MODES],
|
|
165
|
+
sessionReadRedaction: true,
|
|
164
166
|
sideQuery: true,
|
|
165
167
|
agentRoster: facts.agentRoster(deps).available,
|
|
166
168
|
retention: retentionAdvertisement(deps.config),
|
|
@@ -2,12 +2,13 @@ import { sendError, sendJson } from "../send.js";
|
|
|
2
2
|
import { explicitOperatorOk, gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
3
3
|
import { assertNever } from "../route-ctx.js";
|
|
4
4
|
import * as facts from "../../capability-facts.js";
|
|
5
|
-
import { DEVICE_PLATFORM_OS, DEVICE_PUBKEY_MAX_BYTES, DEVICE_TEXT_LIMITS, assertDeviceIdShape, assertDevicePubkeyShape, assertRootSessionIdShape, sameDeviceOwner, } from "../../device-store.js";
|
|
5
|
+
import { DEVICE_PLATFORM_OS, DEVICE_PUBKEY_MAX_BYTES, DEVICE_TEXT_LIMITS, assertDeviceIdShape, assertDevicePubkeyShape, assertRootSessionIdShape, isDeviceAbsolutePath, sameDeviceOwner, } from "../../device-store.js";
|
|
6
6
|
import { DEFAULT_ENROLL_TOKEN_TTL_SEC } from "../../device-enrollment.js";
|
|
7
7
|
import { deviceOwnerDomainOf } from "../device-placement.js";
|
|
8
8
|
import { echoKeyName } from "../../request-key-closure.js";
|
|
9
9
|
const DEVICE_NOT_FOUND_MESSAGE = "device not found";
|
|
10
10
|
const NO_DEVICE_DOMAIN_MESSAGE = "device management needs a caller domain: this credential carries no verified tenant scope claim (registry auth-bridge JWT `scope`) and is not an explicit operator — connect through the registry auth bridge, or configure OPERATOR_PRINCIPALS";
|
|
11
|
+
const NO_ENROLL_DOMAIN_MESSAGE = "device enrollment needs a registry login that carries a tenant scope: this credential carries no verified tenant scope claim (registry auth-bridge JWT `scope`), and an operator principal carries no scope, so it cannot enroll a device or issue an enroll token — connect through the registry auth bridge with a tenant-scoped login";
|
|
11
12
|
const DEVICE_LANE_REQUIRED_MESSAGE = "device management requires the device execution lane (REMOTE_EXEC=device with a SQL store backend) — this deployment has no device lane wired";
|
|
12
13
|
const _deviceRouteCodesPinned = ["not_found.device", "device.rebind_rev_conflict", "device.rebind_active_run"];
|
|
13
14
|
void _deviceRouteCodesPinned;
|
|
@@ -109,8 +110,8 @@ function parseEnrollBody(res, body) {
|
|
|
109
110
|
return null;
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
|
-
if (workspaceRoot
|
|
113
|
-
sendError(res, 400, "request.field_invalid", "workspaceRoot
|
|
113
|
+
if (!isDeviceAbsolutePath(workspaceRoot)) {
|
|
114
|
+
sendError(res, 400, "request.field_invalid", "workspaceRoot must be an absolute POSIX path (it is the device-side root every instruction resolves against)");
|
|
114
115
|
return null;
|
|
115
116
|
}
|
|
116
117
|
return { token: token, pubkey: pubkey, platformOs: os, platformArch: platformArch, displayName: displayName, workspaceRoot: workspaceRoot };
|
|
@@ -164,7 +165,7 @@ export async function handleDevices(req, res, match, ctx) {
|
|
|
164
165
|
}
|
|
165
166
|
if (match.decl.id === "device-enroll-tokens") {
|
|
166
167
|
if (domain === undefined) {
|
|
167
|
-
sendError(res, 403, "auth.forbidden",
|
|
168
|
+
sendError(res, 403, "auth.forbidden", NO_ENROLL_DOMAIN_MESSAGE);
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
if ((await readClosedBody(req, res, readJson, [])) === null)
|
|
@@ -180,7 +181,7 @@ export async function handleDevices(req, res, match, ctx) {
|
|
|
180
181
|
}
|
|
181
182
|
if (match.decl.id === "device-enroll") {
|
|
182
183
|
if (domain === undefined) {
|
|
183
|
-
sendError(res, 403, "auth.forbidden",
|
|
184
|
+
sendError(res, 403, "auth.forbidden", NO_ENROLL_DOMAIN_MESSAGE);
|
|
184
185
|
return;
|
|
185
186
|
}
|
|
186
187
|
const raw = await readClosedBody(req, res, readJson, ENROLL_BODY_KEYS);
|
|
@@ -16,7 +16,7 @@ export declare function handleLeader(req: IncomingMessage, res: ServerResponse,
|
|
|
16
16
|
export declare const LEADER_ROUTES: readonly [{
|
|
17
17
|
readonly id: "leader-submit";
|
|
18
18
|
readonly path: "/v1/leader";
|
|
19
|
-
readonly billable:
|
|
19
|
+
readonly billable: "submit";
|
|
20
20
|
readonly methods: readonly ["POST"];
|
|
21
21
|
}, {
|
|
22
22
|
readonly id: "leader-detail";
|
|
@@ -51,7 +51,7 @@ export async function handleLeader(req, res, match, ctx) {
|
|
|
51
51
|
throw new Error(`leader endpoint did not claim its own declared route: ${match.decl.id}`);
|
|
52
52
|
}
|
|
53
53
|
export const LEADER_ROUTES = [
|
|
54
|
-
{ id: "leader-submit", path: LEADER_SUBMIT_PATH, billable:
|
|
54
|
+
{ id: "leader-submit", path: LEADER_SUBMIT_PATH, billable: "submit", methods: ["POST"] },
|
|
55
55
|
{ id: "leader-detail", pattern: /^\/v1\/leader\/(?<id>[^/]+)$/, label: "/v1/leader/:id", methods: ["GET"] },
|
|
56
56
|
];
|
|
57
57
|
//# sourceMappingURL=leader.js.map
|
|
@@ -58,7 +58,7 @@ export declare function handleMemoryConsolidation(req: IncomingMessage, res: Ser
|
|
|
58
58
|
export declare const MEMORY_CONSOLIDATION_ROUTES: readonly [{
|
|
59
59
|
readonly id: "memory-consolidation-run";
|
|
60
60
|
readonly path: "/v1/admin/memory/consolidation/run";
|
|
61
|
-
readonly billable:
|
|
61
|
+
readonly billable: "submit";
|
|
62
62
|
readonly methods: readonly ["POST"];
|
|
63
63
|
}, {
|
|
64
64
|
readonly id: "memory-consolidation-status";
|
|
@@ -163,7 +163,7 @@ export async function handleMemoryConsolidation(req, res, match, ctx) {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
export const MEMORY_CONSOLIDATION_ROUTES = [
|
|
166
|
-
{ id: "memory-consolidation-run", path: MEMORY_CONSOLIDATION_RUN_PATH, billable:
|
|
166
|
+
{ id: "memory-consolidation-run", path: MEMORY_CONSOLIDATION_RUN_PATH, billable: "submit", methods: ["POST"] },
|
|
167
167
|
{ id: "memory-consolidation-status", path: MEMORY_CONSOLIDATION_STATUS_PATH, methods: ["GET"] },
|
|
168
168
|
];
|
|
169
169
|
//# sourceMappingURL=memory-consolidation.js.map
|
|
@@ -22,7 +22,7 @@ export declare const NOTIFY_WAKE_ROUTES: readonly [{
|
|
|
22
22
|
readonly id: "session-wake";
|
|
23
23
|
readonly pattern: RegExp;
|
|
24
24
|
readonly label: "/v1/sessions/:id/$verb";
|
|
25
|
-
readonly billable:
|
|
25
|
+
readonly billable: "submit";
|
|
26
26
|
readonly methods: readonly ["POST"];
|
|
27
27
|
}, {
|
|
28
28
|
readonly id: "session-notify";
|
|
@@ -143,7 +143,7 @@ export async function handleNotifyWake(req, res, match, ctx) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
export const NOTIFY_WAKE_ROUTES = [
|
|
146
|
-
{ id: "session-wake", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/wake$/, label: "/v1/sessions/:id/$verb", billable:
|
|
146
|
+
{ id: "session-wake", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/wake$/, label: "/v1/sessions/:id/$verb", billable: "submit", methods: ["POST"] },
|
|
147
147
|
{ id: "session-notify", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/notify$/, label: "/v1/sessions/:id/$verb", methods: ["POST"] },
|
|
148
148
|
];
|
|
149
149
|
//# sourceMappingURL=notify-wake.js.map
|
|
@@ -4,10 +4,10 @@ import { gatedPrincipal, explicitOperatorOk, sendOperatorPrincipalsRequired, sen
|
|
|
4
4
|
import { assertNever } from "../route-ctx.js";
|
|
5
5
|
import * as facts from "../../capability-facts.js";
|
|
6
6
|
import { createLogger } from "../../observability/logger.js";
|
|
7
|
-
import { RULE_IMPORT_RETRY_AFTER_SEC, MAX_RULE_SCOPE_CHARS, parseRuleScope, serializeRuleScope } from "../../rules-consent.js";
|
|
7
|
+
import { RULE_IMPORT_RETRY_AFTER_SEC, RULE_SCOPE_REFUSAL_SENTENCE, MAX_RULE_SCOPE_CHARS, buildCcImportLayerRootRefusal, parseRuleScope, serializeRuleScope } from "../../rules-consent.js";
|
|
8
8
|
import { CONTROL_AND_BIDI_CHARS } from "../../text-bidi.js";
|
|
9
9
|
import { HttpError } from "../../security.js";
|
|
10
|
-
import { MAX_RULE_TEXT_CHARS, RULE_BEHAVIORS } from "@sema-agent/core";
|
|
10
|
+
import { MAX_RULE_TEXT_CHARS, RULE_BEHAVIORS, isValidDurableScope } from "@sema-agent/core";
|
|
11
11
|
import { PERSISTED_RULE_MATCHES, isPersistedRuleTool, isDirectWritableRuleBehavior } from "../../permission-rule-vocab.js";
|
|
12
12
|
const logger = createLogger();
|
|
13
13
|
export const RULES_CC_IMPORT_PREPARE_PATH = "/v1/rules/cc-import/prepare";
|
|
@@ -26,7 +26,7 @@ const CcImportPrepareBodySchema = z
|
|
|
26
26
|
.object({
|
|
27
27
|
layer: z.enum(["userSettings", "projectSettings", "localSettings"]),
|
|
28
28
|
path: z.string().min(1).max(1024),
|
|
29
|
-
root: z.string().
|
|
29
|
+
root: z.string().max(1024),
|
|
30
30
|
content: z.string().max(MAX_LAYER_CONTENT_CHARS),
|
|
31
31
|
})
|
|
32
32
|
.strict())
|
|
@@ -34,6 +34,9 @@ const CcImportPrepareBodySchema = z
|
|
|
34
34
|
.max(MAX_LAYERS),
|
|
35
35
|
})
|
|
36
36
|
.strict();
|
|
37
|
+
function ccImportRootRefused(layer) {
|
|
38
|
+
return !isValidDurableScope({ kind: "project", root: layer.root });
|
|
39
|
+
}
|
|
37
40
|
const TICKET_CONTROL_CHARS_RE = new RegExp(`[${CONTROL_AND_BIDI_CHARS}]`);
|
|
38
41
|
const TicketStringSchema = z.string().min(1).max(190).refine((t) => !TICKET_CONTROL_CHARS_RE.test(t));
|
|
39
42
|
const CcImportRedeemBodySchema = z.object({ ticket: TicketStringSchema }).strict();
|
|
@@ -170,6 +173,11 @@ export async function handleRules(req, res, match, ctx) {
|
|
|
170
173
|
sendError(res, 400, "request.body_shape", "body must be {layers:[{layer,path,root,content}]}");
|
|
171
174
|
return;
|
|
172
175
|
}
|
|
176
|
+
const refusedLayer = parsed.data.layers.findIndex(ccImportRootRefused);
|
|
177
|
+
if (refusedLayer >= 0) {
|
|
178
|
+
sendError(res, 400, "request.body_shape", buildCcImportLayerRootRefusal(refusedLayer));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
173
181
|
const prepared = await lane.prepareImport(principal, parsed.data.layers);
|
|
174
182
|
if (!prepared.ok) {
|
|
175
183
|
sendError(res, 413, "request.payload_too_large", "too many allow entries across the submitted settings layers");
|
|
@@ -231,7 +239,7 @@ async function handleRuleList(res, search, caller, lane, config) {
|
|
|
231
239
|
const scopeRaw = q.get("scope");
|
|
232
240
|
const scopeFilter = scopeRaw === null ? undefined : parseRuleScope(scopeRaw);
|
|
233
241
|
if (scopeRaw !== null && scopeFilter === undefined) {
|
|
234
|
-
sendError(res, 400, "request.query_invalid",
|
|
242
|
+
sendError(res, 400, "request.query_invalid", RULE_SCOPE_REFUSAL_SENTENCE);
|
|
235
243
|
return;
|
|
236
244
|
}
|
|
237
245
|
const cursorRaw = q.get("cursor");
|
|
@@ -305,7 +313,7 @@ async function handleRuleWrite(res, rawBody, caller, lane, config) {
|
|
|
305
313
|
}
|
|
306
314
|
const scope = parseRuleScope(parsed.data.scope);
|
|
307
315
|
if (scope === undefined) {
|
|
308
|
-
sendError(res, 400, "request.body_shape",
|
|
316
|
+
sendError(res, 400, "request.body_shape", RULE_SCOPE_REFUSAL_SENTENCE);
|
|
309
317
|
return;
|
|
310
318
|
}
|
|
311
319
|
const target = resolveTargetPrincipal(parsed.data.principal, caller, config);
|
|
@@ -354,7 +362,7 @@ async function handleRuleRevoke(res, rawBody, caller, lane, config) {
|
|
|
354
362
|
}
|
|
355
363
|
const scope = parseRuleScope(parsed.data.scope);
|
|
356
364
|
if (scope === undefined) {
|
|
357
|
-
sendError(res, 400, "request.body_shape",
|
|
365
|
+
sendError(res, 400, "request.body_shape", RULE_SCOPE_REFUSAL_SENTENCE);
|
|
358
366
|
return;
|
|
359
367
|
}
|
|
360
368
|
const target = resolveTargetPrincipal(parsed.data.principal, caller, config);
|
|
@@ -40,7 +40,7 @@ idemKey: string | undefined): Promise<{
|
|
|
40
40
|
export declare const RUNS_ROUTES: readonly [{
|
|
41
41
|
readonly id: "runs-submit";
|
|
42
42
|
readonly path: "/v1/runs";
|
|
43
|
-
readonly billable:
|
|
43
|
+
readonly billable: "submit";
|
|
44
44
|
readonly methods: readonly ["POST"];
|
|
45
45
|
}, {
|
|
46
46
|
readonly id: "run-steer";
|
|
@@ -99,7 +99,7 @@ export declare const RUN_VERBS_ROUTES: readonly [{
|
|
|
99
99
|
readonly id: "run-subagent-resume";
|
|
100
100
|
readonly pattern: RegExp;
|
|
101
101
|
readonly label: "/v1/runs/:id/subagents/:sub/*";
|
|
102
|
-
readonly billable:
|
|
102
|
+
readonly billable: "resume";
|
|
103
103
|
readonly methods: readonly ["POST"];
|
|
104
104
|
}, {
|
|
105
105
|
readonly id: "run-task-output";
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
905
|
-
{ id: "tasks-submit-stream", path: "/v1/tasks/stream", billable:
|
|
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
|
package/dist/http/server.d.ts
CHANGED
|
@@ -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
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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?}` 恒在
|
package/dist/http/server.js
CHANGED
|
@@ -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
|
|
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
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **机读位的值** —— 通告 `detail` 里的规则文本(`permission.allowed_tool_not_granted` 的 `detail.entries[].entry`、`permission.rule_row_dropped` 的
|
|
3
|
+
* `detail.rows[].rule`)在会话流行与运维日志行上一律是这一只:条目的脱敏原值。显示形不进机读位(壳若再加引号就是两层)。
|
|
4
|
+
*/
|
|
5
|
+
export declare function redactListEntry(entry: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* **人读句里条目的唯一写法** —— 本仓把规则条目 / 工具名原串写进任何一句给人读的话(拒句、起服拒启行)一律调这一只:引擎的条目写者
|
|
8
|
+
* `renderListEntry`(core 7.46.0 #1428 包根导出:JSON 串形带引号,看不见 / 印成空白的字符写成 `\uXXXX`,超长截断)—— 与引擎自己的通告句同一个渲染;
|
|
9
|
+
* 判定拿原串。
|
|
10
|
+
* 🔴 凭据先认、再渲染:渲染把看不见的字符写成 ASCII 转义,之后的脱敏器就认不出夹着零宽字符的凭据 ⇒ 原串上认出凭据时渲染它的脱敏值;
|
|
11
|
+
* 没有凭据的条目渲染**原串**(格式字符留给渲染器写成 `\uXXXX` —— 人要看得见它,不能先被脱敏器剥掉)。
|
|
12
|
+
*/
|
|
13
|
+
export declare function displayListEntry(entry: string): string;
|
|
14
|
+
//# sourceMappingURL=list-entry-display.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { renderListEntry } from "@sema-agent/core";
|
|
2
|
+
import { redactSecretsWithArms } from "./trace/redact.js";
|
|
3
|
+
function scanListEntry(entry) {
|
|
4
|
+
const scan = redactSecretsWithArms(entry);
|
|
5
|
+
return { redacted: scan.text, credential: scan.arms.size > 0 };
|
|
6
|
+
}
|
|
7
|
+
export function redactListEntry(entry) {
|
|
8
|
+
return scanListEntry(entry).redacted;
|
|
9
|
+
}
|
|
10
|
+
export function displayListEntry(entry) {
|
|
11
|
+
const scan = scanListEntry(entry);
|
|
12
|
+
return renderListEntry(scan.credential ? scan.redacted : entry);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=list-entry-display.js.map
|