@sema-agent/server 7.121.0 → 7.123.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 +175 -0
- package/README.md +6 -1
- package/README.zh-CN.md +6 -1
- package/USAGE.md +57 -18
- package/dist/approval-reconciler.d.ts +8 -0
- package/dist/approval-reconciler.js +72 -52
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +52 -22
- package/dist/auto-mode-face.d.ts +16 -5
- package/dist/auto-mode-face.js +7 -4
- package/dist/bench/s1/live-deps.js +1 -0
- package/dist/boot/config-center.js +3 -0
- package/dist/boot/leader.js +1 -0
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +2 -1
- package/dist/boot/resolve-spec.js +1 -4
- package/dist/boot/shutdown.js +6 -1
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/capabilities/memory-notice.d.ts +1 -1
- package/dist/config-catalog.js +4 -4
- package/dist/config-center/apply-effective.d.ts +1 -1
- package/dist/config-center/apply-effective.js +5 -3
- package/dist/config-center/center-request.d.ts +6 -9
- package/dist/config-center/center-request.js +11 -27
- package/dist/config-center/http-client.js +3 -3
- package/dist/config-center/read-warnings.d.ts +56 -35
- package/dist/config-center/read-warnings.js +67 -19
- package/dist/config-center/types.d.ts +7 -8
- package/dist/config-lkg.d.ts +12 -9
- package/dist/config-lkg.js +38 -39
- package/dist/config-provider.d.ts +3 -2
- package/dist/config-provider.js +2 -2
- package/dist/config-types.d.ts +20 -9
- package/dist/config-types.js +1 -1
- package/dist/config.d.ts +10 -10
- package/dist/config.js +92 -127
- package/dist/fleet/drain-entry.d.ts +28 -3
- package/dist/fleet/drain-entry.js +3 -0
- package/dist/fleet/engine-signal.d.ts +54 -23
- package/dist/fleet/engine-signal.js +29 -5
- package/dist/fleet/serving-lease.js +13 -15
- package/dist/fleet-client.d.ts +8 -6
- package/dist/fleet-client.js +4 -3
- package/dist/governance-overrides.d.ts +7 -2
- package/dist/http/admission.js +7 -5
- package/dist/http/approval-sweeps.js +2 -2
- package/dist/http/checkpoint-ctx.d.ts +60 -11
- package/dist/http/checkpoint-ctx.js +23 -9
- package/dist/http/dispatch.js +22 -19
- package/dist/http/health-self-report.d.ts +3 -0
- package/dist/http/resume-legs.js +8 -8
- package/dist/http/route-ctx.d.ts +19 -3
- package/dist/http/route-table.d.ts +3 -0
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/admin-drain.d.ts +5 -2
- package/dist/http/routes/admin-drain.js +14 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -0
- package/dist/http/routes/approvals-assistant.js +6 -6
- package/dist/http/routes/capabilities.js +7 -5
- package/dist/http/routes/fleet.d.ts +1 -0
- package/dist/http/routes/fleet.js +1 -1
- package/dist/http/routes/images.d.ts +1 -0
- package/dist/http/routes/images.js +1 -1
- package/dist/http/routes/leader.js +6 -0
- package/dist/http/routes/runs.d.ts +2 -0
- package/dist/http/routes/runs.js +5 -4
- package/dist/http/routes/sessions.d.ts +1 -0
- package/dist/http/routes/sessions.js +2 -5
- package/dist/http/routes/trace-usage.d.ts +1 -0
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/routes/workflows.d.ts +1 -0
- package/dist/http/routes/workflows.js +1 -1
- package/dist/http/send.d.ts +13 -0
- package/dist/http/send.js +9 -0
- package/dist/http/server.d.ts +3 -0
- package/dist/http/stored-body-replay.d.ts +28 -0
- package/dist/http/stored-body-replay.js +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/leader.d.ts +23 -1
- package/dist/leader/leader.js +6 -1
- package/dist/leader/repair-wire.d.ts +1 -1
- package/dist/leader/repair-wire.js +2 -2
- package/dist/leader/run-entry.d.ts +27 -0
- package/dist/leader/run-entry.js +29 -0
- package/dist/leader/wire.d.ts +10 -0
- package/dist/leader/wire.js +15 -10
- package/dist/model-entry-refusal.d.ts +18 -9
- package/dist/model-entry-refusal.js +19 -14
- package/dist/model-route-endpoint.d.ts +21 -1
- package/dist/model-route-endpoint.js +9 -3
- package/dist/model-route-gap.d.ts +103 -0
- package/dist/model-route-gap.js +80 -0
- package/dist/model-route-url.d.ts +13 -20
- package/dist/model-route-url.js +28 -24
- package/dist/namespaced-rule-name.d.ts +16 -30
- package/dist/namespaced-rule-name.js +2 -20
- package/dist/observability/fail-open.d.ts +23 -10
- package/dist/observability/fail-open.js +8 -8
- package/dist/plugins/approval-ask-store-file.d.ts +1 -0
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +1 -0
- package/dist/plugins/approval-ask-store-memory.js +6 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +12 -0
- package/dist/plugins/approval-ask-store-sql.js +4 -0
- package/dist/plugins/one-time-migrations.js +40 -0
- package/dist/plugins/park-commit-watch.d.ts +19 -5
- package/dist/plugins/park-commit-watch.js +24 -17
- package/dist/plugins/session-policy-store-sql.js +1 -2
- package/dist/plugins/workflow-run-store-sql.d.ts +4 -2
- package/dist/router/route-orchestration.d.ts +4 -2
- package/dist/router/route-orchestration.js +2 -1
- package/dist/rule-entry-list.d.ts +3 -13
- package/dist/rule-entry-list.js +1 -12
- package/dist/run-local.js +4 -2
- package/dist/runtime-governance.d.ts +67 -32
- package/dist/runtime-governance.js +28 -9
- package/dist/session-policy-wire.d.ts +47 -49
- package/dist/session-policy-wire.js +41 -36
- package/dist/session-sync.d.ts +5 -1
- package/dist/session-sync.js +2 -1
- package/dist/sighup-idle.d.ts +7 -1
- package/dist/sighup-idle.js +4 -4
- package/dist/spec-fields.d.ts +4 -4
- package/dist/spec-fields.js +1 -4
- package/dist/task-settings.d.ts +36 -14
- package/dist/task-settings.js +20 -23
- package/dist/tool-approval.d.ts +6 -0
- package/dist/tool-approval.js +13 -1
- package/dist/trace/redact.d.ts +3 -3
- package/dist/unattended-permission-mode.d.ts +17 -13
- package/dist/unattended-permission-mode.js +3 -1
- package/dist/url-shape.d.ts +31 -0
- package/dist/url-shape.js +41 -0
- package/package.json +3 -3
- package/scripts/drain-check-local.mjs +1 -1
package/dist/http/resume-legs.js
CHANGED
|
@@ -29,7 +29,7 @@ import { reasoningDeltaBytes, reasoningSegmentEventId } from "../trace/model-seg
|
|
|
29
29
|
import { errorText } from "../observability/err-text.js";
|
|
30
30
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
31
31
|
import { verifyRoundsFromBody } from "./verify-rounds.js";
|
|
32
|
-
import { storedBodyPermissionMode } from "../task-settings.js";
|
|
32
|
+
import { planReviewDecisionExitsPlan, storedBodyPermissionMode } from "../task-settings.js";
|
|
33
33
|
import { acceptedResumingReceipt, buildHostLegOutcome } from "./route-ctx.js";
|
|
34
34
|
import { mintAndDriveDurableRun } from "./durable-run-leg.js";
|
|
35
35
|
import { createSessionsLocal } from "./routes/sessions.js";
|
|
@@ -43,7 +43,8 @@ import { commitSessionInheritEnv } from "./session-inherit-env.js";
|
|
|
43
43
|
import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
|
|
44
44
|
import { deploymentListAsks } from "../approval.js";
|
|
45
45
|
import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
|
|
46
|
-
import { resumeBlockedByPolicyAnswer, storedBodyReplayAnswer, storedBodyReplayLogFields,
|
|
46
|
+
import { resumeBlockedByPolicyAnswer, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
|
|
47
|
+
import { storedBodyReplayRefusal } from "./stored-body-replay.js";
|
|
47
48
|
import { httpErrorCode } from "./send.js";
|
|
48
49
|
function isCheckpointReopenedFailure(r) {
|
|
49
50
|
const code = failureCodeOf(r.terminal);
|
|
@@ -422,7 +423,7 @@ export function createResumeLegs(ctx) {
|
|
|
422
423
|
return storedBodyReplayAnswer(refusal);
|
|
423
424
|
};
|
|
424
425
|
const resolveResumeSpecOrBlocked = async (ctx, req, auth) => {
|
|
425
|
-
const replay = storedBodyReplayRefusal(ctx);
|
|
426
|
+
const replay = storedBodyReplayRefusal(ctx, deps.config.autonomy);
|
|
426
427
|
if (replay !== undefined)
|
|
427
428
|
return { ok: false, answer: refuseStoredBodyReplay(auth.sessionId, replay) };
|
|
428
429
|
try {
|
|
@@ -1352,7 +1353,7 @@ export function createResumeLegs(ctx) {
|
|
|
1352
1353
|
return refuse(resumeCtxCorruptAnswer(ctxRead.where));
|
|
1353
1354
|
if (ctxRead.kind === "absent")
|
|
1354
1355
|
return refuse({ status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } });
|
|
1355
|
-
const replay = storedBodyReplayRefusal(ctxRead.ctx);
|
|
1356
|
+
const replay = storedBodyReplayRefusal(ctxRead.ctx, deps.config.autonomy);
|
|
1356
1357
|
if (replay !== undefined)
|
|
1357
1358
|
return refuse(refuseStoredBodyReplay(sessionId, replay));
|
|
1358
1359
|
return { ok: true, prepared: { sessionId, token, cp, ctx: ctxRead.ctx } };
|
|
@@ -1364,11 +1365,10 @@ export function createResumeLegs(ctx) {
|
|
|
1364
1365
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
1365
1366
|
const effMode = storedBodyPermissionMode(ctx.body);
|
|
1366
1367
|
if (permissionModeAfter !== undefined && effMode !== "plan") {
|
|
1367
|
-
throw new Error("drivePlanReview: permissionModeAfter was passed for a task
|
|
1368
|
+
throw new Error("drivePlanReview: permissionModeAfter was passed for a task whose stored permission mode is not \"plan\" — the caller must refuse this before any state changes (see the plan_review route's pre-nonce gate)");
|
|
1368
1369
|
}
|
|
1369
|
-
const approvedMode = decision
|
|
1370
|
-
const
|
|
1371
|
-
const rebuildCtx = { ...ctx, body: resumeBody };
|
|
1370
|
+
const approvedMode = planReviewDecisionExitsPlan(decision) && effMode === "plan" ? (permissionModeAfter ?? "default") : undefined;
|
|
1371
|
+
const rebuildCtx = approvedMode === undefined ? ctx : withPermissionModeRewritten(ctx, { ...ctx.body, permissionMode: approvedMode });
|
|
1372
1372
|
const resolved = await resolveResumeSpecOrBlocked(rebuildCtx, req, auth);
|
|
1373
1373
|
if (!resolved.ok)
|
|
1374
1374
|
return resolved.answer;
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -168,6 +168,14 @@ export interface CheckpointCtx {
|
|
|
168
168
|
* 写点只有 `buildCheckpointCtx`(同模块;新的持久化写);续跑腿的写回传原 ctx / 其展开 ⇒ 记号随行。体里没有 `allow` 的行不写它。
|
|
169
169
|
*/
|
|
170
170
|
permissionsAllowReading?: import("./checkpoint-ctx.js").PermissionsAllowReading;
|
|
171
|
+
/**
|
|
172
|
+
* 体里**模式词的读法记号**(闭集只有当前一词 `"entry_word"`):在场 = 这一行体的顶层 `permissionMode` 是这条 run 模式词唯一的源(提交入口盖过运维
|
|
173
|
+
* `AUTONOMY=plan` 的地板;`plan_review` 写回改它),续跑照它读。**服务端写、不来自请求体**。缺席(7.121.x 及以前写下的行 —— 那时运维的 plan 由治理层每段重钳)
|
|
174
|
+
* 而本部署此刻有入口地板、体的词被地板钳会变 ⇒ 续跑腿响亮拒、行字节不动(规则与删除条件的属主 = `http/checkpoint-ctx.ts` 顶注第三问)。
|
|
175
|
+
* 写点两个(写模式词的人盖记号,同模块):`buildCheckpointCtx`(每一次新的持久化写都写)与 `withPermissionModeRewritten`(`plan_review` 改词的写回);
|
|
176
|
+
* 不改词的续跑腿写回传原 ctx ⇒ 记号随行。
|
|
177
|
+
*/
|
|
178
|
+
permissionModeReading?: import("./checkpoint-ctx.js").PermissionModeReading;
|
|
171
179
|
}
|
|
172
180
|
/** B4①(命名化):`driveResumeIntoRunLog` 的入参形(resume 家族共用)——原为 9 成员内联匿名对象。 */
|
|
173
181
|
export interface DriveResumeArgs {
|
|
@@ -448,8 +456,8 @@ export interface RouteLegs {
|
|
|
448
456
|
drivePlanReview(prepared: PreparedPlanReview, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined, req: IncomingMessage | undefined,
|
|
449
457
|
/** [ref] 件 S-1:透传给 {@link DriveResumeArgs.acceptEarly}(200 受理语义)。HTTP `/plan_review` 腿传 `true`。 */
|
|
450
458
|
acceptEarly?: boolean,
|
|
451
|
-
/** S-433:批准之后这条会话继续用的 permission mode(闭集 `PLAN_APPROVE_PERMISSION_MODES_AFTER
|
|
452
|
-
* 上有意义,缺席 ⇒ `"default"`)。语义与判据的唯一属主 = `resume-legs.ts` 的同名形参顶注。 */
|
|
459
|
+
/** S-433:批准之后这条会话继续用的 permission mode(闭集 `PLAN_APPROVE_PERMISSION_MODES_AFTER`;只在把计划带出 plan 的决定
|
|
460
|
+
* —— `approve` / `edit` —— 上有意义,缺席 ⇒ `"default"`)。语义与判据的唯一属主 = `resume-legs.ts` 的同名形参顶注。 */
|
|
453
461
|
permissionModeAfter?: PlanApprovePermissionModeAfter): Promise<{
|
|
454
462
|
status: number;
|
|
455
463
|
body: object;
|
|
@@ -586,7 +594,7 @@ export interface RouteDeclFacts {
|
|
|
586
594
|
* 503 照旧罩着(它判的是「这台机能不能收写」,与方法无关,且必须在解析请求体之前)。 */
|
|
587
595
|
readonly methodDispatched?: true;
|
|
588
596
|
}
|
|
589
|
-
/**
|
|
597
|
+
/** **可分派行**多出来的列(S-146 第三刀):身份 + 它真认的方法(+ S-1281 的订阅类)。纯门行一列都没有(见下)。 */
|
|
590
598
|
interface RouteDeclIdentity {
|
|
591
599
|
/** **全表唯一**的行名(kebab 闭集词)。这是声明行的身份:域 handler 用
|
|
592
600
|
* `switch (match.decl.id)` 闭集分派(漏一口 = 编译红),所以它必须稳定、唯一、且与路径解耦
|
|
@@ -601,6 +609,14 @@ interface RouteDeclIdentity {
|
|
|
601
609
|
* ② 2 条「一条路径住着两只域」的跨域行按方法拆成两行各归各域(`/v1/tasks` = trace-usage 的 GET +
|
|
602
610
|
* tasks 的 POST;`/v1/memory/export` = memory-policy 的 GET + memory-bundle 的 POST)。 */
|
|
603
611
|
readonly methods: readonly RouteMethod[];
|
|
612
|
+
/**
|
|
613
|
+
* 「这条 GET 开的是一条**只读订阅**」—— 一条 SSE 读面(推送队列 / 账本 tail),它看着别人干活,自己不是活(S-1281)。
|
|
614
|
+
* 在场 ⇒ 分派器不把它记进在飞(`drainState.inflight()`:排空等的、`/health` 报的、SIGHUP 空闲窗与附着租约读的那一只),只记进附着
|
|
615
|
+
* (`attachedStreams()`:壳还挂在这只引擎上);排空进入那一拍(以及硬停起点)由分派器发一帧 `draining` 终帧后关掉它
|
|
616
|
+
* (`drainState.endSubscriptions`)。闭集 = 八条 GET SSE 读面,逐行理由见各行注;**唯一**一条「在飞腿的流」`POST /v1/tasks/stream`
|
|
617
|
+
* 不带它 —— 那条流就是 sync 腿本身(关流 = 断连语义),照旧算在飞。漏标一条订阅的方向是**多算在飞**(排空多等),不是少等。
|
|
618
|
+
*/
|
|
619
|
+
readonly subscription?: true;
|
|
604
620
|
}
|
|
605
621
|
/**
|
|
606
622
|
* **可分派行**:一条真路由。匹配式二选一 —— `path`(精确;标签**就是** path 自身,所以没有 label 列,
|
|
@@ -141,6 +141,9 @@ export declare function billableKindOf(url: string): BillableKind | undefined;
|
|
|
141
141
|
* `billable=false`(零模型工作),塞进那张名单会让它们连带吃 drain 与 roster-pending 两道 503 ——
|
|
142
142
|
* 而收编与规则读写在排空期/roster 未落时做完全无害。 */
|
|
143
143
|
export declare function isCredentialGatedRewrite(method: string, url: string): boolean;
|
|
144
|
+
/** S-1281:这次请求是不是一条**只读订阅**(`RouteDeclFacts.subscription` 那一列;方法也要对上 —— 订阅行恒只认 GET)。
|
|
145
|
+
* 分派器在 admission 那一拍(第一个 await 之前)同步判它:订阅不进在飞计数,只进附着集与订阅集。 */
|
|
146
|
+
export declare function isSubscriptionRequest(method: string, url: string): boolean;
|
|
144
147
|
/** 见 `RouteDeclFacts.methodDispatched` 的头注(单 URL 多方法端点)。 */
|
|
145
148
|
export declare function isMethodDispatchedSubmitPath(url: string): boolean;
|
|
146
149
|
/** 一次分派查找的结果:命中的行与它的参数,外加**这条路径上所有行**的方法并集(405 的 `Allow`)。 */
|
package/dist/http/route-table.js
CHANGED
|
@@ -111,6 +111,7 @@ const LABEL_BY_PATH = new Map(ROUTE_DECLARATIONS.flatMap((d) => ("path" in d ? [
|
|
|
111
111
|
const SHAPED_LABEL_DECLS = ROUTE_DECLARATIONS.filter((d) => !("path" in d) && typeof d.label === "string");
|
|
112
112
|
const BILLABLE_DECLS = ROUTE_DECLARATIONS.filter((d) => d.billable !== undefined);
|
|
113
113
|
const METHOD_DISPATCHED_DECLS = ROUTE_DECLARATIONS.filter((d) => d.methodDispatched === true);
|
|
114
|
+
const SUBSCRIPTION_DECLS = ROUTE_DECLARATIONS.filter(isDispatchDecl).filter((d) => d.subscription === true);
|
|
114
115
|
const CREDENTIAL_GATED_BY_METHOD = (() => {
|
|
115
116
|
const m = new Map();
|
|
116
117
|
for (const d of ROUTE_DECLARATIONS)
|
|
@@ -142,6 +143,9 @@ export function isCredentialGatedRewrite(method, url) {
|
|
|
142
143
|
const rows = CREDENTIAL_GATED_BY_METHOD.get(method.toUpperCase());
|
|
143
144
|
return rows !== undefined && rows.some((d) => declMatches(d, url));
|
|
144
145
|
}
|
|
146
|
+
export function isSubscriptionRequest(method, url) {
|
|
147
|
+
return SUBSCRIPTION_DECLS.some((d) => d.methods.includes(method) && matchDecl(d, url) !== null);
|
|
148
|
+
}
|
|
145
149
|
export function isMethodDispatchedSubmitPath(url) {
|
|
146
150
|
return METHOD_DISPATCHED_DECLS.some((d) => declMatches(d, url));
|
|
147
151
|
}
|
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* 进入排空经 `drainState.enter`(收尾链装的唯一入口)。
|
|
7
7
|
* · 门序 = operator 面的既有口径(`routes/retention-ops.ts` 逐字):身份 → 授权 → 验型;billable=false,**刻意不吃排空 503**
|
|
8
8
|
* (排空中的重放必须能拿到 `already_draining` 回执,吃了就自锁)。
|
|
9
|
-
* ·
|
|
10
|
-
*
|
|
9
|
+
* · 判序:体坏 ⇒ 400 `request.field_invalid`(零状态变化)→ 没有收尾链(嵌入宿主 / 测试夹具)⇒ 501 `capability.drain_state_required`
|
|
10
|
+
* → 身份不符 ⇒ 409 `conflict.fleet_identity`(零状态变化;拒句点名不符的字段,不回显值)→ 入口:已在排空 / 硬停已在进行 ⇒ 200 回执
|
|
11
|
+
* (这一发的期限不会被采用);这一发会进入排空而 `deadlineAt` 离进入那一拍超过 `FLEET_LEASE_OUTAGE_MAX_MS`(`config.fleetLeaseOutageMaxMs`,
|
|
12
|
+
* 本口随入参声明的准入上界,`fleet/drain-entry.ts` 规则 2)⇒ 入口答拒、本口 400 `request.field_invalid`(零状态变化;拒句点名字段、上界的
|
|
13
|
+
* 席名与值,不回显请求带的期限)。
|
|
11
14
|
* · 首次 ⇒ 200 回执 `state:"draining"`;同一 `actionId`(或别的动作)再来 ⇒ 200 `already_draining`,回显第一次的 `since` / `deadlineAt`,不重置。
|
|
12
15
|
* · 人手形:`actionId` 自取(推荐 `manual:<uuid>`),身份照样四元比对 —— 非 fleet 成员不带 `expectedGeneration`(缺席对缺席);
|
|
13
16
|
* `instanceId` 在场才比(从本机 `/health` 读);不带 `scope` = 点名缺省 scope(多租引擎要带它自己的 `FLEET_SCOPE`)。
|
|
@@ -39,8 +39,20 @@ export async function handleAdminDrain(req, res, match, ctx) {
|
|
|
39
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
40
|
return;
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const answer = enter({ actionId: request.actionId, deadlineAt: request.deadlineAt, deadlineBoundMs: deps.config.fleetLeaseOutageMaxMs });
|
|
43
|
+
switch (answer.state) {
|
|
44
|
+
case "deadline_beyond_bound":
|
|
45
|
+
sendError(res, 400, "request.field_invalid", `the drain request's deadlineAt is further from now than FLEET_LEASE_OUTAGE_MAX_MS (${answer.boundMs} ms) on this engine's clock — the longest this engine stays up without serving; send a nearer deadline — nothing was changed`);
|
|
46
|
+
return;
|
|
47
|
+
case "draining":
|
|
48
|
+
case "already_draining":
|
|
49
|
+
sendJson(res, 200, answer);
|
|
50
|
+
return;
|
|
51
|
+
default: {
|
|
52
|
+
const unhandled = answer;
|
|
53
|
+
throw new Error(`drain entry: unknown answer ${JSON.stringify(unhandled)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
44
56
|
}
|
|
45
57
|
default:
|
|
46
58
|
return assertNever(match.decl.id);
|
|
@@ -151,6 +151,7 @@ export declare const APPROVALS_ASSISTANT_ROUTES: readonly [{
|
|
|
151
151
|
readonly id: "approvals-stream";
|
|
152
152
|
readonly path: "/v1/approvals/stream";
|
|
153
153
|
readonly methods: readonly ["GET"];
|
|
154
|
+
readonly subscription: true;
|
|
154
155
|
}, {
|
|
155
156
|
readonly id: "assistant-inbox";
|
|
156
157
|
readonly path: "/v1/assistant/inbox";
|
|
@@ -7,7 +7,7 @@ import { isParkedRunStatus } from "../../plugins/store-contracts.js";
|
|
|
7
7
|
import { principalFrom, decodeCheckpointScope, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../../security.js";
|
|
8
8
|
import { editedPlanBinding, isWellFormedUnicode, verifyDirectDoorProof } from "../../principal-jwt.js";
|
|
9
9
|
import { MAX_APPROVAL_REASON_CHARS, projectQuestionAnswer, questionAnswerFromProjection } from "../../approval-hmac.js";
|
|
10
|
-
import { PLAN_APPROVE_PERMISSION_MODES_AFTER, isPlanApprovePermissionModeAfter, storedBodyPermissionMode } from "../../task-settings.js";
|
|
10
|
+
import { PLAN_APPROVE_PERMISSION_MODES_AFTER, isPlanApprovePermissionModeAfter, planReviewDecisionExitsPlan, storedBodyPermissionMode } from "../../task-settings.js";
|
|
11
11
|
import { MAX_APPROVAL_NONCE_CHARS } from "../../plugins/approval-nonce-store.js";
|
|
12
12
|
import { redactedPreview, redactSecrets } from "../../trace/redact.js";
|
|
13
13
|
import { redactRiskDescriptor } from "../../observability/run-terminal.js";
|
|
@@ -59,7 +59,7 @@ const DECISION_DOOR_FAMILY = {
|
|
|
59
59
|
"assistant-task-plan-review": {
|
|
60
60
|
accepted: PLAN_REVIEW_BODY_DECLARED_KEYS,
|
|
61
61
|
requestNoun: "plan-review-request",
|
|
62
|
-
costliestTypo: "a misspelled `permissionModeAfter` silently leaves this task
|
|
62
|
+
costliestTypo: "a misspelled `permissionModeAfter` silently leaves this task asking before every write after you approved its plan",
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
65
|
const SUBMIT_BODY_NEAR_MISS_KEYS = ["permissionMode"];
|
|
@@ -534,8 +534,8 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
534
534
|
sendError(res, 400, "request.field_invalid", `permissionModeAfter must be one of ${PLAN_APPROVE_PERMISSION_MODES_AFTER.map((m) => JSON.stringify(m)).join(" | ")} when present (the permission mode this task continues in once the plan is approved; absent = "default", which keeps asking before each write)`);
|
|
535
535
|
return;
|
|
536
536
|
}
|
|
537
|
-
if (decision
|
|
538
|
-
sendError(res, 400, "request.field_conflict", `permissionModeAfter is only valid with decision 'approve'
|
|
537
|
+
if (!planReviewDecisionExitsPlan(decision) && body.permissionModeAfter !== undefined) {
|
|
538
|
+
sendError(res, 400, "request.field_conflict", `permissionModeAfter is only valid with a decision that carries the plan out ('approve' or 'edit'; got '${decision}')`);
|
|
539
539
|
return;
|
|
540
540
|
}
|
|
541
541
|
if (body.reason !== undefined && typeof body.reason !== "string") {
|
|
@@ -610,7 +610,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
610
610
|
if (body.permissionModeAfter !== undefined) {
|
|
611
611
|
const storedCtx = plan.prepared.ctx;
|
|
612
612
|
if (storedCtx.body !== undefined && storedBodyPermissionMode(storedCtx.body) !== "plan") {
|
|
613
|
-
sendError(res, 400, "request.field_conflict", "permissionModeAfter does not apply to this task: it was not
|
|
613
|
+
sendError(res, 400, "request.field_conflict", "permissionModeAfter does not apply to this task: it was not started in plan mode (its stored permission mode is not \"plan\"), so approving the plan returns it to the mode it was in before planning");
|
|
614
614
|
return;
|
|
615
615
|
}
|
|
616
616
|
}
|
|
@@ -910,7 +910,7 @@ export async function streamApprovals(req, res, cs, live, scope, governance, pol
|
|
|
910
910
|
}
|
|
911
911
|
export const APPROVALS_ASSISTANT_ROUTES = [
|
|
912
912
|
{ id: "approvals-list", path: "/v1/approvals", methods: ["GET"] },
|
|
913
|
-
{ id: "approvals-stream", path: "/v1/approvals/stream", methods: ["GET"] },
|
|
913
|
+
{ id: "approvals-stream", path: "/v1/approvals/stream", methods: ["GET"], subscription: true },
|
|
914
914
|
{ id: "assistant-inbox", path: "/v1/assistant/inbox", methods: ["GET"] },
|
|
915
915
|
{ id: "assistant-tasks", path: "/v1/assistant/tasks", methods: ["GET"] },
|
|
916
916
|
{ id: "approval-decide", pattern: /^\/v1\/approvals\/(?<sessionId>[^/]+)\/decide$/, label: "/v1/approvals/:id/decide", methods: ["POST"] },
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { PERMISSION_MODES, expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
|
|
2
2
|
import { modelImageInput, isModelAllowlisted, selectableCatalog } from "../../model-select.js";
|
|
3
|
+
import { buildDroppedEntriesWire } from "../../config-center/read-warnings.js";
|
|
3
4
|
import { modelKeyPoisonOf, resolveModelApiKey } from "../../key-resolver.js";
|
|
4
5
|
import { projectSealedKeyCapability } from "../../sealed-key.js";
|
|
5
6
|
import { cwdHonored, deviceCwdHonored } from "../../task-cwd.js";
|
|
6
7
|
import { taskWriteFaceRefusal } from "../../hosted-posture.js";
|
|
7
|
-
import {
|
|
8
|
+
import { leaderEntryRefusal, leaderRunEntry } from "../../leader/run-entry.js";
|
|
9
|
+
import { autonomyKeepsOpenWriteFace, autonomyModeFloor } from "../../runtime-governance.js";
|
|
8
10
|
import { DEVICE_PROTOCOL_VERSION, DEVICE_WS_PATH } from "../../device-ws-protocol.js";
|
|
9
11
|
import { a2aInjectionHonored } from "../../task-a2a.js";
|
|
10
12
|
import { buildEmptyMcpProbeFace, buildMcpInjectionRefusal, buildMcpProbeCacheKey, buildMcpProbeInflightRefusal, buildMcpProbeRateKey, buildMcpProbeRateRefusal, createMcpProbeLocal, buildMcpProbeDeclarationRefusal, buildMcpProbeIncompleteRefusal, McpProbeIncomplete, MCP_PROBE_HTTP_STATUS, MCP_PROBE_MAX_INFLIGHT, MCP_PROBE_VERDICT_TIMEOUT_MS, createMcpProbeWalk, readMcpProbeBody, } from "../../capabilities/mcp-probe.js";
|
|
@@ -64,7 +66,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
64
66
|
return;
|
|
65
67
|
}
|
|
66
68
|
const entitlementSource = centerEntitlementSourceWired(deps.config);
|
|
67
|
-
const { fault: autoModeFault, ...autoMode } = await judgeAutoModeArming(deps.autoModeFace, { requestedMode, principal: gatedPrincipal(req, deps.config), localDeny: deps.config.permissionsDisableAutoMode });
|
|
69
|
+
const { fault: autoModeFault, ...autoMode } = await judgeAutoModeArming(deps.autoModeFace, { requestedMode, floor: autonomyModeFloor(deps.config.autonomy), principal: gatedPrincipal(req, deps.config), localDeny: deps.config.permissionsDisableAutoMode });
|
|
68
70
|
if (autoModeFault !== undefined)
|
|
69
71
|
deps.logger?.warn("auto_mode_face_resolver_fault", { fault: autoModeFault });
|
|
70
72
|
const rulesLaneForCaller = facts.ruleLaneForCaller(deps, gatedPrincipal(req, deps.config)).available;
|
|
@@ -82,7 +84,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
82
84
|
? { stream: true, sessionScope: true, observe: true, resume: "snapshot", bgNotifyFailClosed: true, steerPriority: "engine", hookFailureNotice: true, leaseClient: true, generationEcho: true }
|
|
83
85
|
: false,
|
|
84
86
|
workspace: facts.workspaceBrowse().available,
|
|
85
|
-
leader: facts.leaderLane(deps).available,
|
|
87
|
+
leader: facts.leaderLane(deps).available && leaderEntryRefusal(leaderRunEntry(deps.config.autonomy)) === undefined,
|
|
86
88
|
images: facts.imagePool(deps).available,
|
|
87
89
|
memory: false,
|
|
88
90
|
memoryWrite: false,
|
|
@@ -144,7 +146,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
144
146
|
skillToolLists: true,
|
|
145
147
|
projectContext: cwdHonored(deps.config),
|
|
146
148
|
callerCwd: cwdHonored(deps.config) || deviceCwdHonored(deps.config),
|
|
147
|
-
taskWriteFaceOpen: taskWriteFaceRefusal(deps.config) === undefined &&
|
|
149
|
+
taskWriteFaceOpen: taskWriteFaceRefusal(deps.config) === undefined && autonomyKeepsOpenWriteFace(deps.config.autonomy),
|
|
148
150
|
executionLane: projectExecutionLaneCapability(deps.config),
|
|
149
151
|
mcpInjection: facts.mcpInjection(deps).available,
|
|
150
152
|
a2a: true,
|
|
@@ -253,7 +255,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
253
255
|
}));
|
|
254
256
|
const nonAlias = Object.entries(deps.config.models).filter(([n]) => n !== "default");
|
|
255
257
|
const defaultName = (nonAlias.find(([, m]) => m === deps.config.model) ?? nonAlias.find(([, m]) => m.id === deps.config.model.id))?.[0];
|
|
256
|
-
sendJson(res, 200, { models, default: deps.config.model.id, defaultModel: { id: deps.config.model.id, ...(defaultName ? { name: defaultName } : {}) } });
|
|
258
|
+
sendJson(res, 200, { models, default: deps.config.model.id, defaultModel: { id: deps.config.model.id, ...(defaultName ? { name: defaultName } : {}) }, droppedEntries: buildDroppedEntriesWire(deps.config.droppedCatalogEntries) });
|
|
257
259
|
return;
|
|
258
260
|
}
|
|
259
261
|
default:
|
|
@@ -14,6 +14,7 @@ export declare const FLEET_ROUTES: readonly [{
|
|
|
14
14
|
readonly id: "fleet-stream";
|
|
15
15
|
readonly path: "/v1/fleet/stream";
|
|
16
16
|
readonly methods: readonly ["GET"];
|
|
17
|
+
readonly subscription: true;
|
|
17
18
|
}];
|
|
18
19
|
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
19
20
|
export type FleetRouteId = RouteIdsOf<typeof FLEET_ROUTES>;
|
|
@@ -239,6 +239,6 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
|
|
|
239
239
|
});
|
|
240
240
|
}
|
|
241
241
|
export const FLEET_ROUTES = [
|
|
242
|
-
{ id: "fleet-stream", path: "/v1/fleet/stream", methods: ["GET"] },
|
|
242
|
+
{ id: "fleet-stream", path: "/v1/fleet/stream", methods: ["GET"], subscription: true },
|
|
243
243
|
];
|
|
244
244
|
//# sourceMappingURL=fleet.js.map
|
|
@@ -55,6 +55,7 @@ export declare const IMAGES_ROUTES: readonly [{
|
|
|
55
55
|
readonly pattern: RegExp;
|
|
56
56
|
readonly label: "/v1/images/bakes/:id/events";
|
|
57
57
|
readonly methods: readonly ["GET"];
|
|
58
|
+
readonly subscription: true;
|
|
58
59
|
}, {
|
|
59
60
|
readonly id: "image-bake-cancel";
|
|
60
61
|
readonly pattern: RegExp;
|
|
@@ -643,7 +643,7 @@ export const IMAGES_ROUTES = [
|
|
|
643
643
|
{ id: "image-bake-claim-next", path: "/v1/images/bakes/claim", methods: ["POST"] },
|
|
644
644
|
{ id: "image-select", path: "/v1/images/select", methods: ["POST"] },
|
|
645
645
|
{ id: "image-register", path: "/v1/images/register", methods: ["POST"] },
|
|
646
|
-
{ id: "image-bake-events", pattern: /^\/v1\/images\/bakes\/(?<bakeId>[^/]+)\/events$/, label: "/v1/images/bakes/:id/events", methods: ["GET"] },
|
|
646
|
+
{ id: "image-bake-events", pattern: /^\/v1\/images\/bakes\/(?<bakeId>[^/]+)\/events$/, label: "/v1/images/bakes/:id/events", methods: ["GET"], subscription: true },
|
|
647
647
|
{ id: "image-bake-cancel", pattern: /^\/v1\/images\/bakes\/(?<bakeId>[^/]+)\/cancel$/, label: "/v1/images/bakes/:id/cancel", methods: ["POST"] },
|
|
648
648
|
{ id: "image-bake-claim", pattern: /^\/v1\/images\/bakes\/(?<bakeId>[^/]+)\/claim$/, label: "/v1/images/bakes/:id/claim", methods: ["POST"] },
|
|
649
649
|
{ id: "image-bake-ingest", pattern: /^\/v1\/images\/bakes\/(?<bakeId>[^/]+)\/ingest$/, label: "/v1/images/bakes/:id/ingest", methods: ["POST"] },
|
|
@@ -2,6 +2,7 @@ import { sendJson, sendError } from "../send.js";
|
|
|
2
2
|
import { gatedPrincipal, headerStr, sendPrincipalRefusal } from "../principal-gate.js";
|
|
3
3
|
import { assertNever } from "../route-ctx.js";
|
|
4
4
|
import * as facts from "../../capability-facts.js";
|
|
5
|
+
import { leaderEntryRefusal, leaderRunEntry } from "../../leader/run-entry.js";
|
|
5
6
|
export const LEADER_SUBMIT_PATH = "/v1/leader";
|
|
6
7
|
export async function handleLeader(req, res, match, ctx) {
|
|
7
8
|
switch (match.decl.id) {
|
|
@@ -35,6 +36,11 @@ export async function handleLeader(req, res, match, ctx) {
|
|
|
35
36
|
sendError(res, 400, "request.invalid_json", "invalid JSON body");
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
39
|
+
const laneRefusal = leaderEntryRefusal(leaderRunEntry(deps.config.autonomy));
|
|
40
|
+
if (laneRefusal !== undefined) {
|
|
41
|
+
sendError(res, 422, "request.autonomy_lane_refused", laneRefusal);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
38
44
|
const r = await endpoint.handle("POST", LEADER_SUBMIT_PATH, body, requester, headerStr(req.headers["idempotency-key"]) ?? null);
|
|
39
45
|
if (r) {
|
|
40
46
|
sendJson(res, r.status, r.body);
|
|
@@ -72,6 +72,7 @@ export declare const RUNS_ROUTES: readonly [{
|
|
|
72
72
|
readonly pattern: RegExp;
|
|
73
73
|
readonly label: "/v1/runs/:id/events";
|
|
74
74
|
readonly methods: readonly ["GET"];
|
|
75
|
+
readonly subscription: true;
|
|
75
76
|
}, {
|
|
76
77
|
readonly id: "run-detail";
|
|
77
78
|
readonly pattern: RegExp;
|
|
@@ -90,6 +91,7 @@ export declare const RUN_VERBS_ROUTES: readonly [{
|
|
|
90
91
|
readonly pattern: RegExp;
|
|
91
92
|
readonly label: "/v1/runs/:id/subagents/:sub/*";
|
|
92
93
|
readonly methods: readonly ["GET"];
|
|
94
|
+
readonly subscription: true;
|
|
93
95
|
}, {
|
|
94
96
|
readonly id: "run-subagent-steer";
|
|
95
97
|
readonly pattern: RegExp;
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -33,7 +33,8 @@ import { mintAndDriveDurableRun } from "../durable-run-leg.js";
|
|
|
33
33
|
import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
|
|
34
34
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
35
35
|
import { readResumeCtx } from "../resume-ctx-read.js";
|
|
36
|
-
import { storedBodyReplayAnswer, storedBodyReplayLogFields
|
|
36
|
+
import { storedBodyReplayAnswer, storedBodyReplayLogFields } from "../checkpoint-ctx.js";
|
|
37
|
+
import { storedBodyReplayRefusal } from "../stored-body-replay.js";
|
|
37
38
|
const STEER_PRIORITY_WORDS = INJECTION_TIERS.map((tier) => `'${tier}'`);
|
|
38
39
|
const STEER_PRIORITY_UNION = STEER_PRIORITY_WORDS.join(" | ");
|
|
39
40
|
const STEER_PRIORITY_CHOICES = STEER_PRIORITY_WORDS.length <= 2 ? STEER_PRIORITY_WORDS.join(" or ") : `${STEER_PRIORITY_WORDS.slice(0, -1).join(", ")}, or ${STEER_PRIORITY_WORDS[STEER_PRIORITY_WORDS.length - 1]}`;
|
|
@@ -647,7 +648,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
647
648
|
mintResult.noResumeCtx = true;
|
|
648
649
|
return;
|
|
649
650
|
}
|
|
650
|
-
const unservable = storedBodyReplayRefusal(resumeRead.ctx);
|
|
651
|
+
const unservable = storedBodyReplayRefusal(resumeRead.ctx, deps.config.autonomy);
|
|
651
652
|
if (unservable !== undefined) {
|
|
652
653
|
mintResult.unservable = unservable;
|
|
653
654
|
return;
|
|
@@ -1662,12 +1663,12 @@ export const RUNS_ROUTES = [
|
|
|
1662
1663
|
{ id: "run-detach", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/detach$/, label: "/v1/runs/:id/detach", methods: ["POST"] },
|
|
1663
1664
|
{ id: "run-cancel", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/cancel$/, label: "/v1/runs/:id/cancel", methods: ["POST"] },
|
|
1664
1665
|
{ id: "run-compact", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/compact$/, label: "/v1/runs/:id/compact", methods: ["POST"] },
|
|
1665
|
-
{ id: "run-events", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/events$/, label: "/v1/runs/:id/events", methods: ["GET"] },
|
|
1666
|
+
{ id: "run-events", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/events$/, label: "/v1/runs/:id/events", methods: ["GET"], subscription: true },
|
|
1666
1667
|
{ id: "run-detail", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)$/, label: "/v1/runs/:id", methods: ["GET"] },
|
|
1667
1668
|
];
|
|
1668
1669
|
export const RUN_VERBS_ROUTES = [
|
|
1669
1670
|
{ id: "run-subagent-output", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/output$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["GET"] },
|
|
1670
|
-
{ id: "run-subagent-stream", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/stream$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["GET"] },
|
|
1671
|
+
{ id: "run-subagent-stream", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/stream$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["GET"], subscription: true },
|
|
1671
1672
|
{ id: "run-subagent-steer", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/steer$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["POST"] },
|
|
1672
1673
|
{ id: "run-subagent-resume", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/resume$/, label: "/v1/runs/:id/subagents/:sub/*", billable: "resume", methods: ["POST"] },
|
|
1673
1674
|
{ id: "run-task-output", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/tasks\/(?<tid>[^/]+)\/output$/, label: "/v1/runs/:id/tasks/:tid/*", methods: ["GET"] },
|
|
@@ -79,6 +79,7 @@ export declare const SESSIONS_ROUTES: readonly [{
|
|
|
79
79
|
readonly pattern: RegExp;
|
|
80
80
|
readonly label: "/v1/sessions/:id/$verb";
|
|
81
81
|
readonly methods: readonly ["GET"];
|
|
82
|
+
readonly subscription: true;
|
|
82
83
|
}, {
|
|
83
84
|
readonly id: "session-fork";
|
|
84
85
|
readonly pattern: RegExp;
|
|
@@ -794,15 +794,12 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
794
794
|
case "not-an-object":
|
|
795
795
|
sendError(res, 400, "request.body_shape", describeSessionPermissionRulesProblem(read.problem));
|
|
796
796
|
return;
|
|
797
|
-
case "field-not-
|
|
797
|
+
case "field-not-rule-list":
|
|
798
798
|
sendError(res, 400, "request.field_invalid", describeSessionPermissionRulesProblem(read.problem));
|
|
799
799
|
return;
|
|
800
800
|
case "legacy-tool-name":
|
|
801
801
|
sendError(res, 400, "rules.legacy_tool_name", describeSessionPermissionRulesProblem(read.problem), { ...read.problem.refusal });
|
|
802
802
|
return;
|
|
803
|
-
case "unreachable-tool-name":
|
|
804
|
-
sendError(res, 400, "request.field_invalid", describeSessionPermissionRulesProblem(read.problem));
|
|
805
|
-
return;
|
|
806
803
|
default: {
|
|
807
804
|
const unhandled = read.problem;
|
|
808
805
|
throw new Error(`unhandled SessionPermissionRules problem: ${JSON.stringify(unhandled)}`);
|
|
@@ -844,7 +841,7 @@ export const SESSIONS_ROUTES = [
|
|
|
844
841
|
{ id: "session-mcp", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/mcp$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
845
842
|
{ id: "session-a2a", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/a2a$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
846
843
|
{ id: "session-head", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/head$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
847
|
-
{ id: "session-events", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/events$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
844
|
+
{ id: "session-events", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/events$/, label: "/v1/sessions/:id/$verb", methods: ["GET"], subscription: true },
|
|
848
845
|
{ id: "session-fork", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/fork$/, label: "/v1/sessions/:id/$verb", methods: ["POST"] },
|
|
849
846
|
{ id: "session-policy-get", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/policy$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
850
847
|
{ id: "session-policy-put", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/policy$/, label: "/v1/sessions/:id/$verb", methods: ["PUT"] },
|
|
@@ -38,6 +38,7 @@ export declare const TRACE_USAGE_ROUTES: readonly [{
|
|
|
38
38
|
readonly pattern: RegExp;
|
|
39
39
|
readonly label: "/v1/tasks/:id/$sub";
|
|
40
40
|
readonly methods: readonly ["GET"];
|
|
41
|
+
readonly subscription: true;
|
|
41
42
|
}, {
|
|
42
43
|
readonly id: "task-trace-artifacts";
|
|
43
44
|
readonly pattern: RegExp;
|
|
@@ -302,7 +302,7 @@ export const TRACE_USAGE_ROUTES = [
|
|
|
302
302
|
{ id: "task-trace-list", path: "/v1/tasks", methods: ["GET"] },
|
|
303
303
|
{ id: "task-tool-result", pattern: /^\/v1\/tasks\/(?<taskId>[^/]+)\/tool-results\/(?<ref>[^/]+)$/, label: "/v1/tasks/:id/tool-results/:ref", methods: ["GET"] },
|
|
304
304
|
{ id: "task-trace-turns", pattern: /^\/v1\/tasks\/(?<taskId>[^/]+)\/turns$/, label: "/v1/tasks/:id/$sub", methods: ["GET"] },
|
|
305
|
-
{ id: "task-trace-stream", pattern: /^\/v1\/tasks\/(?<taskId>[^/]+)\/stream$/, label: "/v1/tasks/:id/$sub", methods: ["GET"] },
|
|
305
|
+
{ id: "task-trace-stream", pattern: /^\/v1\/tasks\/(?<taskId>[^/]+)\/stream$/, label: "/v1/tasks/:id/$sub", methods: ["GET"], subscription: true },
|
|
306
306
|
{ id: "task-trace-artifacts", pattern: /^\/v1\/tasks\/(?<taskId>[^/]+)\/artifacts$/, label: "/v1/tasks/:id/$sub", methods: ["GET"] },
|
|
307
307
|
{ id: "usage-analytics", pattern: /^\/v1\/usage\/(?<sub>summary|series|breakdown)$/, label: "/v1/usage/$sub", methods: ["GET"] },
|
|
308
308
|
];
|
|
@@ -15,6 +15,7 @@ export declare const WORKFLOWS_ROUTES: readonly [{
|
|
|
15
15
|
readonly pattern: RegExp;
|
|
16
16
|
readonly label: "/v1/workflows/:id/$sub";
|
|
17
17
|
readonly methods: readonly ["GET"];
|
|
18
|
+
readonly subscription: true;
|
|
18
19
|
}, {
|
|
19
20
|
readonly id: "workflow-journal";
|
|
20
21
|
readonly pattern: RegExp;
|
|
@@ -416,7 +416,7 @@ function sendWorkflowsUnavailable(res, why) {
|
|
|
416
416
|
}
|
|
417
417
|
export const WORKFLOWS_ROUTES = [
|
|
418
418
|
{ id: "workflows-list", path: "/v1/workflows", methods: ["GET"] },
|
|
419
|
-
{ id: "workflow-stream", pattern: /^\/v1\/workflows\/(?<runId>[^/]+)\/stream$/, label: "/v1/workflows/:id/$sub", methods: ["GET"] },
|
|
419
|
+
{ id: "workflow-stream", pattern: /^\/v1\/workflows\/(?<runId>[^/]+)\/stream$/, label: "/v1/workflows/:id/$sub", methods: ["GET"], subscription: true },
|
|
420
420
|
{ id: "workflow-journal", pattern: /^\/v1\/workflows\/(?<runId>[^/]+)\/journal$/, label: "/v1/workflows/:id/$sub", methods: ["GET"] },
|
|
421
421
|
{ id: "workflow-detail", pattern: /^\/v1\/workflows\/(?<runId>[^/]+)$/, label: "/v1/workflows/:id", methods: ["GET"] },
|
|
422
422
|
];
|
package/dist/http/send.d.ts
CHANGED
|
@@ -47,6 +47,19 @@ export declare const SSE_MAX_STREAM_MS: number;
|
|
|
47
47
|
* 要么给 approvals 减 type=消费端破坏),属候裁,不在本批的零行为码范围内。
|
|
48
48
|
*/
|
|
49
49
|
export declare const SSE_HEARTBEAT_IDLE_MS = 15000;
|
|
50
|
+
/**
|
|
51
|
+
* S-1281 —— 服务端**主动**收一条已开的 SSE 流:一帧 `event: error`(帧形与流帽那一帧同一套 `{type:"error", errorCode, message}`,
|
|
52
|
+
* 消费端按既有「服务端收流 ⇒ 重连」协议处置,SDK 的流读口把它当一帧事件交出再重连)后 `end()`。
|
|
53
|
+
*
|
|
54
|
+
* 收流的是分派器,不是流自己的泵:泵此刻可能正停在一次 await 上,醒来还会写一帧。Node 上 `end()` 之后、`close` 之前的那一写会发一个
|
|
55
|
+
* `error` 事件(实测 v24.2.0:无监听 ⇒ 进程级未捕获;`close` 之后的写只回 `false`,不再发)。⇒ 先挂一只**只吸收这一种**
|
|
56
|
+
* (`ERR_STREAM_WRITE_AFTER_END`)的监听再 `end()`:那一帧本来就无处可去(连接是我们关的),别的错照旧抛(不改既有失败形)。
|
|
57
|
+
* 已收尾的流不碰(幂等;排空入口与硬停起点各调一次)。
|
|
58
|
+
*/
|
|
59
|
+
export declare function endSseWithError(res: ServerResponse, frame: {
|
|
60
|
+
readonly errorCode: string;
|
|
61
|
+
readonly message: string;
|
|
62
|
+
}): void;
|
|
50
63
|
export declare function sendJson(res: ServerResponse, status: number, body: unknown): void;
|
|
51
64
|
/** A6([ref] lens1 §C3/lens3 §C3a): canonical error-response sender for sites that already carry a
|
|
52
65
|
* machine `errorCode` — `errorCode` is a required positional arg (not optional-bag) so a call site
|
package/dist/http/send.js
CHANGED
|
@@ -17,6 +17,15 @@ export function sseHeaders(res, extra) {
|
|
|
17
17
|
}
|
|
18
18
|
export const SSE_MAX_STREAM_MS = 15 * 60 * 1000;
|
|
19
19
|
export const SSE_HEARTBEAT_IDLE_MS = 15_000;
|
|
20
|
+
export function endSseWithError(res, frame) {
|
|
21
|
+
if (res.writableEnded || res.destroyed)
|
|
22
|
+
return;
|
|
23
|
+
res.on("error", (err) => {
|
|
24
|
+
if (err.code !== "ERR_STREAM_WRITE_AFTER_END")
|
|
25
|
+
throw err;
|
|
26
|
+
});
|
|
27
|
+
res.end(`event: error\ndata: ${JSON.stringify({ type: "error", errorCode: frame.errorCode, message: frame.message })}\n\n`);
|
|
28
|
+
}
|
|
20
29
|
export function sendJson(res, status, body) {
|
|
21
30
|
res.writeHead(status, { "content-type": "application/json" });
|
|
22
31
|
res.end(JSON.stringify(body));
|
package/dist/http/server.d.ts
CHANGED
|
@@ -684,6 +684,9 @@ export interface ServiceDeploymentDeps {
|
|
|
684
684
|
* • [ref]:`attachedStreams` 同样 ASSIGNED BY createServer —— 本副本当前打开着的 SSE 流条数
|
|
685
685
|
* (= 引擎附着租约集的大小)。消费方是 `boot/engine-lease.ts` 的自退判据;与 `inflight` 同族,
|
|
686
686
|
* 同一个共享盒子,不另开第二条 main↔server 的通道。
|
|
687
|
+
* • S-1281:`inflight` 只数**工作** —— 只读订阅流(路由表 `subscription` 列的八条 GET SSE 读面)不进 admission 计数,只进
|
|
688
|
+
* `attachedStreams`;`endSubscriptions`(同样 ASSIGNED BY createServer)发 `draining` 终帧后关掉它们,收尾链在排空进入那一拍与
|
|
689
|
+
* 硬停起点各调一次。
|
|
687
690
|
*/
|
|
688
691
|
drainState?: import("../fleet/drain-entry.js").DrainState;
|
|
689
692
|
/** 编排器片 2 件 3:服务租约此刻的读数(`fleet/serving-lease.ts` 的客户端交出;引擎信号铸点与计费准入判同读)。缺席 = 没装租约客户端
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「这条停驻行的持久化体在本版上能否重放」—— **唯一**判据(三问一只;五个问点同调:续跑驱动段 `resume-legs.ts` `resolveResumeSpecOrBlocked`、
|
|
3
|
+
* plan_review 的 prepare 段、SLA 拒扫腿的跳过臂 `approval-sweeps.ts`、终态 run 的 steer 铸唤醒卡之前 `routes/runs.ts`;`resolveSpec` 本身留作提交腿的拒点)。
|
|
4
|
+
* 三问的规则、记号、拒句与删除条件的属主 = `http/checkpoint-ctx.ts` 顶注;答复 = 那里的 `storedBodyReplayAnswer`。
|
|
5
|
+
*
|
|
6
|
+
* 为什么判据住在这里而不是 `checkpoint-ctx.ts`:第三问要读体的**生效模式词**(`task-settings.ts` `storedBodyPermissionMode`,请求设置的解析层)与本部署此刻的
|
|
7
|
+
* **入口地板**(`runtime-governance.ts` `autonomyModeFloor`)—— 这两只属主带着配置层的包外依赖;`checkpoint-ctx.ts` 被随包的本地排空脚本经编译产物 import,
|
|
8
|
+
* 它的闭包只许 node 内建 / 引擎包根(`test/drain-check-local.test.ts` 的闭包门)。判据只有这一只,前两问的谓词照旧住在各自属主里。
|
|
9
|
+
*/
|
|
10
|
+
import type { PermissionMode } from "@sema-agent/core";
|
|
11
|
+
import { type Autonomy } from "../runtime-governance.js";
|
|
12
|
+
import { type StoredBodyReplayRefusal } from "./checkpoint-ctx.js";
|
|
13
|
+
import type { CheckpointCtx } from "./route-ctx.js";
|
|
14
|
+
/**
|
|
15
|
+
* 第三问:本行体里的模式词能否按当前读法重放。不能 ⇔ 本部署此刻有入口地板 ∧ 行上没有当前的模式词读法记号 ∧ 体的生效词进这块地板会变
|
|
16
|
+
* (`runtime-governance.ts` `entryPermissionMode`,与提交入口的盖章同一只函数;判的是「词变不变」而不是 `clamped` —— 体无词的旧行进地板取地板本身,
|
|
17
|
+
* 那也是变)⇒ 返回拒因的两个事实(体的词、地板);能 ⇒ `undefined`。
|
|
18
|
+
*/
|
|
19
|
+
export declare function permissionModeReadingUnstated(ctx: CheckpointCtx, floor: PermissionMode | undefined): {
|
|
20
|
+
stored: PermissionMode | undefined;
|
|
21
|
+
floor: PermissionMode;
|
|
22
|
+
} | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* 三问一只判据:不能重放 ⇒ 返回拒的那条规则;能 ⇒ `undefined`。顺序:allow 读法记号 → 技能拒侧读成空 → 模式词读法记号(几问同中时答先中的那一问 ——
|
|
25
|
+
* 任一问中都不重放,句子只需一句)。`autonomy` = 本部署**此刻**的 `config.autonomy`(活 config:热改即生效);地板由它的唯一属主算,问点不自己算。
|
|
26
|
+
*/
|
|
27
|
+
export declare function storedBodyReplayRefusal(ctx: CheckpointCtx, autonomy: Autonomy | undefined): StoredBodyReplayRefusal | undefined;
|
|
28
|
+
//# sourceMappingURL=stored-body-replay.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { storedBodyPermissionMode } from "../task-settings.js";
|
|
2
|
+
import { autonomyModeFloor, entryPermissionMode } from "../runtime-governance.js";
|
|
3
|
+
import { skillDenyInertEntryOf } from "../rule-entry-list.js";
|
|
4
|
+
import { allowListReadingEraOf, allowListReadingUnstated, permissionModeReadingStated } from "./checkpoint-ctx.js";
|
|
5
|
+
export function permissionModeReadingUnstated(ctx, floor) {
|
|
6
|
+
if (floor === undefined || permissionModeReadingStated(ctx))
|
|
7
|
+
return undefined;
|
|
8
|
+
const stored = storedBodyPermissionMode(ctx.body);
|
|
9
|
+
return entryPermissionMode(stored, floor).effective === stored ? undefined : { stored, floor };
|
|
10
|
+
}
|
|
11
|
+
export function storedBodyReplayRefusal(ctx, autonomy) {
|
|
12
|
+
if (allowListReadingUnstated(ctx))
|
|
13
|
+
return { kind: "allow_list_reading", era: allowListReadingEraOf(ctx) };
|
|
14
|
+
const entry = skillDenyInertEntryOf(ctx.body);
|
|
15
|
+
if (entry !== undefined)
|
|
16
|
+
return { kind: "skill_deny_inert", entry };
|
|
17
|
+
const mode = permissionModeReadingUnstated(ctx, autonomyModeFloor(autonomy));
|
|
18
|
+
if (mode !== undefined)
|
|
19
|
+
return { kind: "permission_mode_reading", ...mode };
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=stored-body-replay.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -29,5 +29,7 @@ export { createKeyResolver, resolveModelApiKey, type ModelKeyRef } from "./key-r
|
|
|
29
29
|
export { isModelKeyPoison, ModelKeyPoisonedError, type ModelKeyPoison, type ModelKeyPoisonReason } from "./key-resolver.js";
|
|
30
30
|
export { RETIRED_ENVS, type RetiredEnv, type RetiredEnvKind, type RetiredEnvWire } from "./config.js";
|
|
31
31
|
export { CATALOG_ENTRY_HEADER_LANES, CATALOG_ENTRY_HEADER_NAMES, type CatalogEntryHeaderName } from "./model-entry-headers.js";
|
|
32
|
+
export { judgeModelRouteGap, type ModelRouteGapReading, type ModelRouteGapOptions } from "./model-route-gap.js";
|
|
33
|
+
export type { ModelRouteGap } from "./model-route-endpoint.js";
|
|
32
34
|
export type { UserInputLedgerStore, UserInputAcceptance, NewUserInputAcceptance, OpenScanCursor, OpenScanPage, SettleHow, WriterGoneEvidence } from "./user-input-ledger.js";
|
|
33
35
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -26,4 +26,5 @@ export { createKeyResolver, resolveModelApiKey } from "./key-resolver.js";
|
|
|
26
26
|
export { isModelKeyPoison, ModelKeyPoisonedError } from "./key-resolver.js";
|
|
27
27
|
export { RETIRED_ENVS } from "./config.js";
|
|
28
28
|
export { CATALOG_ENTRY_HEADER_LANES, CATALOG_ENTRY_HEADER_NAMES } from "./model-entry-headers.js";
|
|
29
|
+
export { judgeModelRouteGap } from "./model-route-gap.js";
|
|
29
30
|
//# sourceMappingURL=index.js.map
|