@sema-agent/server 7.118.0 → 7.120.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 +139 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +26 -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 +6 -6
- 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/execution-env.js +3 -2
- package/dist/boot/fatal-exit.d.ts +7 -1
- package/dist/boot/fatal-exit.js +2 -1
- package/dist/boot/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/runner-deps.d.ts +4 -3
- package/dist/boot/runner-deps.js +7 -3
- package/dist/boot/stage-01-config.js +2 -0
- 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/builtin-tools.d.ts +31 -8
- package/dist/capabilities/builtin-tools.js +22 -1
- 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 +42 -13
- package/dist/capabilities/scenarios.js +36 -9
- package/dist/capabilities/send-user-file-tool.d.ts +6 -3
- package/dist/capabilities/send-user-file-tool.js +1 -1
- package/dist/config-catalog.js +5 -5
- package/dist/config-lkg.d.ts +33 -5
- package/dist/config-lkg.js +55 -11
- package/dist/config-types.d.ts +8 -1
- package/dist/config.js +11 -11
- package/dist/deployment-governance.d.ts +8 -3
- package/dist/deployment-governance.js +7 -4
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +4 -0
- package/dist/device-ws-hub.js +17 -9
- package/dist/device-ws-protocol.d.ts +15 -0
- package/dist/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 +6 -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 +5 -3
- package/dist/http/routes/devices.js +3 -3
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +13 -6
- 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/list-entry-display.d.ts +14 -0
- package/dist/list-entry-display.js +14 -0
- 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/namespaced-rule-name.js +2 -6
- package/dist/observability/fail-open.d.ts +9 -1
- package/dist/observability/fail-open.js +9 -1
- package/dist/operator-ask.d.ts +7 -6
- 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 +14 -0
- package/dist/plugins/one-time-migrations.js +90 -3
- package/dist/plugins/permission-rule-store-file.js +2 -6
- package/dist/plugins/permission-rule-store-sql.d.ts +64 -6
- package/dist/plugins/permission-rule-store-sql.js +166 -59
- package/dist/plugins/remote-env-device.d.ts +12 -1
- package/dist/plugins/remote-env-device.js +13 -5
- package/dist/plugins/scheduler-support.d.ts +8 -4
- package/dist/plugins/scheduler-support.js +5 -5
- 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.d.ts +0 -1
- package/dist/rule-entry-list.js +5 -10
- package/dist/rules-consent.d.ts +12 -3
- package/dist/rules-consent.js +5 -4
- package/dist/run-local.js +6 -3
- package/dist/security.d.ts +5 -3
- package/dist/security.js +4 -0
- package/dist/sema-config-dir.d.ts +19 -0
- package/dist/sema-config-dir.js +33 -0
- package/dist/session-policy-wire.js +10 -14
- package/dist/spec-fields.d.ts +7 -2
- package/dist/store-posture.d.ts +14 -6
- package/dist/store-posture.js +7 -4
- package/dist/task-settings.d.ts +30 -58
- 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/engine-notice-wire.d.ts +6 -4
- package/dist/trace/engine-notice-wire.js +16 -9
- package/dist/trace/redact.d.ts +6 -0
- package/dist/trace/redact.js +9 -9
- package/dist/trace/self-minted-wire.d.ts +15 -1
- package/dist/trace/self-minted-wire.js +2 -0
- package/dist/user-input-ledger.d.ts +19 -15
- package/dist/user-input-ledger.js +12 -20
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +56 -14
- package/package.json +2 -2
package/dist/config.js
CHANGED
|
@@ -20,6 +20,8 @@ import {} from "./posture-source.js";
|
|
|
20
20
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
21
21
|
import { describeUnmatchableRuleName, ruleEntryToolName, unmatchableRuleNameOf } from "./namespaced-rule-name.js";
|
|
22
22
|
import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
|
|
23
|
+
import { declaredSemaConfigDir } from "./sema-config-dir.js";
|
|
24
|
+
import { lkgPathOverrideOf } from "./config-lkg.js";
|
|
23
25
|
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
|
|
24
26
|
import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
|
|
25
27
|
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
@@ -29,6 +31,7 @@ import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js
|
|
|
29
31
|
import { modelProviderRefusal } from "./model-provider.js";
|
|
30
32
|
import { modelRouteUrlRefusal } from "./model-route-url.js";
|
|
31
33
|
import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
|
|
34
|
+
import { displayListEntry } from "./list-entry-display.js";
|
|
32
35
|
import { buildDisplaySafeUrl, redactSecrets } from "./trace/redact.js";
|
|
33
36
|
import { allowlistedEnv } from "./server-secret-env.js";
|
|
34
37
|
import { ENV_NAME_ALLOWLIST_KNOBS, ENV_NAME_ALLOWLIST_KNOB_NAMES, describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
|
|
@@ -842,13 +845,13 @@ function buildUnusedCoordinatesRefusal(keys, auto) {
|
|
|
842
845
|
const engine = keys.length === 1 ? (keys[0] === "PG_HOST" ? "pg" : "mysql") : "mysql|pg";
|
|
843
846
|
const probedHostKey = auto?.autoProbes === "pg" ? "PG_HOST" : "MYSQL_HOST";
|
|
844
847
|
const why = auto !== undefined
|
|
845
|
-
? `SESSION_BACKEND=auto
|
|
848
|
+
? `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
849
|
: "no durable session store is selected (neither DB_BACKEND=mysql|pg nor SESSION_BACKEND=mysql|auto), so the store would run IN MEMORY";
|
|
847
850
|
return (`${keys.join(" and ")} ${keys.length === 1 ? "is" : "are"} set but the current configuration never uses ${keys.length === 1 ? "it" : "them"}: ${why}. ` +
|
|
848
851
|
`Refusing to boot rather than running in memory silently. ` +
|
|
849
852
|
`To persist: DB_BACKEND=${engine} (or SESSION_BACKEND=mysql for the MySQL-protocol engine); ` +
|
|
850
853
|
`to run in memory: remove the coordinates, or set DB_BACKEND=memory; ` +
|
|
851
|
-
`
|
|
854
|
+
`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
855
|
}
|
|
853
856
|
function parseStoreDomain(ctx) {
|
|
854
857
|
const { requirePrincipal } = ctx;
|
|
@@ -1209,17 +1212,11 @@ function parseDegradeOn(words) {
|
|
|
1209
1212
|
}
|
|
1210
1213
|
return words;
|
|
1211
1214
|
}
|
|
1212
|
-
const PRESENTED_NAME_MAX = 160;
|
|
1213
|
-
function presentToolName(name) {
|
|
1214
|
-
const clean = name.replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
1215
|
-
return clean.length <= PRESENTED_NAME_MAX ? clean : `${clean.slice(0, PRESENTED_NAME_MAX)}…(${name.length} chars)`;
|
|
1216
|
-
}
|
|
1217
1215
|
function ccRuleFormGuidance(name) {
|
|
1218
1216
|
if (!name.includes("(") && !name.includes(")"))
|
|
1219
1217
|
return undefined;
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
`Drop the parentheses to gate the tool itself ("${presentToolName(name.split("(")[0] ?? name)}"); ` +
|
|
1218
|
+
return (`CC-form rule specifier — this list matches the WHOLE tool name verbatim, so it can only ever match a tool literally named ${displayListEntry(name)}. ` +
|
|
1219
|
+
`Drop the parentheses to gate the tool itself (${displayListEntry(name.split("(")[0] ?? name)}); ` +
|
|
1223
1220
|
`to decide per COMMAND use \`runtime.commandPolicy\` ({command, decision} — bare argv[0] names); ` +
|
|
1224
1221
|
`to put every shell call behind an approval use MANUAL_MODE_SHELL_GATE=always`);
|
|
1225
1222
|
}
|
|
@@ -1264,7 +1261,7 @@ export function unmatchableToolNamesRefusal(source, names, judgment) {
|
|
|
1264
1261
|
return undefined;
|
|
1265
1262
|
const message = (`${source} has ${bad.length === 1 ? "an entry" : "entries"} that can never take effect — ${bad.length === 1 ? "it" : "each"} would never match a live tool or cannot be compiled into a rule ` +
|
|
1266
1263
|
`(names are read exactly as written: no alias folding, no protocol prefix added): ` +
|
|
1267
|
-
bad.map((b) => `${
|
|
1264
|
+
bad.map((b) => `${displayListEntry(b.name)} → ${b.guidance}`).join("; ")).replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
1268
1265
|
return { message, names: bad.map((b) => b.name) };
|
|
1269
1266
|
}
|
|
1270
1267
|
const APPROVAL_GRANT_ENV = "APPROVAL_GRANT";
|
|
@@ -1899,6 +1896,8 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1899
1896
|
assertReadDenyBuiltinsResolvable(tiers, exclude);
|
|
1900
1897
|
return { ...(tiers !== undefined ? { readDenyBuiltinTiers: tiers } : {}), ...(exclude !== undefined ? { readDenyBuiltinExclude: exclude } : {}) };
|
|
1901
1898
|
})(),
|
|
1899
|
+
...((dir) => (dir !== undefined ? { agentConfigDir: dir } : {}))(declaredSemaConfigDir()),
|
|
1900
|
+
...((p) => (p !== undefined ? { configLkgPath: p } : {}))(lkgPathOverrideOf(process.env.CONFIG_LKG_PATH)),
|
|
1902
1901
|
...((h) => (h !== undefined ? { remoteExecHomeDir: h } : {}))((() => {
|
|
1903
1902
|
const raw = process.env.REMOTE_EXEC_HOME_DIR;
|
|
1904
1903
|
if (raw === undefined || raw === "")
|
|
@@ -2337,6 +2336,7 @@ const ORCHESTRATION_GROUP_KEYS = [
|
|
|
2337
2336
|
"backgroundAgentParkClaimStaleMs", "delegationEntryCaps", "rosterRetentionMs", "leaderRunStaleMs", "scratchpadSweepTtlMs", "sandboxPkgSource",
|
|
2338
2337
|
"sessionAutoTitle", "sessionAutoTitleSource", "selectEnvironmentTool", "envFactsEnabled", "toolDeferLongtail", "lspEnabled", "lspHostEnabled", "lspEnvAllow",
|
|
2339
2338
|
"imageBakes", "readFace", "readFaceSource", "taskWriteFaceEscalation", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude",
|
|
2339
|
+
"agentConfigDir", "configLkgPath",
|
|
2340
2340
|
];
|
|
2341
2341
|
const LIMITS_HTTP_GROUP_KEYS = [
|
|
2342
2342
|
"port", "attachmentOrphanGraceMs", "workspaceFileMaxBytes", "attachmentMaxBytes", "attachmentMimeAllowlist",
|
|
@@ -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 实参 —— 从折叠属主的签名位**推导**,不复制形状
|
|
@@ -10,6 +11,8 @@ export interface DeploymentGovernanceConfigView {
|
|
|
10
11
|
readonly commandPolicy?: ServiceConfig["commandPolicy"];
|
|
11
12
|
readonly manualModeShellGate?: ServiceConfig["manualModeShellGate"];
|
|
12
13
|
readonly sensitiveWritePatterns: ServiceConfig["sensitiveWritePatterns"];
|
|
14
|
+
/** core 7.46.0 #1419:部署声明的配置根 —— 守卫集的 `.sema/…` 模式在根下同样守(与 Runner 的同名座同一个值,`loadConfig` 铸)。 */
|
|
15
|
+
readonly agentConfigDir?: ServiceConfig["agentConfigDir"];
|
|
13
16
|
/**
|
|
14
17
|
* 本部署的**本地店根**(`LOCAL_DATA_ROOT → CONFIG_LOCAL_DIR → AGENT_DATA_DIR → ~/.ai-agent`)—— 在本口只喂
|
|
15
18
|
* **誊本门**(`<localDataRoot>/sessions`)。S-133 起它**不再**当守卫集的 `dataRoot`(那是引擎记忆根,见
|
|
@@ -91,8 +94,10 @@ export interface DurableApprovalSeat {
|
|
|
91
94
|
readonly kind: "durable";
|
|
92
95
|
/** 活体问答面;`undefined` ⇒ 恒 durable park 的原形门。 */
|
|
93
96
|
readonly question: LiveQuestionFace | undefined;
|
|
97
|
+
/** 本会话的「本会话放行」读面(部署名单那一问的站着的答案;铸点 `approval.ts` 的 `createDeploymentListExemption`)。缺席 = 这条腿没有会话 / 没有豁免店 ⇒ 名单照问。 */
|
|
98
|
+
readonly exemption?: DeploymentListExemption;
|
|
94
99
|
}
|
|
95
|
-
/** live 席:Runner 的活 onAsk 当场服 ask(无 park)。 */
|
|
100
|
+
/** live 席:Runner 的活 onAsk 当场服 ask(无 park、无会话豁免店:run-local 一次性腿)。 */
|
|
96
101
|
export interface LiveApprovalSeat {
|
|
97
102
|
readonly kind: "live";
|
|
98
103
|
}
|
|
@@ -182,7 +187,7 @@ type WriteTargetForm = "unstamped-relative" | "absolute-or-stamped";
|
|
|
182
187
|
* · `writeTargetGuard`(守卫集策略自带,shell 写站问「这个绝对目标守不守」)—— 只在本层的形**覆盖守卫的入参形**时带上
|
|
183
188
|
* ({@link WRITE_TARGET_GUARD_DOMAIN}):真身层带;词法层不带(它的 env 对绝对形报错 ⇒ core 的「判不了就守」会把每一次
|
|
184
189
|
* shell 写都判成受守,正与两层射程互补的原设计相反)。这不是给某一形开的特例,是同一条「成员描述它守得住的那部分」的读法;
|
|
185
|
-
* · `projection` / `
|
|
190
|
+
* · `projection` / `pathRules` / `nameSets` —— 守卫集策略不带,本包装也不报(报了就是替它声称一张它没有的拒表)。
|
|
186
191
|
* 修前(core 7.33.x)这里是一只 `{ check }` 字面量,7.34.0 起那只字面量把真身层的守卫剥掉了 —— 沙箱 lane 的 shell 写站
|
|
187
192
|
* 因此看不见部署守卫集(`cp x .env` 在 acceptEdits 下被引擎 place 并答掉)。
|
|
188
193
|
*/
|
|
@@ -263,7 +268,7 @@ export declare function buildApprovalGrantNotice(config: {
|
|
|
263
268
|
* `undefined`**:治理层是 tighten-only 的叠加层,没有基线可叠时它自己也产不出「门在场」这件事。
|
|
264
269
|
*
|
|
265
270
|
* 三形,按席穷举(S-694,席的定义见 {@link ApprovalSeat}):
|
|
266
|
-
* · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto
|
|
271
|
+
* · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto/预算/本会话的豁免行全在
|
|
267
272
|
* `createDurableAskPolicy` 里)。gated `ask` 由 core 变成 durable checkpoint suspend。
|
|
268
273
|
* · `live` ⇒ **同一只**三张名单审批门,不叠问答门。gated `ask` 由 core 交给 Runner 的活 onAsk 席
|
|
269
274
|
* (本口**不**做 ask→deny 映射:无人可问时的拒由那只席说,判据与文案都归它)。
|
|
@@ -87,18 +87,20 @@ export function createDeploymentGovernanceInputs(config, pathAdjudication, roots
|
|
|
87
87
|
const sensitivePathLayers = config.sensitiveWritePatterns.length > 0
|
|
88
88
|
? (() => {
|
|
89
89
|
const dataRootOpt = roots.engineDataRoot !== undefined ? { dataRoot: roots.engineDataRoot } : {};
|
|
90
|
+
const configRootOpt = config.agentConfigDir !== undefined ? { agentConfigDir: config.agentConfigDir } : {};
|
|
90
91
|
const realTarget = createSensitivePathPolicy({
|
|
91
92
|
env: pathAdjudication.env,
|
|
92
93
|
patterns: config.sensitiveWritePatterns,
|
|
93
94
|
...(pathAdjudication.cwd !== undefined ? { rootPath: pathAdjudication.cwd } : {}),
|
|
94
95
|
...dataRootOpt,
|
|
96
|
+
...configRootOpt,
|
|
95
97
|
});
|
|
96
98
|
const transcriptGuard = config.localDataRoot !== undefined ? [createTranscriptIntegrityPolicy({ sessionsDirs: [join(config.localDataRoot, "sessions")] })] : [];
|
|
97
99
|
if (pathAdjudication.cwd !== undefined)
|
|
98
100
|
return [realTarget, ...transcriptGuard];
|
|
99
101
|
return [
|
|
100
102
|
scopedToWriteTargetForm(realTarget, "absolute-or-stamped"),
|
|
101
|
-
scopedToWriteTargetForm(createSensitivePathPolicy({ env: new RelativeTargetLexicalEnv(), patterns: config.sensitiveWritePatterns, ...dataRootOpt }), "unstamped-relative"),
|
|
103
|
+
scopedToWriteTargetForm(createSensitivePathPolicy({ env: new RelativeTargetLexicalEnv(), patterns: config.sensitiveWritePatterns, ...dataRootOpt, ...configRootOpt }), "unstamped-relative"),
|
|
102
104
|
...transcriptGuard,
|
|
103
105
|
];
|
|
104
106
|
})()
|
|
@@ -126,9 +128,9 @@ export function buildApprovalGrantNotice(config) {
|
|
|
126
128
|
export function createApprovalBaselinePolicy(config, seat) {
|
|
127
129
|
switch (seat.kind) {
|
|
128
130
|
case "durable":
|
|
129
|
-
return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config));
|
|
131
|
+
return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config, seat.exemption));
|
|
130
132
|
case "live":
|
|
131
|
-
return createApprovalListPolicy(config);
|
|
133
|
+
return createApprovalListPolicy(config, undefined);
|
|
132
134
|
case "absent":
|
|
133
135
|
if (hasApprovalListIntent(config)) {
|
|
134
136
|
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 +144,13 @@ export function createApprovalBaselinePolicy(config, seat) {
|
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
|
-
function createApprovalListPolicy(config) {
|
|
147
|
+
function createApprovalListPolicy(config, exemption) {
|
|
146
148
|
return createDurableAskPolicy({
|
|
147
149
|
requireApproval: config.approvalRequire,
|
|
148
150
|
deny: config.approvalDeny,
|
|
149
151
|
autoBudget: config.approvalAutoBudget,
|
|
150
152
|
neverAuto: config.approvalNeverAuto,
|
|
153
|
+
...(exemption !== undefined ? { exemption } : {}),
|
|
151
154
|
});
|
|
152
155
|
}
|
|
153
156
|
//# sourceMappingURL=deployment-governance.js.map
|
package/dist/device-store.d.ts
CHANGED
|
@@ -161,6 +161,13 @@ export declare const DEVICE_TEXT_LIMITS: {
|
|
|
161
161
|
readonly connEpoch: 64;
|
|
162
162
|
readonly replicaId: 128;
|
|
163
163
|
};
|
|
164
|
+
/**
|
|
165
|
+
* 设备侧路径「是不是绝对路径」的**唯一判据**(v1 设备都是 POSIX:`platformOs` 闭集 `darwin` | `linux`;用 `posix` 而不是 `node:path`,
|
|
166
|
+
* server 跑在什么平台上与设备路径语义无关)。两处读它、不写第二份:入籍口(`http/routes/devices.ts`,`workspaceRoot` 不过 ⇒ 400)与
|
|
167
|
+
* 铸 env(`plugins/remote-env-device.ts` `effectiveDeviceCwd`,有效工作目录不过 ⇒ 响亮拒;那一道守的是入籍口收紧之前落库的存量行与
|
|
168
|
+
* 会话 / 委派链来的 cwd)。
|
|
169
|
+
*/
|
|
170
|
+
export declare function isDeviceAbsolutePath(p: string): boolean;
|
|
164
171
|
export declare function assertDeviceOwnerShape(owner: DeviceOwner): void;
|
|
165
172
|
export declare function assertDeviceIdShape(deviceId: string): void;
|
|
166
173
|
export declare function assertRootSessionIdShape(rootSessionId: string): void;
|
package/dist/device-store.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomInt } from "node:crypto";
|
|
2
|
+
import { posix } from "node:path";
|
|
2
3
|
export const DEVICE_ERROR_CODES = {
|
|
3
4
|
NOT_BOUND: "device.not_bound",
|
|
4
5
|
OFFLINE: "device.offline",
|
|
@@ -86,6 +87,9 @@ export const DEVICE_TEXT_LIMITS = {
|
|
|
86
87
|
connEpoch: 64,
|
|
87
88
|
replicaId: 128,
|
|
88
89
|
};
|
|
90
|
+
export function isDeviceAbsolutePath(p) {
|
|
91
|
+
return posix.isAbsolute(p);
|
|
92
|
+
}
|
|
89
93
|
export function assertDeviceOwnerShape(owner) {
|
|
90
94
|
assertIdentityKey("owner.tenant", owner.tenant, DEVICE_KEY_MAX_BYTES);
|
|
91
95
|
assertIdentityKey("owner.subject", owner.subject, DEVICE_KEY_MAX_BYTES);
|
package/dist/device-ws-hub.js
CHANGED
|
@@ -425,7 +425,20 @@ export function createDeviceWsHub(options) {
|
|
|
425
425
|
clearTimeout(conn.handshakeTimer);
|
|
426
426
|
conn.handshakeTimer = null;
|
|
427
427
|
releasePreAuthSlot(conn);
|
|
428
|
-
|
|
428
|
+
const helloAck = {
|
|
429
|
+
t: "helloAck",
|
|
430
|
+
ok: true,
|
|
431
|
+
heartbeatIntervalMs,
|
|
432
|
+
maxInflight: maxInflightPerDevice,
|
|
433
|
+
gen: conn.genStr,
|
|
434
|
+
pendingInstructions: pendingIds,
|
|
435
|
+
chunkWatermarks,
|
|
436
|
+
reconnectGraceMs,
|
|
437
|
+
cancelGraceMs,
|
|
438
|
+
chunkGapTimeoutMs,
|
|
439
|
+
executionCeilingGraceMs: execGraceMs,
|
|
440
|
+
};
|
|
441
|
+
sendServerFrame(conn, helloAck);
|
|
429
442
|
logger?.info?.("device_connected", { deviceId: row.deviceId, generation: acquired.generation, epoch: frame.epoch, replicaId });
|
|
430
443
|
metrics?.inc("device_connected_total");
|
|
431
444
|
}
|
|
@@ -469,12 +482,7 @@ export function createDeviceWsHub(options) {
|
|
|
469
482
|
}
|
|
470
483
|
}
|
|
471
484
|
function failOnChunkGap(p) {
|
|
472
|
-
|
|
473
|
-
settle(p, {
|
|
474
|
-
ok: false,
|
|
475
|
-
errorCode: "outcome_unknown",
|
|
476
|
-
message: `the output stream had a gap (missing chunk streamSeq ${missing}) so the result cannot be reported faithfully — ${OUTCOME_UNKNOWN_SENTENCE}`,
|
|
477
|
-
});
|
|
485
|
+
settle(p, outcomeUnknown(`the output stream had a gap (missing chunk streamSeq ${p.nextStreamSeq}) so the result cannot be reported faithfully`));
|
|
478
486
|
}
|
|
479
487
|
function chunkPayloadBytes(frame) {
|
|
480
488
|
return frame.dataB64 === undefined ? 0 : Math.floor((frame.dataB64.length * 3) / 4);
|
|
@@ -549,11 +557,11 @@ export function createDeviceWsHub(options) {
|
|
|
549
557
|
if (complaint !== null) {
|
|
550
558
|
metrics?.inc("device_stream_terminator_missing_total");
|
|
551
559
|
sendServerFrame(conn, { t: "resultAck", gen: conn.genStr, instructionId: p.instructionId });
|
|
552
|
-
settle(p,
|
|
560
|
+
settle(p, outcomeUnknown(complaint));
|
|
553
561
|
return;
|
|
554
562
|
}
|
|
555
563
|
sendServerFrame(conn, { t: "resultAck", gen: conn.genStr, instructionId: p.instructionId });
|
|
556
|
-
settle(p, outcome);
|
|
564
|
+
settle(p, !outcome.ok && outcome.errorCode === "outcome_unknown" ? outcomeUnknown(outcome.message) : outcome);
|
|
557
565
|
}
|
|
558
566
|
function onDeviceText(conn, text) {
|
|
559
567
|
conn.lastFrameAt = Date.now();
|
|
@@ -235,6 +235,21 @@ export type DeviceHelloAckFrame = {
|
|
|
235
235
|
gen: string;
|
|
236
236
|
pendingInstructions: string[];
|
|
237
237
|
chunkWatermarks: Record<string, number>;
|
|
238
|
+
/**
|
|
239
|
+
* 四个**宽限位**(additive,协议 §5.6 不升版):本连接所在 server 副本**此刻生效**的四只预算,值与汇聚端
|
|
240
|
+
* 计时器读的是同一只常量 —— 不是文档里的「默认值」,是这台部署的实际值。executor 据此定自己的重连
|
|
241
|
+
* 退避上界 / 取消应答时限 / 补片节奏 / 执行兜底预算,不必抄文档里的数。
|
|
242
|
+
* · `reconnectGraceMs`:断连后在途不判丢的窗(R4;运维旋钮 `DEVICE_RECONNECT_GRACE_MS`);
|
|
243
|
+
* · `cancelGraceMs`:`cancel` 送达后等终局回执的窗(R11);
|
|
244
|
+
* · `chunkGapTimeoutMs`:等缺失 `streamSeq` 的上限(R8);
|
|
245
|
+
* · `executionCeilingGraceMs`:`timeoutMs` 之后云侧兜底再等的宽限(R12:`timeoutMs` + 本值)。
|
|
246
|
+
* **可选位**:7.120+ 服务端恒发;缺席 = 老服务端,按协议 §1.5 回落文档缺省(不得当 0)。型上标可选,是为了让拿这只型读老服务端帧的
|
|
247
|
+
* 消费方被编译器逼着处理缺席;本服务自己的铸点按 `Required<DeviceHelloAckFrame>` 构造(`device-ws-hub.ts`),漏一位编译红。
|
|
248
|
+
*/
|
|
249
|
+
reconnectGraceMs?: number;
|
|
250
|
+
cancelGraceMs?: number;
|
|
251
|
+
chunkGapTimeoutMs?: number;
|
|
252
|
+
executionCeilingGraceMs?: number;
|
|
238
253
|
};
|
|
239
254
|
export type DeviceHelloRejectFrame = {
|
|
240
255
|
t: "helloReject";
|
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";
|
|
@@ -68,6 +69,10 @@ const WORKER_SWAP_REDEEMABLE = {
|
|
|
68
69
|
write_face_damaged: false,
|
|
69
70
|
skill_grants_damaged: false,
|
|
70
71
|
constraint_chain_missing: false,
|
|
72
|
+
hands_coordinates_damaged: false,
|
|
73
|
+
installation_record_absent: true,
|
|
74
|
+
installation_record_misaligned: false,
|
|
75
|
+
installation_record_invalid: false,
|
|
71
76
|
};
|
|
72
77
|
function checkpointRowRedeemableElsewhere(reason) {
|
|
73
78
|
return reason !== undefined && WORKER_SWAP_REDEEMABLE[reason] === true;
|
|
@@ -274,7 +279,7 @@ export function createResumeLegs(ctx) {
|
|
|
274
279
|
const rememberableTool = paForGrant !== undefined &&
|
|
275
280
|
paForGrant.toolName &&
|
|
276
281
|
paForGrant.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
277
|
-
standingAnswerDurablyRecordable(parkRowStandingAnswerFacts(paForGrant, cp.gate, deps.config))
|
|
282
|
+
standingAnswerDurablyRecordable(parkRowStandingAnswerFacts(paForGrant, cp.gate, deps.config, deploymentListAsks(deps.config, { toolName: paForGrant.toolName })))
|
|
278
283
|
? paForGrant.toolName
|
|
279
284
|
: null;
|
|
280
285
|
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";
|