@sema-agent/server 7.68.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.
Files changed (64) hide show
  1. package/USAGE.md +19 -2
  2. package/dist/adoption/plan.js +11 -0
  3. package/dist/approval-card.d.ts +26 -0
  4. package/dist/approval-card.js +11 -0
  5. package/dist/approval.js +5 -4
  6. package/dist/boot/config-center.d.ts +6 -7
  7. package/dist/boot/config-center.js +31 -8
  8. package/dist/boot/leader.js +1 -1
  9. package/dist/boot/workflow-orchestration.d.ts +2 -0
  10. package/dist/boot/workflow-orchestration.js +10 -2
  11. package/dist/budget.js +1 -1
  12. package/dist/config-catalog.js +20 -1
  13. package/dist/config-center/apply-effective.d.ts +8 -1
  14. package/dist/config-center/apply-effective.js +23 -9
  15. package/dist/config-center/facade.d.ts +1 -1
  16. package/dist/config-center/facade.js +1 -1
  17. package/dist/config-center/read-face.d.ts +30 -1
  18. package/dist/config-center/read-face.js +33 -15
  19. package/dist/config-center/restart-signal.js +2 -1
  20. package/dist/config-center/types.d.ts +20 -5
  21. package/dist/config-provider.js +1 -0
  22. package/dist/config-types.d.ts +17 -2
  23. package/dist/config.d.ts +0 -21
  24. package/dist/config.js +19 -6
  25. package/dist/http/route-ctx.d.ts +39 -1
  26. package/dist/http/routes/approvals-assistant.js +1 -0
  27. package/dist/http/routes/capabilities.js +1 -0
  28. package/dist/http/routes/workflows.js +15 -7
  29. package/dist/http/server.d.ts +10 -2
  30. package/dist/http/server.js +70 -24
  31. package/dist/leader/wire.d.ts +9 -2
  32. package/dist/leader/wire.js +6 -6
  33. package/dist/main.js +6 -5
  34. package/dist/model-compat.d.ts +81 -0
  35. package/dist/model-compat.js +52 -0
  36. package/dist/observability/fail-open.d.ts +4 -0
  37. package/dist/observability/fail-open.js +4 -0
  38. package/dist/operator-ask.d.ts +46 -0
  39. package/dist/operator-ask.js +4 -0
  40. package/dist/orchestration/workflow-agent-session-index.d.ts +96 -0
  41. package/dist/orchestration/workflow-agent-session-index.js +146 -0
  42. package/dist/orchestration/workflow-journal-entry.d.ts +78 -0
  43. package/dist/orchestration/workflow-journal-entry.js +20 -0
  44. package/dist/orchestration/workflow-notify-journal.d.ts +43 -1
  45. package/dist/orchestration/workflow-notify-journal.js +43 -3
  46. package/dist/parked-decide.d.ts +115 -5
  47. package/dist/parked-decide.js +94 -26
  48. package/dist/plugins/pg-pool.js +8 -0
  49. package/dist/plugins/store-backend.d.ts +5 -0
  50. package/dist/plugins/store-backend.js +3 -1
  51. package/dist/plugins/tidb-pool.js +8 -0
  52. package/dist/plugins/workflow-journal-store-sql.js +10 -3
  53. package/dist/plugins/workflow-run-store-sql.d.ts +30 -0
  54. package/dist/plugins/workflow-run-store-sql.js +40 -0
  55. package/dist/router/route-orchestration.js +2 -1
  56. package/dist/run-local.js +1 -1
  57. package/dist/runs.js +2 -0
  58. package/dist/runtime-governance.js +5 -4
  59. package/dist/tool-approval.d.ts +20 -0
  60. package/dist/tool-approval.js +2 -1
  61. package/dist/trace/core-keyset-guard.d.ts +3 -3
  62. package/dist/trace/engine-notice-wire.d.ts +1 -1
  63. package/dist/trace/engine-notice-wire.js +1 -0
  64. package/package.json +3 -3
