@sema-agent/server 7.92.1 → 7.92.2

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.
@@ -34,7 +34,7 @@ export declare function canBatchTransition(from: BatchState, to: BatchState): bo
34
34
  * ── `legKey`(取代数值 `leg`,一腿一凭据)─────────────────────────────────────────────────────────────
35
35
  * = `sha256(resume checkpoint token)` 的 hex,**首腿 = 空串**。一次 park→resume 的 token 就是这条腿的
36
36
  * 天然身份:同 token 重投 = 同一腿(幂等,正确);新 park ⇒ 新 token ⇒ 新腿 ⇒ 新 askId。
37
- * 取摘要而非原始 token 是因为 token 是能力凭据(本仓有 `stripResumeToken` 专门把它从可重放账本里
37
+ * 取摘要而非原始 token 是因为 token 是能力凭据(本仓有 `sealResultForExit` 专门把它从可重放账本里
38
38
  * 剥掉),而 `askId` 是 wire 可见值 ⇒ **只存/只喂摘要**。用它当轴顺带消掉了原裁需要的 `task_run` 加列
39
39
  * 与 `markResuming` 返回加宽(后者有第二个调用点 `http/routes/runs.ts:463`,加宽会误杀已成功 resume 的
40
40
  * running run)。残留边界:同一 run 内**不经 checkpoint token** 的重入形若未来出现会得到同一 legKey ——
@@ -1,5 +1,4 @@
1
- import { terminalProjection } from "@sema-agent/core";
2
- import { pausedCauseOf } from "../../terminal.js";
1
+ import { pausedCauseOf, outwardPlane } from "../../terminal.js";
3
2
  import { leafBudgetFields, s1DebugGit } from "./runner-ctx.js";
4
3
  import {} from "./tasks.js";
5
4
  import { decideApproval, decidePlan } from "./reviewer.js";
@@ -78,12 +77,12 @@ export async function driveSupSuspendResume(initial, deps, implSpec, trap, advan
78
77
  let exitedViaBreak = false;
79
78
  for (let leg = 0; leg < MAX_LEGS; leg++) {
80
79
  foldCost(vr.stats);
81
- const legPlane = terminalProjection(vr.terminal);
80
+ const legPlane = outwardPlane(vr.terminal);
82
81
  if (process.env.S1_DEBUG_SUPCOST)
83
82
  console.error(`[SUPCOST leg=${leg}] status=${legPlane.status} reason=${vr.verification?.unverifiedReason} c1µ=${vr.stats?.costBreakdown?.llmRootMicroUsd} accC1µ=${c1Acc.llmRootMicroUsd} hrGates=${vr.stats?.humanReview?.gates?.length}`);
84
83
  const paused = pausedCauseOf(vr.terminal);
85
84
  const gateKind = paused?.gate.kind;
86
- const parkWord = paused === undefined ? undefined : terminalProjection(paused).status;
85
+ const parkWord = paused === undefined ? undefined : outwardPlane(paused).status;
87
86
  if (parkWord === "suspended") {
88
87
  const cp = await deps.getCheckpoint(implSpec.checkpointStore, paused.token, implSpec.durableApprovalScope);
89
88
  const binding = bindingFromCheckpoint(cp);
@@ -178,7 +177,7 @@ export async function runArm(arm, trap, seed, ctx, deps, supStore) {
178
177
  finishedAt: ctx.clock.now(),
179
178
  });
180
179
  }
181
- const soloPlane = terminalProjection(vr.terminal);
180
+ const soloPlane = outwardPlane(vr.terminal);
182
181
  if (s1DebugGit())
183
182
  console.error("[S1 SOLO vr.status]", soloPlane.status, "| stats:", JSON.stringify(vr.stats)?.slice(0, 200));
184
183
  const oracle = pickOracleVerdicts(await deps.runOracle());
@@ -225,7 +224,7 @@ export async function runArm(arm, trap, seed, ctx, deps, supStore) {
225
224
  finishedAt: ctx.clock.now(),
226
225
  });
227
226
  }
228
- const supPlane = terminalProjection(vr.terminal);
227
+ const supPlane = outwardPlane(vr.terminal);
229
228
  if (s1DebugGit())
230
229
  console.error(`[S1 SUP vr.status driver=${trap.supDriver}]`, supPlane.status, "| repairTerminal:", repairTerminal, "| verdict:", vr.verification?.verdict, "| unverifiedReason:", vr.verification?.unverifiedReason, "| checkpointGate:", JSON.stringify(pausedCauseOf(vr.terminal)?.gate), "| error:", vr.error, "| stats:", JSON.stringify(vr.stats)?.slice(0, 180));
231
230
  const oracle = pickOracleVerdicts(await deps.runOracle());
@@ -1,4 +1,4 @@
1
- import { terminalProjection } from "@sema-agent/core";
1
+ import { outwardPlane } from "../../terminal.js";
2
2
  import { Runner, runWithVerification, resumeWithVerification, runRepairLoop, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, } from "@sema-agent/core";
3
3
  import { loadConfig } from "../../config.js";
4
4
  import { createBrain } from "../../brain.js";
