@sema-agent/server 7.68.0 → 7.70.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/README.md +3 -0
- package/README.zh-CN.md +3 -0
- package/USAGE.md +122 -9
- package/dist/adoption/plan.js +11 -0
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-card.d.ts +27 -0
- package/dist/approval-card.js +11 -0
- package/dist/approval-reconciler.d.ts +0 -54
- package/dist/approval-reconciler.js +2 -2
- package/dist/approval.js +5 -4
- package/dist/boot/config-center.d.ts +6 -7
- package/dist/boot/config-center.js +31 -8
- package/dist/boot/leader.js +1 -1
- package/dist/boot/parked-revive-gate.js +10 -9
- package/dist/boot/runner-deps.d.ts +35 -4
- package/dist/boot/runner-deps.js +2 -6
- package/dist/boot/workflow-orchestration.d.ts +2 -0
- package/dist/boot/workflow-orchestration.js +10 -2
- package/dist/budget.js +3 -2
- package/dist/config-catalog.d.ts +31 -2
- package/dist/config-catalog.js +61 -24
- package/dist/config-center/apply-effective.d.ts +8 -1
- package/dist/config-center/apply-effective.js +23 -9
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/read-face.d.ts +30 -1
- package/dist/config-center/read-face.js +33 -15
- package/dist/config-center/restart-signal.js +2 -1
- package/dist/config-center/types.d.ts +20 -5
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +17 -2
- package/dist/config.d.ts +0 -21
- package/dist/config.js +19 -6
- package/dist/http/route-ctx.d.ts +39 -1
- package/dist/http/routes/approvals-assistant.js +4 -12
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/diagnostics.js +4 -0
- package/dist/http/routes/runs.js +2 -3
- package/dist/http/routes/tasks.js +22 -18
- package/dist/http/routes/workflows.js +15 -7
- package/dist/http/server.d.ts +10 -2
- package/dist/http/server.js +74 -26
- package/dist/leader/wire.d.ts +9 -2
- package/dist/leader/wire.js +6 -6
- package/dist/main.js +8 -7
- package/dist/model-compat.d.ts +81 -0
- package/dist/model-compat.js +52 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/observability/metrics.d.ts +0 -6
- package/dist/observability/metrics.js +3 -1
- package/dist/observability/permission-rule-events.d.ts +20 -0
- package/dist/observability/permission-rule-events.js +28 -0
- package/dist/observability/run-terminal-log.d.ts +9 -19
- package/dist/observability/run-terminal-log.js +183 -32
- package/dist/observability/tool-trace.d.ts +12 -0
- package/dist/observability/tool-trace.js +5 -1
- package/dist/operator-ask.d.ts +46 -0
- package/dist/operator-ask.js +4 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +96 -0
- package/dist/orchestration/workflow-agent-session-index.js +146 -0
- package/dist/orchestration/workflow-journal-entry.d.ts +78 -0
- package/dist/orchestration/workflow-journal-entry.js +20 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +43 -1
- package/dist/orchestration/workflow-notify-journal.js +43 -3
- package/dist/parked-decide.d.ts +115 -5
- package/dist/parked-decide.js +94 -26
- package/dist/plugins/pg-pool.js +8 -0
- package/dist/plugins/store-backend.d.ts +7 -1
- package/dist/plugins/store-backend.js +3 -1
- package/dist/plugins/tidb-pool.js +8 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.d.ts +30 -0
- package/dist/plugins/workflow-run-store-sql.js +40 -0
- package/dist/posture-source.d.ts +23 -3
- package/dist/posture-source.js +1 -1
- package/dist/read-face-posture.d.ts +8 -1
- package/dist/read-face-posture.js +1 -0
- package/dist/router/route-orchestration.js +2 -1
- package/dist/run-cancel-context.d.ts +50 -1
- package/dist/run-cancel-context.js +22 -0
- package/dist/run-local.d.ts +14 -6
- package/dist/run-local.js +2 -2
- package/dist/runs.d.ts +0 -51
- package/dist/runs.js +40 -23
- package/dist/runtime-governance.js +5 -4
- package/dist/tool-approval.d.ts +31 -5
- package/dist/tool-approval.js +5 -4
- package/dist/trace/core-keyset-guard.d.ts +9 -5
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/ledger-events.d.ts +17 -1
- package/dist/trace/ledger-events.js +6 -1
- package/dist/trace/ledger-sink.d.ts +10 -8
- package/dist/trace/ledger-sink.js +2 -1
- package/dist/trace/project.d.ts +3 -2
- package/dist/trace/redact.d.ts +28 -0
- package/dist/trace/redact.js +50 -27
- package/package.json +4 -4
package/dist/http/server.d.ts
CHANGED
|
@@ -95,7 +95,10 @@ export interface ServiceCoreDeps {
|
|
|
95
95
|
/** `opts.onNotice` (L-167, core 7.9.1):装配期铸下的、**这条会话**的用户面通告(今天只有一族:请求腿
|
|
96
96
|
* MCP 注入被丢弃)。装配跑在通告腿开口**之前**(`prepareSpec` → 路由注册 `registerEngineNoticeLeg`),
|
|
97
97
|
* 所以它不能在铸点直接 `route()` ——收下来交给调用腿,由腿在开口那一拍连同 `pending` 一起投。
|
|
98
|
-
* 缺席(
|
|
98
|
+
* 缺席(桩)⇒ 只走结构化日志终点,行为逐字不变。
|
|
99
|
+
* ⚠️ 7.69.0(R-1)起 **resume 家族也递这一位** —— 四条 resume 腿收敛到 `resolveResumeSpec` 一只 resolver,
|
|
100
|
+
* 通告口接一次、四腿同得(见 http/server.ts 的 `resolveResumeSpec` 顶注);此处旧注写作「resume 家族缺席」
|
|
101
|
+
* 已过期,按新行为改。 */
|
|
99
102
|
resolveSpec: (body: TaskRequestBody, req: IncomingMessage | undefined, auth?: RequestAuth, opts?: {
|
|
100
103
|
leg?: "fresh" | "resume";
|
|
101
104
|
liveStream?: boolean;
|
|
@@ -156,6 +159,11 @@ export interface ServiceStoreDeps {
|
|
|
156
159
|
* (subscribeWorkflow) and works whenever self-orchestration is on, store or not. Runs are owner-gated by
|
|
157
160
|
* `scope` (= the creating principal). Also drives `capabilities.workflowsList` (= "can durably LIST runs"). */
|
|
158
161
|
workflowRunStore?: WorkflowRunStore;
|
|
162
|
+
/** S-185 车CM:workflow 出身 park 的**有界 join** 索引 —— 与 `workflowRunStore` **成对**才构成第三条
|
|
163
|
+
* 车道(索引只回答「去哪条 run 找」,行本体与赎回键仍以 run 的 blob 为准)。与 RunnerDeps 侧同实例
|
|
164
|
+
* (boot/workflow-orchestration.ts);任一缺席 = 那条车道不存在(workflow 出身的 park 落 legacy 腿,
|
|
165
|
+
* 与本批之前逐字同形)。契约与单写者纪律的属主 = orchestration/workflow-agent-session-index.ts。 */
|
|
166
|
+
workflowAgentSessionIndex?: import("../orchestration/workflow-agent-session-index.js").WorkflowAgentSessionIndex;
|
|
159
167
|
/** [ref] journal 读面(PAIR-REVIEW 修车批):GET /v1/workflows/:runId/journal 的数据源——与 runner 侧
|
|
160
168
|
* workflowJournalStore 同一实例(main.ts backend.workflowJournal());缺席=501(与 workflowRunStore 同门)。 */
|
|
161
169
|
workflowJournalStore?: import("@sema-agent/core").WorkflowJournalStore;
|
|
@@ -681,7 +689,7 @@ export interface ServiceDeploymentDeps {
|
|
|
681
689
|
* `workflowModelAllowlistFor(config)` 算一次(与 createRunnerDeps 喂给 core `workflowGovernanceBaseline.
|
|
682
690
|
* workflowModelAllowlist` 的同一只函数、同一 config 代际),多租户 fail-closed 的 undefined 在装配点
|
|
683
691
|
* 折 `[]` ⇒ 生产装配下本席**恒在场**。为什么不让能力面每请求活派生:core 5.49 起 models plane 经
|
|
684
|
-
* `Runner.
|
|
692
|
+
* `Runner.swapDeps` 永久热,而 governance allowlist 是 Runner 构造期快照 —— 活派生会在热加目录键后
|
|
685
693
|
* 长期广告一个 core 必拒的词(教学面超前执法面,直到重启)。广告**引擎真快照**让「能力面教的」与
|
|
686
694
|
* 「core 收到的 allowlist」结构性恒等。缺席(遗留 stub 装配)⇒ 能力面回落活 config 派生(测试便利,
|
|
687
695
|
* 生产路径到不了)。 */
|
package/dist/http/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { redactErrorMessage, redactHead } from "../observability/run-terminal-lo
|
|
|
3
3
|
import { once } from "node:events";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow, GLOBAL_USAGE_KEY, usageRetryAfterMs, terminalProjection, PAUSE_REGISTRY } from "@sema-agent/core";
|
|
6
|
-
import { decideParkedAgent,
|
|
6
|
+
import { decideParkedAgent, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
7
7
|
import { isModelAllowlisted, matchCatalogModel } from "../model-select.js";
|
|
8
8
|
import {} from "../config-center/facade.js";
|
|
9
9
|
import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isDestructiveSessionWrite, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
|
|
@@ -17,6 +17,7 @@ import { normalizeApproachNotice, unknownTaskAgentModelRef, validateTaskAgents }
|
|
|
17
17
|
import { isValidCwd, MAX_ADDITIONAL_DIRS } from "../task-cwd.js";
|
|
18
18
|
import { runInBackground, evictIfConflict, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
|
|
19
19
|
import { failureCodeOf, nestedPauseOf, stripResumeToken } from "../terminal.js";
|
|
20
|
+
import { mintCancelledResult } from "../run-cancel-context.js";
|
|
20
21
|
import { personDecision } from "../host-decision.js";
|
|
21
22
|
import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
|
|
22
23
|
import { recordResumeResourceWindow } from "../resource-window.js";
|
|
@@ -1175,29 +1176,60 @@ export function createHttpServer(rawDeps) {
|
|
|
1175
1176
|
if (parkedForkLeaseDenial)
|
|
1176
1177
|
return parkedForkLeaseDenial;
|
|
1177
1178
|
const ownerLeaseAdmittedHere = req !== undefined;
|
|
1178
|
-
if (req !== undefined
|
|
1179
|
+
if (req !== undefined) {
|
|
1179
1180
|
const parkedAgentStore = deps.backgroundAgentStore;
|
|
1180
1181
|
const parkedTool = deps.parkedReviveTool;
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
...(deps.
|
|
1188
|
-
|
|
1189
|
-
|
|
1182
|
+
const parkWarn = (event, fields) => deps.logger?.warn?.(event, fields);
|
|
1183
|
+
const origin = await findParkOrigin({
|
|
1184
|
+
...(parkedAgentStore !== undefined && parkedTool !== undefined ? { agentStore: parkedAgentStore } : {}),
|
|
1185
|
+
...(deps.workflowAgentSessionIndex !== undefined && deps.workflowRunStore !== undefined
|
|
1186
|
+
? { workflowIndex: deps.workflowAgentSessionIndex, workflowRunStore: deps.workflowRunStore }
|
|
1187
|
+
: {}),
|
|
1188
|
+
...(deps.logger ? { warn: parkWarn } : {}),
|
|
1189
|
+
}, { token, scope: cp.scope, sessionId: cp.sessionId });
|
|
1190
|
+
const parkedReq = {
|
|
1190
1191
|
token, scope: cp.scope, sessionId: cp.sessionId, pendingAction: cp.pendingAction,
|
|
1191
1192
|
decision, hostDecision, ...(reason !== undefined ? { reason } : {}),
|
|
1192
1193
|
...(binding !== undefined ? { binding: { ...(binding.boundCallId !== undefined ? { boundCallId: binding.boundCallId } : {}), ...(binding.boundInputHash !== undefined ? { boundInputHash: binding.boundInputHash } : {}), ...(binding.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}) } } : {}),
|
|
1193
|
-
...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
|
|
1194
1194
|
...(answer !== undefined ? { answer } : {}),
|
|
1195
|
-
|
|
1196
|
-
|
|
1195
|
+
...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
|
|
1196
|
+
};
|
|
1197
|
+
if (origin?.kind === "background" && parkedAgentStore !== undefined && parkedTool !== undefined) {
|
|
1198
|
+
const parked = await withPrincipal(decodeCheckpointScope(cp.scope), () => decideParkedAgent({
|
|
1199
|
+
agentStore: parkedAgentStore,
|
|
1200
|
+
checkpointStore: cs,
|
|
1201
|
+
registry: defaultTaskRegistry,
|
|
1202
|
+
reviveTool: parkedTool,
|
|
1203
|
+
...(deps.parkedKnownAgentTypes ? { knownAgentTypes: deps.parkedKnownAgentTypes } : {}),
|
|
1204
|
+
...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
|
|
1205
|
+
...(deps.logger ? { warn: parkWarn } : {}),
|
|
1206
|
+
}, parkedReq, { handle: origin.handle, row: origin.row }));
|
|
1197
1207
|
void ctxPromise.catch(() => { });
|
|
1198
1208
|
noteLateSettleIfAccepted(parked);
|
|
1199
1209
|
return parked;
|
|
1200
1210
|
}
|
|
1211
|
+
if (origin?.kind === "workflow") {
|
|
1212
|
+
void ctxPromise.catch(() => { });
|
|
1213
|
+
const plan = planWorkflowParkedDecide(origin, parkedReq);
|
|
1214
|
+
if (!plan.ok)
|
|
1215
|
+
return plan.deny;
|
|
1216
|
+
const hostToken = await cs
|
|
1217
|
+
.findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
|
|
1218
|
+
.catch(() => undefined);
|
|
1219
|
+
if (!hostToken) {
|
|
1220
|
+
return {
|
|
1221
|
+
status: 409,
|
|
1222
|
+
body: {
|
|
1223
|
+
error: "the workflow's originating session is not parked awaiting this run — the deployment can only deliver the decision on the host's next turn; retry once the host session parks, or resume the workflow run yourself",
|
|
1224
|
+
errorCode: "decide.workflow_host_not_parked",
|
|
1225
|
+
runId: plan.runId,
|
|
1226
|
+
},
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
return await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, [
|
|
1230
|
+
{ runId: plan.runId, token, outcome: plan.outcome },
|
|
1231
|
+
]);
|
|
1232
|
+
}
|
|
1201
1233
|
}
|
|
1202
1234
|
let ctx;
|
|
1203
1235
|
try {
|
|
@@ -1210,8 +1242,9 @@ export function createHttpServer(rawDeps) {
|
|
|
1210
1242
|
return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
|
|
1211
1243
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
1212
1244
|
let spec;
|
|
1245
|
+
let engineNotices = [];
|
|
1213
1246
|
try {
|
|
1214
|
-
spec = await
|
|
1247
|
+
({ spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth));
|
|
1215
1248
|
}
|
|
1216
1249
|
catch (e) {
|
|
1217
1250
|
if (e instanceof HttpError) {
|
|
@@ -1244,10 +1277,15 @@ export function createHttpServer(rawDeps) {
|
|
|
1244
1277
|
hostDecision,
|
|
1245
1278
|
};
|
|
1246
1279
|
const verifyRounds = verifyRoundsFromBody(ctx.body);
|
|
1247
|
-
const driven = await driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, ...(boundGrant ? { onResumeCommitted: boundGrant } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}), ...(ownerLeaseAdmittedHere ? { ownerLeaseAdmitted: true } : {}) });
|
|
1280
|
+
const driven = await driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(boundGrant ? { onResumeCommitted: boundGrant } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}), ...(ownerLeaseAdmittedHere ? { ownerLeaseAdmitted: true } : {}) });
|
|
1248
1281
|
noteLateSettleIfAccepted(driven);
|
|
1249
1282
|
return driven;
|
|
1250
1283
|
}
|
|
1284
|
+
const resolveResumeSpec = async (body, req, auth) => {
|
|
1285
|
+
const engineNotices = [];
|
|
1286
|
+
const spec = await deps.resolveSpec({ ...body, resumeAt: undefined }, req, auth, { leg: "resume", onNotice: (n) => engineNotices.push(n) });
|
|
1287
|
+
return { spec, engineNotices };
|
|
1288
|
+
};
|
|
1251
1289
|
async function driveResumeIntoRunLog(args) {
|
|
1252
1290
|
if (args.acceptEarly !== true)
|
|
1253
1291
|
return await driveResumeLeg(args);
|
|
@@ -1269,6 +1307,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1269
1307
|
return await Promise.race([accepted, leg]);
|
|
1270
1308
|
}
|
|
1271
1309
|
async function driveResumeLeg(args, onAccepted) {
|
|
1310
|
+
const resumeLegStartedAt = Date.now();
|
|
1272
1311
|
const { runner: legRunner, token, sessionId, principal, fleetScope, taskConfig, resumeObjective, outcome, verifyRounds } = args;
|
|
1273
1312
|
const legKey = createHash("sha256").update(String(token)).digest("hex");
|
|
1274
1313
|
const streamApprovalOn = resolveStreamApprovalGate({
|
|
@@ -1503,6 +1542,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1503
1542
|
const append = (type, data) => (rs && taskId ? serialAppend(type, data) : Promise.resolve());
|
|
1504
1543
|
unregisterEngineNotice = registerEngineNoticeLeg({
|
|
1505
1544
|
sessionId,
|
|
1545
|
+
...(args.engineNotices !== undefined && args.engineNotices.length > 0 ? { pending: [...args.engineNotices] } : {}),
|
|
1506
1546
|
durable: (row) => void append("engine_notice", row).catch((err) => {
|
|
1507
1547
|
recordFailOpen("server.engine-notice.durable-append-failed", `leg=resume code=${row.code} task=${taskId ?? ""}`);
|
|
1508
1548
|
deps.logger?.warn?.("engine_notice_append_failed", { taskId, noticeCode: row.code, sessionId: row.sessionId, err: err instanceof Error ? err.message : String(err) });
|
|
@@ -1532,6 +1572,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1532
1572
|
resumeLegLive = true;
|
|
1533
1573
|
const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
|
|
1534
1574
|
const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
|
|
1575
|
+
...(args.workflowParkedResume !== undefined ? { workflowParkedResume: args.workflowParkedResume } : {}),
|
|
1535
1576
|
onForwardEvent: (e) => {
|
|
1536
1577
|
fleetPub?.onForwardEvent(e);
|
|
1537
1578
|
{
|
|
@@ -1758,7 +1799,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1758
1799
|
}
|
|
1759
1800
|
}
|
|
1760
1801
|
if (claimedRow && taskId && deps.runStore) {
|
|
1761
|
-
const c = { taskId, sessionId,
|
|
1802
|
+
const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: e.message });
|
|
1762
1803
|
await deps.runStore.setTerminal(taskId, "failed", c, e.message).catch(() => undefined);
|
|
1763
1804
|
}
|
|
1764
1805
|
settleFleet("failed");
|
|
@@ -1813,7 +1854,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1813
1854
|
}
|
|
1814
1855
|
if (cancelCtrl.signal.aborted) {
|
|
1815
1856
|
if (taskId && deps.runStore) {
|
|
1816
|
-
const c = { taskId, sessionId,
|
|
1857
|
+
const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: "cancelled by user" });
|
|
1817
1858
|
await deps.runStore.setTerminal(taskId, "failed", c, "cancelled by user").catch(() => undefined);
|
|
1818
1859
|
}
|
|
1819
1860
|
settleFleet("failed");
|
|
@@ -1929,12 +1970,12 @@ export function createHttpServer(rawDeps) {
|
|
|
1929
1970
|
if (!ctx)
|
|
1930
1971
|
return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
|
|
1931
1972
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
1932
|
-
const spec = await
|
|
1973
|
+
const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
|
|
1933
1974
|
const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
|
|
1934
1975
|
const verifyRounds = verifyRoundsFromBody(ctx.body);
|
|
1935
|
-
return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, verifyRounds, ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
|
|
1976
|
+
return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, verifyRounds, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
|
|
1936
1977
|
}
|
|
1937
|
-
async function resumeWake(sessionId, message, caller, req, acceptEarly) {
|
|
1978
|
+
async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowParkedResume) {
|
|
1938
1979
|
const cs = deps.checkpointStore;
|
|
1939
1980
|
const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
|
|
1940
1981
|
const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
|
|
@@ -1988,7 +2029,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1988
2029
|
if (!ctx)
|
|
1989
2030
|
return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
|
|
1990
2031
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
1991
|
-
const spec = await
|
|
2032
|
+
const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
|
|
1992
2033
|
const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
|
|
1993
2034
|
return driveResumeIntoRunLog({
|
|
1994
2035
|
runner: runnerFor(spec),
|
|
@@ -2001,8 +2042,10 @@ export function createHttpServer(rawDeps) {
|
|
|
2001
2042
|
outcome: { gate: "wake", ...(message !== undefined ? { message: { text: message, trusted } } : {}) },
|
|
2002
2043
|
verifyRounds: undefined,
|
|
2003
2044
|
mintFreshRun: { source: "wake" },
|
|
2045
|
+
...(engineNotices.length > 0 ? { engineNotices } : {}),
|
|
2004
2046
|
...(acceptEarly === true ? { acceptEarly: true } : {}),
|
|
2005
2047
|
...(req !== undefined ? { callerInitiated: true } : {}),
|
|
2048
|
+
...(workflowParkedResume !== undefined ? { workflowParkedResume } : {}),
|
|
2006
2049
|
});
|
|
2007
2050
|
}
|
|
2008
2051
|
async function resumePlanReview(sessionId, decision, editedPlan, reason, req, log, acceptEarly, decider) {
|
|
@@ -2066,7 +2109,7 @@ export function createHttpServer(rawDeps) {
|
|
|
2066
2109
|
if (!ctx)
|
|
2067
2110
|
return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
|
|
2068
2111
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
2069
|
-
const spec = await
|
|
2112
|
+
const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
|
|
2070
2113
|
const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
|
|
2071
2114
|
const verifyRounds = verifyRoundsFromBody(ctx.body);
|
|
2072
2115
|
const outcome = {
|
|
@@ -2075,7 +2118,7 @@ export function createHttpServer(rawDeps) {
|
|
|
2075
2118
|
...(editedPlan !== undefined ? { editedPlan } : {}),
|
|
2076
2119
|
...(reason ? { reason } : {}),
|
|
2077
2120
|
};
|
|
2078
|
-
return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
|
|
2121
|
+
return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
|
|
2079
2122
|
}
|
|
2080
2123
|
function recordTaskResult(result) {
|
|
2081
2124
|
const plane = terminalProjection(result.terminal);
|
|
@@ -2314,7 +2357,7 @@ export function createHttpServer(rawDeps) {
|
|
|
2314
2357
|
return;
|
|
2315
2358
|
const expired = await cs.listExpiredApprovalGates(now).catch(() => []);
|
|
2316
2359
|
const isParkedOwned = async (sessionId) => {
|
|
2317
|
-
if (deps.backgroundAgentStore === undefined)
|
|
2360
|
+
if (deps.backgroundAgentStore === undefined && deps.workflowAgentSessionIndex === undefined)
|
|
2318
2361
|
return false;
|
|
2319
2362
|
try {
|
|
2320
2363
|
const token = await cs.findPendingTokenBySession(sessionId, undefined, { gateKinds: APPROVAL_GATE_KINDS });
|
|
@@ -2323,7 +2366,12 @@ export function createHttpServer(rawDeps) {
|
|
|
2323
2366
|
const cp = await cs.get(token);
|
|
2324
2367
|
if (!cp)
|
|
2325
2368
|
return false;
|
|
2326
|
-
return (await
|
|
2369
|
+
return (await findParkOrigin({
|
|
2370
|
+
...(deps.backgroundAgentStore !== undefined ? { agentStore: deps.backgroundAgentStore } : {}),
|
|
2371
|
+
...(deps.workflowAgentSessionIndex !== undefined && deps.workflowRunStore !== undefined
|
|
2372
|
+
? { workflowIndex: deps.workflowAgentSessionIndex, workflowRunStore: deps.workflowRunStore }
|
|
2373
|
+
: {}),
|
|
2374
|
+
}, { token, scope: cp.scope, sessionId: cp.sessionId })) !== undefined;
|
|
2327
2375
|
}
|
|
2328
2376
|
catch {
|
|
2329
2377
|
return false;
|
package/dist/leader/wire.d.ts
CHANGED
|
@@ -46,8 +46,15 @@ export interface LeaderWireConfig {
|
|
|
46
46
|
* 委派 caps」只能有一个答案。缺席 = 展开空对象 = 五处字面量键缺席(与接线前逐字相同)。
|
|
47
47
|
* 修前(≤7.53)这五只 Runner 一席都没有:运维写的 READ_DENY_PATTERNS / MEMORY_DELEGATION_EVIDENCE 等对
|
|
48
48
|
* leader 编排的 planner / worker / repair / conflict 四类任务**静默不生效**(core 内建 deny 表仍在)。
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
*
|
|
50
|
+
* 🔴 **是 provider 不是快照**(S-174 追加,codex 对抗复审 r1 [high] 验真后修):本车道的 Runner 是
|
|
51
|
+
* **逐调用现构**的,而 S-174 起 `config.readFace` 在进程内**会变**(center 下发的 read face 转成了
|
|
52
|
+
* `Runner.swapDeps` 热换席)。boot 期取一次值再五处复用,等于让 leader 车道永远停在起服那一刻的 READ
|
|
53
|
+
* 姿态:读面(`/v1/capabilities`、`/v1/diagnostics/wiring`)报新档、`restart.reasons` 也不再要求重启,
|
|
54
|
+
* 而**新建的 leader Runner 仍在旧档上跑** —— 组织把 `open` 收紧成 `roots` 之后这条腿还在 open,方向是
|
|
55
|
+
* fail-OPEN。改成函数席之后每只 Runner 构造时现读,与主车道经 swap 门换代**同代**;已经准备好的腿
|
|
56
|
+
* 保留它准备时的那一份(core 的自然快照语义,两条腿同律)。 */
|
|
57
|
+
deploymentPosture?: () => DeploymentPostureSeats;
|
|
51
58
|
/**
|
|
52
59
|
* [ref]([ref])—— **计费/遥测追踪席**,原样递给本车道的每一只 Runner。唯一属主 =
|
|
53
60
|
* `boot/budget-tracing.ts` 的 `createTracer(...)`(主 runner / subRunner / hook runner 经共享基座吃的
|
package/dist/leader/wire.js
CHANGED
|
@@ -150,7 +150,7 @@ export function createLeaderRunner(cfg) {
|
|
|
150
150
|
const { repairRounds, repairBudgetUsd, conflictRounds, repairLoopOn, measureGatesOn, repairLoopAttempts, oracleFlakyK, replanBudgetUsd, } = leaderLoopConfig({ repairRounds: cfg.repairRounds, conflictRounds: cfg.conflictRounds });
|
|
151
151
|
const onNoticeSeat = createEngineNoticeSeat(cfg.logger);
|
|
152
152
|
const governanceSeat = cfg.governance ?? {};
|
|
153
|
-
const deploymentPostureSeat = cfg.deploymentPosture ?? {};
|
|
153
|
+
const deploymentPostureSeat = () => cfg.deploymentPosture?.() ?? {};
|
|
154
154
|
const tracerSeat = cfg.tracer ? { tracer: cfg.tracer } : {};
|
|
155
155
|
const usageWindowSeat = cfg.usageWindows && cfg.usageWindowStore ? { usageWindows: cfg.usageWindows, usageWindowStore: cfg.usageWindowStore } : {};
|
|
156
156
|
const promptSourceSeat = cfg.promptSource ? { promptSource: cfg.promptSource } : {};
|
|
@@ -172,7 +172,7 @@ export function createLeaderRunner(cfg) {
|
|
|
172
172
|
throw new Error(`oracle still present after remove (${f.path}) — refusing repair (measurement integrity)`);
|
|
173
173
|
}
|
|
174
174
|
try {
|
|
175
|
-
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
175
|
+
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat(), ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
176
176
|
const objective = [
|
|
177
177
|
`The integrated project at ${repoDir} fails its build/test. Make the MINIMAL change to the working tree so this command exits 0 (cd into the repo and run it yourself to confirm):`,
|
|
178
178
|
` ${testCmd}`,
|
|
@@ -216,7 +216,7 @@ export function createLeaderRunner(cfg) {
|
|
|
216
216
|
throw new Error(`oracle still present after remove (${f.path}) — refusing conflict-resolve (measurement integrity)`);
|
|
217
217
|
}
|
|
218
218
|
try {
|
|
219
|
-
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
219
|
+
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat(), ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
220
220
|
const objective = buildConflictResolverObjective({ workerId, repoDir, patch, conflictText });
|
|
221
221
|
const res = await runner
|
|
222
222
|
.runTaskStream({
|
|
@@ -272,7 +272,7 @@ export function createLeaderRunner(cfg) {
|
|
|
272
272
|
if (Array.isArray(body.subtasks) && body.subtasks.length > 0) {
|
|
273
273
|
return validateSubtasks(body.subtasks);
|
|
274
274
|
}
|
|
275
|
-
const planRunner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat });
|
|
275
|
+
const planRunner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat(), ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat });
|
|
276
276
|
const runRoute = async (prompt) => {
|
|
277
277
|
const res = await planRunner.runTaskStream({
|
|
278
278
|
objective: prompt,
|
|
@@ -363,7 +363,7 @@ export function createLeaderRunner(cfg) {
|
|
|
363
363
|
workerId: sub.workerId, sessionId, branch: sub.branch,
|
|
364
364
|
baseSha,
|
|
365
365
|
runner: keepCtxWarm(new Runner({
|
|
366
|
-
brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat,
|
|
366
|
+
brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat(), ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat,
|
|
367
367
|
...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}),
|
|
368
368
|
...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}),
|
|
369
369
|
executionEnvFactory: async (ctx) => withStaging(await envFactory(ctx), stage, async (e) => {
|
|
@@ -383,7 +383,7 @@ export function createLeaderRunner(cfg) {
|
|
|
383
383
|
const baseSha = await sh(env)(`cd ${repo} && git rev-parse HEAD`);
|
|
384
384
|
return {
|
|
385
385
|
workerId: sub.workerId, sessionId, branch: sub.branch, baseSha,
|
|
386
|
-
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 })),
|
|
386
|
+
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 })),
|
|
387
387
|
diffEnv: env,
|
|
388
388
|
destroy: () => env.destroy().then(() => { }),
|
|
389
389
|
spec: { ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec() },
|
package/dist/main.js
CHANGED
|
@@ -179,7 +179,7 @@ async function main() {
|
|
|
179
179
|
...(toolTracer ? composeHooks(createPermissionDeniedMeter(metrics), toolTracer) : createPermissionDeniedMeter(metrics)),
|
|
180
180
|
...(config.hooksTimeoutMs !== undefined ? { timeoutMs: config.hooksTimeoutMs } : {}),
|
|
181
181
|
};
|
|
182
|
-
const { sqlWorkflowRunStore, workflowNotifyJournal, workflowCompletionInbox, deliverWorkflowCompletion, workflowNotifyGate, fleetBus, workflowRunStore, workflowJournalStore, outcomeSink, workflowRecoverOpts, workflowAgentRegistry, subagentSteerRegistry, } = createWorkflowOrchestration({ config, logger, metrics, localRoot, backend, getRunStore: () => runStore });
|
|
182
|
+
const { sqlWorkflowRunStore, workflowNotifyJournal, workflowCompletionInbox, deliverWorkflowCompletion, workflowNotifyGate, fleetBus, workflowRunStore, workflowJournalStore, outcomeSink, workflowRecoverOpts, workflowAgentRegistry, subagentSteerRegistry, workflowAgentSessionIndex, } = createWorkflowOrchestration({ config, logger, metrics, localRoot, backend, getRunStore: () => runStore });
|
|
183
183
|
let parkedAskRedeem;
|
|
184
184
|
const { elicitation, question, toolApproval, durableEnabled, streamApprovalGate, approvalAskAudit, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec } = createLiveCoordinators({
|
|
185
185
|
config, logger, backend, sendUserFileTaskEnvs, ruleConsent,
|
|
@@ -272,7 +272,7 @@ async function main() {
|
|
|
272
272
|
config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
|
|
273
273
|
pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
|
|
274
274
|
sessionCaptureRecordStore,
|
|
275
|
-
memorySyncRunner, toolResultStore: runnerOffloadStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
|
|
275
|
+
memorySyncRunner, toolResultStore: runnerOffloadStore, checkpointStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
|
|
276
276
|
legacyRewindBoundaryProbe: backend?.legacyRewindBoundaryProbe ? backend.legacyRewindBoundaryProbe.bind(backend) : undefined,
|
|
277
277
|
permissionRuleStore: permissionRuleProvider,
|
|
278
278
|
executionEnvFactory, lspManager, fleetBus, deploymentHooks, workflowRunStore, workflowJournalStore,
|
|
@@ -345,6 +345,7 @@ async function main() {
|
|
|
345
345
|
rosterStore,
|
|
346
346
|
deploymentHooks,
|
|
347
347
|
toolResultStore: runnerOffloadStore,
|
|
348
|
+
checkpointStore,
|
|
348
349
|
sessionPolicyStore,
|
|
349
350
|
usageWindowStore,
|
|
350
351
|
orgMemoryAdmission,
|
|
@@ -356,7 +357,6 @@ async function main() {
|
|
|
356
357
|
loadProjectMemory: runnerDeps.loadProjectMemory ? runnerDeps.loadProjectMemory : undefined,
|
|
357
358
|
probeInstructionSources: runnerDeps.probeInstructionSources ? runnerDeps.probeInstructionSources : undefined,
|
|
358
359
|
onError: runnerDeps.onError ? runnerDeps.onError : undefined,
|
|
359
|
-
checkpointStore: checkpointStore ? checkpointStore : undefined,
|
|
360
360
|
};
|
|
361
361
|
const subRunner = new Runner(subRunnerDeps);
|
|
362
362
|
const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
|
|
@@ -546,20 +546,20 @@ async function main() {
|
|
|
546
546
|
["sub", subRunner],
|
|
547
547
|
["handslessSub", handslessSubRunner],
|
|
548
548
|
];
|
|
549
|
-
const
|
|
549
|
+
const swapRunnerDeps = (next) => {
|
|
550
550
|
for (let i = 0; i < bootRunners.length; i++) {
|
|
551
551
|
const [name, rn] = bootRunners[i];
|
|
552
552
|
try {
|
|
553
|
-
rn.
|
|
553
|
+
rn.swapDeps(next);
|
|
554
554
|
}
|
|
555
555
|
catch (err) {
|
|
556
556
|
if (i > 0)
|
|
557
|
-
logger.error("
|
|
557
|
+
logger.error("runner_deps_swap_split", { failedAt: name, seats: Object.keys(next), swapped: bootRunners.slice(0, i).map(([n]) => n), err: String(err) });
|
|
558
558
|
throw err;
|
|
559
559
|
}
|
|
560
560
|
}
|
|
561
561
|
};
|
|
562
|
-
configCenter.startRefreshLoop({ runnerTierFrozen, pricing, limitSync,
|
|
562
|
+
configCenter.startRefreshLoop({ runnerTierFrozen, pricing, limitSync, swapRunnerDeps });
|
|
563
563
|
configCenter.initKeyResolver();
|
|
564
564
|
const { ownerAware, sessionAudit, sessionWatchRegistry, purgeSession, instrumentDegenerate, planCacheProbe } = createSessionFaces({
|
|
565
565
|
deviceStore: deviceLane?.store,
|
|
@@ -610,6 +610,7 @@ async function main() {
|
|
|
610
610
|
backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
|
|
611
611
|
sessionStorage: ownerAware,
|
|
612
612
|
workflowRunStore: workflowRunStore ? workflowRunStore : undefined,
|
|
613
|
+
workflowAgentSessionIndex: workflowAgentSessionIndex ? workflowAgentSessionIndex : undefined,
|
|
613
614
|
workflowJournalStore: workflowJournalStore ? workflowJournalStore : undefined,
|
|
614
615
|
imageIndex,
|
|
615
616
|
imageBakes,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Model.compat` —— 一只模型对 **openai-completions 线路**的兼容声明(core `OpenAICompletionsCompat`;
|
|
3
|
+
* 声明面 settings-schema 1.10.0 `ModelEntry.compat`)。本模块是**两条腿共用的唯一判据点 + 唯一铸点**,
|
|
4
|
+
* 与 {@link import("./mcp-tool-faces.js").readMcpToolFaces} 同一个「判据单点、多腿一致」的形。
|
|
5
|
+
*
|
|
6
|
+
* ## 它治的病(1.10.0 CHANGELOG + core 型面顶注双向亲读)
|
|
7
|
+
* 引擎的 openai brain 一直按这五键发请求,但声明写不进来:
|
|
8
|
+
* · settings-schema ≤1.9.0 的 `ModelEntry` 没有 `compat` 键 ⇒ 目录条目写了在 parse 期被 strip 剥掉;
|
|
9
|
+
* · `extraBody` 也穿不过(引擎的 OpenAI 保留键表拦下 `chat_template_kwargs` / `enable_thinking`);
|
|
10
|
+
* · env 侧只有 `MODEL_REASONING_EFFORT_LEVELS` 一根旋钮,`thinkingFormat` 无处可写。
|
|
11
|
+
* 真需求 = vLLM / Qwen 类**缺省开思考**的网关:关思考只能靠 `chat_template_kwargs.enable_thinking=false`
|
|
12
|
+
* (`thinkingFormat: "qwen-chat-template"`),否则 auto 档每一轮都白烧一段思考。
|
|
13
|
+
*
|
|
14
|
+
* ## 三张对表全部**从 core 的型面铸**,本仓一个词都不枚举
|
|
15
|
+
* `THINKING_LEVEL_TABLE` / `THINKING_FORMAT_TABLE` / `MAX_TOKENS_FIELD_TABLE` 都是 `Record<闭集词, true>`,
|
|
16
|
+
* `COMPAT_KEY_READERS` 是 `{ [K in keyof Required<OpenAICompletionsCompat>]: 读器 }` 的**映射型**:
|
|
17
|
+
* core 加词/删词、加键/删键,四张表当场 tsc 红(`MODEL_DEGRADE_ON` 的 `Set<DegradeReason>` 同款纪律)。
|
|
18
|
+
* 映射型这一步是刻意的 —— 只钉「键集相等」会留下一个洞:core 加一键、有人把它补进白名单却忘了写读器,
|
|
19
|
+
* 于是这个键被**允许但不读** = 静默丢。读器与键集是**同一张表**,那个洞在结构上不存在。
|
|
20
|
+
*
|
|
21
|
+
* ## 坏声明的方向:**丢 compat 键、模型本体保留、一条点名 warn**(降级通道,不是拒绝面)
|
|
22
|
+
* 与 `toolFaces` 的「整条 server 不进」**刻意分歧**,理由是键的方向不同:`toolFaces` 声明的是写围栏 /
|
|
23
|
+
* 敏感路径策略(**收窄**键,丢它 = 运维声明的保护静默失效);`compat` 声明的是**线路拼法**(这台网关吃
|
|
24
|
+
* 哪种 thinking 参数、max tokens 叫什么名),丢它 = 回落到 core 的自动推断 = **今天这一刻的行为**,
|
|
25
|
+
* 权限/审批/凭据一格不动。[ref] 要的是「安全轴不许静默 fail-open」,本键不在安全轴上,而丢键仍然
|
|
26
|
+
* **响亮**(`model_compat_dropped` 点名模型与哪一段不合)—— 与 `mcp_content_class_dropped` 同形。
|
|
27
|
+
* 反方向(整只模型不进)的代价则不成比例:一个拼错的 `thinkingFormat` 会让这只模型从目录里消失,
|
|
28
|
+
* 用户面直接是 `request.unknown_reference`。
|
|
29
|
+
*
|
|
30
|
+
* 判形**整只判**:任一键不合 ⇒ 整只声明不收(和 `toolFaces` 每只工具「applied whole or not at all」
|
|
31
|
+
* 同律)。留下好的那几键 = 让运维以为整条声明生效了,而实际发出去的请求是半张脸。
|
|
32
|
+
*/
|
|
33
|
+
import type { Model } from "@sema-agent/core";
|
|
34
|
+
/** core `OpenAICompletionsCompat` —— core 的 index 没有按名导出它,从 `Model` 的**型面**取
|
|
35
|
+
* (`Model<TApi>` 的 `compat` 按 api 分叉;openai 那支就是它)。这不是抄一份结构,是引用同一个型。 */
|
|
36
|
+
export type OpenAICompletionsCompat = NonNullable<Model<"openai-completions">["compat"]>;
|
|
37
|
+
/** 六档思考梯(core `ThinkingLevel`)。`MODEL_DEFAULT_THINKING` / `MODEL_REASONING_EFFORT_LEVELS` /
|
|
38
|
+
* `compat.reasoningEffortLevels` 三处共用**这一张**表 —— [ref] 件⑤ 起就是「上游加一档而这里不补 ⇒
|
|
39
|
+
* 编译红」的锁步姿势,本模块只是把它挪到了它真正的家(compat 的一个键)。 */
|
|
40
|
+
export type ModelThinkingLevel = NonNullable<Model["defaultThinking"]>;
|
|
41
|
+
/** 六档闭集(有序:低→高,取自上表的书写序),目录行 `enumValues` 与拒因文案的单一真源。 */
|
|
42
|
+
export declare const THINKING_LEVELS: readonly ModelThinkingLevel[];
|
|
43
|
+
export declare const isThinkingTier: (w: unknown) => w is ModelThinkingLevel;
|
|
44
|
+
/** `thinkingFormat` 七词闭集(core:openai / openrouter / deepseek / together / zai / qwen / qwen-chat-template)。 */
|
|
45
|
+
export type ModelThinkingFormat = NonNullable<OpenAICompletionsCompat["thinkingFormat"]>;
|
|
46
|
+
export declare const THINKING_FORMATS: readonly ModelThinkingFormat[];
|
|
47
|
+
export declare const isThinkingFormat: (w: unknown) => w is ModelThinkingFormat;
|
|
48
|
+
/** `maxTokensField` 两词闭集(缺席 ⇒ core 按模型 id 推断,见 `inferMaxTokensField`)。 */
|
|
49
|
+
export type ModelMaxTokensField = NonNullable<OpenAICompletionsCompat["maxTokensField"]>;
|
|
50
|
+
export declare const MAX_TOKENS_FIELDS: readonly ModelMaxTokensField[];
|
|
51
|
+
type CompatKey = keyof Required<OpenAICompletionsCompat>;
|
|
52
|
+
/** 五键闭集(书写序取自上表),未知键拒因文案的单一真源。 */
|
|
53
|
+
export declare const MODEL_COMPAT_KEYS: readonly CompatKey[];
|
|
54
|
+
/**
|
|
55
|
+
* 一条目录条目的 `compat` 判形结果:
|
|
56
|
+
* · `{ ok: true, compat }` —— 合形(`compat` 缺席 = 这条条目没有声明,或声明了一个**空**对象:
|
|
57
|
+
* 两者对 core 同义,都不铸键);
|
|
58
|
+
* · `{ ok: false, issue }` —— 判不出形,调用方**整只丢**这条声明并留一句 warn(见模块头注)。
|
|
59
|
+
* `issue` 只带键名与哪一段不合,**不带值** —— 与 `source` / `toolFaces` 同一条留痕纪律。
|
|
60
|
+
*/
|
|
61
|
+
export type ModelCompatRead = {
|
|
62
|
+
ok: true;
|
|
63
|
+
compat?: OpenAICompletionsCompat;
|
|
64
|
+
} | {
|
|
65
|
+
ok: false;
|
|
66
|
+
issue: string;
|
|
67
|
+
};
|
|
68
|
+
/** 从任一腿的原始条目里读出 `compat` —— **本地 config.d 腿与 center 腿共用的唯一判据点**。 */
|
|
69
|
+
export declare function readModelCompat(v: unknown): ModelCompatRead;
|
|
70
|
+
/**
|
|
71
|
+
* `Model.compat` 的**唯一铸点**(env 腿与目录腿都经这里)。层序 = 优先级,**前层逐键赢、后层补缺席**
|
|
72
|
+
* (BL-8:目录条目声明 ?? env 缺省 —— 不是整只替换,否则一条只写了 `thinkingFormat` 的目录条目会把
|
|
73
|
+
* 部署级的 `MODEL_REASONING_EFFORT_LEVELS` 静默吃掉,正是 `contextWindow`/`maxTokens` 那一族的老病)。
|
|
74
|
+
*
|
|
75
|
+
* 归一两条(两腿因此对同一个空值给出同一个下场):值 `undefined` = 这一层没声明这一键;
|
|
76
|
+
* `reasoningEffortLevels` 为**空表** = 没声明这一键(env 腿的空 CSV 与目录腿的 `[]` 同义)。
|
|
77
|
+
* 全空 ⇒ 回 `undefined` = **不铸这个键**(缺席 = core 走自己的推断 = 今日行为)。
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildModelCompat(...layers: ReadonlyArray<OpenAICompletionsCompat | undefined>): OpenAICompletionsCompat | undefined;
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=model-compat.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const THINKING_LEVEL_TABLE = { minimal: true, low: true, medium: true, high: true, xhigh: true, max: true };
|
|
2
|
+
export const THINKING_LEVELS = Object.keys(THINKING_LEVEL_TABLE);
|
|
3
|
+
export const isThinkingTier = (w) => typeof w === "string" && THINKING_LEVELS.includes(w);
|
|
4
|
+
const THINKING_FORMAT_TABLE = { openai: true, openrouter: true, deepseek: true, together: true, zai: true, qwen: true, "qwen-chat-template": true };
|
|
5
|
+
export const THINKING_FORMATS = Object.keys(THINKING_FORMAT_TABLE);
|
|
6
|
+
export const isThinkingFormat = (w) => typeof w === "string" && THINKING_FORMATS.includes(w);
|
|
7
|
+
const MAX_TOKENS_FIELD_TABLE = { max_tokens: true, max_completion_tokens: true };
|
|
8
|
+
export const MAX_TOKENS_FIELDS = Object.keys(MAX_TOKENS_FIELD_TABLE);
|
|
9
|
+
const COMPAT_KEY_READERS = {
|
|
10
|
+
supportsReasoningEffort: (v) => (typeof v === "boolean" ? v : undefined),
|
|
11
|
+
requiresReasoningContentOnAssistantMessages: (v) => (typeof v === "boolean" ? v : undefined),
|
|
12
|
+
maxTokensField: (v) => (typeof v === "string" && MAX_TOKENS_FIELDS.includes(v) ? v : undefined),
|
|
13
|
+
thinkingFormat: (v) => (isThinkingFormat(v) ? v : undefined),
|
|
14
|
+
reasoningEffortLevels: (v) => (Array.isArray(v) && v.every(isThinkingTier) ? v : undefined),
|
|
15
|
+
};
|
|
16
|
+
export const MODEL_COMPAT_KEYS = Object.keys(COMPAT_KEY_READERS);
|
|
17
|
+
const isCompatKey = (k) => MODEL_COMPAT_KEYS.includes(k);
|
|
18
|
+
export function readModelCompat(v) {
|
|
19
|
+
if (v === undefined)
|
|
20
|
+
return { ok: true };
|
|
21
|
+
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
22
|
+
return { ok: false, issue: `compat is ${v === null ? "null" : Array.isArray(v) ? "an array" : typeof v}, not an object of wire-compat keys` };
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const [k, raw] of Object.entries(v)) {
|
|
25
|
+
if (!isCompatKey(k))
|
|
26
|
+
return { ok: false, issue: `unknown key ${JSON.stringify(k)} (valid: ${MODEL_COMPAT_KEYS.join(" | ")})` };
|
|
27
|
+
const read = COMPAT_KEY_READERS[k](raw);
|
|
28
|
+
if (read === undefined)
|
|
29
|
+
return { ok: false, issue: `key ${JSON.stringify(k)} does not fit the wire-compat shape` };
|
|
30
|
+
out[k] = read;
|
|
31
|
+
}
|
|
32
|
+
return Object.keys(out).length > 0 ? { ok: true, compat: out } : { ok: true };
|
|
33
|
+
}
|
|
34
|
+
export function buildModelCompat(...layers) {
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const layer of layers) {
|
|
37
|
+
if (layer === undefined)
|
|
38
|
+
continue;
|
|
39
|
+
for (const k of MODEL_COMPAT_KEYS) {
|
|
40
|
+
if (k in out)
|
|
41
|
+
continue;
|
|
42
|
+
const v = layer[k];
|
|
43
|
+
if (v === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
if (k === "reasoningEffortLevels" && Array.isArray(v) && v.length === 0)
|
|
46
|
+
continue;
|
|
47
|
+
out[k] = v;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=model-compat.js.map
|
|
@@ -13,6 +13,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
13
13
|
readonly cls: "F";
|
|
14
14
|
readonly note: "S-136 合并重扫确认项(`terminal.ts persistedPlaneOf`,持久 blob 的平面读面):盘上一条 `terminal` 因由形不合(`paused` 缺 gate / 词出闭集)让 core 的 `terminalProjection` 抛 ⇒ 本读面对这一行答**全格缺席**而不是整只 500。放行的最坏后果=一条坏行在列表/舰队读面上显示为无终局词;计数 + probe 带原句,便于按行回溯。";
|
|
15
15
|
};
|
|
16
|
+
readonly "server.workflow-park-index.sync-failed": {
|
|
17
|
+
readonly cls: "P-DEBT";
|
|
18
|
+
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 去查。";
|
|
19
|
+
};
|
|
16
20
|
readonly "server.trace.gate-outcome-defective": {
|
|
17
21
|
readonly cls: "F";
|
|
18
22
|
readonly note: "S-136(core 7.6.0 design/390 S6-A,`trace/project.ts` 的 `screenedGate`,写腿与两条读腿共用):一条 `tool_end` / 账本行携带的 `GateOutcome` 过不了 core 的 `screenGateOutcome`(I1–I4 不变量,或某一格词出闭集)⇒ **整条 gate 记录不上帧**(report + withhold,core 契约逐字给出的两种响亮形之一;绝不「静默修补」成一条自洽的记录)。放行的最坏后果=这一帧的消费端渲染不出「谁拒的 / 那次等待怎么结束的」,退回到 `isError` + 文案;它**不改变任何裁决**(门早已在 core 里判完,这里只是观测面)。反方向(把一条自相矛盾的记录照发)更坏:消费端按 I2/I3 读出来的结论会是假的。计数让「某个世代的产生者一直在发坏记录」显形。";
|
|
@@ -4,6 +4,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
4
4
|
cls: "F",
|
|
5
5
|
note: "S-136 合并重扫确认项(`terminal.ts persistedPlaneOf`,持久 blob 的平面读面):盘上一条 `terminal` 因由形不合(`paused` 缺 gate / 词出闭集)让 core 的 `terminalProjection` 抛 ⇒ 本读面对这一行答**全格缺席**而不是整只 500。放行的最坏后果=一条坏行在列表/舰队读面上显示为无终局词;计数 + probe 带原句,便于按行回溯。",
|
|
6
6
|
},
|
|
7
|
+
"server.workflow-park-index.sync-failed": {
|
|
8
|
+
cls: "P-DEBT",
|
|
9
|
+
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 去查。",
|
|
10
|
+
},
|
|
7
11
|
"server.trace.gate-outcome-defective": {
|
|
8
12
|
cls: "F",
|
|
9
13
|
note: "S-136(core 7.6.0 design/390 S6-A,`trace/project.ts` 的 `screenedGate`,写腿与两条读腿共用):一条 `tool_end` / 账本行携带的 `GateOutcome` 过不了 core 的 `screenGateOutcome`(I1–I4 不变量,或某一格词出闭集)⇒ **整条 gate 记录不上帧**(report + withhold,core 契约逐字给出的两种响亮形之一;绝不「静默修补」成一条自洽的记录)。放行的最坏后果=这一帧的消费端渲染不出「谁拒的 / 那次等待怎么结束的」,退回到 `isError` + 文案;它**不改变任何裁决**(门早已在 core 里判完,这里只是观测面)。反方向(把一条自相矛盾的记录照发)更坏:消费端按 I2/I3 读出来的结论会是假的。计数让「某个世代的产生者一直在发坏记录」显形。",
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tiny in-process metrics registry with Prometheus text exposition — no dependency.
|
|
3
|
-
*
|
|
4
|
-
* Per-instance (each container exposes its own counters); aggregate across instances at the scraper.
|
|
5
|
-
* Supports labeled counters, gauges (static + dynamic), and duration histograms.
|
|
6
|
-
*/
|
|
7
1
|
export type Labels = Record<string, string>;
|
|
8
2
|
export declare class Metrics {
|
|
9
3
|
private counters;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildPermissionRuleEventsHelp } from "./permission-rule-events.js";
|
|
1
2
|
const DEFAULT_BUCKETS = [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60];
|
|
2
3
|
function labelKey(labels) {
|
|
3
4
|
return Object.keys(labels)
|
|
@@ -260,8 +261,9 @@ export function createMetrics() {
|
|
|
260
261
|
m.counter("stale_cache_conflict_total", "Warm session-cache entries evicted by a cross-instance write while cached (S25)");
|
|
261
262
|
m.counter("breaker_writethrough_failed_total", "Circuit-breaker cross-replica write-through failures (LOW), by backend");
|
|
262
263
|
m.counter("web_search_bad_payload_total", "Web-search provider responses whose results field was not an array (LOW), by provider");
|
|
264
|
+
m.counter("auto_mode_classifier_unavailable_total", "Auto-mode gate denials caused by a classifier OUTAGE, by cause (core AUTO_MODE_UNAVAILABLE_CAUSES: error/timeout; 'other' = this build cannot name the word). Distinct from permission_denied_total{deniedBy=\"classifier\"}, which also counts genuine safety blocks — a spike here means the classifier itself is failing (gateway/credential fault, or too slow), not that the model kept asking for dangerous things");
|
|
263
265
|
m.counter("permission_denied_total", "Tool-gate denials by the LAYER whose verdict is the deny (core's DeniedBy: policy/hook/org/classifier/plan_mode/compliance/write_protection/ask_resolution/persisted_rule; 'other' = this build cannot name the layer; label is `deniedBy` since server 7.64.0 — it was `source` over core's retired PermissionDeniedSource; goal B4 always-on deny meter)");
|
|
264
|
-
m.counter("permission_rule_events_total",
|
|
266
|
+
m.counter("permission_rule_events_total", buildPermissionRuleEventsHelp());
|
|
265
267
|
m.counter("compaction_events_total", "Compaction lifecycle events by outcome (started/completed/failed/skipped) and trigger");
|
|
266
268
|
m.counter("hook_llm_calls_total", "prompt/agent hook entries that completed a model call (hooks 3b), by type");
|
|
267
269
|
m.counter("auth_bridge_verify_total", "Registry SSO JWT verifications (auth-bridge), by outcome");
|