@sema-agent/server 7.104.0 → 7.106.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 +114 -0
- package/USAGE.md +193 -52
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -0
- package/deploy/sema-up/cluster-up.sh +15 -2
- package/deploy/sema-up/kube-up.sh +8 -3
- package/deploy/sema-up/lib/flagshape.sh +29 -0
- package/deploy/sema-up/lib/parse-machines.py +4 -1
- package/deploy/sema-up/sema-up.sh +11 -9
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +3 -1
- package/dist/approval-card.d.ts +1 -1
- package/dist/approval.d.ts +6 -1
- package/dist/approval.js +4 -4
- package/dist/auth-bridge.d.ts +14 -0
- package/dist/auth-bridge.js +7 -0
- package/dist/auto-mode-face.d.ts +49 -40
- package/dist/auto-mode-face.js +6 -68
- package/dist/bench/s1/live-deps.d.ts +9 -2
- package/dist/bench/s1/live-deps.js +13 -8
- package/dist/boot/budget-tracing.js +3 -2
- package/dist/boot/config-center.d.ts +4 -0
- package/dist/boot/config-center.js +95 -37
- package/dist/boot/coordinators.d.ts +1 -0
- package/dist/boot/coordinators.js +3 -1
- package/dist/boot/device-lane.d.ts +2 -1
- package/dist/boot/execution-env.d.ts +11 -0
- package/dist/boot/execution-env.js +30 -6
- package/dist/boot/execution-lane.js +2 -3
- package/dist/boot/leader.d.ts +2 -1
- package/dist/boot/leader.js +2 -0
- package/dist/boot/parked-revive-gate.d.ts +10 -7
- package/dist/boot/parked-revive-gate.js +4 -11
- package/dist/boot/resolve-spec.d.ts +1 -4
- package/dist/boot/resolve-spec.js +44 -21
- package/dist/boot/runner-deps.d.ts +5 -8
- package/dist/boot/runner-deps.js +5 -10
- package/dist/boot/side-query-lane.d.ts +2 -2
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +2 -1
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +3 -2
- package/dist/boot/stage-06-runners.js +2 -5
- package/dist/boot/stage-07-capability-layer.d.ts +2 -2
- package/dist/boot/stage-07-capability-layer.js +1 -2
- package/dist/boot/stage-08-reapers.d.ts +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -2
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -2
- package/dist/boot/stage-10-http-server.js +11 -7
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/stores.js +6 -4
- package/dist/brain.d.ts +24 -2
- package/dist/brain.js +29 -10
- package/dist/budget.js +3 -0
- package/dist/capabilities/center-wiring.d.ts +64 -0
- package/dist/capabilities/center-wiring.js +24 -0
- package/dist/capabilities/composite-members.d.ts +89 -0
- package/dist/capabilities/composite-members.js +20 -0
- package/dist/capabilities/execution-lane.d.ts +3 -0
- package/dist/capabilities/execution-lane.js +5 -2
- package/dist/config-catalog.d.ts +30 -5
- package/dist/config-catalog.js +75 -26
- package/dist/config-center/apply-effective.d.ts +1 -1
- package/dist/config-center/apply-effective.js +7 -8
- package/dist/config-center/apply-ledger.d.ts +17 -0
- package/dist/config-center/apply-ledger.js +5 -0
- package/dist/config-center/center-request.d.ts +17 -0
- package/dist/config-center/center-request.js +4 -0
- package/dist/config-center/pull-state.d.ts +29 -0
- package/dist/config-center/pull-state.js +2 -0
- package/dist/config-types.d.ts +41 -14
- package/dist/config-types.js +1 -0
- package/dist/config.d.ts +50 -2
- package/dist/config.js +111 -23
- package/dist/cross-session-settings.d.ts +1 -1
- package/dist/deployment-governance.d.ts +12 -1
- package/dist/deployment-governance.js +2 -0
- package/dist/execution-lane-caps.d.ts +30 -2
- package/dist/execution-lane-caps.js +12 -0
- package/dist/file-history-disclosure.d.ts +7 -31
- package/dist/file-history-disclosure.js +12 -0
- package/dist/fleet/fleet-bus.d.ts +5 -1
- package/dist/fleet-client.d.ts +18 -13
- package/dist/fleet-client.js +32 -22
- package/dist/fleet-lease.d.ts +72 -47
- package/dist/fleet-lease.js +66 -14
- package/dist/governance-http-status.d.ts +0 -1
- package/dist/governance-http-status.js +0 -1
- package/dist/hosted-posture.d.ts +6 -17
- package/dist/hosted-posture.js +11 -6
- package/dist/http/admission.d.ts +1 -1
- package/dist/http/admission.js +14 -9
- package/dist/http/approval-sweeps.js +13 -1
- package/dist/http/dispatch.d.ts +18 -0
- package/dist/http/dispatch.js +18 -5
- package/dist/http/durable-run-leg.js +2 -0
- package/dist/http/request-guards.d.ts +25 -0
- package/dist/http/request-guards.js +25 -5
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +19 -11
- package/dist/http/route-ctx.d.ts +9 -0
- package/dist/http/route-table.d.ts +15 -4
- package/dist/http/route-table.js +6 -5
- package/dist/http/routes/approvals-assistant.js +0 -1
- package/dist/http/routes/capabilities.js +12 -11
- package/dist/http/routes/config-catalog.js +2 -1
- package/dist/http/routes/diagnostics.js +2 -0
- package/dist/http/routes/fleet.d.ts +3 -2
- package/dist/http/routes/images.js +0 -1
- package/dist/http/routes/runs.js +2 -8
- package/dist/http/routes/session-sync.js +6 -1
- package/dist/http/routes/sessions.js +5 -2
- package/dist/http/routes/side-query.js +8 -1
- package/dist/http/routes/tasks.js +4 -1
- package/dist/http/routes/trace-usage.d.ts +2 -0
- package/dist/http/routes/trace-usage.js +4 -9
- package/dist/http/server.d.ts +29 -8
- package/dist/http/server.js +4 -1
- package/dist/http/session-inherit-env.d.ts +37 -0
- package/dist/http/session-inherit-env.js +6 -0
- package/dist/http/sse-lifecycle.d.ts +1 -1
- package/dist/http/sse-log.d.ts +12 -9
- package/dist/http/sse-log.js +1 -8
- package/dist/http/wire-types.d.ts +19 -28
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/key-resolver.d.ts +74 -19
- package/dist/key-resolver.js +65 -7
- package/dist/leader/leader.d.ts +6 -0
- package/dist/leader/leader.js +2 -1
- package/dist/leader/repair-wire.js +2 -1
- package/dist/leader/wire.d.ts +9 -1
- package/dist/leader/wire.js +10 -6
- package/dist/observability/fail-open.d.ts +9 -1
- package/dist/observability/fail-open.js +9 -1
- package/dist/observability/metrics.js +3 -1
- package/dist/observability/permission-rule-events.d.ts +1 -1
- package/dist/observability/permission-rule-events.js +1 -1
- package/dist/observability/run-terminal.d.ts +82 -9
- package/dist/observability/run-terminal.js +112 -9
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/observability/tool-trace.d.ts +2 -0
- package/dist/operator-ask.d.ts +29 -1
- package/dist/operator-ask.js +6 -0
- package/dist/parked-decide.d.ts +14 -1
- package/dist/parked-decide.js +8 -1
- package/dist/plugins/image-bake-store-sql.d.ts +0 -2
- package/dist/plugins/image-bake-store-sql.js +0 -5
- package/dist/plugins/retention-store-sql.js +1 -0
- package/dist/plugins/run-store-sql.d.ts +3 -2
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/session-policy-store-sql.d.ts +38 -7
- package/dist/plugins/session-policy-store-sql.js +73 -18
- package/dist/plugins/sql-driver.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +17 -6
- package/dist/plugins/store-backend.js +16 -5
- package/dist/plugins/tidb-pool.js +5 -4
- package/dist/read-face-posture.d.ts +25 -4
- package/dist/read-face-posture.js +9 -0
- package/dist/request-key-closure.js +2 -2
- package/dist/request-value-closure.d.ts +17 -5
- package/dist/request-value-closure.js +12 -0
- package/dist/router/route-orchestration.d.ts +11 -0
- package/dist/router/route-orchestration.js +7 -6
- package/dist/rules-consent.js +0 -11
- package/dist/run-local.js +4 -2
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +4 -4
- package/dist/runtime-caps-resolver.d.ts +4 -10
- package/dist/runtime-governance.d.ts +8 -11
- package/dist/runtime-governance.js +10 -41
- package/dist/sealed-key.d.ts +0 -40
- package/dist/sealed-key.js +0 -20
- package/dist/server-secret-env.d.ts +11 -12
- package/dist/session-policy-wire.d.ts +100 -12
- package/dist/session-policy-wire.js +71 -8
- package/dist/session-titler.d.ts +11 -0
- package/dist/session-titler.js +6 -2
- package/dist/sql-engine-posture.d.ts +2 -2
- package/dist/store-posture.d.ts +53 -0
- package/dist/store-posture.js +32 -0
- package/dist/task-settings.d.ts +34 -45
- package/dist/task-settings.js +46 -25
- package/dist/tool-approval.d.ts +51 -22
- package/dist/tool-approval.js +44 -8
- package/dist/tool-declaration-http-status.d.ts +12 -0
- package/dist/tool-declaration-http-status.js +12 -0
- package/dist/trace/core-keyset-guard.d.ts +7 -7
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +5 -0
- package/dist/trace/project.d.ts +13 -7
- package/dist/trace/project.js +54 -6
- package/dist/trace/redact.d.ts +7 -0
- package/dist/trace/redact.js +153 -61
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/unattended-permission-mode.d.ts +110 -0
- package/dist/unattended-permission-mode.js +65 -0
- package/package.json +3 -3
- package/dist/boot/session-shell-gate-registry.d.ts +0 -39
- package/dist/boot/session-shell-gate-registry.js +0 -21
- package/dist/plugins/memory-session-policy-store.d.ts +0 -46
- package/dist/plugins/memory-session-policy-store.js +0 -52
- package/dist/roster-bound-http-status.d.ts +0 -6
- package/dist/roster-bound-http-status.js +0 -6
package/dist/trace/project.d.ts
CHANGED
|
@@ -156,8 +156,9 @@ export declare function toolStartEventData(ev: {
|
|
|
156
156
|
* 🔴 **筛子是一只总函数**(S-239,codex 对抗复审 r1 [medium] 亲核复现后修)—— 上面纪律②许诺的
|
|
157
157
|
* 「坏形 ⇒ report + withhold,**不抛**」此前是靠**逐条枚举**坏形兑现的,枚举不到的那些照样抛:
|
|
158
158
|
* `projectSettlement` 对 `note` / `who.approver` 先 `redactSecrets` 再截断,而 `redactSecrets` 会在
|
|
159
|
-
* 病态输入上爆回溯栈(`RangeError`:≥2^23 单段那一形已由 S-219 的 `atLeast`
|
|
160
|
-
*
|
|
159
|
+
* 病态输入上爆回溯栈(`RangeError`:≥2^23 单段那一形已由 S-219 的 `atLeast` 铸点闭掉,JSON 秘密字段臂的转义对循环与
|
|
160
|
+
* PEM 栅栏臂的标签交替循环由 S-265 换成手写扫描器闭掉 —— 已知形全部闭掉,本段写的是「当时为什么要总函数化」),
|
|
161
|
+
* 两条叶各能抛一次。总函数化照留:它兑现的是「坏字节一律 withhold」这条**结构**,不依赖脱敏器今天抛不抛。
|
|
161
162
|
*
|
|
162
163
|
* 抛出去的代价按腿算,而且一条比一条重:写腿落账那处的 `.catch` 看不到**同步**投影期的异常;读腿上它
|
|
163
164
|
* 直接逃出 `formatEvent` / 投影函数 —— 账本裸回放腿的那一次会当场断流(那一行不上帧、游标不前进、后面的
|
|
@@ -295,7 +296,9 @@ export declare function toolEndEventData(ev: {
|
|
|
295
296
|
* **不等于** `tool_end` 的 duration)。
|
|
296
297
|
*
|
|
297
298
|
* ## 信任划线
|
|
298
|
-
* · `toolCallId` / `toolName` ——
|
|
299
|
+
* · `toolCallId` / `toolName` —— 调用 id(🔴 S-283 订正:**服务商**在 tool_use / tool_calls 里给的,引擎只在缺席时自铸兜底 ——
|
|
300
|
+
* 此前写「引擎铸」不实)与名册里的工具名(与 `tool_start` 逐字同源)⇒ verbatim。调用 id 这一族在**逐帧面**上的形状闸
|
|
301
|
+
* 不在 S-283 射程(它是全 wire 的 join 键,要闸得所有面一起闸,见 run-terminal `admitProviderEcho` 顶注与车QO 收车档)。
|
|
299
302
|
* · `elapsedTimeSeconds` —— 引擎铸的整秒,单调、一 call 一个原点 ⇒ 有限数才铸键。
|
|
300
303
|
* · `output` —— 🔴 **UNTRUSTED RAW**,core 逐字「exactly as `tool_end.output`: process output that may
|
|
301
304
|
* carry secrets, paths or PII; core bounds only its SIZE, and a consumer MUST redact before persisting
|
|
@@ -575,8 +578,9 @@ export declare function workspaceChangedEventData(ev: {
|
|
|
575
578
|
* 两词:core 只看合成后的 caps deny 位,本仓分得出这个 false 是 center 下发的还是本地 env 写的)。
|
|
576
579
|
* ⚠️ **`settings_denied` 不折进 `denied`,它折进 `no_intent`**(codex 车BO r1 [medium] 验真后纠):
|
|
577
580
|
* settings 层的 kill-switch 折的是**模式**(`effectivePermissionMode`:auto → default),于是 resolve-spec
|
|
578
|
-
* 阶段④
|
|
579
|
-
* **caps**(`applyLocalAutoModeDeny`)
|
|
581
|
+
* 阶段④ 写的 `TaskSpec.permissionMode` 是 `"default"`(S-877 前是「不写意图座 `autoModeRequested`」)⇒ core 的武装谓词第一项就落空;
|
|
582
|
+
* 而本地 env 腿折的是 **caps**(`applyLocalAutoModeDeny`),模式照写 `"auto"` ⇒ core 才报 `denied`(并按 `operator_deny` 折 default、发
|
|
583
|
+
* `config.permission_mode_unavailable`)。两条腿在 manifest 上
|
|
580
584
|
* **分不开**,要分辨得读 `/v1/capabilities`。对照表与保鲜钉在 `test/wiring-manifest-projection.test.ts`
|
|
581
585
|
* 的 147③ 节,分岔的行为面格在 `test/permission-mode-shell-gate-translation.test.ts`;
|
|
582
586
|
* 本函数**只透传**,一个字都不换。
|
|
@@ -925,8 +929,10 @@ export declare function contextUsageEventData(ev: {
|
|
|
925
929
|
*
|
|
926
930
|
* `responseModel`(core 7.29.0 #1029):**供应商自己**报的、回答这一 turn 的模型名,**只在**它 ≠ 请求所带 id
|
|
927
931
|
* 时在场。缺席 = 「报了同名」或「没报」,**一个读法**(请求 id 读 `TaskResult.model`)。值是供应商铸的名字
|
|
928
|
-
* (与 `apiFailure.requestId` 同类:不是模型写的自由文本)⇒
|
|
929
|
-
*
|
|
932
|
+
* (与 `apiFailure.requestId` 同类:不是模型写的自由文本)⇒ 🔴 **S-283 起过 provider 回声的形状闸**
|
|
933
|
+
* ({@link admitProviderEcho}:长度 1–128 的可见 ASCII 原样,越界 ⇒ 不铸键 + 计数,与 core #1204 源头闸同一条;不过脱敏器),
|
|
934
|
+
* 与结果面的孪生 `TaskResult.responseModel` 同一只闸 —— 同一个名字在两张脸上不许两种待遇。非串 / 空串同样不铸键(空串在
|
|
935
|
+
* 那条谓词的长度下界之外;core 的读律 `providerReportedModel` 对空串也答「没报」)。
|
|
930
936
|
*/
|
|
931
937
|
export declare function turnEndEventData(ev: {
|
|
932
938
|
usage?: unknown;
|
package/dist/trace/project.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { redactErrorMessage, redactModelText } from "../observability/run-terminal.js";
|
|
2
|
-
import { MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, screenGateOutcome, TEAM_ROLES, ToolRoster, ToolRosterDelta, WRITE_PROTECTION_TARGET_VIEWS } from "@sema-agent/core";
|
|
1
|
+
import { admitProviderEcho, redactErrorMessage, redactModelText } from "../observability/run-terminal.js";
|
|
2
|
+
import { isAskClass, isAskOrigin, isPermissionMode, isPersistedRuleMandate, MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, screenGateOutcome, TEAM_ROLES, ToolRoster, ToolRosterDelta, WRITE_PROTECTION_TARGET_VIEWS } from "@sema-agent/core";
|
|
3
3
|
import { Value } from "typebox/value";
|
|
4
4
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
5
5
|
import { redactSecrets, redactDeep } from "./redact.js";
|
|
@@ -103,6 +103,34 @@ export function screenedGate(v) {
|
|
|
103
103
|
return undefined;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
function modeLedgerDefect(d) {
|
|
107
|
+
const classesOk = (v) => Array.isArray(v) && v.every((c) => isAskClass(c));
|
|
108
|
+
if (d.kind === "denied") {
|
|
109
|
+
if (d.mode !== undefined && !isPermissionMode(d.mode))
|
|
110
|
+
return "disposition.mode is outside the closed set";
|
|
111
|
+
if (d.askClass !== undefined && !isAskClass(d.askClass))
|
|
112
|
+
return "disposition.askClass is outside the closed set";
|
|
113
|
+
if (d.classes !== undefined && !classesOk(d.classes))
|
|
114
|
+
return "disposition.classes is not a list of ask classes";
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (d.released === undefined)
|
|
118
|
+
return false;
|
|
119
|
+
const r = d.released;
|
|
120
|
+
if (r === null || typeof r !== "object")
|
|
121
|
+
return "disposition.released is not an object";
|
|
122
|
+
const o = r;
|
|
123
|
+
if (!isPermissionMode(o.mode) || !isAskClass(o.askClass) || !classesOk(o.classes))
|
|
124
|
+
return "disposition.released.mode / askClass / classes is outside its closed set";
|
|
125
|
+
if (o.origin !== undefined && !isAskOrigin(o.origin))
|
|
126
|
+
return "disposition.released.origin is outside the closed set";
|
|
127
|
+
if (o.mandate !== undefined && !isPersistedRuleMandate(o.mandate))
|
|
128
|
+
return "disposition.released.mandate is outside the closed set";
|
|
129
|
+
if (o.reason !== undefined && !(typeof o.reason === "string" && Object.hasOwn(RELEASE_REASON_WORDS, o.reason)))
|
|
130
|
+
return "disposition.released.reason is outside the closed set";
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const RELEASE_REASON_WORDS = { rule_unreadable: true };
|
|
106
134
|
function screenedGateOrThrow(v) {
|
|
107
135
|
if (v === null || typeof v !== "object") {
|
|
108
136
|
if (v !== undefined)
|
|
@@ -121,6 +149,7 @@ function screenedGateOrThrow(v) {
|
|
|
121
149
|
(d.classifier !== undefined && (d.classifier === null || typeof d.classifier !== "object") && "disposition.classifier is not an object") ||
|
|
122
150
|
(classifierFallbackRaw !== undefined && (classifierFallbackRaw === null || typeof classifierFallbackRaw !== "object") && "disposition.classifier.fallback is not an object") ||
|
|
123
151
|
(classifierFallbackRaw !== undefined && classifierFallbackRaw !== null && typeof classifierFallbackRaw === "object" && typeof classifierFallbackRaw.cause !== "string" && "disposition.classifier.fallback.cause is not a string") ||
|
|
152
|
+
modeLedgerDefect(d) ||
|
|
124
153
|
(g.settlement !== undefined && (g.settlement === null || typeof g.settlement !== "object") && "settlement is not an object") ||
|
|
125
154
|
(g.settlement !== undefined && g.settlement !== null && typeof g.settlement.kind !== "string" && "settlement.kind is not a string") ||
|
|
126
155
|
(g.origin !== undefined && typeof g.origin !== "string" && "origin is not a string") ||
|
|
@@ -149,10 +178,29 @@ function screenedGateOrThrow(v) {
|
|
|
149
178
|
recordFailOpen("server.trace.gate-outcome-defective", "settlement.who is not an object with a party in the closed set");
|
|
150
179
|
return undefined;
|
|
151
180
|
}
|
|
181
|
+
const rel = d.kind === "allowed" ? d.released : undefined;
|
|
182
|
+
const released = rel === undefined
|
|
183
|
+
? undefined
|
|
184
|
+
: {
|
|
185
|
+
mode: rel.mode,
|
|
186
|
+
askClass: rel.askClass,
|
|
187
|
+
classes: [...rel.classes],
|
|
188
|
+
...(rel.origin === undefined ? {} : { origin: rel.origin }),
|
|
189
|
+
...(rel.mandate === undefined ? {} : { mandate: rel.mandate }),
|
|
190
|
+
...(rel.reason === undefined ? {} : { reason: rel.reason }),
|
|
191
|
+
};
|
|
152
192
|
return {
|
|
153
193
|
disposition: d.kind === "denied"
|
|
154
|
-
? {
|
|
155
|
-
|
|
194
|
+
? {
|
|
195
|
+
kind: "denied",
|
|
196
|
+
deniedBy: d.deniedBy,
|
|
197
|
+
...(d.cause === undefined ? {} : { cause: d.cause }),
|
|
198
|
+
...(classifier === undefined ? {} : { classifier }),
|
|
199
|
+
...(d.mode === undefined ? {} : { mode: d.mode }),
|
|
200
|
+
...(d.askClass === undefined ? {} : { askClass: d.askClass }),
|
|
201
|
+
...(d.classes === undefined ? {} : { classes: [...d.classes] }),
|
|
202
|
+
}
|
|
203
|
+
: { kind: "allowed", ...(classifier === undefined ? {} : { classifier }), ...(released === undefined ? {} : { released }) },
|
|
156
204
|
...(settlement === undefined ? {} : { settlement }),
|
|
157
205
|
...(g.origin === undefined ? {} : { origin: g.origin }),
|
|
158
206
|
};
|
|
@@ -300,7 +348,7 @@ export function taskNotificationEventData(ev) {
|
|
|
300
348
|
? {
|
|
301
349
|
apiFailure: {
|
|
302
350
|
...(typeof n.apiFailure.status === "number" ? { status: n.apiFailure.status } : {}),
|
|
303
|
-
...(
|
|
351
|
+
...(admitProviderEcho(n.apiFailure.requestId, "task_notification.apiFailure.requestId") ? { requestId: n.apiFailure.requestId } : {}),
|
|
304
352
|
},
|
|
305
353
|
}
|
|
306
354
|
: {}),
|
|
@@ -756,7 +804,7 @@ export function turnEndEventData(ev) {
|
|
|
756
804
|
...(ev.usage ? { usage: ev.usage } : {}),
|
|
757
805
|
...(ev.usageMissing === true ? { usageMissing: true } : {}),
|
|
758
806
|
...(typeof ev.stopReason === "string" ? { stopReason: ev.stopReason } : {}),
|
|
759
|
-
...(
|
|
807
|
+
...(admitProviderEcho(ev.responseModel, "turn_end.responseModel") ? { responseModel: ev.responseModel } : {}),
|
|
760
808
|
...identityFields(ev),
|
|
761
809
|
};
|
|
762
810
|
}
|
package/dist/trace/redact.d.ts
CHANGED
|
@@ -72,6 +72,13 @@ declare const DISPLAY_SAFE_URL_TOKEN = "\u00ABredacted:url\u00BB";
|
|
|
72
72
|
/** S-776:出网代理 URL 的回显尺(`outbound-dispatcher.ts` `displayProxyUrl`)形不干净时整只用**同一枚**占位 —— 单独一行导出,
|
|
73
73
|
* 上面那行字面量保持 `const NAME = "…";` 形(`test/api-notice-text-freeze.test.ts` §6 的冻结扫描按这一形认替换 token)。 */
|
|
74
74
|
export { DISPLAY_SAFE_URL_TOKEN };
|
|
75
|
+
/** 测试可见(S-265 真值表的两半):结构段里**表行只作声明、实现是手写扫描器**的臂 —— 声明正则的**新拷贝**
|
|
76
|
+
* (`lastIndex` 不与模块内那一枚共享)+ 扫描器在一份原文上产的候选跨度 `[起, 止)`。臂名是闭集 ⇒ 穷尽 `switch`
|
|
77
|
+
* (少一个 `case` = 缺返回 = 编译红)。 */
|
|
78
|
+
export declare function structuralScannerForTest(arm: "json-secret-field" | "pem-fence"): {
|
|
79
|
+
declared: RegExp;
|
|
80
|
+
spans: (text: string) => Array<[number, number]>;
|
|
81
|
+
};
|
|
75
82
|
/** 测试可见:扫描器 indexOf 实际扫过的字节数(摊还线性 ⇒ ≤ 常数 × 输入长)。 */
|
|
76
83
|
export declare function ppkScanBytesForTest(): number;
|
|
77
84
|
export declare function resetPpkScanBytesForTest(): void;
|
package/dist/trace/redact.js
CHANGED
|
@@ -486,13 +486,12 @@ function deriveLiteralHead(re) {
|
|
|
486
486
|
function scanResumeAfterHit(start, end, back) {
|
|
487
487
|
return Math.max(start + 1, end - back);
|
|
488
488
|
}
|
|
489
|
-
const spanOffset = (a, m) => (a.outside === 1 ? m[1].length : 0);
|
|
490
489
|
function collectRegexSpans(a, text, out) {
|
|
491
490
|
const g = a.g;
|
|
492
491
|
g.lastIndex = 0;
|
|
493
492
|
for (let m = g.exec(text); m !== null; m = g.exec(text)) {
|
|
494
493
|
const end = m.index + m[0].length;
|
|
495
|
-
out.push({ start: m.index
|
|
494
|
+
out.push({ start: m.index, end, repl: a.repl, rank: a.rank, order: a.order });
|
|
496
495
|
g.lastIndex = scanResumeAfterHit(m.index, end, a.back);
|
|
497
496
|
}
|
|
498
497
|
}
|
|
@@ -585,7 +584,7 @@ function collectStickySpans(a, text, at, out) {
|
|
|
585
584
|
if (m === null)
|
|
586
585
|
continue;
|
|
587
586
|
const end = p + m[0].length;
|
|
588
|
-
out.push({ start: p
|
|
587
|
+
out.push({ start: p, end, repl, rank, order });
|
|
589
588
|
pos = scanResumeAfterHit(p, end, back);
|
|
590
589
|
}
|
|
591
590
|
}
|
|
@@ -935,6 +934,47 @@ function collectSsh2FenceSpans(text, span) {
|
|
|
935
934
|
from = scanResumeAfterHit(fence, end, SSH2_FENCE_OPEN.length);
|
|
936
935
|
}
|
|
937
936
|
}
|
|
937
|
+
const PEM_END_OPEN = "-----END";
|
|
938
|
+
const PEM_FENCE_BODY_MAX = 8192;
|
|
939
|
+
function pemFenceLineEnd(text, labelFrom) {
|
|
940
|
+
const close = text.indexOf(PEM_FENCE_CLOSE, labelFrom);
|
|
941
|
+
return close >= 0 && isPemFenceLabel(text, labelFrom, close) ? close + PEM_FENCE_CLOSE.length : -1;
|
|
942
|
+
}
|
|
943
|
+
function collectPemFenceSpans(text, span) {
|
|
944
|
+
let endFrom = -1;
|
|
945
|
+
let endAt = -1;
|
|
946
|
+
let endAfter = -1;
|
|
947
|
+
const earliestEndFence = (from) => {
|
|
948
|
+
if (endFrom >= 0 && from >= endFrom && (endAt < 0 || endAt >= from))
|
|
949
|
+
return;
|
|
950
|
+
let b = text.indexOf(PEM_END_OPEN, from);
|
|
951
|
+
let after = -1;
|
|
952
|
+
while (b >= 0 && (after = pemFenceLineEnd(text, b + PEM_END_OPEN.length)) < 0)
|
|
953
|
+
b = text.indexOf(PEM_END_OPEN, b + 1);
|
|
954
|
+
endFrom = from;
|
|
955
|
+
endAt = b;
|
|
956
|
+
endAfter = after;
|
|
957
|
+
};
|
|
958
|
+
for (let from = 0;;) {
|
|
959
|
+
const fence = text.indexOf(PEM_FENCE_OPEN, from);
|
|
960
|
+
if (fence < 0)
|
|
961
|
+
return;
|
|
962
|
+
const bodyFrom = pemFenceLineEnd(text, fence + PEM_FENCE_OPEN.length);
|
|
963
|
+
if (bodyFrom < 0) {
|
|
964
|
+
from = fence + 1;
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
earliestEndFence(bodyFrom);
|
|
968
|
+
if (endAt < 0)
|
|
969
|
+
return;
|
|
970
|
+
if (endAt - bodyFrom > PEM_FENCE_BODY_MAX) {
|
|
971
|
+
from = fence + 1;
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
span(fence, endAfter);
|
|
975
|
+
from = scanResumeAfterHit(fence, endAfter, PEM_FENCE_OPEN.length);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
938
978
|
function collectPemFragmentSpans(text, json, span) {
|
|
939
979
|
let cur = findPemFence(text, 0);
|
|
940
980
|
let inherited = false;
|
|
@@ -1145,6 +1185,18 @@ const SSH2_FENCE_PATTERN = (() => {
|
|
|
1145
1185
|
throw new Error("redact: SSH2 fence rule missing from FULL_ARM_PATTERNS");
|
|
1146
1186
|
return row;
|
|
1147
1187
|
})();
|
|
1188
|
+
const PEM_FENCE_PATTERN = (() => {
|
|
1189
|
+
const row = FULL_ARM_PATTERNS.find(([re]) => re.source.startsWith(PEM_FENCE_OPEN));
|
|
1190
|
+
if (row === undefined)
|
|
1191
|
+
throw new Error("redact: PEM fence rule missing from FULL_ARM_PATTERNS");
|
|
1192
|
+
return row;
|
|
1193
|
+
})();
|
|
1194
|
+
const JSON_SECRET_FIELD_PATTERN = (() => {
|
|
1195
|
+
const rows = FULL_ARM_PATTERNS.filter(([, repl]) => repl === '$1"«redacted»"');
|
|
1196
|
+
if (rows.length !== 1)
|
|
1197
|
+
throw new Error(`redact: expected exactly one JSON secret-field rule (replacement '$1"«redacted»"') in FULL_ARM_PATTERNS, found ${rows.length}`);
|
|
1198
|
+
return rows[0];
|
|
1199
|
+
})();
|
|
1148
1200
|
const isRegexSpace = (c) => c === 32 || (c >= 9 && c <= 13) || c === 0xa0 || c === 0x1680 || (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;
|
|
1149
1201
|
const isLineTerminator = (c) => c === 10 || c === 13 || c === 0x2028 || c === 0x2029;
|
|
1150
1202
|
const isControlCode = (c) => c < 0x20 || c === 0x7f;
|
|
@@ -1360,6 +1412,78 @@ function collectUrlCredentialSpans(text, json, span) {
|
|
|
1360
1412
|
i = valueEnd - 1;
|
|
1361
1413
|
}
|
|
1362
1414
|
}
|
|
1415
|
+
const JSON_FIELD_ANCHOR = '"';
|
|
1416
|
+
const CREDENTIAL_NAME_MAX = Math.max(...CREDENTIAL_NAME_WORDS.map((w) => w.length));
|
|
1417
|
+
function jsonSecretFieldValueQuoteAt(text, p) {
|
|
1418
|
+
const n = text.length;
|
|
1419
|
+
let name = "";
|
|
1420
|
+
let i = p + 1;
|
|
1421
|
+
for (; i < n; i++) {
|
|
1422
|
+
let c = text.charCodeAt(i);
|
|
1423
|
+
if (c === CH_QUOTE)
|
|
1424
|
+
break;
|
|
1425
|
+
if (i - p > CREDENTIAL_NAME_MAX)
|
|
1426
|
+
return -1;
|
|
1427
|
+
if (c >= CH_A && c <= CH_Z)
|
|
1428
|
+
c += 32;
|
|
1429
|
+
if (!((c >= CH_a && c <= CH_z) || (c >= CH_0 && c <= CH_9) || c === CH_UNDERSCORE || c === CH_MINUS))
|
|
1430
|
+
return -1;
|
|
1431
|
+
name += String.fromCharCode(c);
|
|
1432
|
+
}
|
|
1433
|
+
if (i >= n || !CREDENTIAL_NAME_SET.has(name))
|
|
1434
|
+
return -1;
|
|
1435
|
+
i += 1;
|
|
1436
|
+
while (i < n && isRegexSpace(text.charCodeAt(i)))
|
|
1437
|
+
i += 1;
|
|
1438
|
+
if (text.charCodeAt(i) !== CH_COLON)
|
|
1439
|
+
return -1;
|
|
1440
|
+
i += 1;
|
|
1441
|
+
while (i < n && isRegexSpace(text.charCodeAt(i)))
|
|
1442
|
+
i += 1;
|
|
1443
|
+
return text.charCodeAt(i) === CH_QUOTE ? i : -1;
|
|
1444
|
+
}
|
|
1445
|
+
function jsonStringEndAt(text, q) {
|
|
1446
|
+
const n = text.length;
|
|
1447
|
+
for (let i = q + 1; i < n;) {
|
|
1448
|
+
const c = text.charCodeAt(i);
|
|
1449
|
+
if (c === CH_QUOTE)
|
|
1450
|
+
return i + 1;
|
|
1451
|
+
if (c === CH_BACKSLASH) {
|
|
1452
|
+
if (i + 1 < n && !isLineTerminator(text.charCodeAt(i + 1))) {
|
|
1453
|
+
i += 2;
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
return -1;
|
|
1457
|
+
}
|
|
1458
|
+
i += 1;
|
|
1459
|
+
}
|
|
1460
|
+
return -1;
|
|
1461
|
+
}
|
|
1462
|
+
function collectJsonSecretFieldSpans(text, span) {
|
|
1463
|
+
for (let p = text.indexOf(JSON_FIELD_ANCHOR); p >= 0;) {
|
|
1464
|
+
const q = jsonSecretFieldValueQuoteAt(text, p);
|
|
1465
|
+
const end = q < 0 ? -1 : jsonStringEndAt(text, q);
|
|
1466
|
+
if (end < 0) {
|
|
1467
|
+
p = text.indexOf(JSON_FIELD_ANCHOR, p + 1);
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
span(q, end);
|
|
1471
|
+
p = text.indexOf(JSON_FIELD_ANCHOR, scanResumeAfterHit(p, end, JSON_FIELD_ANCHOR.length));
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
export function structuralScannerForTest(arm) {
|
|
1475
|
+
const collect = (scan) => (text) => {
|
|
1476
|
+
const out = [];
|
|
1477
|
+
scan(text, (start, end) => out.push([start, end]));
|
|
1478
|
+
return out;
|
|
1479
|
+
};
|
|
1480
|
+
switch (arm) {
|
|
1481
|
+
case "json-secret-field":
|
|
1482
|
+
return { declared: new RegExp(JSON_SECRET_FIELD_PATTERN[0].source, JSON_SECRET_FIELD_PATTERN[0].flags), spans: collect(collectJsonSecretFieldSpans) };
|
|
1483
|
+
case "pem-fence":
|
|
1484
|
+
return { declared: new RegExp(PEM_FENCE_PATTERN[0].source, PEM_FENCE_PATTERN[0].flags), spans: collect(collectPemFenceSpans) };
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1363
1487
|
let ppkScanBytes = 0;
|
|
1364
1488
|
export function ppkScanBytesForTest() { return ppkScanBytes; }
|
|
1365
1489
|
export function resetPpkScanBytesForTest() { ppkScanBytes = 0; }
|
|
@@ -1462,56 +1586,19 @@ const armPool = (() => {
|
|
|
1462
1586
|
throw new Error(`redact: arm-pool replacement ${JSON.stringify(token)} may only use a leading $1 (the run kept outside the span)`);
|
|
1463
1587
|
return body;
|
|
1464
1588
|
};
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
let out = "";
|
|
1469
|
-
for (; i < src.length; i++) {
|
|
1470
|
-
const c = src[i];
|
|
1471
|
-
if (c === "\\") {
|
|
1472
|
-
const e = src[i + 1];
|
|
1473
|
-
if (e === undefined || !HEAD_LITERAL_ESCAPES.has(e))
|
|
1474
|
-
break;
|
|
1475
|
-
if ("*+?{".includes(src[i + 2] ?? ""))
|
|
1476
|
-
break;
|
|
1477
|
-
out += src.slice(i, i + 2);
|
|
1478
|
-
i += 1;
|
|
1479
|
-
continue;
|
|
1480
|
-
}
|
|
1481
|
-
if ("([{|).^$*+?".includes(c))
|
|
1482
|
-
break;
|
|
1483
|
-
if ("*+?{".includes(src[i + 1] ?? ""))
|
|
1484
|
-
break;
|
|
1485
|
-
out += c;
|
|
1486
|
-
}
|
|
1487
|
-
if (out.length === 0)
|
|
1488
|
-
throw new Error(`redact: structural arm /${src}/ has no literal anchor head — its same-arm rewind amount cannot be derived`);
|
|
1489
|
-
return out;
|
|
1490
|
-
};
|
|
1491
|
-
const regexMember = ([re, token], rank, order, structural) => {
|
|
1589
|
+
const regexMember = ([re, token], rank, order) => {
|
|
1590
|
+
if (token.startsWith("$1"))
|
|
1591
|
+
throw new Error(`redact: regex arm /${re.source}/ cannot carry an outside-span $1 prefix — that form belongs to the structural scanners`);
|
|
1492
1592
|
const repl = spanRepl(token);
|
|
1493
|
-
const outside = token.startsWith("$1") ? 1 : 0;
|
|
1494
1593
|
const y = VALUE_ARM_STICKY.get(re);
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
headFirst = undefined;
|
|
1502
|
-
}
|
|
1503
|
-
else {
|
|
1504
|
-
const d = deriveLiteralHead(re);
|
|
1505
|
-
back = Math.max(d.len - 1, 0);
|
|
1506
|
-
headSrc = d.src;
|
|
1507
|
-
headFirst = d.first;
|
|
1508
|
-
if (y !== undefined && (d.len === 0 || headSrc === undefined || headFirst === undefined)) {
|
|
1509
|
-
throw new Error(`redact: boundary arm /${re.source}/ has a zero-length structural head — the bounded head screen would admit every candidate position`);
|
|
1510
|
-
}
|
|
1594
|
+
const d = deriveLiteralHead(re);
|
|
1595
|
+
const back = Math.max(d.len - 1, 0);
|
|
1596
|
+
const headSrc = d.src;
|
|
1597
|
+
const headFirst = d.first;
|
|
1598
|
+
if (y !== undefined && (d.len === 0 || headSrc === undefined || headFirst === undefined)) {
|
|
1599
|
+
throw new Error(`redact: boundary arm /${re.source}/ has a zero-length structural head — the bounded head screen would admit every candidate position`);
|
|
1511
1600
|
}
|
|
1512
|
-
|
|
1513
|
-
throw new Error(`redact: value arm /${re.source}/ cannot carry an outside-span $1 prefix`);
|
|
1514
|
-
const arm = { g: re, y, back, headSrc, headFirst, repl, rank, order, outside };
|
|
1601
|
+
const arm = { g: re, y, back, headSrc, headFirst, repl, rank, order };
|
|
1515
1602
|
const boundary = y === undefined || headSrc === undefined || headFirst === undefined ? undefined : { ...arm, y, headSrc, headFirst };
|
|
1516
1603
|
return {
|
|
1517
1604
|
token,
|
|
@@ -1541,24 +1628,29 @@ const armPool = (() => {
|
|
|
1541
1628
|
headFirst: undefined,
|
|
1542
1629
|
};
|
|
1543
1630
|
};
|
|
1631
|
+
const structuralScannerMember = (row, order) => {
|
|
1632
|
+
if (row === PEM_FENCE_PATTERN)
|
|
1633
|
+
return scannerMember(row[1], order, 1, (text, _json, _observe, span) => collectPemFenceSpans(text, span));
|
|
1634
|
+
if (row === SSH2_FENCE_PATTERN)
|
|
1635
|
+
return scannerMember(row[1], order, 2, (text, _json, _observe, span) => collectSsh2FenceSpans(text, span));
|
|
1636
|
+
if (row === URL_CREDENTIAL_PATTERN)
|
|
1637
|
+
return scannerMember(row[1], order, 2, (text, json, _observe, span) => collectUrlCredentialSpans(text, json, span));
|
|
1638
|
+
if (row === JSON_SECRET_FIELD_PATTERN)
|
|
1639
|
+
return scannerMember(row[1], order, 1, (text, _json, _observe, span) => collectJsonSecretFieldSpans(text, span));
|
|
1640
|
+
throw new Error(`redact: structural row /${row[0].source}/ has no hand-written scanner — structural grammars are declared as regexes but never executed as one (S-102 / S-265)`);
|
|
1641
|
+
};
|
|
1544
1642
|
const members = [];
|
|
1545
|
-
for (const row of STRUCTURAL_SECRET_PATTERNS)
|
|
1546
|
-
|
|
1547
|
-
members.push(row === URL_CREDENTIAL_PATTERN
|
|
1548
|
-
? scannerMember(row[1], order, 2, (text, json, _observe, span) => collectUrlCredentialSpans(text, json, span))
|
|
1549
|
-
: row === SSH2_FENCE_PATTERN
|
|
1550
|
-
? scannerMember(row[1], order, 2, (text, _json, _observe, span) => collectSsh2FenceSpans(text, span))
|
|
1551
|
-
: regexMember(row, ARM_RANK.structural, order, true));
|
|
1552
|
-
}
|
|
1643
|
+
for (const row of STRUCTURAL_SECRET_PATTERNS)
|
|
1644
|
+
members.push(structuralScannerMember(row, members.length));
|
|
1553
1645
|
members.push(scannerMember(PPK_CONTAINER_TOKEN, members.length, 2, (text, _json, observe, span) => collectPpkContainerSpans(text, observe, span)));
|
|
1554
1646
|
members.push(scannerMember(PEM_FRAGMENT_TOKEN, members.length, 2, (text, json, _observe, span) => collectPemFragmentSpans(text, json, span)));
|
|
1555
1647
|
members.push(scannerMember(HEADLESS_PPK_TOKEN, members.length, 2, (text, _json, _observe, span) => collectHeadlessPpkSpans(text, span)));
|
|
1556
1648
|
for (const row of KEYWORD_SECRET_PATTERNS)
|
|
1557
|
-
members.push(regexMember(row, ARM_RANK.keyword, members.length
|
|
1649
|
+
members.push(regexMember(row, ARM_RANK.keyword, members.length));
|
|
1558
1650
|
for (const row of SHAPE_SECRET_PATTERNS)
|
|
1559
|
-
members.push(regexMember(row, ARM_RANK.shape, members.length
|
|
1651
|
+
members.push(regexMember(row, ARM_RANK.shape, members.length));
|
|
1560
1652
|
for (const row of FRAGMENT_SECRET_PATTERNS)
|
|
1561
|
-
members.push(regexMember(row, ARM_RANK.fragment, members.length
|
|
1653
|
+
members.push(regexMember(row, ARM_RANK.fragment, members.length));
|
|
1562
1654
|
for (const [re] of KEYWORD_SECRET_PATTERNS) {
|
|
1563
1655
|
if (!LEXICAL_KEYWORD_ARMS.has(re))
|
|
1564
1656
|
throw new Error(`redact: keyword arm /${re.source}/ is not registered as lexical — build it with keywordArm() so the bounded-unit scope can exclude it`);
|
|
@@ -168,7 +168,7 @@ export declare const WIRE_PROJECTION_FACES: {
|
|
|
168
168
|
readonly wiring_manifest_tenant: {
|
|
169
169
|
readonly projector: "wiringManifestEventData";
|
|
170
170
|
readonly coreType: `WiringManifest & ${string}`;
|
|
171
|
-
readonly excluded: readonly ["governance", "configFingerprint", "autoConsolidation", "readDeny", "peerPresence"];
|
|
171
|
+
readonly excluded: readonly ["governance", "configFingerprint", "autoConsolidation", "readDeny", "peerPresence", "permissionMode"];
|
|
172
172
|
readonly unwrapped: readonly ["manifest"];
|
|
173
173
|
readonly minted: readonly [];
|
|
174
174
|
};
|
|
@@ -40,7 +40,7 @@ export const WIRE_PROJECTION_FACES = {
|
|
|
40
40
|
wiring_manifest_tenant: {
|
|
41
41
|
projector: "wiringManifestEventData",
|
|
42
42
|
coreType: `WiringManifest & ${arm("wiring_manifest")}`,
|
|
43
|
-
excluded: ["governance", "configFingerprint", "autoConsolidation", "readDeny", "peerPresence"],
|
|
43
|
+
excluded: ["governance", "configFingerprint", "autoConsolidation", "readDeny", "peerPresence", "permissionMode"],
|
|
44
44
|
unwrapped: ["manifest"],
|
|
45
45
|
minted: [],
|
|
46
46
|
},
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-877 候裁 15 → B(主会话裁定 2026-09-29,收车档 ⑩)—— `UNATTENDED_PERMISSION_MODE`:**无人值守腿的模式,运维一处表态**。
|
|
3
|
+
*
|
|
4
|
+
* ## 规则(一句)
|
|
5
|
+
* 本仓**自己铸** spec、**没有调用方**能在请求上表态模式、也**没有审批席**的腿 —— leader worker(`src/leader/wire.ts`
|
|
6
|
+
* provisionWorker 两车道)、leader 的两只修复腿(集成沙箱修复 `mkRepair`、模块门修复 `runModuleGate` / 单代理修复环
|
|
7
|
+
* `attachRepairLoopDeps`)、run-local(`src/run-local.ts`,一次性 CLI)—— 它们的 `TaskSpec.permissionMode` 由本旋钮给,
|
|
8
|
+
* 经 {@link unattendedPermissionMode} 这**一只**读函数取值,各腿不各读 config、不各写默认。
|
|
9
|
+
*
|
|
10
|
+
* ## 为什么要有它(对 v7.105.0 回归的修,codex 对抗复审 F1 [high] 亲核成立)
|
|
11
|
+
* core 7.34.0 [ref]「Absent = `default`」:不带 `permissionMode` 的 spec 按 `default` 跑 —— 模式写门对每一次写先问、
|
|
12
|
+
* 形状层对非只读 shell 先问。上面三类腿**没有人**能答那一问(leader 的 Runner 不接 `onAsk`,零名单部署也不给 durable 席;
|
|
13
|
+
* run-local 无 TTY 时同形)⇒ headless 当场拒:worker `completed`、零写入。7.105.0 上这些腿不带模式也不问
|
|
14
|
+
* (缺席臂只给 base、`shellGate` 不写)。core 同段逐字给出路:「A library embedder that writes files with no approver
|
|
15
|
+
* now declares `permissionMode: "acceptEdits"` … or `"bypassPermissions"` … or wires an approver」——本仓对自己铸的
|
|
16
|
+
* 那几条 spec 就是那个 embedder。
|
|
17
|
+
*
|
|
18
|
+
* ## 三问
|
|
19
|
+
* · **谁需要**:用 leader / run-local 的部署(零审批名单是常态)—— 缺省值必须等于 7.105.0 的行为,否则升级即断。
|
|
20
|
+
* · **谁受伤**:想让无人值守腿更严的运维 —— **补偿** = 把旋钮设成 `acceptEdits`(根内写不问、非只读命令仍问 ⇒ 无人可答即拒)
|
|
21
|
+
* / `default` / `dontAsk`(一切会问的都拒)/ `plan`。部署的审批名单(`APPROVAL_REQUIRE` 等)与治理层的问(运维强制、ask 规则、
|
|
22
|
+
* hook)在任何一个词下都**照问**:bypassPermissions 只放行「普通」一类(core 7.34.0 [ref] 仲裁表,本仓不重推)。
|
|
23
|
+
* · **什么补偿**:缺省 = `bypassPermissions` —— 写门 / 形状层与 7.105.0 同(不问);词表外的值起服拒启([ref],不静默回缺省)。
|
|
24
|
+
* 候裁 18 → D:缺省 bypass 会释放写保护 ⇒ 本腿把写保护从问改成拒({@link createUnattendedWriteProtectionPolicy});另六类释放 = 相对 7.105.0 的宽化,
|
|
25
|
+
* 如实披露(见那只函数的「射程外」)。**过渡形**:core 7.37.0(S-906)后退成 `default` + 按类作答的 onAsk,旋钮硬删(拒启点名)。
|
|
26
|
+
*
|
|
27
|
+
* ## 写面:本旋钮只给模式的**仲裁**,不给 bypassPermissions 在 core 模式表里的**写面 open**
|
|
28
|
+
* core 7.34.0 起 `modePolicyOf("bypassPermissions").writeFace === "open"`(工作区根之外也能写)。7.105.0 上这几条腿**没有模式**,
|
|
29
|
+
* 写面恒是引擎缺省 `"roots"`。所以 {@link buildUnattendedLegSpec} 同时在任务席写 `writeFace: "roots"`(core 容纳面解析序:
|
|
30
|
+
* 显式任务席在模式那一行**之前**,`dist/tools/fs/containment-face.d.ts` resolveContainmentFace 第 3 / 3′ 行)—— 缺省与 7.105.0
|
|
31
|
+
* **逐字**同形,运维把旋钮设成任何词也不会顺手拿到根外写面(那是 HTTP 腿上 `TASK_WRITE_FACE_ESCALATION` 管的另一件事,
|
|
32
|
+
* S-591;这几条腿不是请求,不经那道门)。⚠️ 这一格是按裁定的**意图**(「= 7.105.0 行为」)施工、比裁定字面多出的一半,
|
|
33
|
+
* 收车档 ⑩ 单列,主会话可翻。
|
|
34
|
+
*
|
|
35
|
+
* ## 射程外(成文)
|
|
36
|
+
* · HTTP 腿(`POST /v1/tasks|runs`):模式在请求上(`permissionMode` / `settings.permissions.defaultMode`),缺席 = core `default`
|
|
37
|
+
* 原样过境(候裁 5 裁 A)—— 那里有调用方能表态,本旋钮不参与。
|
|
38
|
+
* · leader 的 planner 路由(无手 Runner,没有写 / shell 可问)不读本旋钮。
|
|
39
|
+
* · 中心下发 `RuntimeCaps.bypassPermissions === false` 时 core 把 bypass 折回 `default` 并发 `config.permission_mode_unavailable`
|
|
40
|
+
* —— 组织级拒优先。本仓今天不写这一位([ref] 未闭环),故这条今天只在 core 侧可达。
|
|
41
|
+
*/
|
|
42
|
+
import { type PermissionMode, type RunnerDeps, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
43
|
+
/** 旋钮的出厂值(主会话裁定候裁 15 B / 18 D)。写面由 {@link buildUnattendedLegSpec} 钉 roots;写保护由 {@link createUnattendedWriteProtectionPolicy} 改拒。 */
|
|
44
|
+
export declare const UNATTENDED_PERMISSION_MODE_DEFAULT: "bypassPermissions";
|
|
45
|
+
/** 解析结果:值 + 它从哪来(boot 一行 `unattended_permission_mode` 的两个字段)。 */
|
|
46
|
+
export interface UnattendedPermissionModeSetting {
|
|
47
|
+
readonly value: PermissionMode;
|
|
48
|
+
readonly source: "env" | "default";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `UNATTENDED_PERMISSION_MODE` 的**唯一**解析口。词表 = core `PERMISSION_MODES` 本尊(不手抄 —— core 加词本口随之受理,
|
|
52
|
+
* 减词本口随之拒启);两侧空白裁掉后**逐字**比较(不折大小写:`Default` / `BYPASS` 落拒启臂)。
|
|
53
|
+
* 未设 / 空串 ⇒ 出厂值,来源 `default`;词表外 ⇒ **拒启**,拒因点名旋钮与全部合法词([ref]:安全轴旋钮不许「以为设了其实没设」)。
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseUnattendedPermissionMode(raw: string | undefined): UnattendedPermissionModeSetting;
|
|
56
|
+
/** 三类无人值守腿的**唯一**读函数(见模块头注「规则」)。 */
|
|
57
|
+
export declare function unattendedPermissionMode(config: {
|
|
58
|
+
readonly unattendedPermissionMode: PermissionMode;
|
|
59
|
+
}): PermissionMode;
|
|
60
|
+
/** 无人值守腿铸进 spec 的两键(纯数据 ⇒ `build*`):模式 = 旋钮值;写面 = `"roots"`(理由见模块头注「写面」)。 */
|
|
61
|
+
export declare function buildUnattendedLegSpec(mode: PermissionMode): Required<Pick<TaskSpec, "permissionMode" | "writeFace">>;
|
|
62
|
+
/**
|
|
63
|
+
* 🔴 **S-877 候裁 18 → D(主会话续裁 2026-09-29)—— 无审批席的腿上,写保护从「问」改成「拒」**。
|
|
64
|
+
*
|
|
65
|
+
* **病**(codex 续车轮 R2-F1):core `MODE_ARBITRATION` 里 `bypassPermissions` 释放 `write_protection` 一类(`bypassImmune:false`)⇒ 旋钮缺省下写保护表里的
|
|
66
|
+
* 目标零询问放行;7.105.0 这些腿无模式 ⇒ 写保护是一问 ⇒ 无人可答 ⇒ 拒。core 7.35 没有「放普通问、留保护问」的模式,也没有限定模式可释放类的座。
|
|
67
|
+
*
|
|
68
|
+
* **规则(一条)**:本腿的模式**会释放**写保护一问时(读 core 仲裁本尊 `arbitrateByMode`,不手抄模式词),把**本腿引擎判官用的同一张写保护表**
|
|
69
|
+
* (`runnerSeats` = 构造本腿 Runner 的那份 deps 的两只座 {@link UnattendedLegRunnerSeats};`writeProtectedPaths` 缺席 = core 源表 `WRITE_PROTECTED_DEFAULT_TABLE`)经 core 的
|
|
70
|
+
* `compileWriteProtection` 编成判官,每一行铸成一条 **core 路径拒行**(`ToolPolicy.pathDenyRules`,`faces:["write"]`)—— 引擎的写面把它们并进本腿的拒表:
|
|
71
|
+
* 写工具的路径解析与 shell 站(重定向 / `tee` / `sed -i` / `cp`·`mv`·`touch` 操作数)都按拒表拒,模式**不释放拒**。**路径匹配全在 core**:每行的
|
|
72
|
+
* `reaches` 就是判官自己的 `matchPath`(同一只折叠、同一只数据根放行 —— `dataRoot` 与引擎判官同源:本腿 Runner 的 `memoryEngineDir`),本仓不写一个字的
|
|
73
|
+
* 路径比较。模式不释放写保护(`default` / `acceptEdits` / `plan` / `auto` / `dontAsk`)⇒ 不铸(那一问照旧问,有席的腿上人能答 —— run-local 有 TTY 时)。
|
|
74
|
+
*
|
|
75
|
+
* **射程外**(如实,收车档 ⑩ 候裁 18):缺省 bypass 还释放 `probe` / `tool_marks_mcp` / `tool_marks_a2a` / `transcript_write` / `rule_unreadable` /
|
|
76
|
+
* `artifact_shared_read` 六类(core 对它们没有「拒」的座),相对 7.105.0 是宽化 —— CHANGELOG「谁受伤」与 MIGRATION 写明,补偿 = 旋钮 `acceptEdits` / `default`。
|
|
77
|
+
* **过渡形**:core 7.37.0(S-906,`AskRequest.askClass?` / `classes?` 到货)后本仓改成 `default` 模式 + fail-closed 按类作答的 `onAsk`,本座与旋钮一并硬删。
|
|
78
|
+
* **续跑**(codex r3 F1):本层是活闭包、进不了 checkpoint,而 core 续跑在 spec 不说模式时取 checkpoint 记下的模式 ⇒ durable 续跑必须重挂整条腿 ——
|
|
79
|
+
* leader worker 的续跑上下文带 `CheckpointCtx.unattendedLeg`,续跑家族据此让 `resolveSpec` 在全部折叠之后调 {@link createUnattendedLeg} / {@link withUnattendedLeg}。
|
|
80
|
+
* **已知过拒**(codex r3 F2,fail-closed 方向,随过渡形退役):core 的路径拒座在 shell 站把 `cp` 的**源**操作数也按写目标判(CC 同形,cli280.js:216993
|
|
81
|
+
* `cp:"write"`),引擎写保护判官只判真写目标 ⇒ 从写保护目标复制出来在本层下被拒、7.105.0 上照跑。本仓不补路径比较(那是第二个判官),特征化钉在
|
|
82
|
+
* test/run-local.test.ts「🚧 codex r3 F2」。
|
|
83
|
+
* **附带效果**(S-877 四续 ⑫b,随过渡形退役):本层只能是一只 `ToolPolicy`(路径拒座挂在策略上,`check` 必填 —— 这里恒 `allow`),原先没有 toolPolicy
|
|
84
|
+
* 的腿因此被引擎读成「有策略层」:系统提示多 `# Execution environment` 一块、UNGATED 告警不再发。7.35.2 上表达不了「只供拒行、不算策略层」;
|
|
85
|
+
* 特征化钉在 test/unattended-permission-mode.test.ts「🚧 四续 ⑫b」。
|
|
86
|
+
*
|
|
87
|
+
* 返回 `undefined` = 本腿不需要这一层(模式不释放写保护,或运维把表清空 `[]`)。活对象(带判官闭包)⇒ `create*`。
|
|
88
|
+
*/
|
|
89
|
+
export declare function createUnattendedWriteProtectionPolicy(mode: PermissionMode, runnerSeats: UnattendedLegRunnerSeats): ToolPolicy | undefined;
|
|
90
|
+
/** 无人值守腿的全部铸件:模式 + 写面(纯数据)与写保护拒层(活对象,可缺席)。由 {@link createUnattendedLeg} 铸、{@link withUnattendedLeg} 并进 spec。 */
|
|
91
|
+
export interface UnattendedLeg {
|
|
92
|
+
readonly spec: Required<Pick<TaskSpec, "permissionMode" | "writeFace">>;
|
|
93
|
+
readonly writeProtection: ToolPolicy | undefined;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 引擎写保护判官读的两只 Runner 座(`RunnerDeps` 的同名切片):表(`writeProtectedPaths`,缺席 = core 源表)与数据根放行(`memoryEngineDir`,缺席 = 引擎缺省)。
|
|
97
|
+
* 调用方递**构造本腿 Runner 的那一份** deps(或它的展开源)—— 表与根从哪来、根要不要在,都由那份 deps 说了算,腿的接线处不再单独读任何一只坐标
|
|
98
|
+
* (本模块是唯一读者;S-877 四续:接线处各读一份就是「两边同一张表、同一个根」这句话的第二、三个写者)。
|
|
99
|
+
*/
|
|
100
|
+
export type UnattendedLegRunnerSeats = Pick<RunnerDeps, "writeProtectedPaths" | "memoryEngineDir">;
|
|
101
|
+
/** 三处无人值守腿(leader worker / 修复腿 / run-local)与续跑重挂的**唯一**铸点:`mode` 取 {@link unattendedPermissionMode},`runnerSeats` = 本腿 Runner 的 deps
|
|
102
|
+
* (引擎写保护判官与本层拒行同一张表、同一个数据根)。 */
|
|
103
|
+
export declare function createUnattendedLeg(mode: PermissionMode, runnerSeats: UnattendedLegRunnerSeats): UnattendedLeg;
|
|
104
|
+
/**
|
|
105
|
+
* 把无人值守腿并进一条 spec —— 三处腿的**唯一**合并口:两键覆盖(本腿的模式与写面由旋钮说了算),写保护拒层与 spec 已有的 `toolPolicy` **合成**
|
|
106
|
+
* (`combinePolicies`,deny-wins,携带 `pathDenyRules`)而不是覆盖(leader worker 的 durable 名单门、run-local 的治理链都在那里)。
|
|
107
|
+
* 调用点必须在 spec 其余键(含治理折叠)**之后**调它,否则后铸的 `toolPolicy` 会把拒层盖掉。
|
|
108
|
+
*/
|
|
109
|
+
export declare function withUnattendedLeg<S extends Partial<TaskSpec>>(spec: S, leg: UnattendedLeg): S & Required<Pick<TaskSpec, "permissionMode" | "writeFace">>;
|
|
110
|
+
//# sourceMappingURL=unattended-permission-mode.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ASK_CLASSES, PERMISSION_MODES, arbitrateByMode, combinePolicies, compileWriteProtection, foldArbitrationRow } from "@sema-agent/core";
|
|
2
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
3
|
+
export const UNATTENDED_PERMISSION_MODE_DEFAULT = "bypassPermissions";
|
|
4
|
+
export function parseUnattendedPermissionMode(raw) {
|
|
5
|
+
const trimmed = (raw ?? "").trim();
|
|
6
|
+
if (trimmed === "")
|
|
7
|
+
return { value: UNATTENDED_PERMISSION_MODE_DEFAULT, source: "default" };
|
|
8
|
+
const word = PERMISSION_MODES.find((m) => m === trimmed);
|
|
9
|
+
if (word === undefined) {
|
|
10
|
+
throw new Error(`env UNATTENDED_PERMISSION_MODE="${trimmed.slice(0, 120)}" is not a permission mode — use one of: ${PERMISSION_MODES.join(", ")} ` +
|
|
11
|
+
`(or leave it unset for the default "${UNATTENDED_PERMISSION_MODE_DEFAULT}", which keeps the unattended legs — leader workers / repairs and run-local — acting as they did before server 7.106.0). ` +
|
|
12
|
+
`The word is matched EXACTLY (surrounding whitespace trimmed, no case folding). ` +
|
|
13
|
+
`Refusing to boot rather than treating it as unset: that would run those legs under a mode the operator did not choose`);
|
|
14
|
+
}
|
|
15
|
+
return { value: word, source: "env" };
|
|
16
|
+
}
|
|
17
|
+
export function unattendedPermissionMode(config) {
|
|
18
|
+
return config.unattendedPermissionMode;
|
|
19
|
+
}
|
|
20
|
+
export function buildUnattendedLegSpec(mode) {
|
|
21
|
+
return { permissionMode: mode, writeFace: "roots" };
|
|
22
|
+
}
|
|
23
|
+
export function createUnattendedWriteProtectionPolicy(mode, runnerSeats) {
|
|
24
|
+
if (!modeReleases(mode, "write_protection"))
|
|
25
|
+
return undefined;
|
|
26
|
+
const dataRoot = runnerSeats.memoryEngineDir;
|
|
27
|
+
const judge = compileWriteProtection(runnerSeats.writeProtectedPaths, dataRoot !== undefined ? { dataRoot } : undefined);
|
|
28
|
+
if (judge === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
const pathDenyRules = judge.rows.map((row) => ({
|
|
31
|
+
rule: `WriteProtected(${row.name})`,
|
|
32
|
+
tool: "Edit",
|
|
33
|
+
pattern: row.name,
|
|
34
|
+
faces: ["write"],
|
|
35
|
+
reaches: (absolutePath) => judge.matchPath(absolutePath)?.name === row.name,
|
|
36
|
+
}));
|
|
37
|
+
return {
|
|
38
|
+
check: () => ({ action: "allow" }),
|
|
39
|
+
pathDenyRules,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function modeReleases(mode, askClass) {
|
|
43
|
+
const classes = new Set([askClass]);
|
|
44
|
+
return arbitrateByMode(mode, { classes, askClass, row: foldArbitrationRow(classes) }).kind === "released";
|
|
45
|
+
}
|
|
46
|
+
const INTENDED_RELEASES = new Set(["ordinary", "mode"]);
|
|
47
|
+
function releasedProtectiveClasses(mode) {
|
|
48
|
+
return ASK_CLASSES.filter((c) => !INTENDED_RELEASES.has(c) && c !== "write_protection" && modeReleases(mode, c));
|
|
49
|
+
}
|
|
50
|
+
const RELEASED_PROTECTIVE_ASKS_TAG = "server.unattended-leg.mode-releases-protective-asks";
|
|
51
|
+
export function createUnattendedLeg(mode, runnerSeats) {
|
|
52
|
+
const debt = releasedProtectiveClasses(mode);
|
|
53
|
+
if (debt.length > 0)
|
|
54
|
+
recordFailOpen(RELEASED_PROTECTIVE_ASKS_TAG, `mode=${mode} released=${debt.join(",")}`);
|
|
55
|
+
return { spec: buildUnattendedLegSpec(mode), writeProtection: createUnattendedWriteProtectionPolicy(mode, runnerSeats) };
|
|
56
|
+
}
|
|
57
|
+
export function withUnattendedLeg(spec, leg) {
|
|
58
|
+
const wp = leg.writeProtection;
|
|
59
|
+
return {
|
|
60
|
+
...spec,
|
|
61
|
+
...leg.spec,
|
|
62
|
+
...(wp !== undefined ? { toolPolicy: spec.toolPolicy !== undefined ? combinePolicies(spec.toolPolicy, wp) : wp } : {}),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=unattended-permission-mode.js.map
|