@sema-agent/server 7.121.0 → 7.122.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 +98 -0
- package/README.md +1 -1
- package/README.zh-CN.md +3 -1
- package/USAGE.md +35 -14
- package/dist/approval-reconciler.d.ts +8 -0
- package/dist/approval-reconciler.js +72 -52
- package/dist/auto-mode-face.d.ts +9 -2
- package/dist/auto-mode-face.js +5 -3
- package/dist/bench/s1/live-deps.js +1 -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 +3 -3
- package/dist/config-center/center-request.d.ts +6 -9
- package/dist/config-center/center-request.js +11 -27
- package/dist/config-types.d.ts +11 -8
- package/dist/config-types.js +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +23 -35
- package/dist/fleet/drain-entry.d.ts +28 -3
- package/dist/fleet/drain-entry.js +3 -0
- package/dist/fleet/serving-lease.js +13 -15
- 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 +17 -5
- 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 +5 -4
- 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 +1 -1
- 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 +27 -0
- package/dist/http/stored-body-replay.js +23 -0
- package/dist/leader/leader.d.ts +22 -1
- package/dist/leader/leader.js +4 -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-route-url.d.ts +13 -20
- package/dist/model-route-url.js +28 -24
- package/dist/observability/fail-open.d.ts +23 -6
- package/dist/observability/fail-open.js +8 -4
- 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 +13 -0
- package/dist/plugins/park-commit-watch.d.ts +19 -5
- package/dist/plugins/park-commit-watch.js +24 -17
- package/dist/router/route-orchestration.d.ts +4 -2
- package/dist/router/route-orchestration.js +2 -1
- package/dist/run-local.js +3 -1
- package/dist/runtime-governance.d.ts +44 -32
- package/dist/runtime-governance.js +23 -9
- package/dist/sighup-idle.d.ts +7 -1
- package/dist/sighup-idle.js +4 -4
- package/dist/task-settings.d.ts +37 -14
- package/dist/task-settings.js +20 -20
- 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 +2 -2
- package/scripts/drain-check-local.mjs +1 -1
package/dist/model-route-url.js
CHANGED
|
@@ -1,33 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildHttpUrlRefusal, httpUrlFlaws } from "./url-shape.js";
|
|
2
2
|
export const GATEWAY_URL_USERINFO_CODE = "config.gateway_url_userinfo";
|
|
3
3
|
export const MODEL_ROUTE_URL_INVALID_CODE = "config.model_route_url_invalid";
|
|
4
4
|
export const MODEL_ROUTE_URL_FRAGMENT_CODE = "config.model_route_url_fragment";
|
|
5
|
+
const MODEL_ROUTE_FORBIDS = ["userinfo", "fragment"];
|
|
5
6
|
export function modelRouteUrlRefusal(envKey, raw, credentialKnob) {
|
|
6
7
|
if (raw === "")
|
|
7
8
|
return undefined;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
9
|
+
const flaws = httpUrlFlaws(raw, MODEL_ROUTE_FORBIDS);
|
|
10
|
+
const first = flaws[0];
|
|
11
|
+
if (first === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
const head = buildHttpUrlRefusal(`env ${envKey}`, raw, flaws);
|
|
14
|
+
switch (first) {
|
|
15
|
+
case "not-http-url":
|
|
16
|
+
return (`${head} [${MODEL_ROUTE_URL_INVALID_CODE}] — ` +
|
|
17
|
+
"a model-route base URL is consumed by fetch, which only sends to an absolute hierarchical http(s) URL; " +
|
|
18
|
+
"half-written forms like \"http://user:pass@\", opaque forms like \"host:8000\" and other schemes are not one, and the first two also hide " +
|
|
19
|
+
"any credentials they carry from every credential-position check. " +
|
|
20
|
+
`Set ${envKey} to a full http(s)://host URL (and put the credential in ${credentialKnob}, never in the URL).`);
|
|
21
|
+
case "userinfo":
|
|
22
|
+
return (`${head} [${GATEWAY_URL_USERINFO_CODE}] — ` +
|
|
23
|
+
"WHATWG forbids constructing a fetch Request from a URL with credentials, so EVERY model call on this route " +
|
|
24
|
+
"fails deterministically at request-construction time (it used to look like a slow network flake: the error was " +
|
|
25
|
+
"retried for ~3 minutes before surfacing). " +
|
|
26
|
+
`Put the credential in ${credentialKnob} (or the matching header knob) instead of the URL, and keep the URL to ` +
|
|
27
|
+
"scheme/host/port/path.");
|
|
28
|
+
case "fragment":
|
|
29
|
+
return (`${head} [${MODEL_ROUTE_URL_FRAGMENT_CODE}] — ` +
|
|
30
|
+
"a fragment is never sent on the wire, so this route would silently target the root's own path and every " +
|
|
31
|
+
"endpoint suffix (/chat/completions, /v1/messages) would land inside the discarded part: the request goes out, " +
|
|
32
|
+
"may even return 200, and points somewhere else without saying so. " +
|
|
33
|
+
`Drop everything from the "#" on in ${envKey}. A per-deployment QUERY is fully supported and rides every ` +
|
|
34
|
+
"request (a gateway's `?api-version=…`); a fragment cannot be.");
|
|
30
35
|
}
|
|
31
|
-
return undefined;
|
|
32
36
|
}
|
|
33
37
|
//# sourceMappingURL=model-route-url.js.map
|
|
@@ -2,9 +2,22 @@ import type { Logger } from "./logger.js";
|
|
|
2
2
|
import type { Metrics } from "./metrics.js";
|
|
3
3
|
/**
|
|
4
4
|
* 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。
|
|
5
|
-
* 每项 `{ cls, note }`:`cls` 是判据表的分类(
|
|
6
|
-
* `P-DEBT
|
|
5
|
+
* 每项 `{ cls, note }`:`cls` 是判据表的分类(见模块头——只有这三类允许走本封装):`F`=纯体验/缓存回退,放行最坏后果只是"难看/不够好";
|
|
6
|
+
* `P-DEBT`=保护型欠账,方向明知不对、显式记债;`R`=裁定定界的放行(另带 `ruling` / `bound`)。`note` 说清这条兜底放行了什么、最坏后果是什么
|
|
7
|
+
* (写给未来读遥测的人,不写来历)。
|
|
7
8
|
*/
|
|
9
|
+
export type FailOpenEntry = {
|
|
10
|
+
readonly cls: "F";
|
|
11
|
+
readonly note: string;
|
|
12
|
+
} | {
|
|
13
|
+
readonly cls: "P-DEBT";
|
|
14
|
+
readonly note: string;
|
|
15
|
+
} | {
|
|
16
|
+
readonly cls: "R";
|
|
17
|
+
readonly ruling: string;
|
|
18
|
+
readonly bound: string;
|
|
19
|
+
readonly note: string;
|
|
20
|
+
};
|
|
8
21
|
export declare const FAIL_OPEN_TAGS: {
|
|
9
22
|
readonly "server.approval-ask-journal.compaction-failed": {
|
|
10
23
|
readonly cls: "F";
|
|
@@ -399,12 +412,16 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
399
412
|
readonly note: "7.120.0(`config-lkg.ts` 的 `landedPathOf`,`lkgReadDenyRows` 每次铸部署姿态座席时调;detail = `segment=<那一段的路径>`):`CONFIG_LKG_PATH` 覆写路径上有一段**目录在、却解不开**(悬空符号链接 / 链接环 / 读不了的目录:`realpath` 失败而 `lstat` 不说「不存在」)⇒ 这一段之下 LKG 真正会落在哪说不出来,读拒行只剩声明的拼写那一行(照旧在),真实落点那一行缺席。段**不存在**(`ENOENT` / `ENOTDIR`:还没建出来)是常态,不进这一臂;末段(文件本身)是悬空链接也不进(落盘的 rename 换掉的是那条目录项,文件就落在声明的拼写上)。归 P 的理由:这一行守的是 LKG 文件(含凭据),链接之后通了、LKG 写到链接目标下,模型按目标路径读得到它,直到重启重铸座席。归 DEBT 的理由:引擎没有「锚定单文件」的读拒行、座席只在 Runner 构造时铸一次,本服务说不出将来的落点;拿文件名在任意深度拒会把用户仓里同名文件一并挡掉。计数非零 = 去看 detail 点名的那一段(链接指向哪、卷挂了没有),修好后重启。";
|
|
400
413
|
};
|
|
401
414
|
readonly "server.fleet.serving-lease-unconfirmed": {
|
|
402
|
-
readonly cls: "
|
|
403
|
-
readonly
|
|
415
|
+
readonly cls: "R";
|
|
416
|
+
readonly ruling: "C-R296";
|
|
417
|
+
readonly bound: "FLEET_LEASE_OUTAGE_MAX_MS";
|
|
418
|
+
readonly note: "`fleet/serving-lease.ts` 的 `renewOnce`(租约生效时每一次续租;detail = `worker=<w> generation=<g> why=<不可达句 | verdict=unknown>`):中心这一次**没有确认**本实例该不该服务 —— 不可达 / 超时 / 非 2xx / 答复读不成判决,或答 `unknown`(此刻没有本实例的生命周期对象)。按设计**什么都不改**(规则 2 第三臂):到期之前照常 `granted`,过期后 `expired` 照常接单,直到过期满 `FLEET_LEASE_OUTAGE_MAX_MS`(`T_max`)才拒新工作(503 `state.lease_expired`,在飞不动、永不自停)。归 R:方向与上界由裁定写定 —— 实例只在中心权威终止(`denied`)或运维覆盖时被替换,`T_max` 只是引擎自己「失联后最多再接单多久」的准入上界,不是替换依据;没确认不是被拒。放行的最坏后果 = 一只其实已被取代的实例在中心失联期间多服务至多「租约剩余 + `T_max`」(中心的代际下限持久,恢复后第一次续租即拒)。计数长期保留:持续非零 = 中心长时间没确认本实例,去看 detail 的 why(连不上 / 中心不认识这只实例)。";
|
|
404
419
|
};
|
|
405
420
|
readonly "server.fleet.serving-lease-capability-absent": {
|
|
406
|
-
readonly cls: "
|
|
407
|
-
readonly
|
|
421
|
+
readonly cls: "R";
|
|
422
|
+
readonly ruling: "C-R296";
|
|
423
|
+
readonly bound: "FLEET_LEASE_OUTAGE_MAX_MS";
|
|
424
|
+
readonly note: "`fleet/serving-lease.ts` 的 `noteCenterCapabilities`(fleet 成员每一次中心能力读数;detail = `worker=<w> generation=<g> was=<enabled|none>`):中心答复体读得出、却**没有宣告** `servingLease`(老中心 `{ ok: true }` / 降级中心 / 位撤掉)⇒ 规则 1:租约不生效,`leaseState: none`,零续租、零 503 —— 这只 fleet 成员此刻**不受围栏**。答复体读不出不进这一臂(那是「没有读数」,上一次的位与钟原样,`fleet-client.ts` `capabilityReadingOf`);已被拒的实例不进(拒是黏的,位消失不复活)。归 R:能力门控是契约规则(中心不宣告 = 中心不执行围栏),裁定写定替换只来自中心权威终止或运维覆盖;位只开关围栏、不碰钟 —— 位缺席期间 `T_max` 的钟照走(最后一次授予的到期 / 本进程第一次见到位的时刻),位一回来即按它判,所以位的抖动绕不过 `FLEET_LEASE_OUTAGE_MAX_MS`。放行的最坏后果 = 中心不执行围栏期间,一只被取代的实例照常服务(这一段由中心的选择定界,不由引擎)。计数长期保留:非零 = 这台中心没宣告租约能力。";
|
|
408
425
|
};
|
|
409
426
|
readonly "server.fleet.announce-reply-unreadable": {
|
|
410
427
|
readonly cls: "P-DEBT";
|
|
@@ -372,12 +372,16 @@ export const FAIL_OPEN_TAGS = {
|
|
|
372
372
|
note: "7.120.0(`config-lkg.ts` 的 `landedPathOf`,`lkgReadDenyRows` 每次铸部署姿态座席时调;detail = `segment=<那一段的路径>`):`CONFIG_LKG_PATH` 覆写路径上有一段**目录在、却解不开**(悬空符号链接 / 链接环 / 读不了的目录:`realpath` 失败而 `lstat` 不说「不存在」)⇒ 这一段之下 LKG 真正会落在哪说不出来,读拒行只剩声明的拼写那一行(照旧在),真实落点那一行缺席。段**不存在**(`ENOENT` / `ENOTDIR`:还没建出来)是常态,不进这一臂;末段(文件本身)是悬空链接也不进(落盘的 rename 换掉的是那条目录项,文件就落在声明的拼写上)。归 P 的理由:这一行守的是 LKG 文件(含凭据),链接之后通了、LKG 写到链接目标下,模型按目标路径读得到它,直到重启重铸座席。归 DEBT 的理由:引擎没有「锚定单文件」的读拒行、座席只在 Runner 构造时铸一次,本服务说不出将来的落点;拿文件名在任意深度拒会把用户仓里同名文件一并挡掉。计数非零 = 去看 detail 点名的那一段(链接指向哪、卷挂了没有),修好后重启。",
|
|
373
373
|
},
|
|
374
374
|
"server.fleet.serving-lease-unconfirmed": {
|
|
375
|
-
cls: "
|
|
376
|
-
|
|
375
|
+
cls: "R",
|
|
376
|
+
ruling: "C-R296",
|
|
377
|
+
bound: "FLEET_LEASE_OUTAGE_MAX_MS",
|
|
378
|
+
note: "`fleet/serving-lease.ts` 的 `renewOnce`(租约生效时每一次续租;detail = `worker=<w> generation=<g> why=<不可达句 | verdict=unknown>`):中心这一次**没有确认**本实例该不该服务 —— 不可达 / 超时 / 非 2xx / 答复读不成判决,或答 `unknown`(此刻没有本实例的生命周期对象)。按设计**什么都不改**(规则 2 第三臂):到期之前照常 `granted`,过期后 `expired` 照常接单,直到过期满 `FLEET_LEASE_OUTAGE_MAX_MS`(`T_max`)才拒新工作(503 `state.lease_expired`,在飞不动、永不自停)。归 R:方向与上界由裁定写定 —— 实例只在中心权威终止(`denied`)或运维覆盖时被替换,`T_max` 只是引擎自己「失联后最多再接单多久」的准入上界,不是替换依据;没确认不是被拒。放行的最坏后果 = 一只其实已被取代的实例在中心失联期间多服务至多「租约剩余 + `T_max`」(中心的代际下限持久,恢复后第一次续租即拒)。计数长期保留:持续非零 = 中心长时间没确认本实例,去看 detail 的 why(连不上 / 中心不认识这只实例)。",
|
|
377
379
|
},
|
|
378
380
|
"server.fleet.serving-lease-capability-absent": {
|
|
379
|
-
cls: "
|
|
380
|
-
|
|
381
|
+
cls: "R",
|
|
382
|
+
ruling: "C-R296",
|
|
383
|
+
bound: "FLEET_LEASE_OUTAGE_MAX_MS",
|
|
384
|
+
note: "`fleet/serving-lease.ts` 的 `noteCenterCapabilities`(fleet 成员每一次中心能力读数;detail = `worker=<w> generation=<g> was=<enabled|none>`):中心答复体读得出、却**没有宣告** `servingLease`(老中心 `{ ok: true }` / 降级中心 / 位撤掉)⇒ 规则 1:租约不生效,`leaseState: none`,零续租、零 503 —— 这只 fleet 成员此刻**不受围栏**。答复体读不出不进这一臂(那是「没有读数」,上一次的位与钟原样,`fleet-client.ts` `capabilityReadingOf`);已被拒的实例不进(拒是黏的,位消失不复活)。归 R:能力门控是契约规则(中心不宣告 = 中心不执行围栏),裁定写定替换只来自中心权威终止或运维覆盖;位只开关围栏、不碰钟 —— 位缺席期间 `T_max` 的钟照走(最后一次授予的到期 / 本进程第一次见到位的时刻),位一回来即按它判,所以位的抖动绕不过 `FLEET_LEASE_OUTAGE_MAX_MS`。放行的最坏后果 = 中心不执行围栏期间,一只被取代的实例照常服务(这一段由中心的选择定界,不由引擎)。计数长期保留:非零 = 这台中心没宣告租约能力。",
|
|
381
385
|
},
|
|
382
386
|
"server.fleet.announce-reply-unreadable": {
|
|
383
387
|
cls: "P-DEBT",
|
|
@@ -173,6 +173,7 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
|
|
|
173
173
|
listPendingByTask(taskId: string, signal?: AbortSignal): Promise<AskRow[]>;
|
|
174
174
|
listPendingBySession(sessionId: string, owner: string | null, signal?: AbortSignal): Promise<AskRow[]>;
|
|
175
175
|
listByCall(call: AskCallCoordinate): Promise<AskRow[]>;
|
|
176
|
+
listParkingBySession(sessionId: string): Promise<AskRow[]>;
|
|
176
177
|
getAsk(askId: string): Promise<AskRow | null>;
|
|
177
178
|
getByIdempotencyKey(taskId: string, key: string): Promise<AskRow | null>;
|
|
178
179
|
getBatch(batchId: string): Promise<BatchRow | null>;
|
|
@@ -256,6 +256,9 @@ export class FileApprovalAskStore {
|
|
|
256
256
|
listByCall(call) {
|
|
257
257
|
return this.core.listByCall(call);
|
|
258
258
|
}
|
|
259
|
+
listParkingBySession(sessionId) {
|
|
260
|
+
return this.core.listParkingBySession(sessionId);
|
|
261
|
+
}
|
|
259
262
|
getAsk(askId) {
|
|
260
263
|
return this.core.getAsk(askId);
|
|
261
264
|
}
|
|
@@ -99,6 +99,7 @@ export declare class InMemoryApprovalAskStore implements ApprovalAskStore {
|
|
|
99
99
|
listPendingByTask(taskId: string, signal?: AbortSignal): Promise<AskRow[]>;
|
|
100
100
|
listPendingBySession(sessionId: string, owner: string | null, signal?: AbortSignal): Promise<AskRow[]>;
|
|
101
101
|
listByCall(call: AskCallCoordinate): Promise<AskRow[]>;
|
|
102
|
+
listParkingBySession(sessionId: string): Promise<AskRow[]>;
|
|
102
103
|
getAsk(askId: string): Promise<AskRow | null>;
|
|
103
104
|
getByIdempotencyKey(taskId: string, key: string): Promise<AskRow | null>;
|
|
104
105
|
resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
|
|
@@ -310,6 +310,12 @@ export class InMemoryApprovalAskStore {
|
|
|
310
310
|
.sort((a, b) => a.createdAtMs - b.createdAtMs || (a.askId < b.askId ? -1 : a.askId > b.askId ? 1 : 0))
|
|
311
311
|
.map((r) => ({ ...r }));
|
|
312
312
|
}
|
|
313
|
+
async listParkingBySession(sessionId) {
|
|
314
|
+
return [...this.asks.values()]
|
|
315
|
+
.filter((r) => r.sessionId === sessionId && r.state === "PARKING")
|
|
316
|
+
.sort((a, b) => a.createdAtMs - b.createdAtMs || (a.askId < b.askId ? -1 : a.askId > b.askId ? 1 : 0))
|
|
317
|
+
.map((r) => ({ ...r }));
|
|
318
|
+
}
|
|
313
319
|
async getAsk(askId) {
|
|
314
320
|
const r = this.asks.get(askId);
|
|
315
321
|
return r ? { ...r } : null;
|
|
@@ -478,6 +478,17 @@ export interface ApprovalAskStore {
|
|
|
478
478
|
* `(task_id, state)` 的 `task_id` 前缀(一条 run 的行数 = 真出过卡的问数;grant 代答的问不落行),不另加索引。
|
|
479
479
|
*/
|
|
480
480
|
listByCall(call: AskCallCoordinate): Promise<AskRow[]>;
|
|
481
|
+
/**
|
|
482
|
+
* 一个会话里**仍在 `PARKING`** 的全部行(序 = `created_at_ms ASC, ask_id ASC`,确定性)。
|
|
483
|
+
*
|
|
484
|
+
* 唯一消费者 = 对账收敛器的 park 提交那一拍(`approval-reconciler.ts` 的 `bindCommittedPark`):一只审批停车行提交了,收敛器要对**这次调用**
|
|
485
|
+
* 的 PARKING 行即时跑判据 1。停车行只陈述会话与调用 id(没有 taskId / legKey ⇒ `listByCall` 用不上);`listByState` 带 LIMIT 且按队列序,
|
|
486
|
+
* 新行排在队尾会被截掉。按会话读是**判据 1 能命中的行的超集**:判据 1 的候选读口按 `ask.sessionId` 查停车行,命中要求停车行的会话就是这一列。
|
|
487
|
+
*
|
|
488
|
+
* 不带租户门(内部读口,不上 wire):租户门在判据 1 的候选读口上(scope = 行的 owner)。索引走既有 `idx_ask_session_state (session_id, state)`。
|
|
489
|
+
* 三 twin 同答:SQL twin 读后按会话逐字再滤一遍(MySQL 协议 VARCHAR 等值的 PAD SPACE,与 `listByCall` 同一条出口对齐)。
|
|
490
|
+
*/
|
|
491
|
+
listParkingBySession(sessionId: string): Promise<AskRow[]>;
|
|
481
492
|
getAsk(askId: string): Promise<AskRow | null>;
|
|
482
493
|
/** 回决幂等回放的读口。🔴 `taskId` **必填**且是键的第一维——UNIQUE 已改 `(task_id, idempotency_key)`
|
|
483
494
|
* (车4 §12-E),裸按 key 全局查在多租户库上既可能回**别人 task** 的行(存在性外泄),也可能在同 key
|
|
@@ -636,6 +647,7 @@ export declare class SqlApprovalAskStore implements ApprovalAskStore {
|
|
|
636
647
|
listPendingByTask(taskId: string, signal?: AbortSignal): Promise<AskRow[]>;
|
|
637
648
|
listPendingBySession(sessionId: string, owner: string | null, signal?: AbortSignal): Promise<AskRow[]>;
|
|
638
649
|
listByCall(call: AskCallCoordinate): Promise<AskRow[]>;
|
|
650
|
+
listParkingBySession(sessionId: string): Promise<AskRow[]>;
|
|
639
651
|
getAsk(askId: string): Promise<AskRow | null>;
|
|
640
652
|
getByIdempotencyKey(taskId: string, key: string): Promise<AskRow | null>;
|
|
641
653
|
deferReconcile(askId: string, expectedState: AskState, expectedRev: number, nowMs: number): Promise<boolean>;
|
|
@@ -679,6 +679,10 @@ export class SqlApprovalAskStore {
|
|
|
679
679
|
const { rows } = await this.db.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND leg_key = ? AND tool_call_id = ? AND source_task_id = ? ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND leg_key = $2 AND tool_call_id = $3 AND source_task_id = $4 ORDER BY created_at_ms ASC, ask_id ASC`), [call.taskId, call.legKey, call.toolCallId, call.sourceTaskId]);
|
|
680
680
|
return rows.map(mapAskRow).filter((r) => askRowOnCall(r, call));
|
|
681
681
|
}
|
|
682
|
+
async listParkingBySession(sessionId) {
|
|
683
|
+
const { rows } = await this.db.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'PARKING' ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'PARKING' ORDER BY created_at_ms ASC, ask_id ASC`), [sessionId]);
|
|
684
|
+
return rows.map(mapAskRow).filter((r) => r.sessionId === sessionId);
|
|
685
|
+
}
|
|
682
686
|
async getAsk(askId) {
|
|
683
687
|
const { rows } = await this.db.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = $1`), [
|
|
684
688
|
askId,
|
|
@@ -149,6 +149,19 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
149
149
|
"出路 = 运维在库里把那只桶的那一行删掉(整桶 OCC 回写,指纹按 `buildRuleBucketChecksum` 重算)",
|
|
150
150
|
reportKeys: ["undurableScopeRuleRows", "undurableScopeRuleBucketsDeferred", "undurableScopeRuleBucketsUnreadable"],
|
|
151
151
|
},
|
|
152
|
+
{
|
|
153
|
+
id: "M12",
|
|
154
|
+
introducedIn: "7.122.0",
|
|
155
|
+
releasedOn: "2026-10-09",
|
|
156
|
+
removableAfter: "2026-11-08",
|
|
157
|
+
removal: "删模式词读法记号的判据与拒臂:`http/stored-body-replay.ts` 的 `permissionModeReadingUnstated`;`http/checkpoint-ctx.ts` 的 `permissionModeReadingStated` / `permissionModeReadingRefusal` / `PERMISSION_MODE_READINGS`、" +
|
|
158
|
+
"`StoredBodyReplayRefusal` 的 `permission_mode_reading` 臂及其日志字段与答复臂、`buildCheckpointCtx` 与 `withPermissionModeRewritten` 两处记号写点" +
|
|
159
|
+
"(后者删后 `resume-legs.ts` `drivePlanReview` 里对它的那一处调用回到只换体的 `{ ...ctx, body }`)、`CheckpointCtx.permissionModeReading`、" +
|
|
160
|
+
"`http/stored-body-replay.ts` `storedBodyReplayRefusal` 的 `autonomy` 形参(只服务这一问)与四个问点(`resume-legs.ts` 两处、`approval-sweeps.ts`、`routes/runs.ts`)传 `deps.config.autonomy` 的那一句",
|
|
161
|
+
afterRemoval: "没有响亮臂:删码之后仍停驻、在 7.121.x 及以前写下的行(把 `APPROVAL_TERMINAL_BACKSTOP_MS` 调长的部署,或跳过了带本条的版本直接升的库)在设了 " +
|
|
162
|
+
"`AUTONOMY=plan` 的部署上按体里的原词重放(运维的 plan 不再被按段重钳)—— 升级前把这些停驻的 run 决掉 / 取消,或升级窗内设 `AUTONOMY=read-only`",
|
|
163
|
+
reportKeys: [],
|
|
164
|
+
},
|
|
152
165
|
];
|
|
153
166
|
export function migrationWindowOpen(window, today) {
|
|
154
167
|
return window.removableAfter === null || today <= window.removableAfter;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 「park 落盘」,有序关停若把前者当后者,就会在 `put` 之下关掉连接池(修前病:park 行没提交,审计账本却已记「结清」)。
|
|
8
8
|
*
|
|
9
9
|
* 本模块给店加**一层**装饰(两店同用,不读 core 私有键 —— [ref]):
|
|
10
|
-
* ① **计在飞 `put
|
|
10
|
+
* ① **计在飞 `put`**(连同订阅者在这一拍的工作,见下):`quiesce(deadlineMs)` 等到零或到期(绝对时刻,`Date.now()` 域),如实返回残余数 —— 收尾链在
|
|
11
11
|
* `backend.close()` 之前等它(`boot/shutdown.ts`),审批协调器的关停收尾也等它(`ToolApprovalCoordinator.drainForShutdown`);
|
|
12
12
|
* ② **报 park 提交** —— 「这只店里有了这一行」的两种观察,同一件事实:一次 `put` 落定(resolve);或一次 `put` 抛错之后,同一 token 的
|
|
13
13
|
* `get` 读到了行(写已落、回执丢了 —— core 的挂起序正是用这一次回读确认提交的,本层观察的就是那一次读,不另读;车内 codex r1)。
|
|
@@ -15,8 +15,14 @@
|
|
|
15
15
|
* branch on `kind` first」)⇒ 把**停车行自己陈述的**调用身份交给订阅者({@link CommittedParkCalls})。抛错且回读不到 = 没提交,不报。
|
|
16
16
|
* 身份取自**读到的那一行**(不是本进程想写的那一只):哪怕 token 撞了别人的行,报出去的也只是那一行真握着的调用。
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* 订阅者契约:**不得抛、不得拒**。订阅者跑在 `put` 已提交之后、返回给 core 之前,`put` **等它跑完**才返回 —— 一只抛错 / 拒绝的订阅者会把一次
|
|
19
|
+
* **已提交**的 park 报成失败;一只不收尾的订阅者会把 core 的停驻挂住(订阅者自己给自己的工作上墙钟上限)。
|
|
20
|
+
* 两个订阅者,各管一件「这只停车行提交了」之后的事:
|
|
21
|
+
* · 审批协调器 —— 有序关停下写候着的审计 settle 行(sink 自吞失败并按 [ref] 留痕,同 tag 扩射程,见协调器);
|
|
22
|
+
* · 对账收敛器 —— S-1280:这次调用的 `PARKING` 行在这一拍收口成 `PARKED`(判据 1 那一只判官的即时调用,`approval-reconciler.ts`
|
|
23
|
+
* `bindCommittedPark`;它自带墙钟预算、失败只留痕,周期扫兜底)。`put` 等它(至多到它的预算)⇒ 健康时 core 的停驻返回之时行已收口;
|
|
24
|
+
* 预算到点它就交还 `put`、不再发新的店调用,已发出的那一次经 {@link ParkCommitWatch.track} 留在在飞里 ⇒ 收尾链的 `quiesce`
|
|
25
|
+
* 在 `backend.close()` 之前等到它落定。
|
|
20
26
|
*/
|
|
21
27
|
import type { CheckpointStore } from "@sema-agent/core";
|
|
22
28
|
/**
|
|
@@ -33,9 +39,17 @@ export interface CommittedParkCalls {
|
|
|
33
39
|
readonly toolCallIds: readonly string[];
|
|
34
40
|
}
|
|
35
41
|
/** 装饰层的观察面(`create*` 产出的活对象的一半;另一半是装饰后的店本身)。 */
|
|
42
|
+
/** park 提交的订阅者(见模块头「订阅者契约」:不得抛、不得拒;异步的那一只 `put` 等它跑完)。 */
|
|
43
|
+
export type ParkCommittedListener = (calls: CommittedParkCalls) => void | Promise<void>;
|
|
36
44
|
export interface ParkCommitWatch {
|
|
37
|
-
/** 订阅 park 提交(
|
|
38
|
-
onParkCommitted(listener:
|
|
45
|
+
/** 订阅 park 提交(见模块头「订阅者契约」)。 */
|
|
46
|
+
onParkCommitted(listener: ParkCommittedListener): void;
|
|
47
|
+
/**
|
|
48
|
+
* 把订阅者在 `put` 交还之后**仍可能在跑**的那段工作记进在飞:`quiesce` 等它落定(成功或失败都算落定),返回同一个 promise。
|
|
49
|
+
* 用途(S-1280):订阅者给自己的工作上了墙钟预算,到点就把 `put` 交还 core —— 而此刻已经发出的那次店调用还没回来;不记进来,
|
|
50
|
+
* 收尾链会读到「静默」并在它底下关池。
|
|
51
|
+
*/
|
|
52
|
+
track<T>(work: Promise<T>): Promise<T>;
|
|
39
53
|
/** 等在飞 `put` 归零或到 `deadlineMs`(绝对时刻,`Date.now()` 域);返回那一刻的残余在飞数(0 = 静默了)。从不抛。 */
|
|
40
54
|
quiesce(deadlineMs: number): Promise<{
|
|
41
55
|
inFlight: number;
|
|
@@ -12,28 +12,16 @@ export function createParkCommitWatch(inner) {
|
|
|
12
12
|
let inFlight = 0;
|
|
13
13
|
const idleWaiters = new Set();
|
|
14
14
|
const unconfirmed = new Set();
|
|
15
|
-
const notify = (row) => {
|
|
15
|
+
const notify = async (row) => {
|
|
16
16
|
const calls = committedParkCallsOf(row);
|
|
17
17
|
if (calls !== undefined)
|
|
18
18
|
for (const l of listeners)
|
|
19
|
-
l(calls);
|
|
19
|
+
await l(calls);
|
|
20
20
|
};
|
|
21
|
-
const
|
|
21
|
+
const counted = async (work) => {
|
|
22
22
|
inFlight += 1;
|
|
23
23
|
try {
|
|
24
|
-
|
|
25
|
-
await inner.put(token, cp);
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
unconfirmed.add(token);
|
|
29
|
-
if (unconfirmed.size > UNCONFIRMED_PUT_TOKENS_MAX) {
|
|
30
|
-
const oldest = unconfirmed.values().next();
|
|
31
|
-
if (oldest.done !== true)
|
|
32
|
-
unconfirmed.delete(oldest.value);
|
|
33
|
-
}
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
notify(cp);
|
|
24
|
+
return await work();
|
|
37
25
|
}
|
|
38
26
|
finally {
|
|
39
27
|
inFlight -= 1;
|
|
@@ -42,13 +30,28 @@ export function createParkCommitWatch(inner) {
|
|
|
42
30
|
wake();
|
|
43
31
|
}
|
|
44
32
|
};
|
|
33
|
+
const put = (token, cp) => counted(async () => {
|
|
34
|
+
try {
|
|
35
|
+
await inner.put(token, cp);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
unconfirmed.add(token);
|
|
39
|
+
if (unconfirmed.size > UNCONFIRMED_PUT_TOKENS_MAX) {
|
|
40
|
+
const oldest = unconfirmed.values().next();
|
|
41
|
+
if (oldest.done !== true)
|
|
42
|
+
unconfirmed.delete(oldest.value);
|
|
43
|
+
}
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
await notify(cp);
|
|
47
|
+
});
|
|
45
48
|
const get = async (token) => {
|
|
46
49
|
if (!unconfirmed.has(token))
|
|
47
50
|
return inner.get(token);
|
|
48
51
|
unconfirmed.delete(token);
|
|
49
52
|
const row = await inner.get(token);
|
|
50
53
|
if (row !== null)
|
|
51
|
-
notify(row);
|
|
54
|
+
await counted(() => notify(row));
|
|
52
55
|
return row;
|
|
53
56
|
};
|
|
54
57
|
const store = new Proxy(inner, {
|
|
@@ -65,6 +68,10 @@ export function createParkCommitWatch(inner) {
|
|
|
65
68
|
onParkCommitted(listener) {
|
|
66
69
|
listeners.push(listener);
|
|
67
70
|
},
|
|
71
|
+
track(work) {
|
|
72
|
+
void counted(() => work).catch(() => undefined);
|
|
73
|
+
return work;
|
|
74
|
+
},
|
|
68
75
|
quiesce(deadlineMs) {
|
|
69
76
|
if (inFlight === 0 || Date.now() >= deadlineMs)
|
|
70
77
|
return Promise.resolve({ inFlight });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Autonomy } from "../runtime-governance.js";
|
|
2
2
|
import type { GovernanceOverrides } from "../governance-overrides.js";
|
|
3
3
|
export type OrchestrationMode = "single" | "supervisor" | "team";
|
|
4
4
|
/** The DETERMINISTIC signals the routing decision reads (extracted by {@link deriveRoutingSignals} from the
|
|
@@ -54,7 +54,9 @@ export declare function deriveRoutingSignals(ctx: RoutingContext): RoutingSignal
|
|
|
54
54
|
* derivation + {@link deriveRoutingSignals} + {@link routeOrchestration} in one tested call; main.ts's resolveSpec
|
|
55
55
|
* calls this). Signal mapping:
|
|
56
56
|
* - `isolatedExecEnv` ⟺ a sandboxed remote-exec backend (`e2b`/`k8s` Kata) — `ssh`/`host`/`adb` are NOT isolated.
|
|
57
|
-
* - hand tools (write_file/bash) are mounted only when an executionEnv exists AND
|
|
57
|
+
* - hand tools (write_file/bash) are mounted only when an executionEnv exists AND the autonomy does not clamp the hands
|
|
58
|
+
* read-only (read off {@link autonomyOverrides} — the one place an autonomy word becomes a hands clamp; `plan` is the
|
|
59
|
+
* engine's plan mode, whose hands stay mounted and are gated at call time, so it is not read-only here).
|
|
58
60
|
* - `canEgress` is left false in v1 (per-tool egress detection deferred; danger derives from non-isolation).
|
|
59
61
|
* - `delegatedOrLongHorizon` is false on the single-task path (the (b) value lives on the /v1/leader path).
|
|
60
62
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { COARSE_SHELL_TOOLS } from "@sema-agent/core";
|
|
2
|
+
import { autonomyOverrides } from "../runtime-governance.js";
|
|
2
3
|
import { operatorAlwaysHumanAsk } from "../operator-ask.js";
|
|
3
4
|
import { heldRuleName } from "../tool-rule-names.js";
|
|
4
5
|
export function routeOrchestration(s) {
|
|
@@ -20,7 +21,7 @@ export function deriveRoutingSignals(ctx) {
|
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
export function routeServiceTask(input) {
|
|
23
|
-
const readOnlyHands = input.autonomy
|
|
24
|
+
const readOnlyHands = autonomyOverrides(input.autonomy).handsReadOnly === true;
|
|
24
25
|
return routeOrchestration(deriveRoutingSignals({
|
|
25
26
|
...(input.explicitTeam ? { explicitMode: "team" } : {}),
|
|
26
27
|
...(input.autonomy ? { autonomy: input.autonomy } : {}),
|
package/dist/run-local.js
CHANGED
|
@@ -14,7 +14,7 @@ import { withUnattendedLeg } from "./unattended-permission-mode.js";
|
|
|
14
14
|
import { buildUnattendedPolicyRefusal } from "./tool-approval.js";
|
|
15
15
|
import { createBrain } from "./brain.js";
|
|
16
16
|
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, buildApprovalGrantNotice, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
|
|
17
|
-
import { applyRuntimeGovernance } from "./runtime-governance.js";
|
|
17
|
+
import { applyRuntimeGovernance, autonomyModeFloor } from "./runtime-governance.js";
|
|
18
18
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
19
19
|
import { taskWallClockSec } from "./task-workflow.js";
|
|
20
20
|
import { applyCatalogToSource } from "./capabilities/center-prompts.js";
|
|
@@ -633,7 +633,9 @@ export async function runLocal(argv, deps = {}) {
|
|
|
633
633
|
...(maxCostUsd !== undefined ? { maxCostUsd } : {}),
|
|
634
634
|
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
635
635
|
};
|
|
636
|
+
const entryFloor = autonomyModeFloor(config.autonomy);
|
|
636
637
|
const spec = withUnattendedLeg(applyLocalGovernance({
|
|
638
|
+
...(entryFloor !== undefined ? { permissionMode: entryFloor } : {}),
|
|
637
639
|
objective: mention.cleanedObjective,
|
|
638
640
|
sessionId,
|
|
639
641
|
backgroundScope: "session",
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
* toolPolicy rule is `combinePolicies(base, override)` (deny-wins) — the command gate can only ADD denies/asks
|
|
15
15
|
* on top of the baseline, never clear it.
|
|
16
16
|
*
|
|
17
|
-
* TRAP #2 — `autonomy` must EXPAND INTO
|
|
18
|
-
* `toolPolicy`), NOT become a parallel enum on the spec (design-research §10: a parallel `autonomy`
|
|
19
|
-
* `handsReadOnly:true` would conflict). The expansion is tighten-only and routed through `tightenTaskSpec`, so
|
|
17
|
+
* TRAP #2 — `autonomy` must EXPAND INTO core primitives (`handsReadOnly` + `writeFace` / `permissionMode` /
|
|
18
|
+
* `shellGate` / a `toolPolicy`), NOT become a parallel enum on the spec (design-research §10: a parallel `autonomy`
|
|
19
|
+
* field + `handsReadOnly:true` would conflict). The expansion is tighten-only and routed through `tightenTaskSpec`, so
|
|
20
20
|
* `auto` (the loosest mode) can never LOOSEN a stricter base — `tightenTaskSpec` THROWS instead.
|
|
21
21
|
*
|
|
22
22
|
* ⚠️ `commandPolicy` is a COARSE name-level filter, NOT a sandbox. `sh -c …` / `$(…)` / pipes / `env X=…` /
|
|
23
23
|
* path-prefixed commands all bypass the argv[0] match (they route to `ask`, fail-closed, but are not blocked by
|
|
24
24
|
* name). Real isolation is the `executionEnv` sandbox. Same caveat core documents on `createCoarseCommandNamePolicy`.
|
|
25
25
|
*/
|
|
26
|
-
import { type ShellGateDoctrine, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
26
|
+
import { type PermissionMode, type ShellGateDoctrine, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
27
27
|
import { GovernanceConfig } from "@sema-agent/settings-schema";
|
|
28
28
|
import { type GovernanceAskMarks } from "./governance-ask-marks.js";
|
|
29
29
|
import { type GovernanceOverrides } from "./governance-overrides.js";
|
|
@@ -33,26 +33,6 @@ export declare const AUTONOMY_MODES: ["read-only", "ask", "plan", "auto"];
|
|
|
33
33
|
* 契约给 `command` 加一条 refine / 给 `decision` 加一个判词,本仓的型当场跟上(或在下面的
|
|
34
34
|
* `RANK` / 穷尽 switch 上编译红),而不是靠人记得把手写联合改一遍。 */
|
|
35
35
|
export type CommandRule = NonNullable<GovernanceConfig["commandPolicy"]>[number];
|
|
36
|
-
/** True when `name` names a DELEGATION tool — the subagent tool(CC-187 canonical `"Agent"`,core
|
|
37
|
-
* `DEFAULT_SUBAGENT_TOOL_NAME`)。5.0.0 [ref]:折叠面退役,RAW 比对 core 单源常量——旧名("Task")
|
|
38
|
-
* 在 core roster 层响亮 miss,不再需要归一;单源常量本身就防了当年 1.202 改名时裸字符串失配的
|
|
39
|
-
* fail-open(那个教训的解=引用常量,不是折叠)。 */
|
|
40
|
-
export declare function isDelegationToolName(name: string): boolean;
|
|
41
|
-
/** The hands-read-only DELEGATION STRIP (the main.ts governed-spec site, extracted here so the actual filter —
|
|
42
|
-
* not just the name predicate — is pinned by tests): a read-only (plan-mode) run does not mount a delegation
|
|
43
|
-
* tool at all.
|
|
44
|
-
*
|
|
45
|
-
* 🔴 原始理由已被上游收编(2026-08-05 亲验安装包 core 5.13.x):子任务 spec 现在**继承** `handsReadOnly`
|
|
46
|
-
* (`dist/agents/subagent.js` 的 `...(ctx.handsReadOnly === true ? { handsReadOnly: true } : {})`,resume
|
|
47
|
-
* resume 腿同款)与 `shellGate`(`dist/core/runner/prepare-task.js` 的 `inheritedGateForChildren`),所以
|
|
48
|
-
* 「子任务会拿到可写的手」这个当年的缺口在 core 侧已经补上。本 strip 因此是**同向的第二层**(更严,不更松):
|
|
49
|
-
* 它保证 read-only 一档下连委派工具面都不铸,与继承是否存在无关。拆掉它是一次行为变更(read-only 任务将
|
|
50
|
-
* 重新能委派),需要单独决定——不要因为「上游修好了」就顺手删。
|
|
51
|
-
*
|
|
52
|
-
* Identity (the same array) when nothing matches, so the caller's spec-object churn stays minimal. */
|
|
53
|
-
export declare function stripDelegationTools<T extends {
|
|
54
|
-
name: string;
|
|
55
|
-
}>(tools: readonly T[]): T[];
|
|
56
36
|
/** The operator autonomy mode (registry `governance.autonomy`) —— 契约派生(见 {@link AUTONOMY_MODES})。 */
|
|
57
37
|
export type Autonomy = NonNullable<GovernanceConfig["autonomy"]>;
|
|
58
38
|
/** 导出给 `test/coarse-shell-tools-mirror.test.ts` 的同源钉用(见那里的顶注)。 */
|
|
@@ -99,20 +79,33 @@ export declare function validateCommandRules(rules: CommandRule[]): string[];
|
|
|
99
79
|
*/
|
|
100
80
|
export declare function compileCommandPolicyLayers(rules: CommandRule[] | undefined): ToolPolicy[];
|
|
101
81
|
/**
|
|
102
|
-
* Expand an autonomy mode into TIGHTEN-ONLY `TaskSpec`
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
82
|
+
* Expand an autonomy mode into the TIGHTEN-ONLY `TaskSpec` FENCES that are re-folded on EVERY leg (first run and every
|
|
83
|
+
* resume rebuild) — the primitives, TRAP #2:
|
|
84
|
+
* - `read-only` → `handsReadOnly: true` **+ `writeFace: "roots"`** (read, never mutate; the §6 verifier read-only
|
|
85
|
+
* boundary). The hands clamp travels to every delegated child by core itself (`childClampSeats` — tighten-only), so the
|
|
86
|
+
* delegation tool stays mounted: a read-only run can delegate, and its children are read-only too.
|
|
106
87
|
* - `ask` → `shellGate: "always"` (every `Bash` command tightens to an `irreversible_ask` durable suspend —
|
|
107
88
|
* the fail-closed default for an unattended deployment with no parsed classifier).
|
|
108
|
-
* - `auto` / `undefined` → `{}` (
|
|
89
|
+
* - `plan` / `auto` / `undefined` → `{}` (nothing re-folded per leg; still subject to the deployment baseline + commandPolicy).
|
|
109
90
|
*
|
|
110
|
-
* 🔴
|
|
91
|
+
* 🔴 **规则:按段重折的只有静态围栏,模式词不在这里。** 只读手 + roots、shell 地板没有出口 —— 每一段续跑都该在,所以每一段都折。
|
|
92
|
+
* `plan` 是有出口的**生命周期词**(`plan_review` 把计划带出 plan),不是围栏:它是**提交入口的模式词地板**({@link autonomyModeFloor}),
|
|
93
|
+
* 盖在每一个 spec 出生点一次:受理门盖进存储的提交体(`task-settings.ts` `stampEntryPermissionMode`)、run-local 盖在它自建的 spec 上、
|
|
94
|
+
* leader run 盖在它出生时的入口体上(`leader/run-entry.ts`;入口词是 `plan` 的 leader run 进不了那条车道 —— worker 会话没有计划审阅通道 ⇒ 受理口响亮拒)。此后一条 run 的模式词
|
|
95
|
+
* 只有存储体一个源,写者是每一个出生点(新的持久化写都经 `http/checkpoint-ctx.ts` `buildCheckpointCtx`)与 `plan_review` 写回。按段折一个模式词 = 第二个源:被批准的计划会在下一次挂起→续跑
|
|
96
|
+
* 被拉回 plan。收紧入参的型上因此没有 `permissionMode`(`governance-overrides.ts`)。(上一个发布版本里本档不碰模式词,按段折的是只读手 +
|
|
97
|
+
* 写面 `roots`、与 `read-only` 同形 —— 运维 plan 那时根本不进 plan 模式,见下一段。)
|
|
98
|
+
*
|
|
99
|
+
* 🔴 **为什么 `plan` 不再与 `read-only` 同形**:修前两档同映「只读手 + roots」,外加 `resolve-spec.ts` 见只读手就剥委派工具 —— 于是 `plan` 有三套
|
|
100
|
+
* 实现(运维 plan = 只读手、用户选 plan = 模式词 + 只读手 + 剥委派、模型自进 plan = 只有模式词),而剥委派撞上请求体的 `agents` 就是 core 的
|
|
101
|
+
* `config.agents.no_delegation_tool` 整轮拒。现在 plan 只有引擎一个判官,`read-only` 只是只读手这一个词。
|
|
102
|
+
*
|
|
103
|
+
* 🔴 **为什么 `read-only` 同时说 `writeFace:"roots"`**([ref] 随批;不是补丁,是把这一档的表态说全):
|
|
111
104
|
* core 7.18.0 的 `resolveContainmentFace` 第 1 行把「只读(verifier)挂载 × 任务层 `writeFace:"open"`」
|
|
112
105
|
* 判成**真实矛盾**并**抛** `config.write_face_readonly_conflict`(理由逐字:那种挂载根本不带写面,
|
|
113
106
|
* 声明一个开放写面 = 为一次调用没有的工具表态)。而 `permissionMode:"bypassPermissions"` 自 [ref] 起
|
|
114
107
|
* 会在阶段④铸出 `writeFace:"open"` —— 于是「`AUTONOMY=read-only` 的部署 × 一个 bypass 请求」这一格
|
|
115
|
-
* 不修就是**整条 run
|
|
108
|
+
* 不修就是**整条 run 当场配置错误失败**(`plan` 一档不需要它:提交入口把模式词钳成 `plan`,bypass 那一列根本不生效)。
|
|
116
109
|
*
|
|
117
110
|
* 修在这里而不是在写点加一条「若 handsReadOnly 则别写」的判据:两个字段**在这一档里是同一件事**
|
|
118
111
|
* (「这条 run 没有写面」),而 `tightenTaskSpec` 本来就把 `writeFace` 当 tighten-only 安全字段收
|
|
@@ -122,6 +115,25 @@ export declare function compileCommandPolicyLayers(rules: CommandRule[] | undefi
|
|
|
122
115
|
* 它照样把键写出来(不是「只在有 open 时才压」——那又是一条判据)。
|
|
123
116
|
*/
|
|
124
117
|
export declare function autonomyOverrides(autonomy: Autonomy | undefined): GovernanceOverrides;
|
|
118
|
+
/**
|
|
119
|
+
* 运维 autonomy 给**提交入口**的模式词地板 —— 唯一属主(规则全文见 {@link autonomyOverrides} 顶注)。`plan` ⇒ `"plan"`:每个新提交的 run
|
|
120
|
+
* 以 plan 起步,请求的词按 core 的模式秩钳到它之下;其余三档与缺席 ⇒ 无地板(它们的表态全在按段的围栏里)。
|
|
121
|
+
* 读者:三个 spec 出生点 —— 受理门(`http/admission.ts`,经 `task-settings.ts` `stampEntryPermissionMode` 盖进存储体)、run-local 自建的 spec、
|
|
122
|
+
* leader run 的入口体(`leader/run-entry.ts` `leaderRunEntry`,同一只盖章;leader 的 HTTP 受理口用它判「这条车道收不收」);能力面的两位 ——
|
|
123
|
+
* `taskWriteFaceOpen` 的治理半({@link autonomyKeepsOpenWriteFace})与 `permissionModeAuto.armed` 的意图(`auto-mode-face.ts`),两位都经 core
|
|
124
|
+
* `clampPermissionMode` 读「地板把请求词钳成什么」,与盖章同一只钳;存量停驻体能否重放的第三问(`http/stored-body-replay.ts`:没有当前读法记号的行,
|
|
125
|
+
* 体的生效词被此刻的地板钳会变 ⇒ 拒)。
|
|
126
|
+
* 闭集**穷尽** switch:契约给 autonomy 加一档 ⇒ 这里编译红([ref])。
|
|
127
|
+
*/
|
|
128
|
+
export declare function autonomyModeFloor(autonomy: Autonomy | undefined): PermissionMode | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* 运维 autonomy 留不留得住请求层「掀开写围栏」那条声明(`permissionMode:"bypassPermissions"`,引擎模式表那一列 `writeFace:"open"`)——
|
|
131
|
+
* 能力面 `taskWriteFaceOpen` 的治理那一半。判据读**两只属主本身**,与一条真提交走的路同序:bypass 声明先过入口地板(core
|
|
132
|
+
* `clampPermissionMode`,与受理门的盖章同一只钳;地板缺席 ⇒ 原样),再交给本档的按段围栏(`tightenGoverned` → core `tightenTaskSpec`),
|
|
133
|
+
* 看折出来的 spec 还开不开写面 —— 显式席 `"roots"` 胜过模式列(`read-only`),入口词被钳成别的词时读那个词的模式列(`plan`)。
|
|
134
|
+
* 本仓不另写一张「哪几档钳写面」的表:档位怎么钳 / 怎么折由两只属主说,写面开不开由引擎的模式表说。
|
|
135
|
+
*/
|
|
136
|
+
export declare function autonomyKeepsOpenWriteFace(autonomy: Autonomy | undefined): boolean;
|
|
125
137
|
/**
|
|
126
138
|
* [ref]:**部署级**判据 —— 本部署的治理层自己是否把 shellGate 抬到 `"always"`。
|
|
127
139
|
*
|
|
@@ -207,7 +219,7 @@ export declare function governanceOriginOf(gate: {
|
|
|
207
219
|
* Apply the operator's runtime governance (autonomy + commandPolicy + manualModeShellGate + the guard set) onto a base
|
|
208
220
|
* `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link tightenTaskSpec} call: commandPolicy compiles to a `toolPolicy`
|
|
209
221
|
* override (combined onto the baseline by `tightenTaskSpec` — TRAP #1), and autonomy expands to
|
|
210
|
-
* `handsReadOnly` / `shellGate` overrides (TRAP #2). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any
|
|
222
|
+
* `handsReadOnly` + `writeFace` / `shellGate` overrides (TRAP #2; the plan floor is the submission entrance's, never a per-leg fold). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any
|
|
211
223
|
* override would LOOSEN a stricter base safety field — surfacing a misconfiguration loudly instead of silently
|
|
212
224
|
* weakening the gate. Returns `base` untouched when there is nothing to apply.
|
|
213
225
|
*
|