@@ -211,7 +211,7 @@ export function buildLiveDeps(rt, trap, seed, cellId) {
211
211
  runWithVerificationSup: async (implSpec) => {
212
212
  const { runner } = await ensureWorker();
213
213
  const result = await runner.runTask(toSupTaskSpec(baseSpec, implSpec, trap));
214
- const pausePlane = result.terminal.kind === "paused" ? terminalProjection(result.terminal).status : undefined;
214
+ const pausePlane = result.terminal.kind === "paused" ? outwardPlane(result.terminal).status : undefined;
215
215
  const unverifiedReason = pausePlane === "needs_review" ? "needs_review" : pausePlane === "suspended" ? "suspended" : "no_verdict";
216
216
  return { ...result, verification: { verdict: "unverified", unverifiedReason, rounds: 0, findings: [] } };
217
217
  },
@@ -1,4 +1,5 @@
1
- import { Runner, terminalProjection, InMemoryToolResultStore, TtlSessionStore, uuidv7, createAllowDenyPolicy, workflowsCapability } from "@sema-agent/core";
1
+ import { Runner, InMemoryToolResultStore, TtlSessionStore, uuidv7, createAllowDenyPolicy, workflowsCapability } from "@sema-agent/core";
2
+ import { outwardPlane } from "../terminal.js";
2
3
  import { createSessionTitler } from "../session-titler.js";
3
4
  import { cardRuleScopeRoot } from "../task-cwd.js";
4
5
  import { ForkRoutingSessionStore } from "../plugins/fork-routing-session-store.js";
@@ -149,7 +150,7 @@ export function runStage06(ctx) {
149
150
  });
150
151
  metrics.inc("hook_llm_calls_total", { type: "agent" });
151
152
  if (result.terminal.kind !== "completed") {
152
- const plane = terminalProjection(result.terminal);
153
+ const plane = outwardPlane(result.terminal);
153
154
  return { ok: false, error: `hook agent ${plane.status}: ${plane.errorMessage ?? ""}` };
154
155
  }
155
156
  return { ok: true, text: result.result ?? "" };
@@ -27,6 +27,7 @@ import { buildMemoryRemoteLaneWarn, memoryEngineBackendFor, memoryEngineRemoteLa
27
27
  import { assertToolResultProvenanceSchema } from "../plugins/tool-result-store-sql.js";
28
28
  import { assertPermissionRuleApprovalSchema, assertPermissionRuleTicketSchema } from "../plugins/permission-rule-store-sql.js";
29
29
  import { assertApprovalAskRuleMaterialSchema } from "../plugins/approval-ask-store-sql.js";
30
+ import { assertSchemaMatchesCode } from "../plugins/schema-consistency.js";
30
31
  import { assertCheckpointColumnSchema } from "../plugins/checkpoint-store-sql.js";
31
32
  import { assertFileHistorySchema } from "../plugins/file-history-store-sql.js";
32
33
  import { resolveStreamApprovalGate } from "../tool-approval.js";
@@ -402,6 +403,14 @@ export async function openStores(ctx) {
402
403
  await assertDeviceAuditRebindEventSchema(mysqlDriver(mysqlPool));
403
404
  }
404
405
  }
406
+ {
407
+ const mysqlPool = backend?.mysqlPool?.();
408
+ const pgPool = backend?.pgPool?.();
409
+ if (pgPool)
410
+ await assertSchemaMatchesCode(async (sql) => (await pgPool.query(sql)).rows, "pg");
411
+ else if (mysqlPool)
412
+ await assertSchemaMatchesCode(async (sql) => (await mysqlPool.query(sql))[0], "mysql");
413
+ }
405
414
  const sessionStore = createSessionStore(config, backend, metrics);