@@ -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, findParkedAgentForCheckpoint } from "../parked-decide.js";
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";
@@ -1175,29 +1175,60 @@ export function createHttpServer(rawDeps) {
1175
1175
  if (parkedForkLeaseDenial)
1176
1176
  return parkedForkLeaseDenial;
1177
1177
  const ownerLeaseAdmittedHere = req !== undefined;
1178
- if (req !== undefined && deps.backgroundAgentStore !== undefined && deps.parkedReviveTool !== undefined) {
1178
+ if (req !== undefined) {
1179
1179
  const parkedAgentStore = deps.backgroundAgentStore;
1180
1180
  const parkedTool = deps.parkedReviveTool;
1181
- const parked = await withPrincipal(decodeCheckpointScope(cp.scope), () => decideParkedAgent({
1182
- agentStore: parkedAgentStore,
1183
- checkpointStore: cs,
1184
- registry: defaultTaskRegistry,
1185
- reviveTool: parkedTool,
1186
- ...(deps.parkedKnownAgentTypes ? { knownAgentTypes: deps.parkedKnownAgentTypes } : {}),
1187
- ...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
1188
- ...(deps.logger ? { warn: (event, fields) => deps.logger?.warn?.(event, fields) } : {}),
1189
- }, {
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 = {
1190
1190
  token, scope: cp.scope, sessionId: cp.sessionId, pendingAction: cp.pendingAction,
1191
1191
  decision, hostDecision, ...(reason !== undefined ? { reason } : {}),
1192
1192
  ...(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
1193
  ...(answer !== undefined ? { answer } : {}),
1195
- }));
1196
- if (parked !== undefined) {
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 }));
1197
1206
  void ctxPromise.catch(() => { });
1198
1207
  noteLateSettleIfAccepted(parked);
1199
1208
  return parked;
1200
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
+ }
1201
1232
  }
1202
1233
  let ctx;
1203
1234
  try {
@@ -1210,8 +1241,9 @@ export function createHttpServer(rawDeps) {
1210
1241
  return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1211
1242
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1212
1243
  let spec;
1244
+ let engineNotices = [];
1213
1245
  try {
1214
- spec = await deps.resolveSpec({ ...ctx.body, resumeAt: undefined }, req, auth);
1246
+ ({ spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth));
1215
1247
  }
1216
1248
  catch (e) {
1217
1249
  if (e instanceof HttpError) {
@@ -1244,10 +1276,15 @@ export function createHttpServer(rawDeps) {
1244
1276
  hostDecision,
1245
1277
  };
1246
1278
  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 } : {}) });
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 } : {}) });
1248
1280
  noteLateSettleIfAccepted(driven);
1249
1281
  return driven;
