@sema-agent/server 7.67.0 → 7.69.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/USAGE.md +19 -2
- package/dist/adoption/plan.js +11 -0
- package/dist/approval-card.d.ts +26 -0
- package/dist/approval-card.js +11 -0
- 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/resolve-spec.js +1 -1
- package/dist/boot/workflow-orchestration.d.ts +2 -0
- package/dist/boot/workflow-orchestration.js +10 -2
- package/dist/budget.js +1 -1
- package/dist/config-catalog.js +20 -1
- 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 +44 -2
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/runs.js +1 -1
- package/dist/http/routes/tasks.js +1 -0
- package/dist/http/routes/workflows.js +15 -7
- package/dist/http/server.d.ts +15 -2
- package/dist/http/server.js +74 -45
- package/dist/leader/wire.d.ts +9 -2
- package/dist/leader/wire.js +6 -6
- package/dist/main.js +6 -5
- package/dist/mcp-tool-faces.js +11 -2
- 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/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 +5 -0
- package/dist/plugins/store-backend.js +3 -1
- package/dist/plugins/tidb-pool.js +8 -0
- package/dist/plugins/workflow-journal-store-sql.js +10 -3
- package/dist/plugins/workflow-run-store-sql.d.ts +30 -0
- package/dist/plugins/workflow-run-store-sql.js +40 -0
- package/dist/router/route-orchestration.js +2 -1
- package/dist/run-local.js +1 -1
- package/dist/runs.d.ts +10 -1
- package/dist/runs.js +4 -1
- package/dist/runtime-governance.js +5 -4
- package/dist/task-mcp.d.ts +50 -6
- package/dist/task-mcp.js +71 -35
- package/dist/tool-approval.d.ts +20 -0
- package/dist/tool-approval.js +2 -1
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +17 -1
- package/dist/trace/engine-notice-wire.js +7 -1
- package/package.json +4 -4
package/dist/http/server.js
CHANGED
|
@@ -2,8 +2,8 @@ import http from "node:http";
|
|
|
2
2
|
import { redactErrorMessage, redactHead } from "../observability/run-terminal-log.js";
|
|
3
3
|
import { once } from "node:events";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow, GLOBAL_USAGE_KEY, usageRetryAfterMs,
|
|
6
|
-
import { decideParkedAgent,
|
|
5
|
+
import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow, GLOBAL_USAGE_KEY, usageRetryAfterMs, terminalProjection, PAUSE_REGISTRY } from "@sema-agent/core";
|
|
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";
|
|
@@ -24,7 +24,6 @@ import { publicStoreProbeError } from "../store-live-probe.js";
|
|
|
24
24
|
import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
|
|
25
25
|
import {} from "../orchestration/workflow-agent-steer.js";
|
|
26
26
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
|
|
27
|
-
import { HANDS_BAND_TOOL_NAMES } from "../capabilities/hands-lane.js";
|
|
28
27
|
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../fleet/fleet-bus.js";
|
|
29
28
|
import { defaultSubagentTailBus, projectTailFrame } from "../fleet/subagent-tail-bus.js";
|
|
30
29
|
import { APPROVAL_GATE_KINDS, createApprovalCardEmitter, isApprovalGateKind, resolveApprovalLeg, resolveStreamApprovalGate } from "../tool-approval.js";
|
|
@@ -119,7 +118,6 @@ const PROCESS_STARTED_AT_MS = Date.now() - Math.round(process.uptime() * 1000);
|
|
|
119
118
|
const MAX_BODY = 8 * 1024 * 1024;
|
|
120
119
|
const MAX_IMAGES_PER_REQUEST = 20;
|
|
121
120
|
const MAX_IMAGE_BASE64_BYTES = 6 * 1024 * 1024;
|
|
122
|
-
const HAND_TOOL_NAMES = HANDS_BAND_TOOL_NAMES;
|
|
123
121
|
function mentionableNames(config, catalog) {
|
|
124
122
|
const allowlist = config.atModelAllowlist ?? [];
|
|
125
123
|
return Object.keys(catalog).filter((n) => n !== "default" && isModelAllowlisted(n, catalog, allowlist));
|
|
@@ -999,8 +997,9 @@ export function createHttpServer(rawDeps) {
|
|
|
999
997
|
}
|
|
1000
998
|
try {
|
|
1001
999
|
const auth = deps.authorize ? await deps.authorize({ req, body }) : undefined;
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1000
|
+
const engineNotices = [];
|
|
1001
|
+
const spec = await deps.resolveSpec(body, req, auth, { leg: "fresh", ...(legFacts?.liveStream === true ? { liveStream: true } : {}), onNotice: (n) => engineNotices.push(n) });
|
|
1002
|
+
return { spec, runner: runnerFor(spec), auth, verify, cascade, jobId: body.jobId, body, ...(engineNotices.length > 0 ? { engineNotices } : {}) };
|
|
1004
1003
|
}
|
|
1005
1004
|
catch (err) {
|
|
1006
1005
|
if (err instanceof HttpError) {
|
|
@@ -1176,29 +1175,60 @@ export function createHttpServer(rawDeps) {
|
|
|
1176
1175
|
if (parkedForkLeaseDenial)
|
|
1177
1176
|
return parkedForkLeaseDenial;
|
|
1178
1177
|
const ownerLeaseAdmittedHere = req !== undefined;
|
|
1179
|
-
if (req !== undefined
|
|
1178
|
+
if (req !== undefined) {
|
|
1180
1179
|
const parkedAgentStore = deps.backgroundAgentStore;
|
|
1181
1180
|
const parkedTool = deps.parkedReviveTool;
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
...(deps.
|
|
1189
|
-
|
|
1190
|
-
|
|
1181
|
+
const parkWarn = (event, fields) => deps.logger?.warn?.(event, fields);
|
|
1182
|
+
const origin = await findParkOrigin({
|
|
1183
|
+
...(parkedAgentStore !== undefined && parkedTool !== undefined ? { agentStore: parkedAgentStore } : {}),
|
|
1184
|
+
...(deps.workflowAgentSessionIndex !== undefined && deps.workflowRunStore !== undefined
|
|
1185
|
+
? { workflowIndex: deps.workflowAgentSessionIndex, workflowRunStore: deps.workflowRunStore }
|
|
1186
|
+
: {}),
|
|
1187
|
+
...(deps.logger ? { warn: parkWarn } : {}),
|
|
1188
|
+
}, { token, scope: cp.scope, sessionId: cp.sessionId });
|
|
1189
|
+
const parkedReq = {
|
|
1191
1190
|
token, scope: cp.scope, sessionId: cp.sessionId, pendingAction: cp.pendingAction,
|
|
1192
1191
|
decision, hostDecision, ...(reason !== undefined ? { reason } : {}),
|
|
1193
1192
|
...(binding !== undefined ? { binding: { ...(binding.boundCallId !== undefined ? { boundCallId: binding.boundCallId } : {}), ...(binding.boundInputHash !== undefined ? { boundInputHash: binding.boundInputHash } : {}), ...(binding.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}) } } : {}),
|
|
1194
|
-
...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
|
|
1195
1193
|
...(answer !== undefined ? { answer } : {}),
|
|
1196
|
-
|
|
1197
|
-
|
|
1194
|
+
...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
|
|
1195
|
+
};
|
|
1196
|
+
if (origin?.kind === "background" && parkedAgentStore !== undefined && parkedTool !== undefined) {
|
|
1197
|
+
const parked = await withPrincipal(decodeCheckpointScope(cp.scope), () => decideParkedAgent({
|
|
1198
|
+
agentStore: parkedAgentStore,
|
|
1199
|
+
checkpointStore: cs,
|
|
1200
|
+
registry: defaultTaskRegistry,
|
|
1201
|
+
reviveTool: parkedTool,
|
|
1202
|
+
...(deps.parkedKnownAgentTypes ? { knownAgentTypes: deps.parkedKnownAgentTypes } : {}),
|
|
1203
|
+
...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
|
|
1204
|
+
...(deps.logger ? { warn: parkWarn } : {}),
|
|
1205
|
+
}, parkedReq, { handle: origin.handle, row: origin.row }));
|
|
1198
1206
|
void ctxPromise.catch(() => { });
|
|
1199
1207
|
noteLateSettleIfAccepted(parked);
|
|
1200
1208
|
return parked;
|
|
1201
1209
|
}
|
|
1210
|
+
if (origin?.kind === "workflow") {
|
|
1211
|
+
void ctxPromise.catch(() => { });
|
|
1212
|
+
const plan = planWorkflowParkedDecide(origin, parkedReq);
|
|
1213
|
+
if (!plan.ok)
|
|
1214
|
+
return plan.deny;
|
|
1215
|
+
const hostToken = await cs
|
|
1216
|
+
.findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
|
|
1217
|
+
.catch(() => undefined);
|
|
1218
|
+
if (!hostToken) {
|
|
1219
|
+
return {
|
|
1220
|
+
status: 409,
|
|
1221
|
+
body: {
|
|
1222
|
+
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",
|
|
1223
|
+
errorCode: "decide.workflow_host_not_parked",
|
|
1224
|
+
runId: plan.runId,
|
|
1225
|
+
},
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
return await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, [
|
|
1229
|
+
{ runId: plan.runId, token, outcome: plan.outcome },
|
|
1230
|
+
]);
|
|
1231
|
+
}
|
|
1202
1232
|
}
|
|
1203
1233
|
let ctx;
|
|
1204
1234
|
try {
|
|
@@ -1211,8 +1241,9 @@ export function createHttpServer(rawDeps) {
|
|
|
1211
1241
|
return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
|
|
1212
1242
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
1213
1243
|
let spec;
|
|
1244
|
+
let engineNotices = [];
|
|
1214
1245
|
try {
|
|
1215
|
-
spec = await
|
|
1246
|
+
({ spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth));
|
|
1216
1247
|
}
|
|
1217
1248
|
catch (e) {
|
|
1218
1249
|
if (e instanceof HttpError) {
|
|
@@ -1224,22 +1255,6 @@ export function createHttpServer(rawDeps) {
|
|
|
1224
1255
|
throw e;
|
|
1225
1256
|
}
|
|
1226
1257
|
const pendingTool = cp.pendingAction.toolName;
|
|
1227
|
-
const pendingToolCanon = pendingTool;
|
|
1228
|
-
const availableTools = new Set((spec.tools ?? []).map((t) => t.name));
|
|
1229
|
-
if (deps.config.remoteExec)
|
|
1230
|
-
for (const name of HAND_TOOL_NAMES)
|
|
1231
|
-
availableTools.add(name);
|
|
1232
|
-
if (deps.checkpointStore)
|
|
1233
|
-
availableTools.add("AskUserQuestion");
|
|
1234
|
-
if (spec.selfOrchestration === true && deps.config.selfOrchestrationEnabled) {
|
|
1235
|
-
availableTools.add(RUN_WORKFLOW_TOOL_NAME);
|
|
1236
|
-
}
|
|
1237
|
-
if (decision === "approve" && pendingToolCanon && !availableTools.has(pendingToolCanon)) {
|
|
1238
|
-
return {
|
|
1239
|
-
status: 422,
|
|
1240
|
-
body: { error: `pending action no longer satisfiable: tool "${pendingToolCanon}" is not in the current task config (scenario changed since suspend)`, errorCode: "conflict.pending_action_unsatisfiable" },
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
1258
|
if (decision === "approve" && pendingTool === "AskUserQuestion" && !answer) {
|
|
1244
1259
|
return { status: 400, body: { error: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", errorCode: "request.field_conflict" } };
|
|
1245
1260
|
}
|
|
@@ -1261,10 +1276,15 @@ export function createHttpServer(rawDeps) {
|
|
|
1261
1276
|
hostDecision,
|
|
1262
1277
|
};
|
|
1263
1278
|
const verifyRounds = verifyRoundsFromBody(ctx.body);
|
|
1264
|
-
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 } : {}) });
|
|
1279
|
+
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 } : {}) });
|
|
1265
1280
|
noteLateSettleIfAccepted(driven);
|
|
1266
1281
|
return driven;
|
|
1267
1282
|
}
|
|
1283
|
+
const resolveResumeSpec = async (body, req, auth) => {
|
|
1284
|
+
const engineNotices = [];
|
|
1285
|
+
const spec = await deps.resolveSpec({ ...body, resumeAt: undefined }, req, auth, { leg: "resume", onNotice: (n) => engineNotices.push(n) });
|
|
1286
|
+
return { spec, engineNotices };
|
|
1287
|
+
};
|
|
1268
1288
|
async function driveResumeIntoRunLog(args) {
|
|
1269
1289
|
if (args.acceptEarly !== true)
|
|
1270
1290
|
return await driveResumeLeg(args);
|
|
@@ -1520,6 +1540,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1520
1540
|
const append = (type, data) => (rs && taskId ? serialAppend(type, data) : Promise.resolve());
|
|
1521
1541
|
unregisterEngineNotice = registerEngineNoticeLeg({
|
|
1522
1542
|
sessionId,
|
|
1543
|
+
...(args.engineNotices !== undefined && args.engineNotices.length > 0 ? { pending: [...args.engineNotices] } : {}),
|
|
1523
1544
|
durable: (row) => void append("engine_notice", row).catch((err) => {
|
|
1524
1545
|
recordFailOpen("server.engine-notice.durable-append-failed", `leg=resume code=${row.code} task=${taskId ?? ""}`);
|
|
1525
1546
|
deps.logger?.warn?.("engine_notice_append_failed", { taskId, noticeCode: row.code, sessionId: row.sessionId, err: err instanceof Error ? err.message : String(err) });
|
|
@@ -1549,6 +1570,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1549
1570
|
resumeLegLive = true;
|
|
1550
1571
|
const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
|
|
1551
1572
|
const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
|
|
1573
|
+
...(args.workflowParkedResume !== undefined ? { workflowParkedResume: args.workflowParkedResume } : {}),
|
|
1552
1574
|
onForwardEvent: (e) => {
|
|
1553
1575
|
fleetPub?.onForwardEvent(e);
|
|
1554
1576
|
{
|
|
@@ -1946,12 +1968,12 @@ export function createHttpServer(rawDeps) {
|
|
|
1946
1968
|
if (!ctx)
|
|
1947
1969
|
return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
|
|
1948
1970
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
1949
|
-
const spec = await
|
|
1971
|
+
const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
|
|
1950
1972
|
const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
|
|
1951
1973
|
const verifyRounds = verifyRoundsFromBody(ctx.body);
|
|
1952
|
-
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 } : {}) });
|
|
1974
|
+
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 } : {}) });
|
|
1953
1975
|
}
|
|
1954
|
-
async function resumeWake(sessionId, message, caller, req, acceptEarly) {
|
|
1976
|
+
async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowParkedResume) {
|
|
1955
1977
|
const cs = deps.checkpointStore;
|
|
1956
1978
|
const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
|
|
1957
1979
|
const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
|
|
@@ -2005,7 +2027,7 @@ export function createHttpServer(rawDeps) {
|
|
|
2005
2027
|
if (!ctx)
|
|
2006
2028
|
return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
|
|
2007
2029
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
2008
|
-
const spec = await
|
|
2030
|
+
const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
|
|
2009
2031
|
const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
|
|
2010
2032
|
return driveResumeIntoRunLog({
|
|
2011
2033
|
runner: runnerFor(spec),
|
|
@@ -2018,8 +2040,10 @@ export function createHttpServer(rawDeps) {
|
|
|
2018
2040
|
outcome: { gate: "wake", ...(message !== undefined ? { message: { text: message, trusted } } : {}) },
|
|
2019
2041
|
verifyRounds: undefined,
|
|
2020
2042
|
mintFreshRun: { source: "wake" },
|
|
2043
|
+
...(engineNotices.length > 0 ? { engineNotices } : {}),
|
|
2021
2044
|
...(acceptEarly === true ? { acceptEarly: true } : {}),
|
|
2022
2045
|
...(req !== undefined ? { callerInitiated: true } : {}),
|
|
2046
|
+
...(workflowParkedResume !== undefined ? { workflowParkedResume } : {}),
|
|
2023
2047
|
});
|
|
2024
2048
|
}
|
|
2025
2049
|
async function resumePlanReview(sessionId, decision, editedPlan, reason, req, log, acceptEarly, decider) {
|
|
@@ -2083,7 +2107,7 @@ export function createHttpServer(rawDeps) {
|
|
|
2083
2107
|
if (!ctx)
|
|
2084
2108
|
return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
|
|
2085
2109
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
2086
|
-
const spec = await
|
|
2110
|
+
const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
|
|
2087
2111
|
const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
|
|
2088
2112
|
const verifyRounds = verifyRoundsFromBody(ctx.body);
|
|
2089
2113
|
const outcome = {
|
|
@@ -2092,7 +2116,7 @@ export function createHttpServer(rawDeps) {
|
|
|
2092
2116
|
...(editedPlan !== undefined ? { editedPlan } : {}),
|
|
2093
2117
|
...(reason ? { reason } : {}),
|
|
2094
2118
|
};
|
|
2095
|
-
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 } : {}) });
|
|
2119
|
+
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 } : {}) });
|
|
2096
2120
|
}
|
|
2097
2121
|
function recordTaskResult(result) {
|
|
2098
2122
|
const plane = terminalProjection(result.terminal);
|
|
@@ -2331,7 +2355,7 @@ export function createHttpServer(rawDeps) {
|
|
|
2331
2355
|
return;
|
|
2332
2356
|
const expired = await cs.listExpiredApprovalGates(now).catch(() => []);
|
|
2333
2357
|
const isParkedOwned = async (sessionId) => {
|
|
2334
|
-
if (deps.backgroundAgentStore === undefined)
|
|
2358
|
+
if (deps.backgroundAgentStore === undefined && deps.workflowAgentSessionIndex === undefined)
|
|
2335
2359
|
return false;
|
|
2336
2360
|
try {
|
|
2337
2361
|
const token = await cs.findPendingTokenBySession(sessionId, undefined, { gateKinds: APPROVAL_GATE_KINDS });
|
|
@@ -2340,7 +2364,12 @@ export function createHttpServer(rawDeps) {
|
|
|
2340
2364
|
const cp = await cs.get(token);
|
|
2341
2365
|
if (!cp)
|
|
2342
2366
|
return false;
|
|
2343
|
-
return (await
|
|
2367
|
+
return (await findParkOrigin({
|
|
2368
|
+
...(deps.backgroundAgentStore !== undefined ? { agentStore: deps.backgroundAgentStore } : {}),
|
|
2369
|
+
...(deps.workflowAgentSessionIndex !== undefined && deps.workflowRunStore !== undefined
|
|
2370
|
+
? { workflowIndex: deps.workflowAgentSessionIndex, workflowRunStore: deps.workflowRunStore }
|
|
2371
|
+
: {}),
|
|
2372
|
+
}, { token, scope: cp.scope, sessionId: cp.sessionId })) !== undefined;
|
|
2344
2373
|
}
|
|
2345
2374
|
catch {
|
|
2346
2375
|
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,
|
|
@@ -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,
|
package/dist/mcp-tool-faces.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TOOL_APPROVAL_CARDS, TOOL_FAMILIES, TOOL_PATH_ACCESSES } from "@sema-agent/core";
|
|
1
|
+
import { TOOL_APPROVAL_CARDS, TOOL_FAMILIES, TOOL_PATH_ABSENCES, TOOL_PATH_ACCESSES, TOOL_PATH_BASES } from "@sema-agent/core";
|
|
2
2
|
function isRecord(v) {
|
|
3
3
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4
4
|
}
|
|
@@ -26,17 +26,26 @@ function readOneFace(raw) {
|
|
|
26
26
|
const pt = raw.pathTarget;
|
|
27
27
|
if (!isRecord(pt) || typeof pt.param !== "string" || !memberOf(TOOL_PATH_ACCESSES, pt.access))
|
|
28
28
|
return undefined;
|
|
29
|
-
if (!onlyKeys(pt, ["param", "access", "aliases", "skillScopeEligible"]))
|
|
29
|
+
if (!onlyKeys(pt, ["param", "access", "aliases", "skillScopeEligible", "base", "absent", "patternParam"]))
|
|
30
30
|
return undefined;
|
|
31
31
|
if (pt.aliases !== undefined && !isStringArray(pt.aliases))
|
|
32
32
|
return undefined;
|
|
33
33
|
if (pt.skillScopeEligible !== undefined && typeof pt.skillScopeEligible !== "boolean")
|
|
34
34
|
return undefined;
|
|
35
|
+
if (pt.base !== undefined && !memberOf(TOOL_PATH_BASES, pt.base))
|
|
36
|
+
return undefined;
|
|
37
|
+
if (pt.absent !== undefined && !memberOf(TOOL_PATH_ABSENCES, pt.absent))
|
|
38
|
+
return undefined;
|
|
39
|
+
if (pt.patternParam !== undefined && typeof pt.patternParam !== "string")
|
|
40
|
+
return undefined;
|
|
35
41
|
out.pathTarget = {
|
|
36
42
|
param: pt.param,
|
|
37
43
|
access: pt.access,
|
|
38
44
|
...(pt.aliases !== undefined ? { aliases: pt.aliases } : {}),
|
|
39
45
|
...(pt.skillScopeEligible === true ? { skillScopeEligible: true } : {}),
|
|
46
|
+
...(pt.base !== undefined ? { base: pt.base } : {}),
|
|
47
|
+
...(pt.absent !== undefined ? { absent: pt.absent } : {}),
|
|
48
|
+
...(pt.patternParam !== undefined ? { patternParam: pt.patternParam } : {}),
|
|
40
49
|
};
|
|
41
50
|
}
|
|
42
51
|
if (raw.renderHints !== undefined) {
|
|
@@ -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 读出来的结论会是假的。计数让「某个世代的产生者一直在发坏记录」显形。",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **运维写下的 ask 一律自报为「显式 ask 规则」** —— 本仓所有 operator/治理面 `ask` 裁决的**唯一**铸造点。
|
|
3
|
+
*
|
|
4
|
+
* ── 为什么存在(成因是一次真红,不是预防性设计)────────────────────────────────────────────────
|
|
5
|
+
* core 7.11.0 [ref] 给 allow 层加了**第二位成员**:一条被 `readOnlyShellVerdict` 证明只读的 shell 命令,
|
|
6
|
+
* 在每一道收紧车道之后**直接放行**(`decisionReason:"read_only"`),没有分类器轮次、没有卡、没有 park。
|
|
7
|
+
* 它的消费谓词(`allowLayerMayClear`,与持久 allow 规则**同一只**)明写不会越过的几件事里,有一件是
|
|
8
|
+
* **显式 ask 规则**(`PermissionResult.matchedAskRule`)。
|
|
9
|
+
*
|
|
10
|
+
* 而本仓的 operator 门此前铸的是**裸** `{action:"ask", message}` —— 一条「someone WROTE 了这条规则」的
|
|
11
|
+
* 事实,从来没有在裁决上说出口。于是 7.11.0 提货那一拍,`APPROVAL_REQUIRE=Bash` 的部署上
|
|
12
|
+
* `echo x` / `cd sub` / `grep -r … ` 这一类**全部静默自动执行**:运维亲手写下的「这只工具必须人批」被
|
|
13
|
+
* 一个自动层清掉了,而且**一声不响**(本仓 7.68.0 的四条 e2e 当场红,那是这条缺口的机器证据)。
|
|
14
|
+
*
|
|
15
|
+
* ── 修法:一条规则,一个铸点 ────────────────────────────────────────────────────────────────
|
|
16
|
+
* core 的 `matchedAskRule` 顶注逐字给了自声明的授权与方向:「A policy self-declaring the member only ever
|
|
17
|
+
* OPTS ITS OWN ask OUT of classifier auto-resolution (**tightening** — the false-claim direction is safe)」。
|
|
18
|
+
* 所以本仓把它做成**不变量**而不是补丁:凡是「运维/部署写下的声明产生的 ask」,一律经本函数铸,自带那条
|
|
19
|
+
* 声明的原文。于是**任何**自动放行层(今天的持久规则与只读判词,明天的第三位成员)都够不着它 —— 规则从
|
|
20
|
+
* 「每来一个新 allow 层就回来补一次名单」变成「运维写的 ask 不可被自动清除」这一句。
|
|
21
|
+
*
|
|
22
|
+
* 🔴 **副作用是有意的、且方向只有一个**:带 `matchedAskRule` 的 ask 同时**退出 auto 模式分类器的自动
|
|
23
|
+
* 裁决**(core:「a person's standing "ask me each time" is not classifier hesitation」)。对
|
|
24
|
+
* `APPROVAL_NEVER_AUTO` 这是把一条**本来就漏**的缝补上(那张表的语义逐字是「always human」,而分类器
|
|
25
|
+
* 今天能替它答);对 `APPROVAL_REQUIRE` / `commandPolicy` 这是收紧一格 —— 与这两个旋钮的字面语义
|
|
26
|
+
* (「必须批准」)一致,且方向是 fail-closed。
|
|
27
|
+
*
|
|
28
|
+
* 🔴 **谁不进这扇门**(逐条,判据在 core 的同一段顶注):
|
|
29
|
+
* · **模式默认**的 ask(`task-settings.ts` 的 plan/default 档折叠)—— core 明写 `matchedAskRule`
|
|
30
|
+
* 「never for a `defaultAction:"ask"` fallback(an unmatched call is default-closed posture, not a
|
|
31
|
+
* per-call instruction)」;把姿态默认伪装成一条写下的规则会让分类器车道的资格判据整条失真。
|
|
32
|
+
* · **钩子**的 ask(`hooks/hook-runner.ts`)—— core 在钩子面**自己盖** `decisionReason:"hook"`
|
|
33
|
+
* (`dist/core/gate-fold.js`),那是 `allowLayerMayClear` 的另一条独立豁免;本仓再盖一次是第二个写者。
|
|
34
|
+
* · **问答工具**(`deployment-governance.ts` 的 `AskUserQuestion`)—— core 的谓词把保留问答工具单列豁免。
|
|
35
|
+
*/
|
|
36
|
+
import type { PermissionResult } from "@sema-agent/core";
|
|
37
|
+
/**
|
|
38
|
+
* 铸一条**运维声明产生的** `ask`。
|
|
39
|
+
*
|
|
40
|
+
* @param rule 产生这条 ask 的**声明原文**(进 `matchedAskRule`)。它是 gate 内部消费的机器位 ——
|
|
41
|
+
* core 顶注逐字:「consumed inside the gate and deliberately NOT copied onto the park/approval-card
|
|
42
|
+
* request」,所以它既不上 wire、也不进卡,写的必须是**真实的那条声明**而不是一句好看的话。
|
|
43
|
+
* @param message 给人看的那句话(照旧上卡)。
|
|
44
|
+
*/
|
|
45
|
+
export declare function operatorAsk(rule: string, message: string): PermissionResult;
|
|
46
|
+
//# sourceMappingURL=operator-ask.d.ts.map
|