@sema-agent/server 7.105.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 +82 -0
- package/USAGE.md +134 -42
- 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 +3 -9
- 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 +35 -0
- package/dist/config.js +69 -16
- 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/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.js +14 -9
- package/dist/http/approval-sweeps.js +13 -1
- package/dist/http/dispatch.js +1 -1
- 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.js +19 -11
- package/dist/http/route-ctx.d.ts +9 -0
- 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/tasks.js +4 -1
- package/dist/http/server.d.ts +20 -4
- package/dist/http/session-inherit-env.d.ts +37 -0
- package/dist/http/session-inherit-env.js +6 -0
- 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 +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/observability/metrics.js +2 -0
- 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 +38 -1
- package/dist/observability/run-terminal.js +2 -5
- 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/session-policy-store-sql.d.ts +38 -7
- package/dist/plugins/session-policy-store-sql.js +64 -22
- package/dist/plugins/sql-driver.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +12 -3
- package/dist/plugins/store-backend.js +16 -5
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/read-face-posture.d.ts +4 -4
- 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/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 +5 -3
- 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 +3 -2
- package/dist/trace/project.js +51 -3
- 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/roster-bound-http-status.d.ts +0 -6
- package/dist/roster-bound-http-status.js +0 -6
package/dist/key-resolver.js
CHANGED
|
@@ -1,4 +1,46 @@
|
|
|
1
|
-
|
|
1
|
+
export function modelKeyPoison(p) {
|
|
2
|
+
const at = new Date().toISOString();
|
|
3
|
+
if (p.source === "sealed")
|
|
4
|
+
return { modelKeyPoison: true, source: "sealed", reason: p.reason, publicKeyId: p.publicKeyId, ...(p.errKind ? { errKind: p.errKind } : {}), at };
|
|
5
|
+
return { modelKeyPoison: true, source: "env", reason: p.reason, apiKeyEnv: p.apiKeyEnv, at };
|
|
6
|
+
}
|
|
7
|
+
export function isModelKeyPoison(v) {
|
|
8
|
+
return typeof v === "object" && v !== null && v.modelKeyPoison === true;
|
|
9
|
+
}
|
|
10
|
+
export function modelKeyPoisonIdentity(p) {
|
|
11
|
+
switch (p.source) {
|
|
12
|
+
case "sealed":
|
|
13
|
+
return p.publicKeyId;
|
|
14
|
+
case "env":
|
|
15
|
+
return p.apiKeyEnv;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function poisonedMessage(modelName, p) {
|
|
19
|
+
switch (p.reason) {
|
|
20
|
+
case "sealed_decrypt_failed":
|
|
21
|
+
case "stale_ciphertext":
|
|
22
|
+
case "no_key_store":
|
|
23
|
+
return (`sealed api key for model "${modelName}" is unusable (${p.reason}; publicKeyId "${modelKeyPoisonIdentity(p)}"; detected ${p.at}) — ` +
|
|
24
|
+
`refusing the gateway-key fallback. Fix custody: re-paste the key in the web UI (it seals against the newest registered public key), ` +
|
|
25
|
+
`or restore this host's private key for that id / complete the rotation so the ciphertext targets a held key.`);
|
|
26
|
+
case "env_missing":
|
|
27
|
+
return (`api key for model "${modelName}" is unusable (env_missing: the catalog entry declares apiKeyEnv "${modelKeyPoisonIdentity(p)}" but this service's environment ` +
|
|
28
|
+
`has no non-empty ${modelKeyPoisonIdentity(p)}; detected ${p.at}) — refusing the gateway-key fallback. Set ${modelKeyPoisonIdentity(p)} in the environment of the ` +
|
|
29
|
+
`process that runs this service (and restart it), or drop apiKeyEnv from the catalog entry to authenticate this model with the deployment's gateway key on purpose.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class ModelKeyPoisonedError extends Error {
|
|
33
|
+
poison;
|
|
34
|
+
reason;
|
|
35
|
+
at;
|
|
36
|
+
constructor(modelName, poison) {
|
|
37
|
+
super(poisonedMessage(modelName, poison));
|
|
38
|
+
this.name = "ModelKeyPoisonedError";
|
|
39
|
+
this.poison = poison;
|
|
40
|
+
this.reason = poison.reason;
|
|
41
|
+
this.at = poison.at;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
2
44
|
export function createKeyResolver(modelApiKeyEnv, env = process.env, modelApiKeys = {}) {
|
|
3
45
|
if (Object.keys(modelApiKeyEnv).length === 0 && Object.keys(modelApiKeys).length === 0)
|
|
4
46
|
return undefined;
|
|
@@ -7,15 +49,31 @@ export function createKeyResolver(modelApiKeyEnv, env = process.env, modelApiKey
|
|
|
7
49
|
return apiKey ? { apiKey } : undefined;
|
|
8
50
|
};
|
|
9
51
|
}
|
|
10
|
-
|
|
52
|
+
function chainLookup(modelName, modelApiKeyEnv, env, modelApiKeys) {
|
|
11
53
|
const sealed = modelApiKeys[modelName];
|
|
12
|
-
if (
|
|
13
|
-
|
|
54
|
+
if (isModelKeyPoison(sealed))
|
|
55
|
+
return { kind: "poison", poison: sealed };
|
|
14
56
|
if (sealed)
|
|
15
|
-
return sealed;
|
|
57
|
+
return { kind: "key", key: sealed };
|
|
16
58
|
const envName = modelApiKeyEnv[modelName];
|
|
17
59
|
if (!envName)
|
|
18
|
-
return
|
|
19
|
-
|
|
60
|
+
return { kind: "undeclared" };
|
|
61
|
+
const value = env[envName];
|
|
62
|
+
return value ? { kind: "key", key: value } : { kind: "poison", poison: modelKeyPoison({ source: "env", reason: "env_missing", apiKeyEnv: envName }) };
|
|
63
|
+
}
|
|
64
|
+
export function modelKeyPoisonOf(modelName, modelApiKeyEnv, env = process.env, modelApiKeys = {}) {
|
|
65
|
+
const r = chainLookup(modelName, modelApiKeyEnv, env, modelApiKeys);
|
|
66
|
+
return r.kind === "poison" ? r.poison : undefined;
|
|
67
|
+
}
|
|
68
|
+
export function resolveModelApiKey(modelName, modelApiKeyEnv, env = process.env, modelApiKeys = {}) {
|
|
69
|
+
const r = chainLookup(modelName, modelApiKeyEnv, env, modelApiKeys);
|
|
70
|
+
switch (r.kind) {
|
|
71
|
+
case "key":
|
|
72
|
+
return r.key;
|
|
73
|
+
case "poison":
|
|
74
|
+
throw new ModelKeyPoisonedError(modelName, r.poison);
|
|
75
|
+
case "undeclared":
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
20
78
|
}
|
|
21
79
|
//# sourceMappingURL=key-resolver.js.map
|
package/dist/leader/leader.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { type RunnerLike, type WorkerReport } from "./fanout.js";
|
|
|
16
16
|
import { type ExecStreamLike } from "./diffout.js";
|
|
17
17
|
import { type MergeDeps, type MergeResult } from "./merge.js";
|
|
18
18
|
import { type TaskSpec, type VerifyConfig, type CheckpointToken, type TerminalCause, type ToolSpec, type RepairTerminal, type OracleTier, type RepairResult } from "@sema-agent/core";
|
|
19
|
+
import { type UnattendedLeg } from "../unattended-permission-mode.js";
|
|
19
20
|
/** [ref] Slice 6: drive a worker's resource-suspend auto-resume to a terminal (or bound) state. Exported
|
|
20
21
|
* for unit tests. Loops `resume(token, slice)` WHILE the result's terminal cause is a park on a
|
|
21
22
|
* `resource_limit` gate. Exits on: a terminal cause that is not a park, a park on any OTHER gate
|
|
@@ -67,6 +68,11 @@ export interface ProvisionedWorker {
|
|
|
67
68
|
export interface LeaderDeps {
|
|
68
69
|
/** Plan a task into N disjoint-file sub-tasks (MVP: simple/hand-authored; adaptive replan = scale). */
|
|
69
70
|
plan: (task: string) => Promise<SubtaskSpec[]>;
|
|
71
|
+
/** S-877 候裁 15 / 18 —— 本车道有手的 run 并进的无人值守腿(模式 = 运维旋钮 `UNATTENDED_PERMISSION_MODE`、写面 roots、模式释放写保护时的
|
|
72
|
+
* 写保护拒层),由 `wire.ts` 经 `createUnattendedLeg` 铸一次递进来。本文件自己起的有手 run(模块门修复)与 `repair-wire.ts` 的单代理修复环按
|
|
73
|
+
* `sub.spec` 起 run、不经 provisionWorker —— 两处都经 `withUnattendedLeg` 并进这一份。**必填**(缺席 = core「Absent = default」⇒ 无人可答的腿上
|
|
74
|
+
* 写与非只读命令被当场拒,codex F1 的病)。 */
|
|
75
|
+
unattendedLeg: UnattendedLeg;
|
|
70
76
|
/** Provision a leader-owned worker (its env seeded with base at `branch` + an env-bound runner). */
|
|
71
77
|
provisionWorker: (sub: SubtaskSpec) => Promise<ProvisionedWorker>;
|
|
72
78
|
/** Ephemeral no-creds integration sandbox + the credentialed Coordinator push (merge(b) deps). */
|
package/dist/leader/leader.js
CHANGED
|
@@ -4,6 +4,7 @@ import { pullDiff } from "./diffout.js";
|
|
|
4
4
|
import { mergeBranches } from "./merge.js";
|
|
5
5
|
import { Runner, runWithVerification, resumeWithVerification } from "@sema-agent/core";
|
|
6
6
|
import { pausedCauseOf, outwardPlane } from "../terminal.js";
|
|
7
|
+
import { withUnattendedLeg } from "../unattended-permission-mode.js";
|
|
7
8
|
const MAX_RESUME_SLICES = 200;
|
|
8
9
|
export async function autoResumeOnResource(initial, signal, resume, maxSlices = MAX_RESUME_SLICES) {
|
|
9
10
|
let vr = initial;
|
|
@@ -200,7 +201,7 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
200
201
|
}
|
|
201
202
|
deps.logger?.info?.("module_repair", { workerId: r.workerId, attempt: attempt + 1, reason: redactHead(verdict.reason ?? "", 500) });
|
|
202
203
|
try {
|
|
203
|
-
const rr = await w.runner.runTaskStream({ ...sub.spec, objective: deps.moduleGate.repairObjective(sub, safeVerdictReason), sessionId: `${w.sessionId}-r${attempt + 1}` }).result();
|
|
204
|
+
const rr = await w.runner.runTaskStream(withUnattendedLeg({ ...sub.spec, objective: deps.moduleGate.repairObjective(sub, safeVerdictReason), sessionId: `${w.sessionId}-r${attempt + 1}` }, deps.unattendedLeg)).result();
|
|
204
205
|
if (r.stats && rr?.stats)
|
|
205
206
|
r.stats = { ...r.stats, tokens: (r.stats.tokens ?? 0) + (rr.stats.tokens ?? 0), costMicroUsd: (r.stats.costMicroUsd ?? 0) + (rr.stats.costMicroUsd ?? 0) };
|
|
206
207
|
}
|
|
@@ -2,6 +2,7 @@ import { runRepairLoop, delimitUntrusted, } from "@sema-agent/core";
|
|
|
2
2
|
import { mkRepairOracle } from "./repair-oracle.js";
|
|
3
3
|
import { outwardPlane } from "../terminal.js";
|
|
4
4
|
import { redactHead } from "../observability/run-terminal.js";
|
|
5
|
+
import { withUnattendedLeg } from "../unattended-permission-mode.js";
|
|
5
6
|
function statusForTerminal(t) {
|
|
6
7
|
return t === "fixed" || t === "candidate_only" ? "completed" : "failed";
|
|
7
8
|
}
|
|
@@ -86,7 +87,7 @@ export function attachRepairLoopDeps(deps, resolve) {
|
|
|
86
87
|
deps.repairLoop = {
|
|
87
88
|
run: async (solo, sub) => {
|
|
88
89
|
const cfg = await resolve(solo, sub);
|
|
89
|
-
const implSpec = { ...sub.spec, sessionId: solo.sessionId };
|
|
90
|
+
const implSpec = withUnattendedLeg({ ...sub.spec, sessionId: solo.sessionId }, deps.unattendedLeg);
|
|
90
91
|
return runRepairLoopForLeader(solo, implSpec, cfg);
|
|
91
92
|
},
|
|
92
93
|
};
|
package/dist/leader/wire.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Brain, type Model, type ModelRoles, type ModelPricing, type TaskSpec, type ExecutionEnvFactory, type RemoteExecutionEnv, type ToolResultStore, type SessionStore, type ExecutionEnv, type RunnerDeps } from "@sema-agent/core";
|
|
1
|
+
import { type Brain, type Model, type ModelRoles, type ModelPricing, type TaskSpec, type ExecutionEnvFactory, type RemoteExecutionEnv, type ToolResultStore, type SessionStore, type ExecutionEnv, type RunnerDeps, type PermissionMode } from "@sema-agent/core";
|
|
2
2
|
import type { CheckpointStoreFull } from "../plugins/store-backend.js";
|
|
3
3
|
import { type LeaderDeps, type LeaderResult } from "./leader.js";
|
|
4
4
|
import { type DeploymentPostureSeats } from "../boot/runner-deps.js";
|
|
@@ -19,6 +19,14 @@ export interface LeaderWireConfig {
|
|
|
19
19
|
* REMOTE_EXEC (e2b or k8s/Kata) — workers and the integration sandbox run on envs from THIS factory.
|
|
20
20
|
* Requires `s3` (diff-out is the worker's self-upload; there is no leader-held env to pull from). */
|
|
21
21
|
envFactory?: ExecutionEnvFactory;
|
|
22
|
+
/**
|
|
23
|
+
* S-877 候裁 15 → B —— 本车道**有手**的每一条 run(worker 两车道、集成沙箱修复、模块门修复、单代理修复环)的
|
|
24
|
+
* `TaskSpec.permissionMode`。**必填**:缺席 = core「Absent = `default`」⇒ 写与非只读命令在无人可答的这条腿上被
|
|
25
|
+
* headless 当场拒(codex F1 的病);必填让每一个构造本配置的地方都得表态,不许静默落回那一形。
|
|
26
|
+
* 唯一取值点 = `boot/leader.ts` 调 `unattendedPermissionMode(config)`(`src/unattended-permission-mode.ts`,与 run-local 同一只);
|
|
27
|
+
* 本文件经 `createUnattendedLeg` / `withUnattendedLeg` 铸并(模式 + 写面 roots + 候裁 18 的写保护拒层)。planner 路由无手,不读。
|
|
28
|
+
*/
|
|
29
|
+
unattendedPermissionMode: PermissionMode;
|
|
22
30
|
/** Durable suspend/resume for SUB-WORKERS ([ref] C4): when the seat yields a value, that worker task carries
|
|
23
31
|
* the durable question/ask policy + checkpoint store, so a gated ask/AskUserQuestion SUSPENDS the worker
|
|
24
32
|
* (checkpoint + paused env) instead of failing — the trigger replan-lite C4 arbitrates on. Mirrors main.ts's
|
package/dist/leader/wire.js
CHANGED
|
@@ -5,6 +5,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { Runner, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, delimitUntrusted, hasDestroy, } from "@sema-agent/core";
|
|
8
|
+
import { createUnattendedLeg, withUnattendedLeg } from "../unattended-permission-mode.js";
|
|
8
9
|
import { RemoteContainerExecutionEnv } from "../plugins/remote-env-e2b.js";
|
|
9
10
|
import { E2B_SANDBOX_HOME } from "../execution-lane-caps.js";
|
|
10
11
|
import { createDurableAskPolicy } from "../approval.js";
|
|
@@ -138,6 +139,7 @@ export function createLeaderRunner(cfg) {
|
|
|
138
139
|
const onNoticeSeat = createEngineNoticeSeat(cfg.logger);
|
|
139
140
|
const governanceSeat = cfg.governance ?? {};
|
|
140
141
|
const deploymentPostureSeat = () => cfg.deploymentPosture?.() ?? {};
|
|
142
|
+
const unattendedLeg = () => createUnattendedLeg(cfg.unattendedPermissionMode, deploymentPostureSeat());
|
|
141
143
|
const tracerSeat = cfg.tracer ? { tracer: cfg.tracer } : {};
|
|
142
144
|
const usageWindowSeat = cfg.usageWindows && cfg.usageWindowStore ? { usageWindows: cfg.usageWindows, usageWindowStore: cfg.usageWindowStore } : {};
|
|
143
145
|
const promptSourceSeat = cfg.promptSource ? { promptSource: cfg.promptSource } : {};
|
|
@@ -169,12 +171,12 @@ export function createLeaderRunner(cfg) {
|
|
|
169
171
|
delimitUntrusted("FAILING OUTPUT (untrusted)", errorText),
|
|
170
172
|
].join("\n");
|
|
171
173
|
const res = await runner
|
|
172
|
-
.runTaskStream({
|
|
174
|
+
.runTaskStream(withUnattendedLeg({
|
|
173
175
|
objective,
|
|
174
176
|
...principalSpec(),
|
|
175
177
|
sessionId: `leader-repair-${Date.now()}-${round}`,
|
|
176
178
|
limits: { maxCostUsd: repairBudgetUsd, maxWalltimeMs: Math.max(300_000, Math.floor(leaderTimeoutMs / 2)) },
|
|
177
|
-
})
|
|
179
|
+
}, unattendedLeg()))
|
|
178
180
|
.result();
|
|
179
181
|
if (res.terminal.kind !== "completed")
|
|
180
182
|
throw new Error(`repair round ${round} did not complete (status=${outwardPlane(res.terminal).status})`);
|
|
@@ -243,6 +245,7 @@ export function createLeaderRunner(cfg) {
|
|
|
243
245
|
const provisionWorker = async (sub) => {
|
|
244
246
|
const sessionId = `leader-${sub.workerId}-${Date.now()}`;
|
|
245
247
|
const durable = cfg.durable?.();
|
|
248
|
+
const resumeCtx = { body: { objective: sub.spec.objective }, unattendedLeg: true };
|
|
246
249
|
let durableSpec = {};
|
|
247
250
|
if (durable) {
|
|
248
251
|
const d = durable;
|
|
@@ -252,7 +255,7 @@ export function createLeaderRunner(cfg) {
|
|
|
252
255
|
durableApproval: { scope: "_leader", ...(d.ttlMs ? { ttlMs: d.ttlMs } : {}) },
|
|
253
256
|
onQuestion: QUESTION_AWAITS_RESUME,
|
|
254
257
|
};
|
|
255
|
-
await d.checkpointStore.putCtx(sessionId,
|
|
258
|
+
await d.checkpointStore.putCtx(sessionId, resumeCtx).catch((e) => {
|
|
256
259
|
cfg.logger?.warn?.("leader_putctx_failed", { workerId: sub.workerId, err: redactHead(e instanceof Error ? e.message : String(e), 2000) });
|
|
257
260
|
});
|
|
258
261
|
}
|
|
@@ -287,7 +290,7 @@ export function createLeaderRunner(cfg) {
|
|
|
287
290
|
runner.runTaskStream = (spec, resume) => {
|
|
288
291
|
const stream = orig(spec, resume);
|
|
289
292
|
const iv = setInterval(() => {
|
|
290
|
-
void d.checkpointStore.putCtx(sessionId,
|
|
293
|
+
void d.checkpointStore.putCtx(sessionId, resumeCtx).catch(() => { });
|
|
291
294
|
}, 45_000);
|
|
292
295
|
iv.unref?.();
|
|
293
296
|
void stream.result().then(() => clearInterval(iv), () => clearInterval(iv));
|
|
@@ -322,7 +325,7 @@ export function createLeaderRunner(cfg) {
|
|
|
322
325
|
}),
|
|
323
326
|
})),
|
|
324
327
|
fetchDiff: () => fetchUploadedDiff(getUrl),
|
|
325
|
-
spec: { ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec(), objective: `${sub.spec.objective}${uploadStepSuffix(W)}` },
|
|
328
|
+
spec: withUnattendedLeg({ ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec(), objective: `${sub.spec.objective}${uploadStepSuffix(W)}` }, unattendedLeg()),
|
|
326
329
|
};
|
|
327
330
|
}
|
|
328
331
|
if (!cfg.e2bApiKey)
|
|
@@ -337,7 +340,7 @@ export function createLeaderRunner(cfg) {
|
|
|
337
340
|
runner: keepCtxWarm(new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat(), ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat, ...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}), ...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}), executionEnv: env })),
|
|
338
341
|
diffEnv: env,
|
|
339
342
|
destroy: () => env.destroy().then(() => { }),
|
|
340
|
-
spec: { ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec() },
|
|
343
|
+
spec: withUnattendedLeg({ ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec() }, unattendedLeg()),
|
|
341
344
|
};
|
|
342
345
|
};
|
|
343
346
|
const provisionIntegrationSandbox = async () => {
|
|
@@ -379,6 +382,7 @@ export function createLeaderRunner(cfg) {
|
|
|
379
382
|
const deps = {
|
|
380
383
|
plan,
|
|
381
384
|
provisionWorker,
|
|
385
|
+
unattendedLeg: unattendedLeg(),
|
|
382
386
|
provisionIntegrationSandbox,
|
|
383
387
|
push,
|
|
384
388
|
testCmd: body.testCmd,
|
|
@@ -25,6 +25,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
25
25
|
readonly cls: "F";
|
|
26
26
|
readonly note: "S-297(`http/routes/sessions.ts lastLegMcpFor`,`/mcp` 面板的可选键 `lastLegMcp`):按会话取最近 run 的索引读 / 那条 run 的账本读**抛错或超期** ⇒ 该键缺席,面板本体照常 200。放行的最坏后果=这一屏少一格「上一次真挂上了什么」(壳退回翻账本),不是门、不是权限、不是凭证 —— 而反方向(让附加键的读失败打掉整张面板)会给一口此前零 MCP 配置下根本不碰账本的**既有**读面新加一条挂掉的理由。缺席本就是本键 wire 上的一等状态(消费端必须会读),所以这条兜底不产生任何新的消费端形态;计数让「某台部署的账本一直读不出来」显形。";
|
|
27
27
|
};
|
|
28
|
+
readonly "server.unattended-leg.mode-releases-protective-asks": {
|
|
29
|
+
readonly cls: "P-DEBT";
|
|
30
|
+
readonly note: "S-877 四续 ⑧(候裁 18 D 的已披露残余,`unattended-permission-mode.ts` 的 `createUnattendedLeg`):无人值守腿(leader worker / 修复腿 / run-local / 续跑重挂)的旋钮 `UNATTENDED_PERMISSION_MODE` 缺省 `bypassPermissions` 释放六类保护问(`probe` / `tool_marks_mcp` / `tool_marks_a2a` / `transcript_write` / `rule_unreadable` / `artifact_shared_read`;写保护一类已改拒)—— 7.105.0 这些腿上它们照问、无人答即拒。放行的最坏后果=无人值守的写 / 读 / MCP·A2A 标记工具调用不再被这几道保护问拦下,`rule_unreadable` 是「规则店读不出照放」(安全轴 fail-open 方向)。core 对这几类没有拒座;S-906(core 7.37.0 按类作答)到货后本臂与旋钮一并硬删。计数 = 铸出的这类腿条数,detail = `mode=<词> released=<类…>`。";
|
|
31
|
+
};
|
|
28
32
|
readonly "server.workflow-park-index.sync-failed": {
|
|
29
33
|
readonly cls: "P-DEBT";
|
|
30
34
|
readonly note: "S-185 车CM(codex r1-F2 [high],验真后登记):workflow 出身 park 的 join 索引(`workflow_agent_session` / local 账本)在**一次成功的 run 写之后**同步失败 ⇒ 该次同步被吞,重试要等这条 run 的**下一次**写。而 park 恰好常常是这条 run 的**最后**一次写(park = run 终局 `failed`)⇒ 那条 park 可能永久 join 不到,`/decide` 结构性退回本批之前的行为(409 `conflict.resume_context_unavailable`)——**方向是 fail-closed**(不误配、不伪造、不放行任何东西),丢的是「人能不能把这条批准送出去」这个能力。归 P 类的理由是它决定**车道选择**(缺行 ⇒ 选中 legacy 腿),归 DEBT 的理由是真正的解要一条**按子代 checkpoint 键的耐久决议回执 + 重启期回填**(与 codex r1-F3 同一件欠账,另车)。计数非零 = 有 park 因此决不掉,运维要按 detail 里的 runId 去查。";
|
|
@@ -263,7 +267,7 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
263
267
|
};
|
|
264
268
|
readonly "server.run-terminal.provider-echo-shaped": {
|
|
265
269
|
readonly cls: "F";
|
|
266
|
-
readonly note: "S-283(7.105.0,`observability/run-terminal.ts` 的 `admitProviderEcho`,唯一判决口;S-889 起动作与 core #1204 / 7.
|
|
270
|
+
readonly note: "S-283(7.105.0,`observability/run-terminal.ts` 的 `admitProviderEcho`,唯一判决口;S-889 起动作与 core #1204 源头闸同一条;S-877 / core 7.35.0 起谓词读 core 包根 `isProviderEchoShape`,core brain 写侧先判(越界 ⇒ trace `brain.echo_dropped`)⇒ 真 brain 链上本 tag 零命中,命中只剩存量行与不经 core brain 写侧的 producer):模型服务商回声、引擎逐字转交的不透明 id(`apiFailure.requestId` / `responseModel`,以及账本孪生 `turn_end.responseModel`、子代通告 `task_notification.apiFailure.requestId` 与 `POST /v1/side-query` 200 体的 `responseModel`;铸点 + 账本汇点两口)越出形状(字符串、长度 1–128、每个码元是可见 ASCII 0x21–0x7E;非串值在外)⇒ **缺席**(键删掉 / 不铸)+ 计数;空串 = 服务商什么都没说 ⇒ 缺席、**不计数**(S-877,core `providerEchoReading` 的读法)。方向是 **fail-closed**(越界字节不上 wire / 不落库 / 不进日志),不截断、不过脱敏器(不透明 id,脱敏会改坏它 —— 与 S-270 structuredOutput 同方向)。放行的最坏后果 = 这枚 id / 模型名在 wire 上缺席(`apiFailure` 留 `{}` = 「provider 故障、无可补充标签」,core 契约原句),运维拿不到它去服务商那边对单;detail 只带座位 / 原长或类型,**不带值**。计数非零 = 这台部署接的端点(或中间网关)在回显越界字节,运维按座位去查那一跳;读面不改写存量行,存量越界行每次回放各计一次。";
|
|
267
271
|
};
|
|
268
272
|
readonly "server.run-terminal.redact-capped": {
|
|
269
273
|
readonly cls: "F";
|
|
@@ -16,6 +16,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
16
16
|
cls: "F",
|
|
17
17
|
note: "S-297(`http/routes/sessions.ts lastLegMcpFor`,`/mcp` 面板的可选键 `lastLegMcp`):按会话取最近 run 的索引读 / 那条 run 的账本读**抛错或超期** ⇒ 该键缺席,面板本体照常 200。放行的最坏后果=这一屏少一格「上一次真挂上了什么」(壳退回翻账本),不是门、不是权限、不是凭证 —— 而反方向(让附加键的读失败打掉整张面板)会给一口此前零 MCP 配置下根本不碰账本的**既有**读面新加一条挂掉的理由。缺席本就是本键 wire 上的一等状态(消费端必须会读),所以这条兜底不产生任何新的消费端形态;计数让「某台部署的账本一直读不出来」显形。",
|
|
18
18
|
},
|
|
19
|
+
"server.unattended-leg.mode-releases-protective-asks": {
|
|
20
|
+
cls: "P-DEBT",
|
|
21
|
+
note: "S-877 四续 ⑧(候裁 18 D 的已披露残余,`unattended-permission-mode.ts` 的 `createUnattendedLeg`):无人值守腿(leader worker / 修复腿 / run-local / 续跑重挂)的旋钮 `UNATTENDED_PERMISSION_MODE` 缺省 `bypassPermissions` 释放六类保护问(`probe` / `tool_marks_mcp` / `tool_marks_a2a` / `transcript_write` / `rule_unreadable` / `artifact_shared_read`;写保护一类已改拒)—— 7.105.0 这些腿上它们照问、无人答即拒。放行的最坏后果=无人值守的写 / 读 / MCP·A2A 标记工具调用不再被这几道保护问拦下,`rule_unreadable` 是「规则店读不出照放」(安全轴 fail-open 方向)。core 对这几类没有拒座;S-906(core 7.37.0 按类作答)到货后本臂与旋钮一并硬删。计数 = 铸出的这类腿条数,detail = `mode=<词> released=<类…>`。",
|
|
22
|
+
},
|
|
19
23
|
"server.workflow-park-index.sync-failed": {
|
|
20
24
|
cls: "P-DEBT",
|
|
21
25
|
note: "S-185 车CM(codex r1-F2 [high],验真后登记):workflow 出身 park 的 join 索引(`workflow_agent_session` / local 账本)在**一次成功的 run 写之后**同步失败 ⇒ 该次同步被吞,重试要等这条 run 的**下一次**写。而 park 恰好常常是这条 run 的**最后**一次写(park = run 终局 `failed`)⇒ 那条 park 可能永久 join 不到,`/decide` 结构性退回本批之前的行为(409 `conflict.resume_context_unavailable`)——**方向是 fail-closed**(不误配、不伪造、不放行任何东西),丢的是「人能不能把这条批准送出去」这个能力。归 P 类的理由是它决定**车道选择**(缺行 ⇒ 选中 legacy 腿),归 DEBT 的理由是真正的解要一条**按子代 checkpoint 键的耐久决议回执 + 重启期回填**(与 codex r1-F3 同一件欠账,另车)。计数非零 = 有 park 因此决不掉,运维要按 detail 里的 runId 去查。",
|
|
@@ -254,7 +258,7 @@ export const FAIL_OPEN_TAGS = {
|
|
|
254
258
|
},
|
|
255
259
|
"server.run-terminal.provider-echo-shaped": {
|
|
256
260
|
cls: "F",
|
|
257
|
-
note: "S-283(7.105.0,`observability/run-terminal.ts` 的 `admitProviderEcho`,唯一判决口;S-889 起动作与 core #1204 / 7.
|
|
261
|
+
note: "S-283(7.105.0,`observability/run-terminal.ts` 的 `admitProviderEcho`,唯一判决口;S-889 起动作与 core #1204 源头闸同一条;S-877 / core 7.35.0 起谓词读 core 包根 `isProviderEchoShape`,core brain 写侧先判(越界 ⇒ trace `brain.echo_dropped`)⇒ 真 brain 链上本 tag 零命中,命中只剩存量行与不经 core brain 写侧的 producer):模型服务商回声、引擎逐字转交的不透明 id(`apiFailure.requestId` / `responseModel`,以及账本孪生 `turn_end.responseModel`、子代通告 `task_notification.apiFailure.requestId` 与 `POST /v1/side-query` 200 体的 `responseModel`;铸点 + 账本汇点两口)越出形状(字符串、长度 1–128、每个码元是可见 ASCII 0x21–0x7E;非串值在外)⇒ **缺席**(键删掉 / 不铸)+ 计数;空串 = 服务商什么都没说 ⇒ 缺席、**不计数**(S-877,core `providerEchoReading` 的读法)。方向是 **fail-closed**(越界字节不上 wire / 不落库 / 不进日志),不截断、不过脱敏器(不透明 id,脱敏会改坏它 —— 与 S-270 structuredOutput 同方向)。放行的最坏后果 = 这枚 id / 模型名在 wire 上缺席(`apiFailure` 留 `{}` = 「provider 故障、无可补充标签」,core 契约原句),运维拿不到它去服务商那边对单;detail 只带座位 / 原长或类型,**不带值**。计数非零 = 这台部署接的端点(或中间网关)在回显越界字节,运维按座位去查那一跳;读面不改写存量行,存量越界行每次回放各计一次。",
|
|
258
262
|
},
|
|
259
263
|
"server.run-terminal.redact-capped": { cls: "F", note: "S-107:终态自由文本(errorMessage/blockedReason/remoteEnvFailures[].message/error 列/done·failed 行)长度 > REDACT_INPUT_CAP(64KB)⇒ 整段换 «redacted:oversized»(fail-closed 取证丢弃,不截断不扫);计数=多少条终态文案被整段丢弃" },
|
|
260
264
|
"server.run-terminal.artifact-capped": { cls: "F", note: "S-107:leader_run 行上的工件字节(salvaged[].patch / candidatePatch.patch / merge.candidatePatch / merge.rej)长度 > ARTIFACT_INPUT_CAP(256KB)⇒ 整份换 «redacted:oversized-artifact»(fail-closed 取证丢弃;原字节仍在 worker 分支/merge 咽喉);计数=多少份工件被整份丢弃" },
|
|
@@ -215,6 +215,7 @@ export function createMetrics() {
|
|
|
215
215
|
m.counter("http_requests_total", "HTTP requests by route/method/status");
|
|
216
216
|
m.histogram("http_request_duration_seconds", "HTTP request duration");
|
|
217
217
|
m.counter("rate_limited_total", "Requests rejected by the rate limiter");
|
|
218
|
+
m.counter("brain_echo_dropped_total", "Provider echo fields (response request-id header / reported model name) the engine's brain dropped at write time for failing the echo shape (visible ASCII, 1-128 chars), by field (requestId|responseModel); non-zero = the endpoint or a gateway in front of it echoes out-of-shape bytes");
|
|
218
219
|
m.counter("mcp_probe_total", `No-run MCP status face outcomes by outcome (${MCP_PROBE_OUTCOMES.join("/")} — see src/capabilities/mcp-probe.ts MCP_PROBE_OUTCOMES; rate_limited has TWO causes: the per-identity fixed window AND the replica-wide in-flight cap, which can refuse caller B because of caller A)`);
|
|
219
220
|
m.counter("tasks_total", "Finished tasks by terminal status");
|
|
220
221
|
m.counter("task_tokens_total", "Total model tokens consumed by tasks");
|
|
@@ -296,6 +297,7 @@ export function createMetrics() {
|
|
|
296
297
|
m.counter("fleet_lease_exhausted_total", "Fleet trace-lease requests rejected because the pool was exhausted");
|
|
297
298
|
m.counter("fleet_lease_auth_error_total", "Fleet trace-lease auth failures");
|
|
298
299
|
m.counter("fleet_lease_fail_open_total", "Fleet trace-lease fail-open grants (lease store unavailable)");
|
|
300
|
+
m.counter("fleet_lease_fail_closed_total", "Fleet quota-lease refusals because the center was unreachable / 5xx / timed out / answered malformed while FLEET_LEASE_FAIL_CLOSED is in effect (billed submissions answered 503 fleet.lease_unavailable)");
|
|
299
301
|
m.counter("metric_label_overflow_total", "Label values collapsed to 'other' by the cardinality guard, by label");
|
|
300
302
|
m.counter("remote_env_exec_idle_timeout_total", "Remote-env exec idle timeouts, by provider");
|
|
301
303
|
m.counter("remote_env_file_timeout_total", "Remote-env file-op timeouts, by provider");
|
|
@@ -4,7 +4,7 @@ export declare const PERMISSION_RULE_EVENT_TABLE: readonly [{
|
|
|
4
4
|
readonly meaning: "a persisted allow rule authorized the call and nobody was asked";
|
|
5
5
|
}, {
|
|
6
6
|
readonly event: "rule_store_unreadable";
|
|
7
|
-
readonly meaning: "the rule store failed to read, so the
|
|
7
|
+
readonly meaning: "the rule store failed to read, so the failed partition's rows were SKIPPED for that read (core C-R172: no store card; its allow rows clear nothing and its deny rows protect nothing until a read succeeds — expect extra cards and missing refusals whose cause is the store, not the model)";
|
|
8
8
|
}, {
|
|
9
9
|
readonly event: "read_only_allowed";
|
|
10
10
|
readonly meaning: "the engine's read-only shell verdict cleared an ask that was NOT an operator-written rule (core decisionReason \"read_only\"; an operator-declared ask carries matchedAskRule and is never cleared this way)";
|
|
@@ -6,7 +6,7 @@ export const PERMISSION_RULE_EVENT_TABLE = [
|
|
|
6
6
|
},
|
|
7
7
|
{
|
|
8
8
|
event: "rule_store_unreadable",
|
|
9
|
-
meaning: "the rule store failed to read, so the
|
|
9
|
+
meaning: "the rule store failed to read, so the failed partition's rows were SKIPPED for that read (core C-R172: no store card; its allow rows clear nothing and its deny rows protect nothing until a read succeeds — expect extra cards and missing refusals whose cause is the store, not the model)",
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
event: "read_only_allowed",
|
|
@@ -125,7 +125,44 @@ export declare function redactErrorMessage(raw: string | undefined): string | un
|
|
|
125
125
|
* `task_run.error` 那一列 `TEXT(65 535 字节)` 定的,套到这里等于用静默的数据损坏换一次脱敏。
|
|
126
126
|
*/
|
|
127
127
|
export declare function redactModelText(raw: string): string;
|
|
128
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* 🔴 **provider 回声字段的形状闸 —— 唯一铸点**(S-283,7.105.0;test [ref] 附带;动作经 S-889 与 core 源头闸归一)。
|
|
130
|
+
*
|
|
131
|
+
* **回声字段是什么**:模型服务商(上游模型 API / 中间网关)在响应里给、引擎**逐字转交**的不透明标识 ——
|
|
132
|
+
* `apiFailure.requestId`(响应头 `request-id` / `x-request-id`,core `brain/errors.js` 的 `readProviderRequestId`)、
|
|
133
|
+
* `responseModel`(服务商自报的模型名,与账本 `turn_end.responseModel` 是孪生)、工具调用 id(`strandedHumanAnswers[].toolCallId` /
|
|
134
|
+
* `stats.humanReview.gates[].toolCallId`;core `brain/openai.js` / `brain/anthropic.js` 取 `tc.id` / `cb.id`,只在缺席时
|
|
135
|
+
* `mintFallbackToolCallId` 自铸兜底)。字节由**部署方接的那个端点**决定,本栈一个都没铸 ⇒ **不可信**。
|
|
136
|
+
*
|
|
137
|
+
* 🔴 **本闸只施加在「这个值出现的每一张脸都被覆盖到」的回声上**(codex r1 F1,实测成立的回归后改判):工具调用 id 是
|
|
138
|
+
* **跨面 join 键** —— 同一枚 id 同时逐字出现在 `tool_start` / `tool_end` / 审批卡等逐帧面上。只在结果面动它 ⇒ 结果面与逐帧面
|
|
139
|
+
* join 不上。所以它的处置词是 `"provider-join-key"`:信任类仍是 provider 回声,但形状闸只能在**全部面的共同入口**一次施加
|
|
140
|
+
* (独立一车,已登记为债),本面逐字。`requestId` / `responseModel` 的每一张脸(结果面四处 + 账本孪生 + 子代通告 + `POST /v1/side-query` 200 体的 `responseModel`,
|
|
141
|
+
* 后者 7.105.0 合并复审 X1 补上 —— `http/routes/side-query.ts` 的 `shapeSideQueryEcho`)本闸都覆盖到了。
|
|
142
|
+
*
|
|
143
|
+
* **只做形状校验,不过脱敏器**(本车裁,与 S-270 / clay [ref] ② 的 `structuredOutput` 逐字回声面同一方向):回声值
|
|
144
|
+
* 语义上是**不透明 id**(工单号 / join 键),脱敏器的值臂会把形似密钥的 id 改坏(`sk-…` 形的 id 整段成 `«redacted:key»`),
|
|
145
|
+
* 而 id 被改坏 = 下游 join / 支持工单对不上;该防的是**形**(超长串撑爆终局体与日志、控制字符 / 终端转义序列改写
|
|
146
|
+
* 显示、bidi / 非 ASCII 冒充),不是「里面像不像密钥」。
|
|
147
|
+
*
|
|
148
|
+
* 🔴 **形状与动作 = core 源头闸同一条规则**(S-889,主会话裁;core **#1204**,原排 7.34.1、随 7.35.0 到货,包根导出 `PROVIDER_ECHO_SHAPE` +
|
|
149
|
+
* `isProviderEchoShape(value: string): boolean`,字母表采本仓提出的这一张):
|
|
150
|
+
* · **形内** = 字符串、长度 1–128、每个码元都是可见 ASCII(0x21–0x7E;空格、空串与一切控制 / 非 ASCII 码元都在外)⇒ 原样;
|
|
151
|
+
* · **越界**(含非串值)⇒ **缺席**(键删掉 / 不铸)+ 计数 `server.run-terminal.provider-echo-shaped`(detail 只带座位与原长 / 类型,
|
|
152
|
+
* **不带值**)。不截断、不带记号 —— 首版的「截成 127 + `…`」与 core 的「越界 ⇒ 缺席」是同一个键上两种处置,那一形 7.105.0 不发;
|
|
153
|
+
* 截断记号、「截过的形是不动点」那套逻辑随之删除(规则变少)。缺席本来就是这几个键在 wire 上的一等状态(`apiFailure` 留下
|
|
154
|
+
* `{}` = 「provider 故障、无可补充标签」,core 契约原句),消费端不需要新读法。
|
|
155
|
+
* · `undefined` 与**空串** = 缺席本身(服务商什么都没说),不计数、不铸键 —— 空串一臂是 S-877 / core 7.35.0 #1204 的读法
|
|
156
|
+
* (`brain/errors.d.ts` 的 `providerEchoReading` 逐字「a non-string or the empty string states nothing (`absent` — no field,
|
|
157
|
+
* nothing counted)」;修前本仓把空串算越界计一次)。非串值本仓**照旧计数**:core 那句说的是 brain 读服务商原始响应,本口读的是
|
|
158
|
+
* 持久行 / 非 core 的 Runner 给的形,一个非串的回声座是坏行,响亮([ref])。
|
|
159
|
+
* ✅ 谓词 = core 包根导出的 `isProviderEchoShape`(S-877 起换调;本地那两行定义已删 —— 字母表同源,本仓不留第二份)。
|
|
160
|
+
* 🔴 **core 7.35.0 起 core 的 brain 在写侧先判一次**(`brain/stream-shared.js` `noteResponseModel` / `brain/errors.js`
|
|
161
|
+
* `readProviderRequestId`:越界 ⇒ 不写、run 里发 trace `brain.echo_dropped {field}`)⇒ 真 brain 链上越界值到不了本口,本口的
|
|
162
|
+
* 计数只剩「不经 core brain 写侧」的 producer(存量行、宿主自己的 Brain / 自接 `sideQuery` 席)。
|
|
163
|
+
* **不进模型上下文**:本仓没有任何一处把这几片回声喂进提示词(它们只上 wire / 落库 / 进日志)——这一闸管的就是那几面。
|
|
164
|
+
*/
|
|
165
|
+
/** 回声座上的值留不留:形内 ⇒ `true`(原样留);`undefined` / 空串 ⇒ `false`(缺席本身,不计数);其余越界 ⇒ `false` + 计数。
|
|
129
166
|
* 调用方按 `false` 删键 / 不铸键 —— 本函数是全部回声座(结果面 / 账本孪生 / 子代通告 / side-query 200 体)的**唯一**判决口。 */
|
|
130
167
|
export declare function admitProviderEcho(value: unknown, seat: string): value is string;
|
|
131
168
|
/** {@link redactTerminalResult} 会碰的全部顶层键 = 现行形里**不是** `"verbatim"` 的行(`"redacted"` ∪ S-283 的
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { redactSecrets, normalizeForRedaction } from "../trace/redact.js";
|
|
3
|
+
import { isProviderEchoShape } from "@sema-agent/core";
|
|
3
4
|
import { isModelTextLedgerEventType, isParkLedgerEventType } from "../trace/ledger-events.js";
|
|
4
5
|
import { isUnsuccessfulRunStatus } from "../plugins/store-contracts.js";
|
|
5
6
|
import { sealResultForExit } from "../terminal.js";
|
|
@@ -97,12 +98,8 @@ export function redactModelText(raw) {
|
|
|
97
98
|
return OVERSIZED_REDACTION_PLACEHOLDER;
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
|
-
const PROVIDER_ECHO_SHAPE = /^[\x21-\x7E]{1,128}$/;
|
|
101
|
-
function isProviderEchoShape(value) {
|
|
102
|
-
return PROVIDER_ECHO_SHAPE.test(value);
|
|
103
|
-
}
|
|
104
101
|
export function admitProviderEcho(value, seat) {
|
|
105
|
-
if (value === undefined)
|
|
102
|
+
if (value === undefined || value === "")
|
|
106
103
|
return false;
|
|
107
104
|
if (typeof value === "string" && isProviderEchoShape(value))
|
|
108
105
|
return true;
|
|
@@ -27,7 +27,7 @@ export const SECRET_ENV_SCRUB_SITES = {
|
|
|
27
27
|
},
|
|
28
28
|
};
|
|
29
29
|
const SCRUB_COUNTER = "secret_env_scrubbed_total";
|
|
30
|
-
const SCRUB_COUNTER_HELP = "secret env entries not handed on as written (dropped
|
|
30
|
+
const SCRUB_COUNTER_HELP = "secret env entries not handed on as written (dropped) to a spawned child process, by site/kind/confidence (core scrubSecretEnv rules + server config-catalog secret names + name-list knobs)";
|
|
31
31
|
const DISCLOSED_CAP = 512;
|
|
32
32
|
const SEP = "=";
|
|
33
33
|
let sink = {};
|
|
@@ -14,6 +14,8 @@ import type { Metrics } from "./metrics.js";
|
|
|
14
14
|
* 🔴 BREAKING(server 7.64.0,core 7.6.0 提货):标签名 `source` → `deniedBy`,词表从七词
|
|
15
15
|
* (`policy/hook/safety/shellGate/planMode/classifier/org`)换成八词
|
|
16
16
|
* (`policy/hook/org/classifier/plan_mode/compliance/write_protection/ask_resolution`)。仪表盘/告警规则要改。
|
|
17
|
+
* 🔁 server 7.106.0(S-877 / core 7.34.0 [ref]):`plan_mode` 这个词退役,换成 `mode`(模式自己拒的那一形:`dontAsk` 拒每一只非授权 ask、
|
|
18
|
+
* `bypassPermissions` 拒判不出保护的写)—— 标签值原样取 core 的 `isDeniedBy`,按 `deniedBy="plan_mode"` 写的告警规则改 `mode`。
|
|
17
19
|
*
|
|
18
20
|
* 🔴 **第二个计数器 `auto_mode_classifier_deny_cause_total{cause}`(server 7.70.0 生为
|
|
19
21
|
* `auto_mode_classifier_unavailable_total`,core 7.12.0 [ref];**7.72.0 / core 7.14.0 [ref] 改名、换座、换词**,见本段末)**
|
package/dist/operator-ask.d.ts
CHANGED
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
* · **问答工具**(`deployment-governance.ts` 的 `AskUserQuestion`)—— core 的谓词把保留问答工具单列豁免。
|
|
35
35
|
*/
|
|
36
36
|
import type { PermissionResult } from "@sema-agent/core";
|
|
37
|
+
/** 本文件两只铸点的返回形 = core `PermissionResult` 的 ask 臂(窄型:`authoredBy` 只长在这一臂上)。 */
|
|
38
|
+
export type OperatorAskResult = Extract<PermissionResult, {
|
|
39
|
+
action: "ask";
|
|
40
|
+
}>;
|
|
37
41
|
/**
|
|
38
42
|
* 铸一条**运维声明产生的** `ask`。
|
|
39
43
|
*
|
|
@@ -42,5 +46,29 @@ import type { PermissionResult } from "@sema-agent/core";
|
|
|
42
46
|
* request」,所以它既不上 wire、也不进卡,写的必须是**真实的那条声明**而不是一句好看的话。
|
|
43
47
|
* @param message 给人看的那句话(照旧上卡)。
|
|
44
48
|
*/
|
|
45
|
-
export declare function operatorAsk(rule: string, message: string):
|
|
49
|
+
export declare function operatorAsk(rule: string, message: string): OperatorAskResult;
|
|
50
|
+
/**
|
|
51
|
+
* 🔴 **S-877 候裁 16 —— 运维声明的「恒问人」**(`APPROVAL_NEVER_AUTO` 的「always human」、耐久资源预算耗尽的「human approval required」):
|
|
52
|
+
* 在 {@link operatorAsk} 之上再盖部署出身 `authoredBy: "deployment"` ⇒ core 类读 `operator_forced`(`ruleClearanceOf`:
|
|
53
|
+
* 「a deployment-authored ask」进强制门 ⇒ `mandated`)—— bypassPermissions 放不掉、**任何**站着的答案(持久规则、会话授权,
|
|
54
|
+
* 任何 scope)退不掉,只有**这一次**的应答能答它。
|
|
55
|
+
*
|
|
56
|
+
* **为什么现在需要**:修前这两类 ask 挡住会话授权靠的是策略层的**次序**(`createDurableAskPolicy` 先判 neverAuto / 预算耗尽,再查
|
|
57
|
+
* 豁免探针)。候裁 16 把会话授权的兑现位收到**审批席**(协调器 `standingGrantSettles`,判据 = `standingAnswerMayRetire`),而审批席
|
|
58
|
+
* 读不到 `matchedAskRule`(core 刻意不上卡)—— 不盖出身,一条 `remember:"session"` 就会在审批席上退役「恒问人」。盖了之后
|
|
59
|
+
* 席的同一只判据按 `mandated` 挡住它,耐久 `/decide` 腿的可记性(park 行 `mandated` 孪生位)同判 ⇒ 回执 `rememberApplied:false`。
|
|
60
|
+
* live 腿修前这里本就是个洞(进程内 `allow_session` 授权可退役 neverAuto 卡),随类收掉。
|
|
61
|
+
*
|
|
62
|
+
* ⚠️ **只**这两类:普通 `APPROVAL_REQUIRE` 仍只经 {@link operatorAsk}(候裁 8 裁 A —— 用户「本会话放行」退役它们是产品设计,不收紧)。
|
|
63
|
+
*/
|
|
64
|
+
export declare function operatorAlwaysHumanAsk(rule: string, message: string): OperatorAskResult;
|
|
65
|
+
/**
|
|
66
|
+
* core 部署出身位 `authoredBy:"deployment"` 的**唯一**盖位函数(S-897 起收成一处):给一只**部署自己的声明**产的 ask 盖上出身 ⇒
|
|
67
|
+
* core 类读 `operator_forced`、`mandated`、不铸 `mandate` 词(表外的门;缺词 = 另有门在)⇒ bypassPermissions 放不掉、站着的答案
|
|
68
|
+
* 退不掉、壳也不会按单个强制词替人答。三个调用者:{@link operatorAlwaysHumanAsk}(`APPROVAL_NEVER_AUTO` / 预算耗尽)、
|
|
69
|
+
* 治理层包装(`runtime-governance.ts` 的 `createGovernanceAskMarkingPolicy`)、SUP 路由姿态(`router/route-orchestration.ts`,
|
|
70
|
+
* 经 {@link operatorAlwaysHumanAsk})。修前前两处各写一份 `{ ...d, authoredBy: "deployment" }`(两个写者),S-897 若再抄就是第三份。
|
|
71
|
+
* 只作用于 ask 臂(类型上就只收 ask);其余键逐字透传。
|
|
72
|
+
*/
|
|
73
|
+
export declare function withDeploymentAuthorship<A extends OperatorAskResult>(ask: A): A;
|
|
46
74
|
//# sourceMappingURL=operator-ask.d.ts.map
|
package/dist/operator-ask.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export function operatorAsk(rule, message) {
|
|
2
2
|
return { action: "ask", message, decisionReason: "rule", matchedAskRule: rule };
|
|
3
3
|
}
|
|
4
|
+
export function operatorAlwaysHumanAsk(rule, message) {
|
|
5
|
+
return withDeploymentAuthorship(operatorAsk(rule, message));
|
|
6
|
+
}
|
|
7
|
+
export function withDeploymentAuthorship(ask) {
|
|
8
|
+
return { ...ask, authoredBy: "deployment" };
|
|
9
|
+
}
|
|
4
10
|
//# sourceMappingURL=operator-ask.js.map
|
package/dist/parked-decide.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 行永 parked、生命周期分叉)。
|
|
10
10
|
*/
|
|
11
11
|
import type { HostDecision } from "./host-decision.js";
|
|
12
|
-
import type { BackgroundAgentRecord, BackgroundAgentStore, CheckpointStore, QuestionAnswer, ResumeOutcome, TaskRegistry, WorkflowRun, WorkflowRunStore } from "@sema-agent/core";
|
|
12
|
+
import type { BackgroundAgentRecord, BackgroundAgentStore, CheckpointStore, QuestionAnswer, ResumeOutcome, TaskRegistry, ToolExecuteContext, WorkflowRun, WorkflowRunStore } from "@sema-agent/core";
|
|
13
13
|
import type { ParkedReviveSurfaceFactory } from "./parked-revive-surface.js";
|
|
14
14
|
import type { RebuiltInheritedGate } from "./boot/parked-revive-gate.js";
|
|
15
15
|
import { type WorkflowRunParkKey, type WorkflowAgentSessionIndex } from "./orchestration/workflow-agent-session-index.js";
|
|
@@ -142,6 +142,19 @@ export interface ParkedDecideDeps {
|
|
|
142
142
|
rebuildInheritedGate?: (row: BackgroundAgentRecord, identity: {
|
|
143
143
|
principal: string | undefined;
|
|
144
144
|
}) => Promise<RebuiltInheritedGate | undefined>;
|
|
145
|
+
/**
|
|
146
|
+
* 🔴 S-877 四续 ⑪ —— 复活子代**自己的**审批席(`ToolExecuteContext.onAsk`:core 的委派工具把它转给子代,子代自己的引擎问 —— 模式写门 /
|
|
147
|
+
* 形状层 —— 在这里答)。本腿直接调 `execute`,不经 Runner,ctx 由本腿铸;不给 ⇒ 子代自己的问落 subRunner 的 `deps.onAsk`(协调器
|
|
148
|
+
* `ask()` 的无 ALS 上下文臂,没有会话身份 ⇒ 读不了会话授权 ⇒ 每条非只读命令 / 每次写都再 park,`remember:"session"` 对复活子代无效;
|
|
149
|
+
* 子代活着时同一问落父的席、带身份、读得到授权 —— 同一个子代两种答案)。生产 = 协调器的窗 0 席 `boundUnattendedAsk`(与 bg run 腿给父任务
|
|
150
|
+
* 装的同一只):身份 = 行的根会话(与 remember 的授权锚同键 {@link grantRootOf})+ 权威 principal;授权读不中 ⇒ 无人值守政策口(park),
|
|
151
|
+
* 不放宽。缺席(协调器未装的部署)⇒ ctx 不带座,逐字今天。
|
|
152
|
+
*/
|
|
153
|
+
approverSeatFor?: (identity: {
|
|
154
|
+
owner: string | null;
|
|
155
|
+
taskId: string;
|
|
156
|
+
sessionId: string;
|
|
157
|
+
}) => NonNullable<ToolExecuteContext["onAsk"]>;
|
|
145
158
|
warn?: (event: string, fields: Record<string, unknown>) => void;
|
|
146
159
|
}
|
|
147
160
|
export interface ParkedDecideRequest {
|
package/dist/parked-decide.js
CHANGED
|
@@ -62,6 +62,9 @@ export async function findParkedAgentForCheckpoint(agentStore, cp) {
|
|
|
62
62
|
}
|
|
63
63
|
return undefined;
|
|
64
64
|
}
|
|
65
|
+
function grantRootOf(row) {
|
|
66
|
+
return row.rootSessionId ?? row.parentSessionId;
|
|
67
|
+
}
|
|
65
68
|
function mintReviveArgs(row, knownAgentTypes) {
|
|
66
69
|
return {
|
|
67
70
|
prompt: row.description ?? "resume parked agent",
|
|
@@ -148,7 +151,7 @@ function sessionGone(taskId) {
|
|
|
148
151
|
}
|
|
149
152
|
export async function decideParkedAgent(deps, req, match) {
|
|
150
153
|
const row = match.row;
|
|
151
|
-
const rememberRoot = req.grantRemember !== undefined ? (row
|
|
154
|
+
const rememberRoot = req.grantRemember !== undefined ? grantRootOf(row) : undefined;
|
|
152
155
|
if (req.grantRemember !== undefined && rememberRoot === undefined) {
|
|
153
156
|
return { status: 400, body: { error: "remember cannot be applied: this parked agent row carries no root/parent session anchor for the exemption", errorCode: "decide.parked_remember_unsupported", taskId: match.handle } };
|
|
154
157
|
}
|
|
@@ -234,9 +237,13 @@ export async function decideParkedAgent(deps, req, match) {
|
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
239
|
const outcome = buildParkedResumeOutcome(req);
|
|
240
|
+
const seatRoot = grantRootOf(row);
|
|
237
241
|
const ctx = {
|
|
238
242
|
toolCallId: `drv-${ticket.claimId}`,
|
|
239
243
|
...(surface.mountsCallerTool !== undefined ? { mountsCallerTool: surface.mountsCallerTool } : {}),
|
|
244
|
+
...(deps.approverSeatFor !== undefined && seatRoot !== undefined
|
|
245
|
+
? { onAsk: deps.approverSeatFor({ owner: decodeCheckpointScope(req.scope) ?? null, taskId: match.handle, sessionId: seatRoot }) }
|
|
246
|
+
: {}),
|
|
240
247
|
reviveClaim: {
|
|
241
248
|
row: { ...ticket.row, parkClaimId: ticket.claimId },
|
|
242
249
|
rev: ticket.reservedRev,
|