@sema-agent/server 7.118.0 → 7.119.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +85 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +24 -13
- package/deploy/sema-up/README.md +2 -0
- package/deploy/sema-up/chart/templates/minio.yaml +2 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
- package/deploy/sema-up/chart/templates/server.yaml +1 -0
- package/deploy/sema-up/chart/values.yaml +7 -1
- package/deploy/sema-up/cluster-up.sh +20 -8
- package/deploy/sema-up/kube-up.sh +29 -16
- package/deploy/sema-up/lib/buckets.sh +21 -0
- package/deploy/sema-up/lib/health.sh +33 -0
- package/deploy/sema-up/sema-up.sh +14 -9
- package/deploy/sema-up/smoke.sh +16 -6
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
- package/dist/approval-ask-audit-store.js +2 -7
- package/dist/approval-card.d.ts +5 -5
- package/dist/approval.d.ts +51 -5
- package/dist/approval.js +76 -13
- package/dist/audit.d.ts +12 -1
- package/dist/audit.js +62 -2
- package/dist/auto-mode-face.d.ts +2 -3
- package/dist/bake-runner/main.js +1 -6
- package/dist/bake-runner/protocol.d.ts +18 -0
- package/dist/bake-runner/protocol.js +22 -2
- package/dist/bake-runner/runner.d.ts +12 -8
- package/dist/bake-runner/runner.js +5 -9
- package/dist/boot/config-center.js +1 -1
- package/dist/boot/coordinators.js +4 -1
- package/dist/boot/device-lane.d.ts +3 -3
- package/dist/boot/device-lane.js +1 -2
- package/dist/boot/leader.d.ts +4 -4
- package/dist/boot/leader.js +1 -2
- package/dist/boot/parked-revive-gate.d.ts +5 -1
- package/dist/boot/parked-revive-gate.js +8 -2
- package/dist/boot/resolve-spec.js +7 -4
- package/dist/boot/stage-07-capability-layer.js +5 -1
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.js +12 -7
- package/dist/capabilities/center-plugins.d.ts +2 -0
- package/dist/capabilities/center-plugins.js +24 -6
- package/dist/capabilities/code-review-council.js +8 -11
- package/dist/capabilities/scenarios.d.ts +34 -7
- package/dist/capabilities/scenarios.js +33 -6
- package/dist/config-catalog.js +3 -3
- package/dist/config.js +2 -2
- package/dist/deployment-governance.d.ts +5 -2
- package/dist/deployment-governance.js +4 -3
- package/dist/host-decision.d.ts +52 -27
- package/dist/host-decision.js +14 -7
- package/dist/http/admission.js +6 -6
- package/dist/http/resume-legs.js +2 -1
- package/dist/http/routes/approvals-assistant.d.ts +25 -3
- package/dist/http/routes/approvals-assistant.js +9 -5
- package/dist/http/routes/capabilities.js +4 -3
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/runs.js +11 -5
- package/dist/http/routes/sessions.js +20 -16
- package/dist/http/wire-types.d.ts +12 -13
- package/dist/images/manifest.d.ts +7 -1
- package/dist/images/manifest.js +18 -16
- package/dist/memory-store-refusal.d.ts +7 -12
- package/dist/memory-store-refusal.js +4 -5
- package/dist/namespaced-rule-name.d.ts +4 -3
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/operator-ask.d.ts +6 -5
- package/dist/plugins/approval-exemption-store.d.ts +4 -0
- package/dist/plugins/approval-exemption-store.js +1 -0
- package/dist/plugins/blob-backend.d.ts +13 -0
- package/dist/plugins/blob-backend.js +26 -0
- package/dist/plugins/local-session-store.d.ts +78 -47
- package/dist/plugins/local-session-store.js +152 -122
- package/dist/plugins/one-time-migrations.d.ts +6 -0
- package/dist/plugins/one-time-migrations.js +19 -1
- package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
- package/dist/plugins/snapshot-bucket-presence.js +40 -0
- package/dist/plugins/store-backend.d.ts +17 -6
- package/dist/plugins/store-backend.js +46 -16
- package/dist/plugins/store-open-wait.d.ts +33 -0
- package/dist/plugins/store-open-wait.js +73 -0
- package/dist/rule-entry-list.js +1 -1
- package/dist/run-local.js +4 -3
- package/dist/security.d.ts +5 -3
- package/dist/security.js +4 -0
- package/dist/session-policy-wire.js +7 -12
- package/dist/spec-fields.d.ts +6 -2
- package/dist/store-posture.d.ts +14 -6
- package/dist/store-posture.js +7 -4
- package/dist/task-settings.d.ts +27 -55
- package/dist/task-settings.js +6 -18
- package/dist/tool-approval.d.ts +97 -43
- package/dist/tool-approval.js +120 -31
- package/dist/trace/redact.d.ts +6 -0
- package/dist/trace/redact.js +9 -9
- package/dist/trace/self-minted-wire.d.ts +1 -1
- package/dist/user-input-ledger.d.ts +19 -15
- package/dist/user-input-ledger.js +12 -20
- package/package.json +2 -2
|
@@ -33,6 +33,7 @@ export function synthDoneFrame(lineOrd, kind, detail) {
|
|
|
33
33
|
}
|
|
34
34
|
return { event: "done", lineOrd, status: "FAILED", exitCode: 124, error: detail?.error ?? "bake hard-deadline exceeded (killed)" };
|
|
35
35
|
}
|
|
36
|
+
export const STDERR_TAIL_LINES = 12;
|
|
36
37
|
export function exitDoneFrame(lineOrd, exitCode, stderrTail) {
|
|
37
38
|
const tail = stderrTail.trim();
|
|
38
39
|
return {
|
|
@@ -40,13 +41,32 @@ export function exitDoneFrame(lineOrd, exitCode, stderrTail) {
|
|
|
40
41
|
lineOrd,
|
|
41
42
|
status: "FAILED",
|
|
42
43
|
exitCode,
|
|
43
|
-
error: tail.length > 0 ? `build.sh exited ${exitCode}: ${lastLines(tail,
|
|
44
|
+
error: tail.length > 0 ? `build.sh exited ${exitCode}: ${lastLines(tail, STDERR_TAIL_LINES)}` : `build.sh exited ${exitCode}`,
|
|
44
45
|
};
|
|
45
46
|
}
|
|
47
|
+
function contentLines(text) {
|
|
48
|
+
return text
|
|
49
|
+
.split(/\r?\n/)
|
|
50
|
+
.map((l) => l.replace(/\r$/, ""))
|
|
51
|
+
.filter((l) => l.length > 0);
|
|
52
|
+
}
|
|
46
53
|
export function lastLines(text, n) {
|
|
47
|
-
const lines = text
|
|
54
|
+
const lines = contentLines(text);
|
|
48
55
|
return lines.slice(Math.max(0, lines.length - n)).join("\n");
|
|
49
56
|
}
|
|
57
|
+
export function createStderrTail() {
|
|
58
|
+
const lines = [];
|
|
59
|
+
return {
|
|
60
|
+
push(raw) {
|
|
61
|
+
for (const l of contentLines(raw)) {
|
|
62
|
+
lines.push(l);
|
|
63
|
+
if (lines.length > STDERR_TAIL_LINES)
|
|
64
|
+
lines.shift();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
text: () => lines.join("\n"),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
50
70
|
export function deadlineMsForProfile(profile, bands) {
|
|
51
71
|
const HEAVY_MS = 60 * 60 * 1000;
|
|
52
72
|
const NORMAL_MS = 45 * 60 * 1000;
|
|
@@ -11,10 +11,12 @@ export interface ClaimedBake {
|
|
|
11
11
|
profile?: string | null;
|
|
12
12
|
bands?: string[] | null;
|
|
13
13
|
}
|
|
14
|
-
/** The
|
|
15
|
-
|
|
14
|
+
/** The heartbeat response (a no-frame `…/ingest` POST) — image-api echoes whether a cancel was requested so the runner
|
|
15
|
+
* kills the group, and whether the lease is still ours. The heartbeat is the ONE place these two bits are read
|
|
16
|
+
* ({@link BakeRunner.runOne}'s supervisor); a frame ingest's ack has no reader, so `ingest` returns nothing. */
|
|
17
|
+
export interface HeartbeatAck {
|
|
16
18
|
accepted: boolean;
|
|
17
|
-
/** The durable cancel flag (§P2.11) —
|
|
19
|
+
/** The durable cancel flag (§P2.11) — read off each HEARTBEAT response (≤ heartbeat-interval lag). */
|
|
18
20
|
cancelRequested?: boolean;
|
|
19
21
|
/** False when the lease was lost/stolen (the runner aborts + kills the child — it no longer owns the build). */
|
|
20
22
|
leaseValid?: boolean;
|
|
@@ -51,10 +53,11 @@ export interface ChildSpawner {
|
|
|
51
53
|
export interface ImageApiClient {
|
|
52
54
|
/** Long-poll for + claim the next queued bake; null when none is claimable right now. */
|
|
53
55
|
claimNext(): Promise<ClaimedBake | null>;
|
|
54
|
-
/** POST one frame to `…/ingest` (carries the per-bake ingest secret header)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
/** POST one frame to `…/ingest` (carries the per-bake ingest secret header). Resolves with nothing: the runner reads no
|
|
57
|
+
* ack off a frame ingest (the cancel / lease bits are read off {@link ImageApiClient.heartbeat} only). */
|
|
58
|
+
ingest(bakeId: string, ingestSecret: string, frame: Record<string, unknown>): Promise<void>;
|
|
59
|
+
/** Heartbeat the lease forward and surface the cancel / lease state — the only call whose response the runner reads. */
|
|
60
|
+
heartbeat(bakeId: string, ingestSecret: string): Promise<HeartbeatAck>;
|
|
58
61
|
}
|
|
59
62
|
/** Host-side ops the runner needs that are NOT image-api calls (fs + a thin docker-push wrapper for the big-blob
|
|
60
63
|
* retry — NOT a build.sh --replay). All injected so the orchestration is testable off build-host. */
|
|
@@ -208,7 +211,8 @@ export declare class BakeRunner {
|
|
|
208
211
|
* reaper + a mid-build fill guard still protect the host). */
|
|
209
212
|
private freeGbOrNull;
|
|
210
213
|
/** Ingest one frame, swallowing transient errors (the buffered-retry of unsent lines is the real-runner's job;
|
|
211
|
-
* image-api's uq_line makes a re-send a no-op, so a dropped frame is recoverable, never a crash).
|
|
214
|
+
* image-api's uq_line makes a re-send a no-op, so a dropped frame is recoverable, never a crash). Returns nothing:
|
|
215
|
+
* the cancel / lease bits are read off the heartbeat response only, never off a frame ingest's ack. */
|
|
212
216
|
private safeIngest;
|
|
213
217
|
}
|
|
214
218
|
/** Allocate the next per-bake lineOrd (monotonic from 1) — the at-least-once ingest dedupe key (§P2.10). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve as resolvePath } from "node:path";
|
|
2
|
-
import { lineToFrame, dockerBuildTickFrame, synthDoneFrame, exitDoneFrame, deadlineMsForProfile, parseDfFreeGb, parseBuildShLine, manifestPathFromLine, } from "./protocol.js";
|
|
2
|
+
import { lineToFrame, dockerBuildTickFrame, synthDoneFrame, exitDoneFrame, deadlineMsForProfile, parseDfFreeGb, parseBuildShLine, manifestPathFromLine, createStderrTail, } from "./protocol.js";
|
|
3
3
|
const DEFAULT_HEARTBEAT_MS = 30_000;
|
|
4
4
|
const DEFAULT_IDLE_POLL_MS = 5_000;
|
|
5
5
|
const DEFAULT_MIN_FREE_GB = 20;
|
|
@@ -137,7 +137,7 @@ export class BakeRunner {
|
|
|
137
137
|
await this.safeIngest(bake, exitDoneFrame(nextOrd(ord), 1, `recipe checkout failed: ${String(err)}`));
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
-
const stderrTail =
|
|
140
|
+
const stderrTail = createStderrTail();
|
|
141
141
|
let lastManifestPath = null;
|
|
142
142
|
let lastImageTag = null;
|
|
143
143
|
let sawDone = false;
|
|
@@ -166,10 +166,7 @@ export class BakeRunner {
|
|
|
166
166
|
}
|
|
167
167
|
enqueueIngest(lineToFrame(raw, nextOrd(ord)));
|
|
168
168
|
},
|
|
169
|
-
onStderrLine: (raw) =>
|
|
170
|
-
if (stderrTail.length < 80)
|
|
171
|
-
stderrTail.push(raw);
|
|
172
|
-
},
|
|
169
|
+
onStderrLine: (raw) => stderrTail.push(raw),
|
|
173
170
|
});
|
|
174
171
|
inFlight.child = child;
|
|
175
172
|
const deadlineMs = deadlineMsForProfile(bake.profile ?? "custom", bake.bands ?? null);
|
|
@@ -230,7 +227,7 @@ export class BakeRunner {
|
|
|
230
227
|
return;
|
|
231
228
|
}
|
|
232
229
|
const code = exit.code ?? (exit.signal ? 137 : 1);
|
|
233
|
-
await this.safeIngest(bake, exitDoneFrame(nextOrd(ord), code, stderrTail.
|
|
230
|
+
await this.safeIngest(bake, exitDoneFrame(nextOrd(ord), code, stderrTail.text()));
|
|
234
231
|
}
|
|
235
232
|
async forwardDone(bake, ord, doneLine, lastManifestPath, lastImageTag) {
|
|
236
233
|
let line = doneLine;
|
|
@@ -309,11 +306,10 @@ export class BakeRunner {
|
|
|
309
306
|
}
|
|
310
307
|
async safeIngest(bake, frame) {
|
|
311
308
|
try {
|
|
312
|
-
|
|
309
|
+
await this.o.api.ingest(bake.bakeId, bake.ingestSecret, frame);
|
|
313
310
|
}
|
|
314
311
|
catch (err) {
|
|
315
312
|
this.o.log.warn("bake_ingest_error", { bakeId: bake.bakeId, lineOrd: frame.lineOrd, err: String(err) });
|
|
316
|
-
return null;
|
|
317
313
|
}
|
|
318
314
|
}
|
|
319
315
|
}
|
|
@@ -624,7 +624,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
624
624
|
if (effective?.skills && config.configCenter) {
|
|
625
625
|
skills = await applyCenterSkills(skills, effective.skills, config.configCenter, logger, undefined, configLkgEnabled() ? defaultSkillCacheDir() : undefined);
|
|
626
626
|
}
|
|
627
|
-
if (effective?.plugins) {
|
|
627
|
+
if (effective?.plugins !== undefined) {
|
|
628
628
|
const pluginOut = await applyCenterPlugins(skills, effective, {
|
|
629
629
|
cacheRoot: join(config.localDataRoot ?? join(homedir(), ".ai-agent"), "plugin-cache"),
|
|
630
630
|
allowHosts: config.pluginsAllowHosts,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ElicitationCoordinator } from "../elicitation.js";
|
|
2
2
|
import { QuestionCoordinator } from "../question.js";
|
|
3
3
|
import { ToolApprovalCoordinator, resolveStreamApprovalGate } from "../tool-approval.js";
|
|
4
|
+
import { deploymentListAsks } from "../approval.js";
|
|
4
5
|
import { FileApprovalAskAuditStore } from "../approval-ask-audit-store.js";
|
|
5
6
|
import { SendUserFileEmitter, SEND_USER_FILE_MAX_BYTES, sendUserFileTool } from "../capabilities/send-user-file-tool.js";
|
|
6
7
|
import { createSandboxFileSend, TaskEnvRegistry } from "../capabilities/sandbox-file-send.js";
|
|
@@ -58,7 +59,9 @@ export function createLiveCoordinators(ctx) {
|
|
|
58
59
|
unattendedPolicy: config.unattendedApprovalPolicy,
|
|
59
60
|
...(getParkedRedeem !== undefined ? { parkedRedeem: getParkedRedeem } : {}),
|
|
60
61
|
...(approvalAskAudit !== undefined ? { askAudit: approvalAskAudit } : {}),
|
|
61
|
-
...(approvalExemptionStore !== undefined
|
|
62
|
+
...(approvalExemptionStore !== undefined
|
|
63
|
+
? { sessionGrants: approvalExemptionStore, deploymentListAsks: (call) => deploymentListAsks(config, call) }
|
|
64
|
+
: {}),
|
|
62
65
|
...(parkCommits !== undefined ? { parkCommits } : {}),
|
|
63
66
|
})
|
|
64
67
|
: undefined;
|
|
@@ -59,9 +59,9 @@ export declare function createDeviceLane(ctx: DeviceLaneCtx): DeviceLane | undef
|
|
|
59
59
|
* `boot/leader.ts` 的 `assertLeaderDurableStore`)。
|
|
60
60
|
*
|
|
61
61
|
* config 层的门①判的是「运维配没配 SQL 坐标」——那句话回答的是**意图**。它回答不了「这次启动**真的
|
|
62
|
-
* 拿到**了店吗」:`openStoreBackendWithFallback`
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* 拿到**了店吗」:`openStoreBackendWithFallback` 曾在 `SESSION_BACKEND=auto` 下,SQL 连不上 /
|
|
63
|
+
* 建表失败 ⇒ `backend=undefined` + 一条 warn 就继续启动(那条降级臂已退役:配了 DSN 的库窗内重试、到窗拒启,见 `plugins/store-open-wait.ts`;
|
|
64
|
+
* 仍走得到本门的形:`DB_BACKEND=mysql|pg` 声明了 SQL 而 `SESSION_BACKEND=memory`,刻意不持久 ⇒ 不开店)。那次启动里 device lane 的四表整个不在,
|
|
65
65
|
* 而 `boot/execution-env.ts` 的第七臂拿不到三件 ⇒ `executionEnvFactory` 落 `undefined` ⇒ core 拿
|
|
66
66
|
* `StubExecutionEnv`:**一台自称在员工设备上执行的 worker 实际在自己进程里跑,而且回执同形**。
|
|
67
67
|
* 那正是 [ref]「执行车道禁静默降级」点名的样板病,也正是 A-1 那条临时门当初要拦的东西 —— 换执法点,
|
package/dist/boot/device-lane.js
CHANGED
|
@@ -40,8 +40,7 @@ export function assertDeviceLaneWired(isDeviceLane, lane, hasFactory) {
|
|
|
40
40
|
return;
|
|
41
41
|
if (!lane) {
|
|
42
42
|
throw new Error("REMOTE_EXEC=device but the device lane could not be wired at boot [device-lane-unwired]: the SQL backend is " +
|
|
43
|
-
"configured yet this replica did not get one
|
|
44
|
-
"when SESSION_BACKEND=auto), so the device registry / session↔device binding / audit tables are absent. " +
|
|
43
|
+
"configured yet this replica did not get one, so the device registry / session↔device binding / audit tables are absent. " +
|
|
45
44
|
"Refusing to serve a lane that would silently fall back to the in-process stub env — a worker claiming to " +
|
|
46
45
|
"execute on the employee's device while actually running inside its own process (#157). Fix the database " +
|
|
47
46
|
"(or unset REMOTE_EXEC) and restart (device-executor-lane-v2 §4.7)");
|
package/dist/boot/leader.d.ts
CHANGED
|
@@ -64,10 +64,10 @@ export declare function leaderK8sMinioGap(leaderEnabled: boolean, leaderProvider
|
|
|
64
64
|
* [ref] 车4 件2 的**能力面**门(codex 交叉复审 R1-high①,验真后修)。
|
|
65
65
|
*
|
|
66
66
|
* config 层的门②判的是 `DB_BACKEND` 的字面值 —— 那句话回答的是「运维要没要 durable」。它回答不了
|
|
67
|
-
* 「这次启动**真的拿到**了 durable 吗」:`openStoreBackendWithFallback`
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* `leaderRun()` 缺席,leader 端点会静默落进单副本内存臂:多副本各自受理同一个 Idempotency-Key、
|
|
67
|
+
* 「这次启动**真的拿到**了 durable 吗」:`openStoreBackendWithFallback` 曾在 `SESSION_BACKEND=auto`
|
|
68
|
+
* 下,SQL 连不上 / 建表失败 ⇒ 返回 `backend=undefined` + 一条 warn 就继续启动(那条降级臂已退役:配了 DSN 的库窗内重试、到窗拒启,
|
|
69
|
+
* 见 `plugins/store-open-wait.ts`)。仍走得到本门的形:`DB_BACKEND=mysql|pg` 声明了 SQL 而 `SESSION_BACKEND=memory`(声明 pg、刻意不持久 ⇒ 不开店)。
|
|
70
|
+
* 那次启动里 `leaderRun()` 缺席,leader 端点会静默落进单副本内存臂:多副本各自受理同一个 Idempotency-Key、
|
|
71
71
|
* 跨副本 GET 404、重启失忆 —— 而这条腿的产物是**真 git push**。
|
|
72
72
|
*
|
|
73
73
|
* ⇒ 判据锚**真拿到了没有**。纯谓词、只吃两个原语(与 {@link leaderK8sMinioGap} 同姿势),便于单测。
|
package/dist/boot/leader.js
CHANGED
|
@@ -15,8 +15,7 @@ export function assertLeaderDurableStore(leaderEnabled, hasLeaderRunStore) {
|
|
|
15
15
|
if (!leaderEnabled || hasLeaderRunStore)
|
|
16
16
|
return;
|
|
17
17
|
throw new Error("LEADER_ENABLED=true but no durable leader-run store is available at boot — the SQL backend is configured " +
|
|
18
|
-
"yet this replica did not get one
|
|
19
|
-
"SESSION_BACKEND=auto). Refusing to serve the leader endpoint from a single-replica in-memory registry: " +
|
|
18
|
+
"yet this replica did not get one. Refusing to serve the leader endpoint from a single-replica in-memory registry: " +
|
|
20
19
|
"the `leader_run` table is what makes a retried submit idempotent and a cross-replica GET answerable, and " +
|
|
21
20
|
"without it a retry starts a SECOND real git push. Fix the database (or unset LEADER_ENABLED) and restart " +
|
|
22
21
|
"(#193 车4 件2 capability gate; the DB_BACKEND literal check in config.ts is the intent half of the same rule)");
|
|
@@ -3,6 +3,7 @@ import type { ServiceConfig } from "../config.js";
|
|
|
3
3
|
import type { RunnerDepsOnAsk } from "./runner-deps.js";
|
|
4
4
|
import { type ApprovalBaselineConfigView, type DeploymentGovernanceConfigView, type LiveQuestionFace } from "../deployment-governance.js";
|
|
5
5
|
import { type SandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
6
|
+
import type { ApprovalExemptionStore } from "../plugins/approval-exemption-store.js";
|
|
6
7
|
/** 本腿读的配置切面(`ServiceConfig` 结构满足)。两个构造口各自的切面 + lane 判别式读的 `remoteExec`。 */
|
|
7
8
|
export interface ParkedReviveGateConfigView extends DeploymentGovernanceConfigView, ApprovalBaselineConfigView {
|
|
8
9
|
readonly remoteExec?: ServiceConfig["remoteExec"];
|
|
@@ -43,6 +44,8 @@ export interface ParkedReviveGateDeps {
|
|
|
43
44
|
* headless auto-deny,比「再问人一次」严且哑。交了之后最坏也是 park,方向 fail-safe。
|
|
44
45
|
*/
|
|
45
46
|
readonly approverSeat: RunnerDepsOnAsk | undefined;
|
|
47
|
+
/** 会话豁免店(部署名单那一问的「本会话放行」读面;`StoreBackend.approvalExemption()` 结构满足)。缺席 = 部署没有 store backend ⇒ 名单照问。 */
|
|
48
|
+
readonly approvalExemptionStore: Pick<ApprovalExemptionStore, "has"> | undefined;
|
|
46
49
|
/**
|
|
47
50
|
* [ref] —— core `RunnerDeps.runtimeCapsResolver` 的**同一只**(main.ts 的 `runtimeCapsResolver`,
|
|
48
51
|
* 单一属主同 `boot/runtime-caps.ts`)。重建条目的第三位决议链元数据 `autoModeArmed` 的唯一判据是
|
|
@@ -64,6 +67,7 @@ export interface ParkedReviveGateDeps {
|
|
|
64
67
|
* 这一座可改读它 —— 接线不在本次提货单内,见 7.106.0 收车档「未闭环」。 */
|
|
65
68
|
readonly parentAutoModeIntent?: ((row: BackgroundAgentRecord) => boolean) | undefined;
|
|
66
69
|
readonly logger: {
|
|
70
|
+
info(event: string, fields?: Record<string, unknown>): void;
|
|
67
71
|
warn(event: string, fields?: Record<string, unknown>): void;
|
|
68
72
|
};
|
|
69
73
|
/** host 腿 cwd 占位的落地根(main.ts 的 `localRoot`;`config.localDataRoot` 优先)。 */
|
|
@@ -90,7 +94,7 @@ export type RebuiltParentConstraint = NonNullable<RebuiltInheritedGate["parentCo
|
|
|
90
94
|
* 赎回腿的父约束链重建([ref] 件二)。
|
|
91
95
|
*
|
|
92
96
|
* 语义:**同/跨副本一致地重建「部署 ⊇ 操作员」两层完整链**——审批基线(durable question 门 + F4 高危写
|
|
93
|
-
* 审批门)作 base 的 `toolPolicy` 座,经**折叠属主** `applyRuntimeGovernance`
|
|
97
|
+
* 审批门 + 部署名单那一问的豁免读面)作 base 的 `toolPolicy` 座,经**折叠属主** `applyRuntimeGovernance`
|
|
94
98
|
* (→ core `tightenTaskSpec`)叠上部署治理段(autonomy / commandPolicy / MANUAL_MODE_SHELL_GATE /
|
|
95
99
|
* 守卫集),取其 `toolPolicy` 装进单层 `parentConstraints`。
|
|
96
100
|
*
|
|
@@ -6,11 +6,12 @@ import { createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from
|
|
|
6
6
|
import { applyRuntimeGovernance } from "../runtime-governance.js";
|
|
7
7
|
import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
8
8
|
import { executionLaneHomeDir } from "../execution-lane-caps.js";
|
|
9
|
+
import { createDeploymentListExemption } from "../approval.js";
|
|
9
10
|
function hostGateCwd(config, localRoot) {
|
|
10
11
|
return join(config.localDataRoot ?? localRoot, "parked-revive-unrooted");
|
|
11
12
|
}
|
|
12
13
|
export function createParkedReviveInheritedGate(deps) {
|
|
13
|
-
const { config, question, logger, localRoot, approverSeat, memoryEngineRoot } = deps;
|
|
14
|
+
const { config, question, approvalExemptionStore, logger, localRoot, approverSeat, memoryEngineRoot } = deps;
|
|
14
15
|
const slots = deps.slots ?? sandboxPathEnvSlots;
|
|
15
16
|
return async (row, identity) => {
|
|
16
17
|
const metadata = await chainMetadataOf(deps, row, identity);
|
|
@@ -26,7 +27,12 @@ export function createParkedReviveInheritedGate(deps) {
|
|
|
26
27
|
...homeBase,
|
|
27
28
|
}
|
|
28
29
|
: { env: createHostAdjudicationEnv(config, localRoot), ...homeBase };
|
|
29
|
-
const
|
|
30
|
+
const rootSessionId = row.rootSessionId;
|
|
31
|
+
const baseline = createApprovalBaselinePolicy(config, {
|
|
32
|
+
kind: "durable",
|
|
33
|
+
question,
|
|
34
|
+
...(approvalExemptionStore !== undefined && rootSessionId ? { exemption: createDeploymentListExemption(approvalExemptionStore, rootSessionId, logger, "parked_revive") } : {}),
|
|
35
|
+
});
|
|
30
36
|
const governed = applyRuntimeGovernance({ objective: "", toolPolicy: baseline }, createDeploymentGovernanceInputs(config, pathAdjudication, { engineDataRoot: memoryEngineRoot }));
|
|
31
37
|
return {
|
|
32
38
|
parentConstraints: [
|
|
@@ -10,7 +10,7 @@ import { stampHandsLane, toolPolicyForHands } from "../capabilities/hands-lane.j
|
|
|
10
10
|
import { buildNoticeLaneFacts, shouldDiscloseNoPersistentMemory, withNoPersistentMemoryNotice } from "../capabilities/memory-notice.js";
|
|
11
11
|
import { applyLongtailDefer } from "../capabilities/tool-defer.js";
|
|
12
12
|
import { assertGuardPatternsUsable, buildApprovalGrantNotice, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs } from "../deployment-governance.js";
|
|
13
|
-
import { gateIntentUnserviceableReason, singleUserAutoAcceptBaselineApplies } from "../approval.js";
|
|
13
|
+
import { createDeploymentListExemption, gateIntentUnserviceableReason, singleUserAutoAcceptBaselineApplies } from "../approval.js";
|
|
14
14
|
import { acceptShellScratchpadDir, buildEnvFacts, egressForRemoteExec, ensureScratchpadDir, resumeFactsForLane, scratchpadCoveringTaskRoot } from "../env-facts.js";
|
|
15
15
|
import { FleetEventBus } from "../fleet/fleet-bus.js";
|
|
16
16
|
import { composeHooks, createTaskHooks } from "../hooks/hook-runner.js";
|
|
@@ -137,9 +137,8 @@ export function createResolveSpec(ctx) {
|
|
|
137
137
|
return stamped;
|
|
138
138
|
};
|
|
139
139
|
const gateScenarioAndAppend = async (body, auth, opts) => {
|
|
140
|
-
const
|
|
140
|
+
const scenarioName = gateScenarioRequest(await principalCaps?.scenarioRuling(auth?.principal), body.scenario, config.defaultScenario, Object.keys(scenarios), logger, auth?.principal);
|
|
141
141
|
gateExecutionLane(await principalCaps?.executionRuling(auth?.principal), executionLaneOf(config.remoteExec?.provider), logger);
|
|
142
|
-
const scenarioName = scenarios[requested] ? requested : "default";
|
|
143
142
|
const cap = selectScenario(scenarios, scenarioName)(body, auth?.principal, auth?.sessionId !== undefined ? { sessionId: auth.sessionId } : {});
|
|
144
143
|
const centerDecls = getCenterPrompts()?.declarations;
|
|
145
144
|
const appendLessPack = centerDecls
|
|
@@ -460,7 +459,11 @@ export function createResolveSpec(ctx) {
|
|
|
460
459
|
a2a: resolveRequestA2a(a2aForScenario(config.a2aPeers, scenarioName), body.a2aPeers, config, logger),
|
|
461
460
|
promptProvider: centerDecls ? centerPromptProvider(centerDecls, scenarioName) : cap.promptProvider,
|
|
462
461
|
toolPolicy: toolPolicyForHands(hands, durableEnabled
|
|
463
|
-
? createApprovalBaselinePolicy(config, {
|
|
462
|
+
? createApprovalBaselinePolicy(config, {
|
|
463
|
+
kind: "durable",
|
|
464
|
+
question: liveQuestionFace,
|
|
465
|
+
...(approvalExemptionStore !== undefined && auth?.sessionId ? { exemption: createDeploymentListExemption(approvalExemptionStore, auth.sessionId, logger) } : {}),
|
|
466
|
+
})
|
|
464
467
|
: singleUserAutoAcceptBaselineApplies(config)
|
|
465
468
|
? createApprovalBaselinePolicy(config, { kind: "absent" })
|
|
466
469
|
: undefined),
|
|
@@ -10,7 +10,7 @@ import { buildForceDurableGateInertNotice, buildNoResponderBootWarns, gateIntent
|
|
|
10
10
|
import { executionLaneOf } from "../execution-lane-caps.js";
|
|
11
11
|
import { loadDeploymentSkills } from "../capabilities/skills.js";
|
|
12
12
|
import { createRepoClient } from "../capabilities/repo-tools.js";
|
|
13
|
-
import { buildScenarios, builtinScenarioDetails } from "../capabilities/scenarios.js";
|
|
13
|
+
import { buildScenarios, builtinScenarioDetails, danglingScenarioRefusal } from "../capabilities/scenarios.js";
|
|
14
14
|
import { RateLimiter } from "../observability/rate-limit.js";
|
|
15
15
|
import { explicitOperatorOk } from "../http/server.js";
|
|
16
16
|
import { selfOrchestrationDenial } from "../task-workflow.js";
|
|
@@ -169,6 +169,7 @@ export async function runStage07(ctx) {
|
|
|
169
169
|
config, question, logger, localRoot,
|
|
170
170
|
memoryEngineRoot: memoryEngine?.root,
|
|
171
171
|
approverSeat: createRunnerDepsOnAsk(toolApproval),
|
|
172
|
+
approvalExemptionStore,
|
|
172
173
|
...(runnerDeps.runtimeCapsResolver ? { resolveRuntimeCaps: runnerDeps.runtimeCapsResolver } : {}),
|
|
173
174
|
autoModeSeatMounted: runnerDeps.autoMode !== undefined,
|
|
174
175
|
})
|
|
@@ -176,6 +177,9 @@ export async function runStage07(ctx) {
|
|
|
176
177
|
const scenarioDetails = builtinScenarioDetails(scenarios, scenarioDeps);
|
|
177
178
|
const builtinScenarioNames = Object.keys(scenarioDetails);
|
|
178
179
|
configCenter.applyCenterScenarios({ scenarioDeps, scenarios, scenarioDetails, builtinScenarioNames });
|
|
180
|
+
const defaultScenarioRefusal = danglingScenarioRefusal(config.defaultScenario, "deployment", Object.keys(scenarios));
|
|
181
|
+
if (defaultScenarioRefusal !== undefined)
|
|
182
|
+
throw new Error(defaultScenarioRefusal);
|
|
179
183
|
logger.info("capabilities", { skills: skills.length, scenarios: Object.keys(scenarios), repoTools: Boolean(repoClient) });
|
|
180
184
|
return { ...ctx, approvalExemptionStore, approvalNonceStore, authorize, imageBakes, imageIndex, parkedReviveInheritedGate, parkedReviveSurface, rateLimiter, resumeAnchorStore, runStore, scenarioDetails, scenarios, selectEnvTool, taskLimitCaps, taskTimeoutSec, userInputLedger, userInputLedgerStore, webSearchProvider: webSearchCfg?.provider };
|
|
181
185
|
}
|
|
@@ -12,6 +12,7 @@ import { backgroundAgentOutput, taskHandleOutput, taskHandleStop } from "../runs
|
|
|
12
12
|
import { SQL_BLOB_DEFAULT_MAX_BYTES } from "../plugins/blob-backend.js";
|
|
13
13
|
import { createStoreLiveProbe } from "../store-live-probe.js";
|
|
14
14
|
import { counterStoreLabel } from "../plugins/store-backend.js";
|
|
15
|
+
import { sessionStoreLabelOf } from "../store-posture.js";
|
|
15
16
|
import { startFleetClientFromEnv } from "../fleet-client.js";
|
|
16
17
|
import { createCenterWiringReader } from "../capabilities/center-wiring.js";
|
|
17
18
|
import { readinessOf } from "../http/readiness.js";
|
|
@@ -133,7 +134,7 @@ export async function runStage10(ctx) {
|
|
|
133
134
|
? createStoreLiveProbe({ probe: () => backend.dbNowMs(), intervalMs: config.storeProbeIntervalMs, logger })
|
|
134
135
|
: undefined;
|
|
135
136
|
const deployment = {
|
|
136
|
-
sessionStoreLabel: config.sessionBackend
|
|
137
|
+
sessionStoreLabel: sessionStoreLabelOf(config.sessionBackend, backend?.kind),
|
|
137
138
|
modelReady: () => configCenter.modelReady(),
|
|
138
139
|
scenarioDetails,
|
|
139
140
|
workflowsCapable,
|
|
@@ -269,7 +270,7 @@ export async function runStage10(ctx) {
|
|
|
269
270
|
model: config.model.id,
|
|
270
271
|
brain: brainSummary(config),
|
|
271
272
|
breakerState: config.resilience.circuitBreaker ? (breakerState ? `shared(${backend.kind})` : "in-process") : "off",
|
|
272
|
-
session: config.sessionBackend
|
|
273
|
+
session: sessionStoreLabelOf(config.sessionBackend, backend?.kind),
|
|
273
274
|
memory: memoryEngine ? `engine(${memoryEngine.root})` : "off",
|
|
274
275
|
instanceId,
|
|
275
276
|
rateLimitPerMin: config.rateLimitPerMin,
|
package/dist/boot/stores.js
CHANGED
|
@@ -9,6 +9,7 @@ import { TiDBMemoryEngineBackend, ensureTiDBMemoryEngineSchema } from "../plugin
|
|
|
9
9
|
import { PgMemoryHistoryStore, PgMemorySyncStore, ensurePgMemoryHistorySchema, ensurePgMemorySyncSchema } from "../plugins/memory-sync-store-pg.js";
|
|
10
10
|
import { TiDBMemoryHistoryStore, TiDBMemorySyncStore, ensureTiDBMemoryHistorySchema, ensureTiDBMemorySyncSchema } from "../plugins/memory-sync-store-tidb.js";
|
|
11
11
|
import { MinioBlobBackend } from "../plugins/blob-backend.js";
|
|
12
|
+
import { ensureSnapshotBucket } from "../plugins/snapshot-bucket-presence.js";
|
|
12
13
|
import { PgBackgroundAgentStore, TiDBBackgroundAgentStore, ensurePgBackgroundAgentSchema, ensureTiDBBackgroundAgentSchema } from "../plugins/background-agent-store-sql.js";
|
|
13
14
|
import { PgMailboxStore, TiDBMailboxStore, ensurePgMailboxSchema, ensureTiDBMailboxSchema } from "../plugins/mailbox-store-sql.js";
|
|
14
15
|
import { createDropOnlyRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
|
|
@@ -48,9 +49,12 @@ export async function openStores(ctx) {
|
|
|
48
49
|
const { config, logger, metrics, localRoot } = ctx;
|
|
49
50
|
let backend;
|
|
50
51
|
let storeDegradedReason;
|
|
52
|
+
const dsnConfigured = !!(config.tidb || config.pg);
|
|
53
|
+
if (config.snapshotBlobStore)
|
|
54
|
+
await ensureSnapshotBucket(config.snapshotBlobStore, config.storeLockWaitMs, logger);
|
|
51
55
|
const wantDb = config.sessionBackend === "tidb" ||
|
|
52
56
|
config.dbBackend === "local" ||
|
|
53
|
-
(config.sessionBackend === "auto" &&
|
|
57
|
+
(config.sessionBackend === "auto" && dsnConfigured);
|
|
54
58
|
if (wantDb) {
|
|
55
59
|
const opened = await openStoreBackendWithFallback(config, logger);
|
|
56
60
|
backend = opened.backend;
|
|
@@ -111,7 +115,7 @@ export async function openStores(ctx) {
|
|
|
111
115
|
metrics.setGauge("snapshot_blob_backend", 1, { backend: config.snapshotBlobStore ? "minio" : "sql" });
|
|
112
116
|
}
|
|
113
117
|
if (config.sessionBackend === "auto")
|
|
114
|
-
config.sessionBackend =
|
|
118
|
+
config.sessionBackend = dsnConfigured ? "tidb" : "memory";
|
|
115
119
|
if (config.dbBackend === "local" && backend)
|
|
116
120
|
config.sessionBackend = "tidb";
|
|
117
121
|
let memoryEngine;
|
|
@@ -414,11 +418,12 @@ export async function openStores(ctx) {
|
|
|
414
418
|
}
|
|
415
419
|
const sessionStore = createSessionStore(config, backend, metrics);
|
|
416
420
|
if (config.requirePrincipal && backend?.kind === "local") {
|
|
417
|
-
throw new Error("REQUIRE_PRINCIPAL=true is not supported on the local file backend (DB_BACKEND=local):
|
|
418
|
-
"
|
|
419
|
-
"
|
|
420
|
-
"(DB_BACKEND=mysql|pg) for multi-tenant, or run local
|
|
421
|
-
"still inject x-agent-principal per request for memory
|
|
421
|
+
throw new Error("REQUIRE_PRINCIPAL=true is not supported on the local file backend (DB_BACKEND=local): the lane is single-user " +
|
|
422
|
+
"by construction — a session created before owners were recorded is claimed by the FIRST principal that submits " +
|
|
423
|
+
"to it during the upgrade window, and the lane's mutual-recognition arms assume one user — so it cannot enforce " +
|
|
424
|
+
"a multi-tenant session boundary. Use a SQL backend (DB_BACKEND=mysql|pg) for multi-tenant, or run local " +
|
|
425
|
+
"single-user with REQUIRE_PRINCIPAL=false (a BFF may still inject x-agent-principal per request for memory " +
|
|
426
|
+
"scoping and audit attribution).");
|
|
422
427
|
}
|
|
423
428
|
if (backend)
|
|
424
429
|
assertCloudSnapshotBlobPosture(backend.kind, config);
|
|
@@ -97,6 +97,8 @@ export declare function materializablePlugins(eff: {
|
|
|
97
97
|
url: string;
|
|
98
98
|
sha?: string;
|
|
99
99
|
}>;
|
|
100
|
+
/** 总入口(条款 3/7/8):逐条物化 + 装载 + 让位合并。materialize 盘位 = `<cacheRoot>/<name>/live`
|
|
101
|
+
* (成功后原子 rename 替换,失败保留旧盘位 = LKG)。 */
|
|
100
102
|
export declare function applyCenterPlugins(baseline: LoadedSkill[], eff: {
|
|
101
103
|
plugins?: unknown;
|
|
102
104
|
}, opts: {
|
|
@@ -108,21 +108,39 @@ export function resolvePluginSkillsRoot(cloneRoot) {
|
|
|
108
108
|
}
|
|
109
109
|
return { ok: true, dir: candidate };
|
|
110
110
|
}
|
|
111
|
+
const PLUGINS_DOMAIN_FAILURE_NAME = "(plugins domain)";
|
|
111
112
|
function declaredPlugins(eff) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
const domain = eff.plugins;
|
|
114
|
+
if (domain === undefined)
|
|
115
|
+
return { ok: true, decls: [] };
|
|
116
|
+
if (Array.isArray(domain))
|
|
117
|
+
return { ok: true, decls: domain };
|
|
118
|
+
if (typeof domain === "object" && domain !== null) {
|
|
119
|
+
const inner = domain.plugins;
|
|
120
|
+
if (Array.isArray(inner))
|
|
121
|
+
return { ok: true, decls: inner };
|
|
122
|
+
return { ok: false, reason: `plugins domain shape rejected: expected an array or { plugins: [...] }, got an object whose plugins member is ${wireTypeWord(inner)}` };
|
|
123
|
+
}
|
|
124
|
+
return { ok: false, reason: `plugins domain shape rejected: expected an array or { plugins: [...] }, got ${wireTypeWord(domain)}` };
|
|
116
125
|
}
|
|
117
126
|
export function materializablePlugins(eff, allowHosts) {
|
|
118
|
-
|
|
127
|
+
const shape = declaredPlugins(eff);
|
|
128
|
+
if (!shape.ok)
|
|
129
|
+
return [];
|
|
130
|
+
return shape.decls.flatMap((decl) => {
|
|
119
131
|
const v = validatePluginDecl(decl, allowHosts);
|
|
120
132
|
return v.ok ? [{ name: v.name, url: v.url, ...(v.sha !== undefined ? { sha: v.sha } : {}) }] : [];
|
|
121
133
|
});
|
|
122
134
|
}
|
|
123
135
|
export async function applyCenterPlugins(baseline, eff, opts) {
|
|
124
|
-
const
|
|
136
|
+
const shape = declaredPlugins(eff);
|
|
125
137
|
const out = { skills: [...baseline], failures: [], letGo: [], lkgUsed: [] };
|
|
138
|
+
if (!shape.ok) {
|
|
139
|
+
opts.logger?.warn("center_plugins_domain_rejected", { reason: shape.reason });
|
|
140
|
+
out.failures.push({ plugin: PLUGINS_DOMAIN_FAILURE_NAME, reason: shape.reason });
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
const declared = shape.decls;
|
|
126
144
|
if (declared.length === 0)
|
|
127
145
|
return out;
|
|
128
146
|
const runGit = opts.runGit ?? (async (args) => {
|
|
@@ -35,8 +35,10 @@ export function createCouncilTool(subRunner, repoTools, objective, opts = {}) {
|
|
|
35
35
|
parameters: Type.Object({}),
|
|
36
36
|
execute: async (_args, ctx) => {
|
|
37
37
|
const active = pickLenses(opts.lensCount);
|
|
38
|
-
const
|
|
39
|
-
const
|
|
38
|
+
const outcomes = await runLensReviews(subRunner, repoTools, objective, ctx.signal, active);
|
|
39
|
+
const lenses = outcomes.filter((o) => o.ok);
|
|
40
|
+
const failedLenses = outcomes.flatMap((o) => (o.ok ? [] : [{ lens: o.lens, reason: o.reason }]));
|
|
41
|
+
const findings = outcomes.map((o) => `### ${o.lens} reviewer\n${o.ok ? o.text : `(lens errored: ${o.reason})`}`).join("\n\n");
|
|
40
42
|
const converge = await runConvergence(subRunner, repoTools, objective, findings, opts, ctx.signal, active);
|
|
41
43
|
const totalTokens = lenses.reduce((s, l) => s + l.tokens, 0) + converge.tokens;
|
|
42
44
|
const totalTurns = lenses.reduce((s, l) => s + l.turns, 0) + converge.turns;
|
|
@@ -46,10 +48,10 @@ export function createCouncilTool(subRunner, repoTools, objective, opts = {}) {
|
|
|
46
48
|
opts.metrics?.inc("council_runs_total", { mode });
|
|
47
49
|
opts.metrics?.inc("task_tokens_total", {}, totalTokens);
|
|
48
50
|
opts.metrics?.observe("council_tokens", totalTokens);
|
|
49
|
-
opts.logger?.info("council_done", { mode, lenses: lenses.length, totalTokens, totalTurns });
|
|
51
|
+
opts.logger?.info("council_done", { mode, lenses: lenses.length, failedLenses, totalTokens, totalTurns });
|
|
50
52
|
return {
|
|
51
53
|
content: converge.review,
|
|
52
|
-
details: { council: { mode, totalTokens, totalTurns, perLens: lenses.map((l) => ({ lens: l.lens, tokens: l.tokens })) } },
|
|
54
|
+
details: { council: { mode, totalTokens, totalTurns, perLens: lenses.map((l) => ({ lens: l.lens, tokens: l.tokens })), failedLenses } },
|
|
53
55
|
};
|
|
54
56
|
},
|
|
55
57
|
};
|
|
@@ -65,16 +67,11 @@ async function runLensReviews(subRunner, repoTools, objective, signal, lenses) {
|
|
|
65
67
|
enableBlockedReport: false,
|
|
66
68
|
signal,
|
|
67
69
|
});
|
|
68
|
-
return { lens: lens.name, text: r.result?.trim() || "(no findings)", tokens: r.stats.tokens, turns: r.stats.turns };
|
|
70
|
+
return { ok: true, lens: lens.name, text: r.result?.trim() || "(no findings)", tokens: r.stats.tokens, turns: r.stats.turns };
|
|
69
71
|
}));
|
|
70
72
|
return settled.map((s, i) => s.status === "fulfilled"
|
|
71
73
|
? s.value
|
|
72
|
-
: {
|
|
73
|
-
lens: lenses[i].name,
|
|
74
|
-
text: `(lens errored: ${s.reason instanceof Error ? s.reason.message : String(s.reason)})`,
|
|
75
|
-
tokens: 0,
|
|
76
|
-
turns: 0,
|
|
77
|
-
});
|
|
74
|
+
: { ok: false, lens: lenses[i].name, reason: s.reason instanceof Error ? s.reason.message : String(s.reason) });
|
|
78
75
|
}
|
|
79
76
|
async function runConvergence(subRunner, repoTools, objective, findings, opts, signal, lenses) {
|
|
80
77
|
if (opts.debate) {
|
|
@@ -174,7 +174,12 @@ export declare function semaIdentityProvider(): PromptProvider;
|
|
|
174
174
|
*/
|
|
175
175
|
export declare function codePromptProvider(brand: boolean): PromptProvider;
|
|
176
176
|
export declare function buildScenarios(deps: ScenarioDeps): Record<string, Scenario>;
|
|
177
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* Resolve the scenario bundle for a name. `undefined` = no preference ⇒ the built-in `default`; any OTHER name must be
|
|
179
|
+
* registered — an unregistered one is a caller bug and THROWS (it never falls back to `default`: a restricted principal's
|
|
180
|
+
* bundle must not silently widen). Request paths get their name from {@link gateScenarioRequest}, whose result is always
|
|
181
|
+
* registered, so the throw is unreachable from them; it is the loud arm for a direct caller that skipped the gate.
|
|
182
|
+
*/
|
|
178
183
|
export declare function selectScenario(scenarios: Record<string, Scenario>, name: string | undefined): Scenario;
|
|
179
184
|
/** web 渲染契约(定稿形状;knobs 现在没有=整字段省略)。 */
|
|
180
185
|
export interface ScenarioDetail {
|
|
@@ -259,16 +264,38 @@ export interface ScenarioRuling {
|
|
|
259
264
|
/** 纯场景名集合(principal/组名不上 wire);空 = 该 principal 无任何指派 = 无治理。 */
|
|
260
265
|
allowlist: string[];
|
|
261
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* 一个场景名的**来源**(闭集:新增一词 ⇒ {@link danglingScenarioRefusal} 的穷尽 switch 编译红)。
|
|
269
|
+
* · `ruling` —— 中心按主体指派的场景(`ScenarioRuling.scenario`);
|
|
270
|
+
* · `deployment` —— 部署自己写的缺省(`DEFAULT_SCENARIO` / config `defaultScenario`)。
|
|
271
|
+
* 请求体显式给的名字不在此列:那是调用方的话,不在册 ⇒ 400 `scenario_unknown`(见 {@link gateScenarioRequest})。
|
|
272
|
+
*/
|
|
273
|
+
export type ScenarioNameSource = "ruling" | "deployment";
|
|
274
|
+
/**
|
|
275
|
+
* 🔴 **悬空场景名的判据与拒句 —— 全仓唯一属主。**
|
|
276
|
+
*
|
|
277
|
+
* 指派 / 部署缺省指向不在册的名字,此前一路被折成 `default`(工具 / 提示 / skills / mcp 整包跟着换,零 warn):受限场景的主体被
|
|
278
|
+
* 悄悄放进更宽的 default。本函数把那条回退链换成两条出路,**没有第三条**:
|
|
279
|
+
* · `deployment` 悬空 ⇒ 起服拒启(`boot/stage-07-capability-layer.ts` 在 center 场景 overlay 之后调它、`throw new Error`);
|
|
280
|
+
* · `ruling` 悬空 ⇒ 该 run 503 `state.scenario_assignment_unknown`({@link gateScenarioRequest} 调它);
|
|
281
|
+
* · `deployment` 悬空到得了 run(只有自己装配 `resolveSpec`、没经过起服的嵌入方)⇒ 同码同判据同一句话 —— 起服拒启与这一条是同一个答案。
|
|
282
|
+
*
|
|
283
|
+
* 返回 `undefined` = 在册;否则是**拒句**(点名名字与来源、给在册词表、说怎么修)。判据按**自有键**(`known` 是场景表的
|
|
284
|
+
* `Object.keys`,`constructor` / `__proto__` 不是场景名)。
|
|
285
|
+
*/
|
|
286
|
+
export declare function danglingScenarioRefusal(name: string, source: ScenarioNameSource, known: readonly string[]): string | undefined;
|
|
262
287
|
/**
|
|
263
288
|
* resolveSpec 的执法入口(指派为默认、body.scenario 可换但限 allowlist)。返回本请求的
|
|
264
|
-
*
|
|
289
|
+
* **在册**生效场景名 —— 返回值一定在 `known` 里:下游直接按它取场景,没有「未知名 → default」的回退臂。
|
|
265
290
|
*
|
|
266
|
-
* - body.scenario
|
|
267
|
-
* ——机器码归 wire、人话归壳/web);集内 ⇒ 用户主动切换赢。allowlist 为空
|
|
268
|
-
* 三层皆无指派 / caps 面故障)⇒ 无治理约束,现状放行(与「null 落兜底链」同一语义:分配面未覆盖 =
|
|
291
|
+
* - body.scenario 显式给出:不在册 ⇒ typed 400 `scenario_unknown`(调用方的错);allowlist 非空且集外 ⇒ typed 拒绝
|
|
292
|
+
* (400 `scenario_not_allowed`,附 allowlist ——机器码归 wire、人话归壳/web);集内 ⇒ 用户主动切换赢。allowlist 为空
|
|
293
|
+
* (无 ruling / 该 principal 三层皆无指派 / caps 面故障)⇒ 无治理约束,现状放行(与「null 落兜底链」同一语义:分配面未覆盖 =
|
|
269
294
|
* 不收紧,恒不比没有治理时更宽;caps 故障不把用户锁死在 default——场景切换不是权限放大面,fail-open
|
|
270
295
|
* 到现状,与 workflows 面的 fail-closed 是刻意的 posture 差异,见 runtime-caps-resolver)。
|
|
271
|
-
* - body.scenario 未给:ruling 胜者 ⇒ 指派为默认;无胜者 ⇒ defaultScenario
|
|
296
|
+
* - body.scenario 未给:ruling 胜者 ⇒ 指派为默认;无胜者 ⇒ defaultScenario 现状链。链上的名字不在册 ⇒ 503
|
|
297
|
+
* `state.scenario_assignment_unknown`(部署的错 / 中心的错,不是调用方的错),**不**折 default
|
|
298
|
+
* ({@link danglingScenarioRefusal})。`principal` 只进那一行运维日志,不参与判定。
|
|
272
299
|
*/
|
|
273
|
-
export declare function gateScenarioRequest(ruling: ScenarioRuling | undefined, bodyScenario: unknown, defaultScenario: string, known: readonly string[], logger?: Logger): string;
|
|
300
|
+
export declare function gateScenarioRequest(ruling: ScenarioRuling | undefined, bodyScenario: unknown, defaultScenario: string, known: readonly string[], logger?: Logger, principal?: string): string;
|
|
274
301
|
//# sourceMappingURL=scenarios.d.ts.map
|