406
415
  if (config.requirePrincipal && backend?.kind === "local") {
407
416
  throw new Error("REQUIRE_PRINCIPAL=true is not supported on the local file backend (DB_BACKEND=local): session/run CONTENT is " +
@@ -1,10 +1,10 @@
1
1
  import { redactErrorMessage, redactHead } from "../observability/run-terminal.js";
2
2
  import { createHash } from "node:crypto";
3
- import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, GLOBAL_USAGE_KEY, terminalProjection, PAUSE_REGISTRY } from "@sema-agent/core";
3
+ import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, GLOBAL_USAGE_KEY, PAUSE_REGISTRY } from "@sema-agent/core";
4
4
  import { decideParkedAgent, denyWorkflowParksUnreadable, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
5
5
  import { HttpError, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
6
6
  import { evictIfConflict, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
7
- import { failureCodeOf, nestedPauseOf, stripResumeToken } from "../terminal.js";
7
+ import { failureCodeOf, nestedPauseOf, sealResultForExit, outwardPlane } from "../terminal.js";
8
8
  import { mintCancelledResult } from "../run-cancel-context.js";
9
9
  import {} from "../host-decision.js";
10
10
  import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
@@ -550,7 +550,7 @@ export function createResumeLegs(ctx) {
550
550
  resumeLegLive = true;
551
551
  const vr = await resumeWithVerification(legRunner, token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, resumeObjective, verifyRounds, verifyInternals);
552
552
  await commitResumeCtx();
553
- let safe = stripResumeToken(vr);
553
+ let safe = sealResultForExit(vr);
554
554
  const vrNested = nestedPauseOf(vr.terminal);
555
555
  const reParkStatus = nestedParkStatusOf(vr);
556
556
  const reSuspended = vrNested !== undefined;
@@ -679,7 +679,7 @@ export function createResumeLegs(ctx) {
679
679
  if (taskId)
680
680
  steerableRuns.set(taskId, stream);
681
681
  if (!taskId || !rs)
682
- return await stream.result();
682
+ return sealResultForExit(await stream.result());
683
683
  onAccepted?.(acceptedResumingReceipt(taskId, sessionId));
684
684
  for await (const ev of stream) {
685
685
  recordTurnActivity(taskId);
@@ -790,7 +790,7 @@ export function createResumeLegs(ctx) {
790
790
  }
791
791
  await flush();
792
792
  const r = await stream.result();
793
- const rPark = r.terminal.kind === "paused" ? terminalProjection(r.terminal).status : undefined;
793
+ const rPark = r.terminal.kind === "paused" ? outwardPlane(r.terminal).status : undefined;
794
794
  if (rPark === "suspended") {
795
795
  const parkCallId = await parkToolCallId(r, deps.checkpointStore);
796
796
  await append("suspended", { gate: r.terminal.kind === "paused" ? r.terminal.gate : null, ...(parkCallId !== undefined ? { toolCallId: parkCallId } : {}) });
@@ -812,7 +812,7 @@ export function createResumeLegs(ctx) {
812
812
  }
813
813
  return r;
814
814
  }
815
- const safe = await attachModelUsage(stripResumeToken(r), { append, getEvents: (id, a) => rs.getEvents(id, a), modelUsage: deps.modelUsage, taskId });
815
+ const safe = await attachModelUsage(sealResultForExit(r), { append, getEvents: (id, a) => rs.getEvents(id, a), modelUsage: deps.modelUsage, taskId });
816
816
  await append("done", { result: safe });
817
817
  if (taskConfig.suggestNextPrompts && safe.terminal.kind === "completed") {
818
818
  const suggestions = await stream.suggestions().catch(() => []);
@@ -951,16 +951,17 @@ export function createResumeLegs(ctx) {
951
951
  }
952
952
  evictIfConflict(deps.runner, sessionId, result);
953
953
  const checkpointReopened = reopenConfirmedPark;
954
- const resumePlane = terminalProjection(result.terminal);
954
+ const sealed = sealResultForExit(result);
955
+ const resumePlane = outwardPlane(sealed.terminal);
955
956
  if (taskId && deps.runStore) {
956
957
  if (resumePlane.status === "suspended" || checkpointReopened)
957
958
  await deps.runStore.setSuspended(taskId);
958
959
  else if (resumePlane.status === "needs_review")
959
960
  await deps.runStore.setNeedsReview(taskId);
960
961
  else
961
- await deps.runStore.setTerminal(taskId, resumePlane.status, stripResumeToken(result), resumePlane.errorMessage ?? null);
962
+ await deps.runStore.setTerminal(taskId, resumePlane.status, sealed, resumePlane.errorMessage ?? null);
962
963
  }
963
- settleFleet(checkpointReopened ? "suspended" : resumePlane.status, fleetRunResiduals(result));
964
+ settleFleet(checkpointReopened ? "suspended" : resumePlane.status, fleetRunResiduals(sealed));
964
965
  return {
965
966
  status: 200,
966
967
  body: {
@@ -271,7 +271,9 @@ async function handleRuleWrite(res, rawBody, caller, lane, operatorPrincipals) {
271
271
  switch (written.status) {
272
272
  case "persisted":
273
273
  case "no-op":
274
- sendJson(res, 200, { status: written.status, rev: written.rev, rule: written.row });
274
+ sendJson(res, 200, written.row === undefined
275
+ ? { status: written.status, rev: written.rev, stillLive: written.stillLive }
276
+ : { status: written.status, rev: written.rev, stillLive: written.stillLive, rule: written.row });
275
277
  return;
276
278
  case "rejected":
277
279
  logger.info("rule write refused", { reason: written.reason, detail: written.detail });
@@ -322,6 +324,10 @@ async function handleRuleRevoke(res, rawBody, caller, lane, operatorPrincipals)
322
324
  logger.info("rule revoke failed", { error: outcome.error });
323
325
  sendError(res, 503, "state.rule_remove_failed", "the rule could not be revoked right now — retry, then reconcile with GET /v1/rules");
324
326
  return;
327
+ default: {
328
+ const unknown = outcome;
329
+ throw new Error(`unknown rule revoke outcome: ${JSON.stringify(unknown)}`);
330
+ }
325
331
  }
326
332
  }
327
333
  export const RULES_ROUTES = [
@@ -1,11 +1,11 @@
1
1
  import { once } from "node:events";
2
2
  import { randomBytes, createHash } from "node:crypto";
3
- import { sessionLogDigest, sessionLogDigestsComparable, StreamingImportValidator, SessionError, SessionPolicyError, validateFileHistoryExport } from "@sema-agent/core";
3
+ import { sessionLogDigest, sessionLogDigestsComparable, StreamingImportValidator, SessionError, validateFileHistoryExport } from "@sema-agent/core";
4
4
  import { StringDecoder } from "node:string_decoder";
5
5
  import { exportSessionManifest, fileHistoryImportFace, replayFileHistory, replayPolicyRecords, overwriteWipeIncapable, wipeSessionAttendants } from "../../session-sync.js";
6
6
  import { classifySyncRelationshipByIds, SyncConflictError } from "../../session-sync-kernel.js";
7
7
  import { isUuidShape } from "../../security.js";
8
- import { sendJson, sendError, msg } from "../send.js";
8
+ import { sendJson, sendError, sendSessionPolicyError, msg } from "../send.js";
9
9
  import { buildDisplaySafeUrl } from "../../trace/redact.js";
10
10
  import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
11
11
  const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
@@ -13,7 +13,7 @@ const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
13
13
  const SYNC_IMPORT_BATCH = 500;
14
14
  import { SHA256_HEX_RE } from "../../digest-form.js";
15
15
  import { assertNever } from "../route-ctx.js";
16
- import { readSessionRulesRecords, describeSessionRulesRecordProblem, sessionPolicyErrorHttp } from "../../session-policy-wire.js";
16
+ import { readSessionRulesRecords, describeSessionRulesRecordProblem } from "../../session-policy-wire.js";
17
17
  export function createSessionSyncLocal() {
18
18
  const pendingStagings = new Map();
19
19
  const importLeases = new Map();
@@ -74,7 +74,15 @@ export async function handleSessionSync(req, res, match, ctx) {
74
74
  if (match.decl.id === "session-sync-manifest") {
75
75
  if (!pullGateOk())
76
76
  return;
77
- const out = await exportSessionManifest(sessionId, deps.backend);
77
+ let out;
78
+ try {
79
+ out = await exportSessionManifest(sessionId, deps.backend);
80
+ }
81
+ catch (e) {
82
+ if (sendSessionPolicyError(res, e))
83
+ return;
84
+ throw e;
85
+ }
78
86
  if (out === null) {
79
87
  sendError(res, 404, "not_found.session", "session not found");
80
88
  return;
@@ -281,8 +289,16 @@ export async function handleSessionSync(req, res, match, ctx) {
281
289
  }
282
290
  }
283
291
  const opOk = explicitOperatorOk(gatedPrincipal(req, deps.config), deps.config.operatorPrincipals);
284
- if (deps.sessionPolicyStore)
285
- await replayPolicyRecords(deps.sessionPolicyStore, sessionId, policy, opOk);
292
+ if (deps.sessionPolicyStore) {
293
+ try {
294
+ await replayPolicyRecords(deps.sessionPolicyStore, sessionId, policy, opOk);
295
+ }
296
+ catch (e) {
297
+ if (sendSessionPolicyError(res, e))
298
+ return;
299
+ throw e;
300
+ }
301
+ }
286
302
  const importingP = scope.fleetWide ? (owner ?? null) : scope.gateOwner;
287
303
  if (deps.resumeAnchorStore)
288
304
  for (const a of anchors)
@@ -499,13 +515,9 @@ export async function handleSessionSync(req, res, match, ctx) {
499
515
  sendError(res, 409, "conflict", "sync conflict", { relation: e.relation });
500
516
  return;
501
517
  }
502
- if (e instanceof SessionPolicyError) {
503
- cleanupStaging(stagingId);
504
- const h = sessionPolicyErrorHttp(e.code);
505
- sendError(res, h.status, h.errorCode, e.message);
506
- return;
507
- }
508
518
  cleanupStaging(stagingId);
519
+ if (sendSessionPolicyError(res, e))
520
+ return;
509
521
  throw e;
510
522
  }
511
523
  return;
@@ -1,12 +1,12 @@
1
- import { materializeA2aTools, materializeMcpTools, sanitizePathComponent, SessionPolicyError } from "@sema-agent/core";
1
+ import { materializeA2aTools, materializeMcpTools, sanitizePathComponent } from "@sema-agent/core";
2
2
  import { a2aForScenario, mcpForScenario } from "../../config-center/facade.js";
3
3
  import { isUuidV7, isUuidShape, legacyUnownedSessionOk } from "../../security.js";
4
4
  import { windowMessages, truncateMessageBlobs } from "../../audit.js";
5
5
  import { redactSecrets } from "../../trace/redact.js";
6
6
  import { recordFailOpen } from "../../observability/fail-open.js";
7
7
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
8
- import { readSessionPermissionRules, describeSessionPermissionRulesProblem, sessionPolicyErrorHttp } from "../../session-policy-wire.js";
9
- import { sendJson, sendError, sseHeaders } from "../send.js";
8
+ import { readSessionPermissionRules, describeSessionPermissionRulesProblem } from "../../session-policy-wire.js";
9
+ import { sendJson, sendError, sendSessionPolicyError, sseHeaders } from "../send.js";
10
10
  import { gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefusal } from "../principal-gate.js";
11
11
  import { assertNever } from "../route-ctx.js";
12
12
  const MCP_STATUS_TTL_MS = 5_000;
@@ -731,7 +731,14 @@ export async function handleSessions(req, res, match, ctx) {
731
731
  }
732
732
  const target = owner;
733
733
  if (match.decl.id === "session-policy-get") {
734
- sendJson(res, 200, { rules: (await store.getRules(sessionId, target)) ?? { rev: 0 } });
734
+ try {
735
+ sendJson(res, 200, { rules: (await store.getRules(sessionId, target)) ?? { rev: 0 } });
736
+ }
737
+ catch (e) {
738
+ if (sendSessionPolicyError(res, e))
739
+ return;
740
+ throw e;
741
+ }
735
742
  return;
736
743
  }
737
744
  let body;
@@ -767,11 +774,8 @@ export async function handleSessions(req, res, match, ctx) {
767
774
  sendJson(res, 200, { rules: stored });
768
775
  }
769
776
  catch (e) {
770
- if (e instanceof SessionPolicyError) {
771
- const h = sessionPolicyErrorHttp(e.code);
772
- sendError(res, h.status, h.errorCode, e.message);
777
+ if (sendSessionPolicyError(res, e))
773
778
  return;
774
- }
775
779
  throw e;
776
780
  }
777
781
  return;
@@ -1,7 +1,7 @@
1
1
  import { redactErrorMessage, redactHead, redactTerminalResult } from "../../observability/run-terminal.js";
2
- import { runWithVerification, runCascade, uuidv7, terminalProjection } from "@sema-agent/core";
2
+ import { runWithVerification, runCascade, uuidv7 } from "@sema-agent/core";
3
3
  import { graftParkToolCallId, markChildrenStoppedByUserOnAbort, resumeAtHttpStatus, HEARTBEAT_MS } from "../../runs.js";
4
- import { stripResumeToken } from "../../terminal.js";
4
+ import { sealResultForExit, outwardPlane } from "../../terminal.js";
5
5
  import { withPrincipal } from "../../observability/principal-context.js";
6
6
  import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../../fleet/fleet-bus.js";
7
7
  import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
@@ -58,7 +58,7 @@ export async function handleTasks(req, res, match, ctx) {
58
58
  const noteRunOutcome = (r) => {
59
59
  if (!r?.terminal)
60
60
  return;
61
- const plane = terminalProjection(r.terminal);
61
+ const plane = outwardPlane(r.terminal);
62
62
  reqState.runStatus = plane.status;
63
63
  if (plane.errorCode)
64
64
  reqState.runErrorCode = plane.errorCode;
@@ -421,7 +421,7 @@ export async function handleTasks(req, res, match, ctx) {
421
421
  userMsgEntryId = ev.entryId;
422
422
  const cancelVerbAtDone = ev.type === "done" && durableTaskId !== undefined && cancelledViaVerb.has(durableTaskId);
423
423
  if (ev.type === "done") {
424
- finalResult = redactTerminalResult(stripResumeToken(await graftParkToolCallId(ev.result, deps.checkpointStore)));
424
+ finalResult = redactTerminalResult(sealResultForExit(await graftParkToolCallId(ev.result, deps.checkpointStore)));
425
425
  if (durableTaskId && finalResult)
426
426
  finalResult = { ...finalResult, taskId: durableTaskId };
427
427
  if (cancelVerbAtDone && finalResult !== undefined) {
@@ -430,7 +430,7 @@ export async function handleTasks(req, res, match, ctx) {
430
430
  finalizeTaskResult(ev.result, principal, prepared.spec.objective, prepared.spec.sessionId);
431
431
  noteRunOutcome(finalResult);
432
432
  putRewindAnchor();
433
- settleFleet(terminalProjection(finalResult.terminal).status, fleetRunResiduals(finalResult));
433
+ settleFleet(outwardPlane(finalResult.terminal).status, fleetRunResiduals(finalResult));
434
434
  if (ledgerSink && finalResult) {
435
435
  if (usageKey && deps.modelUsage && deps.runStore && durableTaskId) {
436
436
  const rs2 = deps.runStore;
@@ -650,7 +650,7 @@ export async function handleTasks(req, res, match, ctx) {
650
650
  }, withApproval)
651
651
  : withApproval());
652
652
  if (durableTaskId && deps.runStore) {
653
- const finalPlane = finalResult ? terminalProjection(finalResult.terminal) : undefined;
653
+ const finalPlane = finalResult ? outwardPlane(finalResult.terminal) : undefined;
654
654
  if (finalPlane?.status === "suspended" && deps.checkpointStore)
655
655
  await deps.runStore.setSuspended(durableTaskId);
656
656
  else if (finalPlane?.status === "needs_review" && deps.checkpointStore)
@@ -717,7 +717,7 @@ export async function handleTasks(req, res, match, ctx) {
717
717
  ledgerTerminal = true;
718
718
  await ledgerSink.onDone(finalResult).catch(() => undefined);
719
719
  }
720
- const retryPlane = terminalProjection(finalResult.terminal);
720
+ const retryPlane = outwardPlane(finalResult.terminal);
721
721
  await deps.runStore.setTerminal(durableTaskId, retryPlane.status, finalResult, retryPlane.errorMessage ?? null);
722
722
  settleFleet(retryPlane.status, fleetRunResiduals(finalResult));
723
723
  }
@@ -860,15 +860,15 @@ export async function handleTasks(req, res, match, ctx) {
860
860
  }
861
861
  const resumeAtStatus = resumeAtHttpStatus(result);
862
862
  if (resumeAtStatus) {
863
- const resumeAtPlane = terminalProjection(result.terminal);
863
+ const resumeAtPlane = outwardPlane(result.terminal);
864
864
  if (durableTaskId && deps.runStore)
865
- await deps.runStore.setTerminal(durableTaskId, "failed", stripResumeToken(result), resumeAtPlane.errorMessage ?? null);
865
+ await deps.runStore.setTerminal(durableTaskId, "failed", sealResultForExit(result), resumeAtPlane.errorMessage ?? null);
866
866
  const retryAfterSec = resumeAtPlane.errorCode === "memory.admission_required" && result.retryAfterMs !== undefined
867
867
  ? Math.max(1, Math.ceil(result.retryAfterMs / 1000))
868
868
  : undefined;
869
869
  return { status: resumeAtStatus, body: { errorCode: resumeAtPlane.errorCode, error: redactErrorMessage(resumeAtPlane.errorMessage) ?? "resume-at failed", ...(retryAfterSec !== undefined ? { retryAfterSec } : {}) } };
870
870
  }
871
- const syncPlane = terminalProjection(result.terminal);
871
+ const syncPlane = outwardPlane(result.terminal);
872
872
  if (syncPlane.status === "suspended" && deps.checkpointStore && deps.runStore && prepared.spec.sessionId) {
873
873
  if (durableTaskId)
874
874
  await deps.runStore.setSuspended(durableTaskId);
@@ -880,10 +880,10 @@ export async function handleTasks(req, res, match, ctx) {
880
880
  return { status: 200, body: withSyncSubmitKeys({ taskId: durableTaskId, sessionId: prepared.spec.sessionId, status: "needs_review" }) };
881
881
  }
882
882
  if (durableTaskId && deps.runStore)
883
- await deps.runStore.setTerminal(durableTaskId, syncPlane.status, stripResumeToken(result), syncPlane.errorMessage ?? null);
883
+ await deps.runStore.setTerminal(durableTaskId, syncPlane.status, sealResultForExit(result), syncPlane.errorMessage ?? null);
884
884
  finalizeTaskResult(result, principal, prepared.spec.objective, prepared.spec.sessionId);
885
885
  noteRunOutcome(result);
886
- return { status: 200, body: withSyncSubmitKeys(redactTerminalResult(stripResumeToken(result))) };
886
+ return { status: 200, body: withSyncSubmitKeys(redactTerminalResult(sealResultForExit(result))) };
887
887
  };
888
888
  const resp = await idemCache.run(idemKey, async () => withRunReceipt(await runSyncSubmitLeg()), (r) => r.status < 400);
889
889
  sendSubmitResult(res, resp);
@@ -60,5 +60,16 @@ export declare function sendError(res: ServerResponse, status: number, errorCode
60
60
  * throw 点里只有 3 个带。为了让「errorCode 恒在」在这些站点也成立(而不是靠豁免表放行),按**状态码**
61
61
  * 给一个诚实的粗码:它只声明「这类拒绝」,不冒充比 throw 点更细的语义。带 `code` 的 throw 点原样优先。 */
62
62
  export declare function httpErrorCode(status: number, code?: string): string;
63
+ /**
64
+ * 🔴 B1 / S-535 —— `SessionPolicyError` → 应答,经 {@link sessionPolicyErrorHttp} 那**一只**映射
65
+ * (`src/session-policy-wire.ts`)。回 `true` = 已应答,调用点 `return`。
66
+ *
67
+ * 为什么是一只共享腿而不是每个站点一句 `instanceof`:映射本身早就收成一处了,而**咨询它的站点**只有两条
68
+ * **写**腿 —— 偏偏 core 7.25.0 那个新词 `corrupt` 是在**读**上抛出来的(店读不出它自己写的那一行:
69
+ * S-529 的两道读回拒 + C1 的体形拒)。于是 `GET /v1/sessions/:id/policy` 与 2c 的 manifest 导出腿把它交给了
70
+ * 域外通用臂 ⇒ `500 internal.error "internal error"`:码丢,连带**修复路径**也丢(同一行 `PUT` 却答
71
+ * `500 corrupt` 并点名那一列)。一只腿之后,新站点只会忘记调它,不会再各自发明一份映射。
72
+ */
73
+ export declare function sendSessionPolicyError(res: ServerResponse, e: unknown): boolean;
63
74
  export declare function msg(e: unknown): string;
64
75
  //# sourceMappingURL=send.d.ts.map
package/dist/http/send.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { SessionPolicyError } from "@sema-agent/core";
2
+ import { sessionPolicyErrorHttp } from "../session-policy-wire.js";
1
3
  export const SSE_RESPONSE_MARK = Symbol("sema.http.sse-response");
2
4
  export function isSseResponse(res) {
3
5
  return res[SSE_RESPONSE_MARK] === true;
@@ -35,6 +37,13 @@ export function httpErrorCode(status, code) {
35
37
  default: return status >= 500 ? "internal.error" : "request.rejected";
36
38
  }
37
39
  }
40
+ export function sendSessionPolicyError(res, e) {
41
+ if (!(e instanceof SessionPolicyError))
42
+ return false;
43
+ const h = sessionPolicyErrorHttp(e.code);
44
+ sendError(res, h.status, h.errorCode, e.message);
45
+ return true;
46
+ }
38
47
  export function msg(e) {
39
48
  return e instanceof Error ? e.message : String(e);
40
49
  }
@@ -1,6 +1,7 @@
1
1
  import http from "node:http";
2
+ import { outwardPlane } from "../terminal.js";
2
3
  import { redactHead } from "../observability/run-terminal.js";
3
- import { validatePendingSteer, terminalProjection } from "@sema-agent/core";
4
+ import { validatePendingSteer } from "@sema-agent/core";
4
5
  import {} from "../config-center/facade.js";
5
6
  import { HttpError } from "../security.js";
6
7
  import {} from "../memory-sync.js";
@@ -117,7 +118,7 @@ export function createHttpServer(rawDeps) {
117
118
  return deps.runnerFor?.(spec) ?? deps.runner;
118
119
  }
119
120
  function recordTaskResult(result) {
120
- const plane = terminalProjection(result.terminal);
121
+ const plane = outwardPlane(result.terminal);
121
122
  deps.metrics?.inc("tasks_total", { status: plane.status });
122
123
  if (result.stats.tokens)
123
124
  deps.metrics?.inc("task_tokens_total", {}, result.stats.tokens);
@@ -1,6 +1,6 @@
1
- import { terminalProjection } from "@sema-agent/core";
1
+ import {} from "@sema-agent/core";
2
2
  import { redactErrorMessage, redactWorkerReportError } from "../observability/run-terminal.js";
3
- import { pausedCauseOf } from "../terminal.js";
3
+ import { pausedCauseOf, outwardPlane } from "../terminal.js";
4
4
  class Semaphore {
5
5
  max;
6
6
  active = 0;
@@ -68,7 +68,7 @@ export async function fanOut(subtasks, opts) {
68
68
  let report;
69
69
  try {
70
70
  const result = await stream.result();
71
- const plane = terminalProjection(result.terminal);
71
+ const plane = outwardPlane(result.terminal);
72
72
  report = { ...base, status: mapStatus(result.terminal), stats: result.stats, ...(plane.errorMessage ? { error: redactErrorMessage(plane.errorMessage) ?? "" } : {}) };
73
73
  }
74
74
  catch (e) {
@@ -2,8 +2,8 @@ import { fanOut } from "./fanout.js";
2
2
  import { redactErrorMessage, redactHead } from "../observability/run-terminal.js";
3
3
  import { pullDiff } from "./diffout.js";
4
4
  import { mergeBranches } from "./merge.js";
5
- import { Runner, runWithVerification, resumeWithVerification, terminalProjection } from "@sema-agent/core";
6
- import { pausedCauseOf } from "../terminal.js";
5
+ import { Runner, runWithVerification, resumeWithVerification } from "@sema-agent/core";
6
+ import { pausedCauseOf, outwardPlane } from "../terminal.js";
7
7
  const MAX_RESUME_SLICES = 200;
8
8
  export async function autoResumeOnResource(initial, signal, resume, maxSlices = MAX_RESUME_SLICES) {
9
9
  let vr = initial;
@@ -59,7 +59,7 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
59
59
  deps.logger?.info?.("worker_resource_resume", { workerId: w.workerId, slice });
60
60
  return runner.resume(token, { gate: "resource_limit", decision: "continue" }, { ...w.spec, sessionId: w.sessionId, signal });
61
61
  });
62
- const plane = terminalProjection(r.terminal);
62
+ const plane = outwardPlane(r.terminal);
63
63
  const paused = pausedCauseOf(r.terminal);
64
64
  if (r.terminal.kind !== "completed") {
65
65
  deps.logger?.warn?.("worker_failed", {
@@ -92,7 +92,7 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
92
92
  deps.logger?.info?.("worker_resource_resume", { workerId: w.workerId, slice });
93
93
  return resumeWithVerification(w.runner, token, { gate: "resource_limit", decision: "continue" }, { ...w.spec, sessionId: w.sessionId, signal }, w.spec.objective ?? "", cfg);
94
94
  });
95
- const vrPlane = terminalProjection(vr.terminal);
95
+ const vrPlane = outwardPlane(vr.terminal);
96
96
  deps.logger?.info?.("verify_outcome", { workerId: w.workerId, status: vrPlane.status, verdict: vr.verification.verdict, rounds: vr.verification.rounds, finding: redactHead(vr.verification.findings[0] ?? "", 220) });
97
97
  const pass = vr.terminal.kind === "completed" && vr.verification.verdict === "PASS";
98
98
  const vrPaused = pausedCauseOf(vr.terminal);
@@ -1,6 +1,6 @@
1
1
  import { runRepairLoop, delimitUntrusted, } from "@sema-agent/core";
2
2
  import { mkRepairOracle } from "./repair-oracle.js";
3
- import { terminalProjection } from "@sema-agent/core";
3
+ import { outwardPlane } from "../terminal.js";
4
4
  import { redactHead } from "../observability/run-terminal.js";
5
5
  function statusForTerminal(t) {
6
6
  return t === "fixed" || t === "candidate_only" ? "completed" : "failed";
@@ -52,7 +52,7 @@ async function runRepairLoopForLeaderInner(solo, implSpec, cfg) {
52
52
  return { report: { ...base, status: "failed", error: safe }, result: undefined };
53
53
  }
54
54
  const terminal = result.repairTerminal;
55
- const plane = terminalProjection(result.terminal);
55
+ const plane = outwardPlane(result.terminal);
56
56
  const report = {
57
57
  ...base,
58
58
  status: terminal === undefined ? "failed" : statusForTerminal(terminal),
@@ -1,4 +1,4 @@
1
- import { terminalProjection } from "@sema-agent/core";
1
+ import { outwardPlane } from "../terminal.js";
2
2
  import { parseNumOrFail } from "../config.js";
3
3
  import { execFileSync } from "node:child_process";
4
4
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
@@ -176,7 +176,7 @@ export function createLeaderRunner(cfg) {
176
176
  })
177
177
  .result();
178
178
  if (res.terminal.kind !== "completed")
179
- throw new Error(`repair round ${round} did not complete (status=${terminalProjection(res.terminal).status})`);
179
+ throw new Error(`repair round ${round} did not complete (status=${outwardPlane(res.terminal).status})`);
180
180
  return { costUsd: res.stats?.costMicroUsd !== undefined ? res.stats.costMicroUsd / 1e6 : undefined, note: res.result?.slice(0, 200) };
181
181
  }
182
182
  finally {
@@ -1,9 +1,9 @@
1
1
  import { redactErrorMessage, redactLedgerEventData, redactTerminalResult } from "../observability/run-terminal.js";
2
+ import { outwardPlane } from "../terminal.js";
2
3
  import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
3
4
  import { join } from "node:path";
4
5
  import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
5
6
  import { randomBytes } from "node:crypto";
6
- import { terminalProjection } from "@sema-agent/core";
7
7
  import { AppendLog, atomicWriteFile, ensureDir, isProcessLive, processFingerprint, readJsonlRecords, sanitizePathComponent, writeThenLink } from "@sema-agent/core";
8
8
  import { recordFailOpen } from "../observability/fail-open.js";
9
9
  import { StoreBootRefusalError } from "./store-contracts.js";
@@ -371,7 +371,7 @@ export class FileRunStore {
371
371
  r.status = status;
372
372
  r.result = result;
373
373
  r.error = error;
374
- r.errorCode = result === null ? null : (terminalProjection(result.terminal).errorCode ?? null);
374
+ r.errorCode = result === null ? null : (outwardPlane(result.terminal).errorCode ?? null);
375
375
  r.updatedAt = new Date();
376
376
  this.persist(r);
377
377
  pending = {
@@ -382,7 +382,7 @@ export class FileRunStore {
382
382
  resultTaskId: result?.taskId,
383
383
  model: result?.model,
384
384
  errorCode: r.errorCode ?? undefined,
385
- errorMessage: (result === null ? undefined : terminalProjection(result.terminal).errorMessage) ?? error ?? undefined,
385
+ errorMessage: (result === null ? undefined : outwardPlane(result.terminal).errorMessage) ?? error ?? undefined,
386
386
  elapsedMs: Math.max(0, Date.now() - r.createdAt.getTime()),
387
387
  };
388
388
  }
@@ -1,6 +1,6 @@
1
1
  import { redactErrorMessage, redactLedgerEventData, redactTerminalResult } from "../observability/run-terminal.js";
2
+ import { outwardPlane } from "../terminal.js";
2
3
  import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
3
- import { terminalProjection } from "@sema-agent/core";
4
4
  import { notifyRunTerminal } from "../observability/run-terminal.js";
5
5
  const ownerEq = (a, b) => (a ?? null) === (b ?? null);
6
6
  export class MemoryRunStore {
@@ -121,7 +121,7 @@ export class MemoryRunStore {
121
121
  r.status = status;
122
122
  r.result = result;
123
123
  r.error = error;
124
- r.errorCode = result === null ? null : (terminalProjection(result.terminal).errorCode ?? null);
124
+ r.errorCode = result === null ? null : (outwardPlane(result.terminal).errorCode ?? null);
125
125
  r.updatedAt = new Date();
126
126
  pending = {
127
127
  runId: taskId,
@@ -131,7 +131,7 @@ export class MemoryRunStore {
131
131
  resultTaskId: result?.taskId,
132
132
  model: result?.model,
133
133
  errorCode: r.errorCode ?? undefined,
134
- errorMessage: (result === null ? undefined : terminalProjection(result.terminal).errorMessage) ?? error ?? undefined,
134
+ errorMessage: (result === null ? undefined : outwardPlane(result.terminal).errorMessage) ?? error ?? undefined,
135
135
  elapsedMs: Math.max(0, Date.now() - r.createdAt.getTime()),
136
136
  };
137
137
  }
@@ -1,5 +1,5 @@
1
1
  import { redactErrorMessage, redactLedgerEventData, redactTerminalResult } from "../observability/run-terminal.js";
2
- import { terminalProjection } from "@sema-agent/core";
2
+ import { outwardPlane } from "../terminal.js";
3
3
  import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
4
4
  import { escapeLike } from "./sql-escape.js";
5
5
  import { pgSanitizeText } from "./pg-safe-json.js";
@@ -120,7 +120,7 @@ export class SqlRunStore {
120
120
  async setTerminal(taskId, status, result, error) {
121
121
  result = redactTerminalResult(result);
122
122
  error = error === null ? null : (redactErrorMessage(error) ?? null);
123
- const errorCode = result === null ? null : (terminalProjection(result.terminal).errorCode ?? null);
123
+ const errorCode = result === null ? null : (outwardPlane(result.terminal).errorCode ?? null);
124
124
  let won = false;
125
125
  let observed;
126
126
  await this.tx(async (conn) => {
@@ -153,7 +153,7 @@ export class SqlRunStore {
153
153
  resultTaskId: result?.taskId,
154
154
  model: result?.model,
155
155
  errorCode: errorCode ?? undefined,
156
- errorMessage: (result === null ? undefined : terminalProjection(result.terminal).errorMessage) ?? error ?? undefined,
156
+ errorMessage: (result === null ? undefined : outwardPlane(result.terminal).errorMessage) ?? error ?? undefined,
157
157
  elapsedMs: observed?.createdAt === undefined ? undefined : Math.max(0, Date.now() - observed.createdAt),
158
158
  });
159
159
  }