1250
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
+ };
1251
1288
  async function driveResumeIntoRunLog(args) {
1252
1289
  if (args.acceptEarly !== true)
1253
1290
  return await driveResumeLeg(args);
@@ -1503,6 +1540,7 @@ export function createHttpServer(rawDeps) {
1503
1540
  const append = (type, data) => (rs && taskId ? serialAppend(type, data) : Promise.resolve());
1504
1541
  unregisterEngineNotice = registerEngineNoticeLeg({
1505
1542
  sessionId,
1543
+ ...(args.engineNotices !== undefined && args.engineNotices.length > 0 ? { pending: [...args.engineNotices] } : {}),
1506
1544
  durable: (row) => void append("engine_notice", row).catch((err) => {
1507
1545
  recordFailOpen("server.engine-notice.durable-append-failed", `leg=resume code=${row.code} task=${taskId ?? ""}`);
1508
1546
  deps.logger?.warn?.("engine_notice_append_failed", { taskId, noticeCode: row.code, sessionId: row.sessionId, err: err instanceof Error ? err.message : String(err) });
@@ -1532,6 +1570,7 @@ export function createHttpServer(rawDeps) {
1532
1570
  resumeLegLive = true;
1533
1571
  const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
1534
1572
  const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
1573
+ ...(args.workflowParkedResume !== undefined ? { workflowParkedResume: args.workflowParkedResume } : {}),
1535
1574
  onForwardEvent: (e) => {
1536
1575
  fleetPub?.onForwardEvent(e);
1537
1576
  {
@@ -1929,12 +1968,12 @@ export function createHttpServer(rawDeps) {
1929
1968
  if (!ctx)
1930
1969
  return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1931
1970
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1932
- const spec = await deps.resolveSpec({ ...ctx.body, resumeAt: undefined }, req, auth);
1971
+ const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
1933
1972
  const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1934
1973
  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 } : {}) });
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 } : {}) });
1936
1975
  }
1937
- async function resumeWake(sessionId, message, caller, req, acceptEarly) {
1976
+ async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowParkedResume) {
1938
1977
  const cs = deps.checkpointStore;
1939
1978
  const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
1940
1979
  const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
@@ -1988,7 +2027,7 @@ export function createHttpServer(rawDeps) {
1988
2027
  if (!ctx)
1989
2028
  return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1990
2029
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1991
- const spec = await deps.resolveSpec({ ...ctx.body, resumeAt: undefined }, req, auth);
2030
+ const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
1992
2031
  const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1993
2032
  return driveResumeIntoRunLog({
1994
2033
  runner: runnerFor(spec),
@@ -2001,8 +2040,10 @@ export function createHttpServer(rawDeps) {
2001
2040
  outcome: { gate: "wake", ...(message !== undefined ? { message: { text: message, trusted } } : {}) },
2002
2041
  verifyRounds: undefined,
2003
2042
  mintFreshRun: { source: "wake" },
2043
+ ...(engineNotices.length > 0 ? { engineNotices } : {}),
2004
2044
  ...(acceptEarly === true ? { acceptEarly: true } : {}),
2005
2045
  ...(req !== undefined ? { callerInitiated: true } : {}),
2046
+ ...(workflowParkedResume !== undefined ? { workflowParkedResume } : {}),
2006
2047
  });
2007
2048
  }
2008
2049
  async function resumePlanReview(sessionId, decision, editedPlan, reason, req, log, acceptEarly, decider) {
@@ -2066,7 +2107,7 @@ export function createHttpServer(rawDeps) {
2066
2107
  if (!ctx)
2067
2108
  return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
2068
2109
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
2069
- const spec = await deps.resolveSpec({ ...ctx.body, resumeAt: undefined }, req, auth);
2110
+ const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
2070
2111
  const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
2071
2112
  const verifyRounds = verifyRoundsFromBody(ctx.body);
2072
2113
  const outcome = {
@@ -2075,7 +2116,7 @@ export function createHttpServer(rawDeps) {
2075
2116
  ...(editedPlan !== undefined ? { editedPlan } : {}),
2076
2117
  ...(reason ? { reason } : {}),
2077
2118
  };
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 } : {}) });
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 } : {}) });
2079
2120
  }
2080
2121
  function recordTaskResult(result) {
2081
2122
  const plane = terminalProjection(result.terminal);
@@ -2314,7 +2355,7 @@ export function createHttpServer(rawDeps) {
2314
2355
  return;
2315
2356
  const expired = await cs.listExpiredApprovalGates(now).catch(() => []);
2316
2357
  const isParkedOwned = async (sessionId) => {
2317
- if (deps.backgroundAgentStore === undefined)
2358
+ if (deps.backgroundAgentStore === undefined && deps.workflowAgentSessionIndex === undefined)
2318
2359
  return false;
2319
2360
  try {
2320
2361
  const token = await cs.findPendingTokenBySession(sessionId, undefined, { gateKinds: APPROVAL_GATE_KINDS });
@@ -2323,7 +2364,12 @@ export function createHttpServer(rawDeps) {
2323
2364
  const cp = await cs.get(token);
2324
2365
  if (!cp)
2325
2366
  return false;
2326
- return (await findParkedAgentForCheckpoint(deps.backgroundAgentStore, { token, scope: cp.scope, sessionId: cp.sessionId })) !== undefined;
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;
2327
2373
  }
2328
2374
  catch {
2329
2375
  return false;
@@ -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
- deploymentPosture?: DeploymentPostureSeats;
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 经共享基座吃的
@@ -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 swapRunnerModels = (plane) => {
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.swapModels({ models: plane.models, tiers: plane.tiers });
553
+ rn.swapDeps(next);
554
554
  }
555
555
  catch (err) {
556
556
  if (i > 0)
557
- logger.error("model_plane_swap_split", { failedAt: name, swapped: bootRunners.slice(0, i).map(([n]) => n), err: String(err) });
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, swapRunnerModels });
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 读出来的结论会是假的。计数让「某个世代的产生者一直在发坏记录」显形。",
@@ -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
@@ -0,0 +1,4 @@
1
+ export function operatorAsk(rule, message) {
2
+ return { action: "ask", message, decisionReason: "rule", matchedAskRule: rule };
3
+ }
4
+ //# sourceMappingURL=operator-ask.js.map