@sema-agent/server 7.118.0 → 7.119.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 +85 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +24 -13
- package/deploy/sema-up/README.md +2 -0
- package/deploy/sema-up/chart/templates/minio.yaml +2 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
- package/deploy/sema-up/chart/templates/server.yaml +1 -0
- package/deploy/sema-up/chart/values.yaml +7 -1
- package/deploy/sema-up/cluster-up.sh +20 -8
- package/deploy/sema-up/kube-up.sh +29 -16
- package/deploy/sema-up/lib/buckets.sh +21 -0
- package/deploy/sema-up/lib/health.sh +33 -0
- package/deploy/sema-up/sema-up.sh +14 -9
- package/deploy/sema-up/smoke.sh +16 -6
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
- package/dist/approval-ask-audit-store.js +2 -7
- package/dist/approval-card.d.ts +5 -5
- package/dist/approval.d.ts +51 -5
- package/dist/approval.js +76 -13
- package/dist/audit.d.ts +12 -1
- package/dist/audit.js +62 -2
- package/dist/auto-mode-face.d.ts +2 -3
- package/dist/bake-runner/main.js +1 -6
- package/dist/bake-runner/protocol.d.ts +18 -0
- package/dist/bake-runner/protocol.js +22 -2
- package/dist/bake-runner/runner.d.ts +12 -8
- package/dist/bake-runner/runner.js +5 -9
- package/dist/boot/config-center.js +1 -1
- package/dist/boot/coordinators.js +4 -1
- package/dist/boot/device-lane.d.ts +3 -3
- package/dist/boot/device-lane.js +1 -2
- package/dist/boot/leader.d.ts +4 -4
- package/dist/boot/leader.js +1 -2
- package/dist/boot/parked-revive-gate.d.ts +5 -1
- package/dist/boot/parked-revive-gate.js +8 -2
- package/dist/boot/resolve-spec.js +7 -4
- package/dist/boot/stage-07-capability-layer.js +5 -1
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.js +12 -7
- package/dist/capabilities/center-plugins.d.ts +2 -0
- package/dist/capabilities/center-plugins.js +24 -6
- package/dist/capabilities/code-review-council.js +8 -11
- package/dist/capabilities/scenarios.d.ts +34 -7
- package/dist/capabilities/scenarios.js +33 -6
- package/dist/config-catalog.js +3 -3
- package/dist/config.js +2 -2
- package/dist/deployment-governance.d.ts +5 -2
- package/dist/deployment-governance.js +4 -3
- package/dist/host-decision.d.ts +52 -27
- package/dist/host-decision.js +14 -7
- package/dist/http/admission.js +6 -6
- package/dist/http/resume-legs.js +2 -1
- package/dist/http/routes/approvals-assistant.d.ts +25 -3
- package/dist/http/routes/approvals-assistant.js +9 -5
- package/dist/http/routes/capabilities.js +4 -3
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/runs.js +11 -5
- package/dist/http/routes/sessions.js +20 -16
- package/dist/http/wire-types.d.ts +12 -13
- package/dist/images/manifest.d.ts +7 -1
- package/dist/images/manifest.js +18 -16
- package/dist/memory-store-refusal.d.ts +7 -12
- package/dist/memory-store-refusal.js +4 -5
- package/dist/namespaced-rule-name.d.ts +4 -3
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/operator-ask.d.ts +6 -5
- package/dist/plugins/approval-exemption-store.d.ts +4 -0
- package/dist/plugins/approval-exemption-store.js +1 -0
- package/dist/plugins/blob-backend.d.ts +13 -0
- package/dist/plugins/blob-backend.js +26 -0
- package/dist/plugins/local-session-store.d.ts +78 -47
- package/dist/plugins/local-session-store.js +152 -122
- package/dist/plugins/one-time-migrations.d.ts +6 -0
- package/dist/plugins/one-time-migrations.js +19 -1
- package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
- package/dist/plugins/snapshot-bucket-presence.js +40 -0
- package/dist/plugins/store-backend.d.ts +17 -6
- package/dist/plugins/store-backend.js +46 -16
- package/dist/plugins/store-open-wait.d.ts +33 -0
- package/dist/plugins/store-open-wait.js +73 -0
- package/dist/rule-entry-list.js +1 -1
- package/dist/run-local.js +4 -3
- package/dist/security.d.ts +5 -3
- package/dist/security.js +4 -0
- package/dist/session-policy-wire.js +7 -12
- package/dist/spec-fields.d.ts +6 -2
- package/dist/store-posture.d.ts +14 -6
- package/dist/store-posture.js +7 -4
- package/dist/task-settings.d.ts +27 -55
- package/dist/task-settings.js +6 -18
- package/dist/tool-approval.d.ts +97 -43
- package/dist/tool-approval.js +120 -31
- package/dist/trace/redact.d.ts +6 -0
- package/dist/trace/redact.js +9 -9
- package/dist/trace/self-minted-wire.d.ts +1 -1
- package/dist/user-input-ledger.d.ts +19 -15
- package/dist/user-input-ledger.js +12 -20
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, WEB_FETCH_TOOL_NAME, assembleCodeTools, defaultTaskRegistry } from "@sema-agent/core";
|
|
2
|
-
import { HttpError } from "../security.js";
|
|
2
|
+
import { HttpError, createCodedHttpError } from "../security.js";
|
|
3
3
|
import { createSubagentExtraTools, nowTool } from "./builtin-tools.js";
|
|
4
4
|
import { parseRepo, repoToolsFor } from "./repo-tools.js";
|
|
5
5
|
import { createCouncilTool, clampRounds } from "./code-review-council.js";
|
|
@@ -120,8 +120,12 @@ export function buildScenarios(deps) {
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
export function selectScenario(scenarios, name) {
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
if (name === undefined)
|
|
124
|
+
return scenarios.default;
|
|
125
|
+
const picked = Object.hasOwn(scenarios, name) ? scenarios[name] : undefined;
|
|
126
|
+
if (picked === undefined)
|
|
127
|
+
throw new Error(`scenario "${name}" is not registered — resolve a request's scenario through gateScenarioRequest, which only returns registered names`);
|
|
128
|
+
return picked;
|
|
125
129
|
}
|
|
126
130
|
function availabilityFields(deps, toolset) {
|
|
127
131
|
const verdict = scenarioAvailability(deps, toolset);
|
|
@@ -295,11 +299,28 @@ export function mergeUserSkills(scenarioSkills, userSkills, logger) {
|
|
|
295
299
|
logger?.warn("user_skills_dropped", { reason: "name reserved by scenario (or duplicate)", names: dropped });
|
|
296
300
|
return out;
|
|
297
301
|
}
|
|
298
|
-
|
|
302
|
+
function registeredScenariosClause(known) {
|
|
303
|
+
return `this deployment's scenarios are: ${known.join(", ")}`;
|
|
304
|
+
}
|
|
305
|
+
export function danglingScenarioRefusal(name, source, known) {
|
|
306
|
+
if (known.includes(name))
|
|
307
|
+
return undefined;
|
|
308
|
+
switch (source) {
|
|
309
|
+
case "ruling":
|
|
310
|
+
return `the scenario assigned to this principal, "${name}", is not a scenario of this deployment — ${registeredScenariosClause(known)}; nothing ran — re-point the assignment (or add that scenario to this deployment) and retry`;
|
|
311
|
+
case "deployment":
|
|
312
|
+
return `DEFAULT_SCENARIO "${name}" does not name a scenario of this deployment — ${registeredScenariosClause(known)}; set DEFAULT_SCENARIO to one of them`;
|
|
313
|
+
default: {
|
|
314
|
+
const unhandled = source;
|
|
315
|
+
return unhandled;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known, logger, principal) {
|
|
299
320
|
if (typeof bodyScenario === "string") {
|
|
300
321
|
const requested = bodyScenario;
|
|
301
322
|
if (!known.includes(requested)) {
|
|
302
|
-
throw new HttpError(400, `unknown scenario "${bodyScenario}" —
|
|
323
|
+
throw new HttpError(400, `unknown scenario "${bodyScenario}" — ${registeredScenariosClause(known)}`, {
|
|
303
324
|
code: "scenario_unknown",
|
|
304
325
|
extra: { known: [...known] },
|
|
305
326
|
});
|
|
@@ -313,6 +334,12 @@ export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known
|
|
|
313
334
|
}
|
|
314
335
|
return requested;
|
|
315
336
|
}
|
|
316
|
-
|
|
337
|
+
const [name, source] = ruling?.scenario != null ? [ruling.scenario, "ruling"] : [defaultScenario, "deployment"];
|
|
338
|
+
const refusal = danglingScenarioRefusal(name, source, known);
|
|
339
|
+
if (refusal !== undefined) {
|
|
340
|
+
logger?.warn("scenario_assignment_unknown", { scenario: name, source, ...(principal !== undefined ? { principal } : {}) });
|
|
341
|
+
throw createCodedHttpError(503, "state.scenario_assignment_unknown", refusal, { detail: { scenario: name, source } });
|
|
342
|
+
}
|
|
343
|
+
return name;
|
|
317
344
|
}
|
|
318
345
|
//# sourceMappingURL=scenarios.js.map
|
package/dist/config-catalog.js
CHANGED
|
@@ -104,7 +104,7 @@ export const CONFIG_CATALOG = [
|
|
|
104
104
|
r("CROSS_SESSION_INBOUND", "approval", "enum", "入站跨会话消息的部署/组织层治理(CC settings 键名 crossSessionInbound 的 managed 层;未设=本层不表态≠ next-turn;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_INBOUND_SETTINGS], configKey: "crossSessionInbound", derivedDefaultNote: "缺席 ⇒ managed 层不设,由其余层与 mode-parity 决定", resolve: (c) => c.crossSessionInbound ?? null }),
|
|
105
105
|
r("DB_BACKEND", "store", "enum", "store 后端引擎选择(mysql 兼容/pg/本机文件店/纯内存)", { enumValues: STORE_BACKENDS, danger: DATA, derivedDefaultNote: "裸 boot ⇒ local(多租户裸 boot ⇒ memory + notice);有 SQL 信号 ⇒ mysql", configKey: "dbBackend", resolve: (c) => c.dbBackend }),
|
|
106
106
|
r("DB_QUERY_TIMEOUT_MS", "store", "number", "per-query DB 超时(ms;坏值回默认姿态+warn,绝不 NaN 进 driver)", { derivedDefaultNote: "缺席 ⇒ 池级不设/计数族 30s(按引擎)", configKey: "dbQueryTimeoutMs" }),
|
|
107
|
-
r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回)", { staticDefault: "code", configKey: "defaultScenario" }),
|
|
107
|
+
r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回);必须是本部署在册的场景名(内建 ∪ 配置中心声明的),不在册 ⇒ 拒启", { staticDefault: "code", configKey: "defaultScenario" }),
|
|
108
108
|
r("DELEGATION_MAX_CONCURRENT", "orchestration", "number", "委派入口并发帽", { derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省(CC parity 20)" }),
|
|
109
109
|
r("DELEGATION_MAX_PER_SESSION", "orchestration", "number", "委派入口每会话累计帽", { derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省(CC parity 200)" }),
|
|
110
110
|
r("DEVICE_DELIVERY_TIMEOUT_MS", "orchestration", "number", "device 车道等在途槽上限(ms;到期 device.busy)", { derivedDefaultNote: "缺席 ⇒ adapter 默认 10s" }),
|
|
@@ -411,7 +411,7 @@ export const CONFIG_CATALOG = [
|
|
|
411
411
|
r("SERVICE_AUTH_TOKEN", "auth", "secret", "共享 service token(写面门;缺席且未开 ALLOW_UNAUTHED_WRITES ⇒ 拒提交)", { danger: SEC }),
|
|
412
412
|
r("SERVICE_AUTH_TOKENS", "auth", "secret", "per-system 凭据表(tok=system CSV;source 从凭据派生,绝不自报)", { danger: SEC }),
|
|
413
413
|
r("SESSION_AUTO_TITLE", "orchestration", "boolean", "首次提交自动铸 session 标题(一次 cheap 调用)", { staticDefault: "true", danger: BILL, configKey: "sessionAutoTitle" }),
|
|
414
|
-
r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto
|
|
414
|
+
r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=配了该引擎的坐标就用那只库、没配就是内存;配了而此刻连不上 ⇒ 在 STORE_LOCK_WAIT_MS 窗内重试、到窗拒启,绝不落内存;回显=boot 解析后的真引擎)", { enumValues: SESSION_BACKENDS, danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => (c.sessionBackend === "tidb" ? "mysql" : c.sessionBackend) }),
|
|
415
415
|
r("SESSION_CACHE_TTL_SEC", "store", "number", "session 读缓存 TTL(秒;0=关)", { staticDefault: "300", configKey: "sessionCacheTtlSec" }),
|
|
416
416
|
r("SESSION_EVENTS_HOT_MS", "limitsHttp", "number", "session SSE 订阅热探针间隔(ms)", { derivedDefaultNote: "缺席 ⇒ 200" }),
|
|
417
417
|
r("SESSION_EVENTS_MAX_CONNS", "limitsHttp", "number", "session SSE 并发连接帽", { derivedDefaultNote: "缺席 ⇒ 256" }),
|
|
@@ -431,7 +431,7 @@ export const CONFIG_CATALOG = [
|
|
|
431
431
|
r("SSH_PORT", "orchestration", "number", "ssh 端口", { derivedDefaultNote: "缺席 ⇒ 22" }),
|
|
432
432
|
r("SSH_PRIVATE_KEY", "orchestration", "secret", "ssh 私钥(adapter 持有,绝不进 workspace)", { danger: SEC }),
|
|
433
433
|
r("SSH_USER", "orchestration", "string", "ssh 用户(必需件)"),
|
|
434
|
-
r("STORE_LOCK_WAIT_MS", "store", "number", "local 数据根 LOCK
|
|
434
|
+
r("STORE_LOCK_WAIT_MS", "store", "number", "store 启动期的有界等待窗(ms):local 数据根 LOCK 撞上活持锁人,或已配置的 SQL 库此刻连不上 / 建表失败;0=当场拒;到窗仍开不了 ⇒ 拒启(local 保持原码,永不放行第二个写者;SQL 点名后端与 host:port,永不退成内存)", { staticDefault: "60000", danger: DATA, configKey: "storeLockWaitMs" }),
|
|
435
435
|
r("STORE_PROBE_INTERVAL_MS", "store", "number", "/health 活体 store 后台探针间隔(ms;0=显式关;仅 SQL 后端挂环)", { staticDefault: "15000", configKey: "storeProbeIntervalMs" }),
|
|
436
436
|
r("STREAM_APPROVAL_ADHOC_GRACE_MS", "approval", "number", "流内审批 adhoc 腿窗后宽限(ms;须 window+grace < orphanTtl,破坏形拒启)", { staticDefault: "60000" }),
|
|
437
437
|
r("STREAM_APPROVAL_ADMIT_MAX_PER_OWNER", "approval", "number", "流内审批 per-owner 未决帽", { staticDefault: "256" }),
|
package/dist/config.js
CHANGED
|
@@ -842,13 +842,13 @@ function buildUnusedCoordinatesRefusal(keys, auto) {
|
|
|
842
842
|
const engine = keys.length === 1 ? (keys[0] === "PG_HOST" ? "pg" : "mysql") : "mysql|pg";
|
|
843
843
|
const probedHostKey = auto?.autoProbes === "pg" ? "PG_HOST" : "MYSQL_HOST";
|
|
844
844
|
const why = auto !== undefined
|
|
845
|
-
? `SESSION_BACKEND=auto
|
|
845
|
+
? `SESSION_BACKEND=auto follows the ${auto.autoProbes} engine (DB_BACKEND ${process.env.DB_BACKEND ? "names it" : "unset ⇒ mysql"}), whose host key ${probedHostKey} is not set`
|
|
846
846
|
: "no durable session store is selected (neither DB_BACKEND=mysql|pg nor SESSION_BACKEND=mysql|auto), so the store would run IN MEMORY";
|
|
847
847
|
return (`${keys.join(" and ")} ${keys.length === 1 ? "is" : "are"} set but the current configuration never uses ${keys.length === 1 ? "it" : "them"}: ${why}. ` +
|
|
848
848
|
`Refusing to boot rather than running in memory silently. ` +
|
|
849
849
|
`To persist: DB_BACKEND=${engine} (or SESSION_BACKEND=mysql for the MySQL-protocol engine); ` +
|
|
850
850
|
`to run in memory: remove the coordinates, or set DB_BACKEND=memory; ` +
|
|
851
|
-
`
|
|
851
|
+
`SESSION_BACKEND=auto persists exactly when the engine's coordinates are configured (DB_BACKEND=pg with PG_HOST, DB_BACKEND=mysql with MYSQL_HOST) — and then refuses to boot, rather than falling back to memory, while the store is unreachable.`);
|
|
852
852
|
}
|
|
853
853
|
function parseStoreDomain(ctx) {
|
|
854
854
|
const { requirePrincipal } = ctx;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FileError, StubExecutionEnv, type ExecutionEnv, type Result, type ToolPolicy } from "@sema-agent/core";
|
|
2
|
+
import { type DeploymentListExemption } from "./approval.js";
|
|
2
3
|
import type { ServiceConfig } from "./config.js";
|
|
3
4
|
import type { applyRuntimeGovernance } from "./runtime-governance.js";
|
|
4
5
|
/** `applyRuntimeGovernance` 的 governance 实参 —— 从折叠属主的签名位**推导**,不复制形状
|
|
@@ -91,8 +92,10 @@ export interface DurableApprovalSeat {
|
|
|
91
92
|
readonly kind: "durable";
|
|
92
93
|
/** 活体问答面;`undefined` ⇒ 恒 durable park 的原形门。 */
|
|
93
94
|
readonly question: LiveQuestionFace | undefined;
|
|
95
|
+
/** 本会话的「本会话放行」读面(部署名单那一问的站着的答案;铸点 `approval.ts` 的 `createDeploymentListExemption`)。缺席 = 这条腿没有会话 / 没有豁免店 ⇒ 名单照问。 */
|
|
96
|
+
readonly exemption?: DeploymentListExemption;
|
|
94
97
|
}
|
|
95
|
-
/** live 席:Runner 的活 onAsk 当场服 ask(无 park)。 */
|
|
98
|
+
/** live 席:Runner 的活 onAsk 当场服 ask(无 park、无会话豁免店:run-local 一次性腿)。 */
|
|
96
99
|
export interface LiveApprovalSeat {
|
|
97
100
|
readonly kind: "live";
|
|
98
101
|
}
|
|
@@ -263,7 +266,7 @@ export declare function buildApprovalGrantNotice(config: {
|
|
|
263
266
|
* `undefined`**:治理层是 tighten-only 的叠加层,没有基线可叠时它自己也产不出「门在场」这件事。
|
|
264
267
|
*
|
|
265
268
|
* 三形,按席穷举(S-694,席的定义见 {@link ApprovalSeat}):
|
|
266
|
-
* · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto
|
|
269
|
+
* · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto/预算/本会话的豁免行全在
|
|
267
270
|
* `createDurableAskPolicy` 里)。gated `ask` 由 core 变成 durable checkpoint suspend。
|
|
268
271
|
* · `live` ⇒ **同一只**三张名单审批门,不叠问答门。gated `ask` 由 core 交给 Runner 的活 onAsk 席
|
|
269
272
|
* (本口**不**做 ask→deny 映射:无人可问时的拒由那只席说,判据与文案都归它)。
|
|
@@ -126,9 +126,9 @@ export function buildApprovalGrantNotice(config) {
|
|
|
126
126
|
export function createApprovalBaselinePolicy(config, seat) {
|
|
127
127
|
switch (seat.kind) {
|
|
128
128
|
case "durable":
|
|
129
|
-
return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config));
|
|
129
|
+
return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config, seat.exemption));
|
|
130
130
|
case "live":
|
|
131
|
-
return createApprovalListPolicy(config);
|
|
131
|
+
return createApprovalListPolicy(config, undefined);
|
|
132
132
|
case "absent":
|
|
133
133
|
if (hasApprovalListIntent(config)) {
|
|
134
134
|
throw new Error("approval baseline: this leg has no approval seat (nobody can answer an ask), yet an approval list is in force " +
|
|
@@ -142,12 +142,13 @@ export function createApprovalBaselinePolicy(config, seat) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
-
function createApprovalListPolicy(config) {
|
|
145
|
+
function createApprovalListPolicy(config, exemption) {
|
|
146
146
|
return createDurableAskPolicy({
|
|
147
147
|
requireApproval: config.approvalRequire,
|
|
148
148
|
deny: config.approvalDeny,
|
|
149
149
|
autoBudget: config.approvalAutoBudget,
|
|
150
150
|
neverAuto: config.approvalNeverAuto,
|
|
151
|
+
...(exemption !== undefined ? { exemption } : {}),
|
|
151
152
|
});
|
|
152
153
|
}
|
|
153
154
|
//# sourceMappingURL=deployment-governance.js.map
|
package/dist/host-decision.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* ⇒ 本仓的规则从「记住一张词×决定的相容表」变成「说出你知道的那两件事」。相容性也不再靠记忆:一次
|
|
16
16
|
* sweep 不可能 `allow`,core pre-CAS 拒。
|
|
17
17
|
*/
|
|
18
|
-
import { type AskOutcome, type ResumeOutcome } from "@sema-agent/core";
|
|
18
|
+
import { type AskOutcome, type HostDecider, type PermissionResult, type ResumeOutcome } from "@sema-agent/core";
|
|
19
19
|
/**
|
|
20
20
|
* 谁结束了这次等待,以及通道自报的归属。
|
|
21
21
|
* · `decidedBy: "person"` —— 一个人经审批通道做的决定(决定体缺席 `decidedBy` 即此);
|
|
@@ -47,19 +47,17 @@ import { type AskOutcome, type ResumeOutcome } from "@sema-agent/core";
|
|
|
47
47
|
* 🔴 **S-558:为什么这一格住在本叶子模块**(此前是 `tool-approval.ts` 的模块私有型):来源位随决议
|
|
48
48
|
* **落持久行**之后,店(`src/plugins/approval-ask-store-sql.ts` 的 `AskRow.settledBy` /
|
|
49
49
|
* `DecideAskInput.settledBy`)与协调器都要说同一个型 —— 而店不能反向依赖协调器。本模块只依赖
|
|
50
|
-
* `@sema-agent/core`,与同住的 {@link
|
|
50
|
+
* `@sema-agent/core`,与同住的 {@link HostDecision}(耐久腿的同位事实)恰好是一对:一条决议的来源
|
|
51
51
|
* 在两条腿上是同一件事,型也该在同一处。
|
|
52
52
|
*/
|
|
53
53
|
export type HostSettledBy = NonNullable<Extract<AskOutcome, {
|
|
54
54
|
allow: boolean;
|
|
55
55
|
}>["settledBy"]>;
|
|
56
|
-
/** 直接取 core `ResumeOutcome` policy_ask 臂的 `hostDecision` 型 —— 本仓不复述那几个词(第二铸点)
|
|
56
|
+
/** 直接取 core `ResumeOutcome` policy_ask 臂的 `hostDecision` 型 —— 本仓不复述那几个词(第二铸点)。它的 `decidedBy` 就是 core 的 `HostDecider`
|
|
57
|
+
* (core 7.45.0 #1387:三个型位读同一只闭集)。**谁结束了这次等待**的闭集只有 core 包根那一只(`HOST_DECIDERS` / `HostDecider` / `isHostDecider`)。 */
|
|
57
58
|
export type HostDecision = Extract<ResumeOutcome, {
|
|
58
59
|
gate: "policy_ask";
|
|
59
60
|
}>["hostDecision"];
|
|
60
|
-
/** **谁结束了这次等待**的闭集,型从 core 的那一格派生(本仓永不抄词表:core 加/退一个词,读它的每一处
|
|
61
|
-
* 编译期先红)。今天 = `"person"` | `"sla_timeout"` | `"policy"`。 */
|
|
62
|
-
export type HostDecidedBy = HostDecision["decidedBy"];
|
|
63
61
|
/**
|
|
64
62
|
* ══ 决定体的出处位 `decidedBy`:一只闭集、一只读法、一只映射 ═══════════════════════════════════════════════
|
|
65
63
|
*
|
|
@@ -68,32 +66,55 @@ export type HostDecidedBy = HostDecision["decidedBy"];
|
|
|
68
66
|
* `POST /v1/tasks/:id/asks/:askId/decision`、耐久 `POST /v1/approvals/:sessionId/decide`),把它交给引擎的只有
|
|
69
67
|
* {@link liveSettledBy}(同步座 `AskOutcome.settledBy`)与 {@link hostDecisionFacts}(停驻座 `hostDecision.decidedBy`)。
|
|
70
68
|
* · 词与决定动词**正交**:策略可以放行也可以拒(core 7.39.0 起引擎铸 `policy_allowed` / `policy_refused`)。唯一的例外是
|
|
71
|
-
* 引擎自己的那一条 —— 一张要人的卡(`requiresRealApproval`)
|
|
69
|
+
* 引擎自己的那一条 —— 一张要人的卡(`requiresRealApproval`)不归策略答,见 {@link heldAskOf}(判官是 core 的 `policyCannotAnswer`)。
|
|
72
70
|
* · `"sla_timeout"` **永不从 wire 收**:那是「等待到点、没人答」,只有本服务自己的截止扫说得出来(`approval-sweeps.ts`
|
|
73
71
|
* 直接铸 `{decidedBy:"sla_timeout"}`,不经本读法)。调用方说它 = 替服务端伪证一件只有服务端看得见的事。
|
|
74
72
|
* · `plan_review` 不读这只键:引擎的 plan_review 结局形没有决定者位(core 的结局规整会把别的键整只重建掉),收下它就是
|
|
75
73
|
* 一句「已记」的空话。那一门照兄弟门字段那一族拒(`approvals-assistant.ts` 的回决门族派生),候 core 给座。
|
|
76
74
|
*
|
|
77
|
-
* 词表的来源:core
|
|
78
|
-
*
|
|
79
|
-
* 不收的词,也不会把 core 新开的词静默 400 掉。core 导出词表之日,本常量改为直接镜像那一只(请托在案)。
|
|
75
|
+
* 词表的来源:core 包根的 `HOST_DECIDERS` / `HostDecider` / `isHostDecider`(core 7.45.0 #1387;此前包根只有型,本仓手写一只闭集常量 + 两道
|
|
76
|
+
* 编译门双向咬它 —— 那份手抄随本版退役)。wire 闭集 = 那只词表去掉扫的那一个词,运行期值与型都从它派生,没有第二份词表。
|
|
80
77
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
/** 只有本服务自己的截止扫说得出的那一个词 —— core 词表里唯一不上 wire 的成员(`satisfies`:core 改名 / 退役它 ⇒ 这里编译红)。 */
|
|
79
|
+
declare const SWEEP_ONLY_DECIDER = "sla_timeout";
|
|
80
|
+
/** wire 上的出处词(决定体 `decidedBy` 的值域)= core 闭集去掉扫的那一个。 */
|
|
81
|
+
export type ApprovalDecidedBy = Exclude<HostDecider, typeof SWEEP_ONLY_DECIDER>;
|
|
82
|
+
/** wire 闭集的运行期值:core `HOST_DECIDERS`(#1387)去掉扫的那一个,顺序随 core。core 加一个词 ⇒ 它在这里自动在场(决定口的门照收;两只座的映射
|
|
83
|
+
* {@link liveSettledBy} 的穷举 `switch` 编译红,由人裁它在同步座上怎么落)。 */
|
|
84
|
+
export declare const APPROVAL_DECIDED_BY: readonly ApprovalDecidedBy[];
|
|
85
|
+
/** 闭集外的拒句(三条决定口同一句;码按各口既有约定,见 {@link readDecidedBy})。成员表派生自 {@link APPROVAL_DECIDED_BY}(词表一个来源;今天字节与 7.118.0 同)。 */
|
|
86
|
+
export declare const DECIDED_BY_OUTSIDE_CLOSED_SET: string;
|
|
86
87
|
/** 直连门上的拒句(与 `remember` / `updatedInput` / `checkpointToken` 同族:键不在决策证明的签名载荷里)。 */
|
|
87
88
|
export declare const DECIDED_BY_NOT_IN_PROOF = "decidedBy is not supported on a direct-door worker (not covered by the decision proof)";
|
|
88
|
-
/** 一张要人的卡上策略的放行(
|
|
89
|
+
/** 一张要人的卡上策略的放行(两条决定口同一句;判官 core `policyCannotAnswer`,问形见 {@link heldAskOf})。 */
|
|
89
90
|
export declare const POLICY_CANNOT_ALLOW_PERSON_ASK = "decidedBy \"policy\" cannot allow this ask \u2014 it demands a person's approval (risk.requiresRealApproval), and a policy's answer is not a person's; answer it as a person, or refuse it as the policy";
|
|
90
91
|
/**
|
|
91
|
-
* 退役键 `settledBy`(7.93.3–7.117.x 的「只配 deny」那一位)
|
|
92
|
-
*
|
|
93
|
-
* 套在一个说了话的体上(
|
|
94
|
-
* 在裁决之前响亮拒它,与同一只 parser 里 `scope`
|
|
92
|
+
* 退役键 `settledBy`(7.93.3–7.117.x 的「只配 deny」那一位)的拒句。三条决定口共用这一句,出句的铸点是 {@link retiredDecisionKeyFacts}(各口不直接引用本常量)。
|
|
93
|
+
* live `/respond` 的解析不是键闭集的(未知键一律忽略),而这只键不是随便一个未知键:它是调用方在**明确陈述**「这是策略答的」—— 忽略它就是把缺席律(person)
|
|
94
|
+
* 套在一个说了话的体上(旧壳的策略拒记成人拒;一张要人的卡上旧形的放行被读成人的放行,绕过「要人的卡不归策略答」那一判)。所以那条口
|
|
95
|
+
* 在裁决之前响亮拒它,与同一只 parser 里 `scope` 的拒同一条先例;耐久两口本来就在键闭集 / strict 体上拒它,句子改由同一铸点出。
|
|
95
96
|
*/
|
|
96
97
|
export declare const SETTLED_BY_RETIRED = "settledBy is retired \u2014 send decidedBy: \"person\" | \"policy\" (who made this decision; absent = \"person\"); ignoring it would record a policy's answer as a person's";
|
|
98
|
+
/**
|
|
99
|
+
* 退役键 `settledBy` 的**拒绝事实** —— 三条工具审批决定口(live `POST /v1/tool-approvals/:id/respond`、耐久 ask 账
|
|
100
|
+
* `POST /v1/tasks/:taskId/asks/:askId/decision`、`POST /v1/approvals/:sessionId/decide`)对它的拒,**句**与**机读位**同出自这一处:
|
|
101
|
+
* · `error` = {@link SETTLED_BY_RETIRED}(三口同一句,没有哪一口自说一句 —— 此前耐久 ask 账说 strict 体的通用句、`/decide` 说键闭集句);
|
|
102
|
+
* · `extra` = `{ field: "settledBy" }`,直接作 `sendError` 的 `extra` / 并进 400 体。`field` 是 400 体上既有的那一把键(`request.field_invalid` 的
|
|
103
|
+
* `settings.permissions.*` / `settings.hooks` / 模型位同键同读法:点名这只拒落在请求体里哪个字段上),不另造码、不另造键。
|
|
104
|
+
* 码三口同为 `request.body_shape`;`/decide` 照旧另带 `unknownKeys` / `unsupportedKeys` 两张键表(退役键仍在 `unknownKeys` 里)。
|
|
105
|
+
* 判在场的口径只有一个:值不是 `undefined`(JSON 里的 `null` / `""` 也算在场 —— 退役键是一句**陈述**,不看它陈述了什么)。
|
|
106
|
+
* 用法(两种出口,各口都只**带**、不改):① 拒的成因就是退役键(live 解析器撞上它 / 耐久 ask 账 strict 解析失败而体里有它 / `/decide` 键闭集门)⇒ 句用 `error`、
|
|
107
|
+
* 体并 `extra`;② 体里同时还有别的毛病而先撞上的是那一个(live 解析器的 `note` / `persistRule` / 决定词,`/decide` 的根形门)⇒ 句仍说那个毛病、体照并 `extra` ——
|
|
108
|
+
* 每个口在自己的**失败出口**调同一只函数,而不是在某条判定的分支里调,否则「机读位在不在」就取决于哪句先撞上。
|
|
109
|
+
*/
|
|
110
|
+
export declare const RETIRED_DECISION_KEY: "settledBy";
|
|
111
|
+
export interface RetiredDecisionKeyFacts {
|
|
112
|
+
readonly error: typeof SETTLED_BY_RETIRED;
|
|
113
|
+
readonly extra: {
|
|
114
|
+
readonly field: typeof RETIRED_DECISION_KEY;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export declare function retiredDecisionKeyFacts(body: unknown): RetiredDecisionKeyFacts | undefined;
|
|
97
118
|
/** {@link readDecidedBy} 的判决(调用点做穷举 `switch` + `never` 兜底,各按自己的码约定发出)。 */
|
|
98
119
|
export type DecidedByReading = {
|
|
99
120
|
kind: "accepted";
|
|
@@ -120,14 +141,17 @@ export declare function readDecidedBy(raw: unknown, directDoorActive: boolean):
|
|
|
120
141
|
*/
|
|
121
142
|
export declare function liveSettledBy(decidedBy: ApprovalDecidedBy): Extract<HostSettledBy, "policy"> | undefined;
|
|
122
143
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
144
|
+
* 引擎自己的那一条例外:**一张要人的卡不归策略答** —— 判官是 core 的 `policyCannotAnswer(question, decidedBy)`(#1395 包根导出;引擎的续跑入口、
|
|
145
|
+
* 执行带与同步座读同一条)。本仓**只在自己的副作用先于引擎落地的两条口**上于裁决之前问它(live `/respond`:会话放行 / 常驻规则 / 一条按 approve
|
|
146
|
+
* 回放的 DECIDED 行;耐久 ask 账:一条宣称 approve 的 DECIDED 行),并且只问**放行**那一向(与引擎续跑入口同:拒永远答得了)。耐久 `/decide` 不判:
|
|
147
|
+
* 引擎在 CAS 之前就拒(`checkpoint.invalid_outcome`,卡留 pending),本口的副作用全在引擎受理之后 —— 在那里再问一次就是第二个判官。
|
|
148
|
+
*
|
|
149
|
+
* 交给判官的「问」= {@link heldAskOf}:本仓两条口持有的都不是引擎的原物(活卡条目记的是铸条目那一次从 `AskRequest` 读定的那一位;耐久 ask 账行上是
|
|
150
|
+
* 卡面 `risk.requiresRealApproval`,读不出按要人读),以引擎的问形(core `HeldQuestion` 的 `ask` 决定臂)交过去 —— 「谁答不了」只在引擎那一处判。
|
|
129
151
|
*/
|
|
130
|
-
export declare function
|
|
152
|
+
export declare function heldAskOf(demandsAPerson: boolean): Extract<PermissionResult, {
|
|
153
|
+
action: "ask";
|
|
154
|
+
}>;
|
|
131
155
|
/**
|
|
132
156
|
* 一次 decide 的**停驻座事实**,一处成形(耐久 `/decide` 与活卡 PARKED 赎回席共用;截止扫直接铸 `{decidedBy:"sla_timeout"}`)。
|
|
133
157
|
*
|
|
@@ -144,4 +168,5 @@ export declare function policyCannotAllow(decidedBy: ApprovalDecidedBy, allow: b
|
|
|
144
168
|
* 迟到结算账本(`approval-ask-audit-store.ts`)记的就是它。按词删一格 = 给账本挖一个只在某个词上出现的洞。
|
|
145
169
|
*/
|
|
146
170
|
export declare function hostDecisionFacts(decidedBy: ApprovalDecidedBy, principal: string | undefined): HostDecision;
|
|
171
|
+
export {};
|
|
147
172
|
//# sourceMappingURL=host-decision.d.ts.map
|
package/dist/host-decision.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { screenApproverAttribution } from "@sema-agent/core";
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { HOST_DECIDERS, isHostDecider, screenApproverAttribution } from "@sema-agent/core";
|
|
2
|
+
const SWEEP_ONLY_DECIDER = "sla_timeout";
|
|
3
|
+
function isApprovalDecidedBy(v) {
|
|
4
|
+
return isHostDecider(v) && v !== SWEEP_ONLY_DECIDER;
|
|
5
|
+
}
|
|
6
|
+
export const APPROVAL_DECIDED_BY = Object.freeze(HOST_DECIDERS.filter(isApprovalDecidedBy));
|
|
7
|
+
export const DECIDED_BY_OUTSIDE_CLOSED_SET = `decidedBy must be ${APPROVAL_DECIDED_BY.map((w) => `"${w}"`).join(" or ")} when present — who made this decision: a person, or this deployment's own policy (absent = "person"); "${SWEEP_ONLY_DECIDER}" is never accepted from a caller (only this server's own deadline sweep says a wait ended with nobody answering)`;
|
|
4
8
|
export const DECIDED_BY_NOT_IN_PROOF = "decidedBy is not supported on a direct-door worker (not covered by the decision proof)";
|
|
5
9
|
export const POLICY_CANNOT_ALLOW_PERSON_ASK = 'decidedBy "policy" cannot allow this ask — it demands a person\'s approval (risk.requiresRealApproval), and a policy\'s answer is not a person\'s; answer it as a person, or refuse it as the policy';
|
|
6
10
|
export const SETTLED_BY_RETIRED = 'settledBy is retired — send decidedBy: "person" | "policy" (who made this decision; absent = "person"); ignoring it would record a policy\'s answer as a person\'s';
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
export const RETIRED_DECISION_KEY = "settledBy";
|
|
12
|
+
export function retiredDecisionKeyFacts(body) {
|
|
13
|
+
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
14
|
+
return undefined;
|
|
15
|
+
return body[RETIRED_DECISION_KEY] !== undefined ? { error: SETTLED_BY_RETIRED, extra: { field: RETIRED_DECISION_KEY } } : undefined;
|
|
9
16
|
}
|
|
10
17
|
export function readDecidedBy(raw, directDoorActive) {
|
|
11
18
|
if (raw === undefined)
|
|
@@ -26,8 +33,8 @@ export function liveSettledBy(decidedBy) {
|
|
|
26
33
|
}
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
|
-
export function
|
|
30
|
-
return
|
|
36
|
+
export function heldAskOf(demandsAPerson) {
|
|
37
|
+
return demandsAPerson ? { action: "ask", requiresRealApproval: true } : { action: "ask" };
|
|
31
38
|
}
|
|
32
39
|
export function hostDecisionFacts(decidedBy, principal) {
|
|
33
40
|
const screened = screenApproverAttribution(principal);
|
package/dist/http/admission.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { APPROVAL_REACH, MCP_IMAGE_MAX_BASE64, isApprovalReach, isThinkingLevel, expandTiers } from "@sema-agent/core";
|
|
1
|
+
import { APPROVAL_REACH, MCP_IMAGE_MAX_BASE64, isApprovalReach, isPermissionMode, isThinkingLevel, expandTiers } from "@sema-agent/core";
|
|
2
2
|
import { isModelAllowlisted, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../model-select.js";
|
|
3
3
|
import { modelUnavailableMessage } from "../model-entry-refusal.js";
|
|
4
4
|
import { HttpError } from "../security.js";
|
|
5
|
-
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS,
|
|
5
|
+
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, PERMISSION_MODE_WORDS, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, taskHooksHonored, taskSettingsKeyIssue } from "../task-settings.js";
|
|
6
6
|
import { taskBodyKeyIssue } from "../request-key-closure.js";
|
|
7
7
|
import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
8
8
|
import * as facts from "../capability-facts.js";
|
|
@@ -524,8 +524,8 @@ export function createAdmission(ctx) {
|
|
|
524
524
|
}
|
|
525
525
|
{
|
|
526
526
|
const v = body.permissionMode;
|
|
527
|
-
if (v !== undefined && !
|
|
528
|
-
sendError(res, 400, "request.field_invalid", `permissionMode must be one of ${
|
|
527
|
+
if (v !== undefined && !isPermissionMode(v)) {
|
|
528
|
+
sendError(res, 400, "request.field_invalid", `permissionMode must be one of ${PERMISSION_MODE_WORDS}`);
|
|
529
529
|
return null;
|
|
530
530
|
}
|
|
531
531
|
}
|
|
@@ -535,8 +535,8 @@ export function createAdmission(ctx) {
|
|
|
535
535
|
const p = s.permissions;
|
|
536
536
|
if (p !== null && typeof p === "object" && !Array.isArray(p)) {
|
|
537
537
|
const m = p.defaultMode;
|
|
538
|
-
if (m !== undefined && !
|
|
539
|
-
sendError(res, 400, "request.field_invalid", `settings.permissions.defaultMode must be one of ${
|
|
538
|
+
if (m !== undefined && !isPermissionMode(m)) {
|
|
539
|
+
sendError(res, 400, "request.field_invalid", `settings.permissions.defaultMode must be one of ${PERMISSION_MODE_WORDS}`);
|
|
540
540
|
return null;
|
|
541
541
|
}
|
|
542
542
|
const d = p.disableAutoMode;
|
package/dist/http/resume-legs.js
CHANGED
|
@@ -41,6 +41,7 @@ import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflic
|
|
|
41
41
|
import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js";
|
|
42
42
|
import { commitSessionInheritEnv } from "./session-inherit-env.js";
|
|
43
43
|
import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
|
|
44
|
+
import { deploymentListAsks } from "../approval.js";
|
|
44
45
|
import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
|
|
45
46
|
import { resumeBlockedByPolicyAnswer, storedBodyReplayAnswer, storedBodyReplayLogFields, storedBodyReplayRefusal } from "./checkpoint-ctx.js";
|
|
46
47
|
import { httpErrorCode } from "./send.js";
|
|
@@ -274,7 +275,7 @@ export function createResumeLegs(ctx) {
|
|
|
274
275
|
const rememberableTool = paForGrant !== undefined &&
|
|
275
276
|
paForGrant.toolName &&
|
|
276
277
|
paForGrant.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
277
|
-
standingAnswerDurablyRecordable(parkRowStandingAnswerFacts(paForGrant, cp.gate, deps.config))
|
|
278
|
+
standingAnswerDurablyRecordable(parkRowStandingAnswerFacts(paForGrant, cp.gate, deps.config, deploymentListAsks(deps.config, { toolName: paForGrant.toolName })))
|
|
278
279
|
? paForGrant.toolName
|
|
279
280
|
: null;
|
|
280
281
|
const boundGrant = onResumeCommitted
|
|
@@ -3,6 +3,7 @@ import type { QuestionAnswer } from "@sema-agent/core";
|
|
|
3
3
|
import type { CheckpointStoreFull } from "../../plugins/store-backend.js";
|
|
4
4
|
import { type ToolApprovalCoordinator } from "../../tool-approval.js";
|
|
5
5
|
import { type GovernancePosture } from "../../runtime-governance.js";
|
|
6
|
+
import { type GateIntentView } from "../../approval.js";
|
|
6
7
|
import { type RouteCtx, type RouteDecl, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
|
|
7
8
|
import { type KeyClosureIssue } from "../../request-key-closure.js";
|
|
8
9
|
/** 族的成员 = 路由表行 id(改名 / 删行 ⇒ 下表的键当场编译红)。 */
|
|
@@ -104,6 +105,27 @@ export declare function checkQuestionAnswer(v: unknown): QuestionAnswerCheck;
|
|
|
104
105
|
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
105
106
|
* = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */
|
|
106
107
|
export declare function handleApprovalsAssistant(req: IncomingMessage, res: ServerResponse, match: RouteMatch<ApprovalsAssistantRouteId>, ctx: RouteCtx): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* 🔴 [ref]-2 / [ref]②:两条 durable 读面(`GET /v1/approvals` + `/v1/approvals/stream`)的**唯一**
|
|
110
|
+
* 投影 —— 脱敏([ref] 件4)+ 出身归因([ref] 的另一半)一次做完。
|
|
111
|
+
*
|
|
112
|
+
* 出身格的病灶如实记:`governanceForced` 此前只在 409 体上算(`buildActiveRunConflict`),而运维队列
|
|
113
|
+
* 的行不算 —— 同一道 park 门,客户端从 409 看得见「这是运维治理层下的」,从队列看不见。它**不是**一个
|
|
114
|
+
* 落库缺口:判据的两半(行上 `riskDescriptor.shellGateDoctrine` + 本部署的治理姿态)在这条读面上都够得着,
|
|
115
|
+
* 缺的只是**没有人算**。所以补法 = 让两面共用 `governanceOriginOf` 这一个属主(在读面算的已知失真、
|
|
116
|
+
* 以及「根治要 core 在 mint 写进行里」的登记,逐字在 `runtime-governance.ts` 那个函数的顶注,不复述)。
|
|
117
|
+
*
|
|
118
|
+
* 🔴 `true` 才写键,恒不写 `false` —— 与活卡帧 / 409 体 / `card_json` 同一条纪律:缺席 = 「没有治理来源
|
|
119
|
+
* 的证据」,不是「这不是治理门」。
|
|
120
|
+
*
|
|
121
|
+
* ⚠️ **`delegation` 刻意不在这里**(同批如实登记,禁编造键):park 行上**没有**这条信息 —— core 的
|
|
122
|
+
* `Checkpoint` / `PendingAction` / `CheckpointGate` 三个面都不带 `{parentToolCallId, depth, agentName}`
|
|
123
|
+
* (`Checkpoint.state.delegationProvenance` 是 [ref] 的内容安全聚合 `{version, coverageStartTurn,
|
|
124
|
+
* sawExternal, incomplete}`,与 ask 的出处链是两件事)。同步腿的这一格来自 `AskRequest.delegation`,
|
|
125
|
+
* 那条路 park 时不随行。⇒ 无供给,诚实缺席;要它得先有 core 侧的属主面。
|
|
126
|
+
*/
|
|
127
|
+
/** 运维队列行投影要的部署事实:治理姿态(出身归因的部署侧一半)+ 三张审批名单(问的属主:部署名单门问不问这一行的调用)。生产两处都递活配置。 */
|
|
128
|
+
export type PendingProjectionDeployment = GovernancePosture & Pick<GateIntentView, "approvalRequire" | "approvalDeny" | "approvalNeverAuto">;
|
|
107
129
|
export declare function streamApprovals(req: IncomingMessage, res: ServerResponse, cs: Pick<CheckpointStoreFull, "listPending">,
|
|
108
130
|
/**
|
|
109
131
|
* 🔴 S-454:**第二条 diff 源** —— `GET /v1/approvals` 的 `livePending` 那**同一只**读函数
|
|
@@ -118,9 +140,9 @@ export declare function streamApprovals(req: IncomingMessage, res: ServerRespons
|
|
|
118
140
|
* 与 `governance` 同一条理由:`pollMs` 只有测试传,而本参数是每个生产调用点都必须表态的那一个。
|
|
119
141
|
*/
|
|
120
142
|
live: Pick<ToolApprovalCoordinator, "listLivePending"> | undefined, scope: string | undefined,
|
|
121
|
-
/** [ref]②:出身归因的部署侧一半(
|
|
122
|
-
* `pollMs` 之前**是刻意的:`pollMs` 只有测试传,而本参数是每个生产调用点都必须给的那一个。 */
|
|
123
|
-
governance?:
|
|
143
|
+
/** [ref]②:出身归因的部署侧一半 + 三张审批名单(问的属主)。缺席 ⇒ 归不出治理出身(键缺席,与 best-effort 同方向)、没有一行读作
|
|
144
|
+
* 部署名单的问(记不住,方向是多问)。**位置在 `pollMs` 之前**是刻意的:`pollMs` 只有测试传,而本参数是每个生产调用点都必须给的那一个。 */
|
|
145
|
+
governance?: PendingProjectionDeployment, pollMs?: number): Promise<void>;
|
|
124
146
|
export declare const APPROVALS_ASSISTANT_ROUTES: readonly [{
|
|
125
147
|
readonly id: "approvals-list";
|
|
126
148
|
readonly path: "/v1/approvals";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { screenedGate } from "../../trace/project.js";
|
|
2
|
-
import { hostDecisionFacts, readDecidedBy, DECIDED_BY_NOT_IN_PROOF, DECIDED_BY_OUTSIDE_CLOSED_SET } from "../../host-decision.js";
|
|
2
|
+
import { hostDecisionFacts, readDecidedBy, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF, DECIDED_BY_OUTSIDE_CLOSED_SET } from "../../host-decision.js";
|
|
3
3
|
import { sameExecutionRecord } from "@sema-agent/core";
|
|
4
4
|
import { DECIDED_REPLAY_SCAN_CAP, approvalPayloadFingerprint } from "../../plugins/checkpoint-store-sql.js";
|
|
5
|
-
import { APPROVAL_GATE_KINDS, standingAnswerDurablyRecordable } from "../../tool-approval.js";
|
|
5
|
+
import { APPROVAL_GATE_KINDS, carriesDeploymentListQuestion, standingAnswerDurablyRecordable } from "../../tool-approval.js";
|
|
6
6
|
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";
|
|
@@ -17,6 +17,7 @@ import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
|
17
17
|
import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
|
|
18
18
|
import { gatedPrincipal, explicitOperatorOk, isOperator, sendPrincipalRefusal } from "../principal-gate.js";
|
|
19
19
|
import { governanceOriginOf } from "../../runtime-governance.js";
|
|
20
|
+
import { deploymentListAsks } from "../../approval.js";
|
|
20
21
|
import { answerGrantsBeyondCall, grantBindingOf } from "../../standing-answer-grant.js";
|
|
21
22
|
import { sendResumeOutcome } from "../route-ctx.js";
|
|
22
23
|
import { assertNever } from "../route-ctx.js";
|
|
@@ -640,13 +641,14 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
640
641
|
const body = (await readJson(req));
|
|
641
642
|
const decision = body?.decision === "approve" ? "approve" : body?.decision === "deny" ? "deny" : undefined;
|
|
642
643
|
if (!decision) {
|
|
643
|
-
sendError(res, 400, "request.body_shape", "body must be { decision: 'approve' | 'deny', reason?, answer?, checkpointToken?, boundCallId?, boundInputHash?, updatedInput?, remember?, decidedBy? }");
|
|
644
|
+
sendError(res, 400, "request.body_shape", "body must be { decision: 'approve' | 'deny', reason?, answer?, checkpointToken?, boundCallId?, boundInputHash?, updatedInput?, remember?, decidedBy? }", retiredDecisionKeyFacts(body)?.extra);
|
|
644
645
|
return;
|
|
645
646
|
}
|
|
646
647
|
{
|
|
647
648
|
const keyIssue = decisionBodyKeyIssue("approval-decide", body);
|
|
648
649
|
if (keyIssue) {
|
|
649
|
-
|
|
650
|
+
const retired = retiredDecisionKeyFacts(body);
|
|
651
|
+
sendError(res, 400, "request.body_shape", retired?.error ?? keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys, ...retired?.extra });
|
|
650
652
|
return;
|
|
651
653
|
}
|
|
652
654
|
}
|
|
@@ -819,7 +821,8 @@ function redactPendingDisclosures(row) {
|
|
|
819
821
|
const next = redactRiskDescriptor(rd);
|
|
820
822
|
return next === rd ? row : { ...row, riskDescriptor: next };
|
|
821
823
|
}
|
|
822
|
-
function projectPendingForWire(row,
|
|
824
|
+
function projectPendingForWire(row, deployment) {
|
|
825
|
+
const governance = deployment;
|
|
823
826
|
const redacted = redactPendingDisclosures(row);
|
|
824
827
|
const governanceForced = governanceOriginOf({ riskDescriptor: row.riskDescriptor ?? undefined }, governance);
|
|
825
828
|
const rememberRefused = !standingAnswerDurablyRecordable({
|
|
@@ -828,6 +831,7 @@ function projectPendingForWire(row, governance) {
|
|
|
828
831
|
mandated: row.mandated === true || row.ruleOffersAbsence === "mandated",
|
|
829
832
|
grantBinding: grantBindingOf(row),
|
|
830
833
|
classes: row.classes,
|
|
834
|
+
deploymentListQuestion: carriesDeploymentListQuestion(row.toolName !== null && deployment !== undefined && deploymentListAsks(deployment, { toolName: row.toolName }), row.classes),
|
|
831
835
|
});
|
|
832
836
|
return { ...redacted, ...(governanceForced ? { governanceForced } : {}), ...(rememberRefused ? { rememberRefused: true } : {}) };
|
|
833
837
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
|
|
1
|
+
import { PERMISSION_MODES, expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
|
|
2
2
|
import { modelImageInput, isModelAllowlisted, selectableCatalog } from "../../model-select.js";
|
|
3
3
|
import { modelKeyPoisonOf, resolveModelApiKey } from "../../key-resolver.js";
|
|
4
4
|
import { projectSealedKeyCapability } from "../../sealed-key.js";
|
|
@@ -23,7 +23,7 @@ import { workflowModelAllowlistFor } from "../../task-workflow.js";
|
|
|
23
23
|
import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
24
24
|
import * as facts from "../../capability-facts.js";
|
|
25
25
|
import { judgeAutoModeArming } from "../../auto-mode-face.js";
|
|
26
|
-
import { coercePermissionModeStrict,
|
|
26
|
+
import { coercePermissionModeStrict, PERMISSION_MODE_WORDS, taskHooksHonored } from "../../task-settings.js";
|
|
27
27
|
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
28
28
|
function routePairingOf(deps, name, m) {
|
|
29
29
|
if (modelKeyPoisonOf(name, deps.config.modelApiKeyEnv ?? {}, process.env, deps.config.modelApiKeys ?? {}) !== undefined)
|
|
@@ -60,7 +60,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
60
60
|
const requestedModeRaw = new URLSearchParams(match.search).get("permissionMode");
|
|
61
61
|
const requestedMode = requestedModeRaw === null ? undefined : coercePermissionModeStrict(requestedModeRaw);
|
|
62
62
|
if (requestedModeRaw !== null && requestedMode === undefined) {
|
|
63
|
-
sendError(res, 400, "request.field_invalid", `permissionMode must be one of ${
|
|
63
|
+
sendError(res, 400, "request.field_invalid", `permissionMode must be one of ${PERMISSION_MODE_WORDS}`);
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
const entitlementSource = centerEntitlementSourceWired(deps.config);
|
|
@@ -160,6 +160,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
160
160
|
streamApproval: facts.streamApproval(deps).available,
|
|
161
161
|
approvalDecisionNote: true,
|
|
162
162
|
approvalDecidedBy: true,
|
|
163
|
+
permissionModes: [...PERMISSION_MODES],
|
|
163
164
|
sideQuery: true,
|
|
164
165
|
agentRoster: facts.agentRoster(deps).available,
|
|
165
166
|
retention: retentionAdvertisement(deps.config),
|
|
@@ -114,7 +114,7 @@ export declare function buildClearanceRowWire(r: OriginClearanceRow): {
|
|
|
114
114
|
baseRev: string;
|
|
115
115
|
origin: {
|
|
116
116
|
at: number;
|
|
117
|
-
cause?: "static" | "
|
|
117
|
+
cause?: "static" | "unattributed" | "observed" | "derived" | undefined;
|
|
118
118
|
taint: "external";
|
|
119
119
|
};
|
|
120
120
|
};
|