@sema-agent/server 7.144.0 → 7.145.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 (73) hide show
  1. package/MIGRATION.md +129 -0
  2. package/README.md +3 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +16 -8
  5. package/dist/audit.d.ts +2 -21
  6. package/dist/audit.js +0 -9
  7. package/dist/boot/session-faces.d.ts +1 -1
  8. package/dist/boot/session-faces.js +3 -3
  9. package/dist/boot/stage-09-leader.d.ts +1 -1
  10. package/dist/boot/stage-10-http-server.d.ts +1 -1
  11. package/dist/boot/stage-10-http-server.js +2 -2
  12. package/dist/config-d-migration.d.ts +3 -2
  13. package/dist/fleet/fleet-bus.d.ts +13 -3
  14. package/dist/fleet/fleet-bus.js +17 -3
  15. package/dist/http/admission.js +2 -1
  16. package/dist/http/checkpoint-ctx.d.ts +1 -0
  17. package/dist/http/checkpoint-ctx.js +1 -0
  18. package/dist/http/durable-run-leg.js +7 -6
  19. package/dist/http/resume-legs.js +5 -3
  20. package/dist/http/route-ctx.d.ts +15 -0
  21. package/dist/http/routes/approvals-assistant.d.ts +20 -1
  22. package/dist/http/routes/approvals-assistant.js +27 -6
  23. package/dist/http/routes/rules.d.ts +1 -1
  24. package/dist/http/routes/rules.js +5 -2
  25. package/dist/http/routes/session-sync.js +12 -0
  26. package/dist/http/routes/tasks.js +12 -9
  27. package/dist/http/run-meta.d.ts +14 -7
  28. package/dist/http/run-meta.js +5 -2
  29. package/dist/http/server.d.ts +13 -5
  30. package/dist/http/server.js +3 -0
  31. package/dist/http/submit-ctx.d.ts +12 -0
  32. package/dist/http/submit-ctx.js +23 -0
  33. package/dist/observability/fail-open.d.ts +15 -3
  34. package/dist/observability/fail-open.js +15 -3
  35. package/dist/plugins/approval-ask-store-file.d.ts +16 -2
  36. package/dist/plugins/approval-ask-store-file.js +21 -1
  37. package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
  38. package/dist/plugins/approval-ask-store-memory.js +11 -8
  39. package/dist/plugins/caching-session-store.d.ts +3 -0
  40. package/dist/plugins/caching-session-store.js +3 -0
  41. package/dist/plugins/checkpoint-store-sql.d.ts +29 -11
  42. package/dist/plugins/checkpoint-store-sql.js +38 -16
  43. package/dist/plugins/file-run-store.d.ts +13 -1
  44. package/dist/plugins/file-run-store.js +11 -1
  45. package/dist/plugins/local-checkpoint-store.d.ts +14 -3
  46. package/dist/plugins/local-checkpoint-store.js +23 -5
  47. package/dist/plugins/local-session-store.d.ts +52 -16
  48. package/dist/plugins/local-session-store.js +47 -14
  49. package/dist/plugins/memory-run-store.d.ts +2 -0
  50. package/dist/plugins/memory-run-store.js +6 -0
  51. package/dist/plugins/one-time-migrations.js +2 -2
  52. package/dist/plugins/run-store-sql.d.ts +4 -0
  53. package/dist/plugins/run-store-sql.js +3 -0
  54. package/dist/plugins/session-store.d.ts +1 -1
  55. package/dist/prompt-screen.d.ts +21 -0
  56. package/dist/prompt-screen.js +8 -0
  57. package/dist/rules-consent.d.ts +63 -51
  58. package/dist/rules-consent.js +6 -10
  59. package/dist/run-acceptance.d.ts +40 -10
  60. package/dist/run-acceptance.js +38 -5
  61. package/dist/runs.js +1 -1
  62. package/dist/security.d.ts +10 -3
  63. package/dist/session-agent.d.ts +39 -0
  64. package/dist/session-agent.js +16 -0
  65. package/dist/session-error-wire.d.ts +4 -4
  66. package/dist/trace/model-segment.d.ts +3 -2
  67. package/dist/trace/model-segment.js +16 -8
  68. package/dist/trace/redact.d.ts +66 -2
  69. package/dist/trace/redact.js +220 -101
  70. package/dist/trace/self-minted-wire.d.ts +73 -1
  71. package/dist/trace/self-minted-wire.js +27 -0
  72. package/package.json +2 -2
  73. package/wire-changes/server@7.145.0.json +146 -0
@@ -230,7 +230,7 @@ export class FleetEventBus {
230
230
  }
231
231
  export function fleetRunPublisher(bus, run) {
232
232
  if (!bus)
233
- return { onStart: () => undefined, onEvent: () => undefined, onForwardEvent: () => undefined, onTerminal: () => undefined };
233
+ return { onStart: () => undefined, onEvent: () => undefined, onForwardEvent: () => undefined, onTerminal: () => undefined, onNamed: () => undefined };
234
234
  let tokens = 0;
235
235
  const startedAt = Date.now();
236
236
  const sessionField = run.sessionId ? { sessionId: run.sessionId } : {};
@@ -282,7 +282,14 @@ export function fleetRunPublisher(bus, run) {
282
282
  };
283
283
  return {
284
284
  onStart() {
285
- bus.publishTask({ id: run.runId, name: run.name, ...(run.description ? { description: run.description } : {}), scope: run.scope, ...sessionField, status: "running", tokens: 0, elapsedMs: 0 });
285
+ bus.publishTask({ id: run.runId, ...(run.name !== undefined ? { name: run.name } : {}), ...(run.description ? { description: run.description } : {}), scope: run.scope, ...sessionField, status: "running", tokens: 0, elapsedMs: 0 });
286
+ },
287
+ onNamed(name) {
288
+ if (settledLeg)
289
+ return;
290
+ if (bus.taskRow(run.runId) === undefined)
291
+ return;
292
+ bus.publishTask({ id: run.runId, name });
286
293
  },
287
294
  onEvent(ev) {
288
295
  if (settledLeg)
@@ -595,7 +602,14 @@ export function buildRunName(preview) {
595
602
  return oneLine.length > RUN_NAME_CHARS ? `${oneLine.slice(0, RUN_NAME_CHARS - 1)}…` : oneLine;
596
603
  }
597
604
  export function fleetRunLabels(objective) {
598
- return { name: buildRunName(buildRunObjectivePreview(objective ?? "")) };
605
+ return objective === undefined ? {} : { name: buildRunName(buildRunObjectivePreview(objective)) };
606
+ }
607
+ export function nameFleetRowOnDelivery(acceptance, publisher) {
608
+ acceptance.onDelivered("fleet-name", async (objective) => {
609
+ const { name } = fleetRunLabels(objective);
610
+ if (name !== undefined)
611
+ publisher.onNamed(name);
612
+ });
599
613
  }
600
614
  export function isTerminalFleetStatus(status) {
601
615
  switch (status) {
@@ -24,6 +24,7 @@ import { legFactsOf } from "../leg-facts.js";
24
24
  import { resolveExecutionContext, stampSpecRoot } from "./execution-context.js";
25
25
  import { callerTrust, requestTrustRefusal } from "./caller-trust.js";
26
26
  import { createRunAcceptance } from "../run-acceptance.js";
27
+ import { promptScreenDeclared } from "../prompt-screen.js";
27
28
  import { sendError, sendHttpError } from "./send.js";
28
29
  import { refuseSubmitBody } from "./request-guards.js";
29
30
  import { sqlWallsOf } from "../plugins/sql-driver.js";
@@ -750,7 +751,7 @@ export function createAdmission(ctx) {
750
751
  const runner = runnerFor(resolved);
751
752
  const spec = stampSpecRoot(resolved, execution);
752
753
  const model = modelIdOf(spec.model);
753
- const acceptance = createRunAcceptance({ sessionTitler: deps.sessionTitler }, { sessionId: spec.sessionId, objective: spec.objective, ...(model !== undefined ? { model } : {}) });
754
+ const acceptance = createRunAcceptance({ sessionTitler: deps.sessionTitler }, { sessionId: spec.sessionId, objective: spec.objective, screened: promptScreenDeclared([deps.deploymentMods, spec.mods]), ...(model !== undefined ? { model } : {}) });
754
755
  const clampNotice = buildEntryModeClampNotice(entryMode, spec.sessionId);
755
756
  const pendingNotices = clampNotice !== undefined ? [clampNotice, ...engineNotices] : engineNotices;
756
757
  const warnings = allowSelectorWarningsOf(admitted, spec.tools ?? []);
@@ -96,6 +96,7 @@ export declare function buildCheckpointCtx(fields: {
96
96
  body: TaskRequestBody;
97
97
  memoryScope?: string | undefined;
98
98
  unattendedLeg?: true;
99
+ promptScreened?: true;
99
100
  writerVersion: string;
100
101
  }, sealer: CheckpointCtxSealer | undefined, sessionId: string): SealedCheckpointCtx;
101
102
  /**
@@ -18,6 +18,7 @@ export function buildCheckpointCtx(fields, sealer, sessionId) {
18
18
  writerVersion: fields.writerVersion,
19
19
  ...(fields.memoryScope !== undefined ? { memoryScope: fields.memoryScope } : {}),
20
20
  ...(fields.unattendedLeg === true ? { unattendedLeg: true } : {}),
21
+ ...(fields.promptScreened === true ? { promptScreened: true } : {}),
21
22
  ...(bodyCarriesAllowList(fields.body) ? { permissionsAllowReading: "allow_layer" } : {}),
22
23
  permissionModeReading: "entry_word",
23
24
  };
@@ -2,8 +2,8 @@ import { uuidv7 } from "@sema-agent/core";
2
2
  import { heldWorkflowParkDecisions } from "../parked-decide.js";
3
3
  import { HEARTBEAT_MS, runInBackground } from "../runs.js";
4
4
  import { heldFor } from "../peer-seat.js";
5
- import { fleetRunPublisher, fleetRunLabels } from "../fleet/fleet-bus.js";
6
- import { cascadeConfig, runMeta } from "./run-meta.js";
5
+ import { fleetRunPublisher, fleetRunLabels, nameFleetRowOnDelivery } from "../fleet/fleet-bus.js";
6
+ import { cascadeConfig, fillRunPreviewOnDelivery, runMeta } from "./run-meta.js";
7
7
  import { declaredApprovalFrameFamilies, resolveStreamApprovalGate } from "../tool-approval.js";
8
8
  import { buildActiveRunConflict } from "./active-run-conflict.js";
9
9
  import { commitExecutionContext } from "./execution-context.js";
@@ -11,9 +11,8 @@ import { rootSessionAbsentAnswer } from "./execution-context.js";
11
11
  import { commitSessionInheritEnv } from "./session-inherit-env.js";
12
12
  import { clearTurnActivity, readTurnActivityMs } from "../turn-activity.js";
13
13
  import { recordResourceWindow } from "../resource-window.js";
14
- import { persistSubmitCtx } from "./submit-ctx.js";
14
+ import { persistSubmissionCtx } from "./submit-ctx.js";
15
15
  import { releaseClaimedRun } from "./claimed-run.js";
16
- import { serviceVersion } from "../service-version.js";
17
16
  import { headReadOf } from "../session-incarnation.js";
18
17
  import { createSessionWorkspaceRecorder, sessionWorkspaceStoreOf } from "../session-workspace.js";
19
18
  export async function mintAndDriveDurableRun(ctx, runStore, args) {
@@ -34,6 +33,7 @@ export async function mintAndDriveDurableRun(ctx, runStore, args) {
34
33
  if (!created.ok) {
35
34
  return { status: 409, body: await buildActiveRunConflict({ runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, sessionId, created.activeTaskId) };
36
35
  }
36
+ fillRunPreviewOnDelivery(args.prepared.acceptance, runStore, taskId);
37
37
  const owner = args.prepared.auth?.principal ?? null;
38
38
  const preLaunchHeartbeat = setInterval(() => {
39
39
  void runStore.heartbeat(taskId, owner).catch(() => { deps.metrics?.inc("run_signal_poll_errors_total", { kind: "heartbeat" }); });
@@ -44,7 +44,7 @@ export async function mintAndDriveDurableRun(ctx, runStore, args) {
44
44
  if (placementReject)
45
45
  return placementReject;
46
46
  if (deps.checkpointStore) {
47
- const ctxRefusal = await persistSubmitCtx(deps.checkpointStore, deps, { runStore, taskId }, sessionId, { body: args.prepared.body, memoryScope: args.prepared.auth?.memoryScope, writerVersion: serviceVersion() });
47
+ const ctxRefusal = await persistSubmissionCtx(deps.checkpointStore, deps, { runStore, taskId }, args.prepared);
48
48
  if (ctxRefusal)
49
49
  return ctxRefusal;
50
50
  }
@@ -81,8 +81,9 @@ export async function mintAndDriveDurableRun(ctx, runStore, args) {
81
81
  scope: args.fleetScope,
82
82
  rootTaskId: taskId,
83
83
  sessionId,
84
- ...fleetRunLabels(args.prepared.spec.objective),
84
+ ...fleetRunLabels(args.prepared.acceptance.objectiveAtSubmit),
85
85
  });
86
+ nameFleetRowOnDelivery(args.prepared.acceptance, fleetPub);
86
87
  void heldFor(args.prepared.peerLease, () => runInBackground(args.prepared.runner, args.prepared.spec, runStore, taskId, deps.metrics, args.prepared.auth?.principal, args.prepared.verify, args.prepared.cascade ? cascadeConfig(deps.config.cascadeLadder, args.prepared.spec.limits?.maxCostUsd) : undefined, deps.instrumentDegenerate, deps.planCacheProbe, deps.config.traceThinking, inflightRuns, preemptableRuns, steerableRuns, deps.modelUsage, deps.elicitation, args.elicitOwner, captureTurnAnchor, fleetPub, captureUserMessageAnchor, deps.question, deps.workflowCompletionInbox, deps.subagentSteerRegistry, (m, x) => deps.logger?.info?.(m, x), deps.sendUserFile, deps.promptManifests, deps.toolApproval
87
88
  ? {
88
89
  coordinator: deps.toolApproval,
@@ -54,6 +54,7 @@ import { ASK_USER_QUESTION_TOOL_NAME, isApprovalGateKind, isEngineQuestionRow }
54
54
  import { deploymentListAsks } from "../approval.js";
55
55
  import { isParkLedgerEventType } from "../trace/ledger-events.js";
56
56
  import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnsealableAnswer } from "./resume-ctx-read.js";
57
+ import { objectiveWithheldIn } from "./submit-ctx.js";
57
58
  import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, resumeRefusalWithCard, sealCheckpointCtx, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
58
59
  import { refusalBody, resumeRefusalFacts } from "./refusal-body.js";
59
60
  import { storedBodyReplayRefusal } from "./stored-body-replay.js";
@@ -615,6 +616,7 @@ export function createResumeLegs(ctx) {
615
616
  async function driveResumeLeg(args, onAccepted) {
616
617
  const resumeLegStartedAt = Date.now();
617
618
  const { runner: legRunner, token, sessionId, principal, fleetScope, taskConfig, resumeObjective, outcome, verifyRounds, resumeCtx, execution } = args;
619
+ const heldObjective = objectiveWithheldIn(resumeCtx.ctx) ? undefined : resumeObjective;
618
620
  const reparkStatus = outcome.gate === "plan_review" ? "needs_review" : "suspended";
619
621
  const commitResumeCtx = () => Promise.resolve().then(() => deps.checkpointStore.putCtx(resumeCtx.sessionId, sealCheckpointCtx({ ...resumeCtx.ctx, writerVersion: serviceVersion() }, deps.checkpointCtxSealing, resumeCtx.sessionId))).catch((e) => {
620
622
  deps.metrics?.inc("resume_ctx_not_persisted_total");
@@ -653,7 +655,7 @@ export function createResumeLegs(ctx) {
653
655
  const created = await deps.runStore.createRun(freshId, sessionId, principal ?? null, deps.instanceId ?? "default", {
654
656
  source: args.mintFreshRun.source,
655
657
  identity: args.mintFreshRun.identity,
656
- objectivePreview: buildRunObjectivePreview(resumeObjective ?? ""),
658
+ objectivePreview: heldObjective === undefined ? null : buildRunObjectivePreview(heldObjective),
657
659
  });
658
660
  if (created.existing) {
659
661
  throw new Error(`wake fresh run: createRun observed an existing row for the freshly minted taskId ${freshId} (no idempotency key was sent) — refusing to treat it as a busy session`);
@@ -670,7 +672,7 @@ export function createResumeLegs(ctx) {
670
672
  freshRunClaimed = true;
671
673
  }
672
674
  const fleetPub = taskId && deps.fleetBus
673
- ? fleetRunPublisher(deps.fleetBus, { runId: taskId, scope: fleetScope, rootTaskId: taskId, sessionId, ...fleetRunLabels(resumeObjective) })
675
+ ? fleetRunPublisher(deps.fleetBus, { runId: taskId, scope: fleetScope, rootTaskId: taskId, sessionId, ...fleetRunLabels(heldObjective) })
674
676
  : undefined;
675
677
  const resumeTaskConfig = taskId ? { ...taskConfig, taskId } : taskConfig;
676
678
  if (deps.toolApproval && taskId) {
@@ -1429,7 +1431,7 @@ export function createResumeLegs(ctx) {
1429
1431
  const { spec, engineNotices } = resolved;
1430
1432
  const hostSpec = { ...spec, objective };
1431
1433
  const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
1432
- prepared: { spec: hostSpec, runner: resolved.runner, legFacts: resolved.legFacts, auth, body: ctx.body, acceptance: createRunAcceptance({}, { objective }), peerLease: resolved.peerLease, execution: resolved.execution, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
1434
+ prepared: { spec: hostSpec, runner: resolved.runner, legFacts: resolved.legFacts, auth, body: ctx.body, ...(objectiveWithheldIn(ctx) ? { bodyFromScreenedCtx: true } : {}), acceptance: createRunAcceptance({}, { objective, screened: false }), peerLease: resolved.peerLease, execution: resolved.execution, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
1433
1435
  fleetScope: resumeFleetScope(req, auth),
1434
1436
  elicitOwner: principal ?? null,
1435
1437
  source: "workflow_parked_decide",
@@ -146,6 +146,12 @@ export interface PreparedTaskSubmission {
146
146
  * 判的是**这一次提交**的体:幂等重放从 run 记录答,不重判、不带。
147
147
  */
148
148
  warnings?: readonly EntryProblem[];
149
+ /**
150
+ * `body` 是从一份标着 `promptScreened` 的续跑输入里取回的存量体(今天唯一的这种提交 = `/decide` 的宿主新 turn:这一跑的目标是本服务铸的指路文本,
151
+ * 体是宿主会话自己的那一份)⇒ 这一跑新写的续跑输入照样标上(`submit-ctx.ts` `persistSubmissionCtx`)。体里的目标不是这一跑的目标,
152
+ * 它能不能展示在它第一次落盘时就定了,这一跑的受理座答不了。缺席 = 体是这次请求自己的,标不标由受理座定。
153
+ */
154
+ bodyFromScreenedCtx?: true;
149
155
  /**
150
156
  * 这次执行的**执行上下文**(唯一解析器 `http/execution-context.ts` `resolveExecutionContext` 的产物:根 · 住客 · 放置态 · 车道 · device 车道的设备意图)。
151
157
  * 提交门在装配之前铸(fresh);`/decide` 的宿主新 turn 交续跑折叠器铸的那一只(continuation)。每个 `runStore.createRun(` 成功点**必须**把它交给
@@ -208,6 +214,15 @@ export interface CheckpointCtx {
208
214
  * 缺席 = 本版之前写下的行(不回填):那一码的体不带两键,如实不猜。不是一次性迁移 —— 这一格是常设的事实位,没有删除条件。
209
215
  */
210
216
  writerVersion?: string;
217
+ /**
218
+ * 体里的目标**不是可展示的那一份**:写这份续跑输入的提交腿上装着 prompt 屏(判据 `prompt-screen.ts` 的 `promptScreenDeclared`),
219
+ * 体里的 `objective` 不是引擎交付的文本 —— 留空,或 verify 形为验证器留着的请求原文(律的属主 = `run-acceptance.ts`「交付的是哪一份」;
220
+ * 写法与那条 P-DEBT 见 `submit-ctx.ts` 头注)。**服务端写、不来自请求体**(与 {@link unattendedLeg} 同为 `body` 的兄弟键)。
221
+ * 读者只有一只:`submit-ctx.ts` 的 `objectiveWithheldIn`(续跑腿的两张展示面 —— wake 新行的预览、fleet 行名 —— 经它问,在场 ⇒ 缺席)。
222
+ * 写点:`buildCheckpointCtx`(提交腿那一笔,经 `persistSubmissionCtx`;体从一份标着它的续跑输入里取回时照样标上);续跑腿的写回传原 ctx / 其展开 ⇒ 标随行。
223
+ * 缺席 = 体里的目标就是交付的那一份(腿上没有屏)。
224
+ */
225
+ promptScreened?: true;
211
226
  }
212
227
  /**
213
228
  * `/decide` 第三条车道交给宿主投递腿(wake:`DriveResumeArgs` / fresh:`DurableRunLegArgs`)的**一件**:这一跑在内存里带着的决定,
@@ -7,6 +7,7 @@ import { type GateIntentView } from "../../approval.js";
7
7
  import { type RouteCtx, type RouteDecl, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
8
8
  import { type KeyClosureIssue } from "../../request-key-closure.js";
9
9
  import { type PeerInboundPendingRow } from "../../peer-inbound-face.js";
10
+ import { type PlacementReadingSessionStore, type SessionAgent } from "../../session-agent.js";
10
11
  /** 族的成员 = 路由表行 id(改名 / 删行 ⇒ 下表的键当场编译红)。 */
11
12
  export type DecisionDoorId = Extract<ApprovalsAssistantRouteId, "approval-decide" | "assistant-task-plan-review">;
12
13
  /** 一道门的静态描述 —— 只写**这道门自己**的东西;路径、路径段、别门的键一概不在这里写(从来源读)。 */
@@ -104,6 +105,19 @@ export type QuestionAnswerCheck = {
104
105
  */
105
106
  export declare function checkQuestionAnswer(v: unknown): QuestionAnswerCheck;
106
107
  export declare function handleApprovalsAssistant(req: IncomingMessage, res: ServerResponse, match: RouteMatch<ApprovalsAssistantRouteId>, ctx: RouteCtx): Promise<void>;
108
+ /** `listPending()` 的行形(两条 durable 读面共用;从 store 的返回类型取,不手抄)。 */
109
+ type PendingRow = Awaited<ReturnType<CheckpointStoreFull["listPending"]>>[number];
110
+ /**
111
+ * 两条 durable 读面共用的行形 —— 行 + [ref]② 追加的**出身格**(派生位,不是列)+ [ref] 行族词(`checkpoint`)+ [ref] 行的会话在委派树里的位置 `agent`
112
+ * (会话视图 `GET /v1/sessions/:id` 体顶层的同名键,同一只读口 `readSessionAgent`;在场 ⇔ 这一次列表读到行的会话是一只 placed 委派子代,见 {@link projectDurableQueue})。
113
+ * 导出只为自铸登记表按键绑型(`trace/self-minted-wire.ts` 的 `approvalQueueRowAgent`)。
114
+ */
115
+ export type PendingRowForWire = PendingRow & {
116
+ kind: "checkpoint";
117
+ governanceForced?: true;
118
+ rememberRefused?: true;
119
+ agent?: SessionAgent;
120
+ };
107
121
  /**
108
122
  * 🔴 [ref]-2 / [ref]②:两条 durable 读面(`GET /v1/approvals` + `/v1/approvals/stream`)的**唯一**
109
123
  * 投影 —— 脱敏([ref] 件4)+ 出身归因([ref] 的另一半)一次做完。
@@ -145,7 +159,12 @@ live: Pick<ToolApprovalCoordinator, "listLivePending"> | undefined,
145
159
  * (`pending` / `resolved`):两族同一扇决断口,帧上的 `kind` 判别。`undefined` = 本腿没有这一半场(流字节只差每帧的 `kind`)。
146
160
  * 位置在 `scope` 之前、与 `live` 并列:每个生产调用点都必须表态。
147
161
  */
148
- peerInbound: (() => readonly PeerInboundPendingRow[]) | undefined, scope: string | undefined,
162
+ peerInbound: (() => readonly PeerInboundPendingRow[]) | undefined,
163
+ /**
164
+ * 🔴 [ref]:停驻行 `agent` 的会话店面 —— 与 `GET /v1/approvals` 同一只(`deps.sessionStorage`),经同一只整批投影 {@link projectDurableQueue}。
165
+ * `undefined` / 店不交 `placementOf` = 这台部署不存委派元组 ⇒ 没有一帧带 `agent`(帧字节与 [ref] 之前逐字相同)。位置在 `scope` 之前、与 `live` 并列:每个生产调用点都必须表态。
166
+ */
167
+ sessions: PlacementReadingSessionStore | undefined, scope: string | undefined,
149
168
  /** [ref]②:出身归因的部署侧一半 + 三张审批名单(问的属主)。缺席 ⇒ 归不出治理出身(键缺席,与 best-effort 同方向)、没有一行读作
150
169
  * 部署名单的问(记不住,方向是多问)。**位置在 `pollMs` 之前**是刻意的:`pollMs` 只有测试传,而本参数是每个生产调用点都必须给的那一个。 */
151
170
  governance?: PendingProjectionDeployment, pollMs?: number): Promise<void>;
@@ -36,6 +36,7 @@ import { seatText } from "../../trace/exit-seat.js";
36
36
  import { approvalQueueKindOfDoorId } from "../../approval-queue-kind.js";
37
37
  import { defaultPeerInboundFace, parsePeerInboundId, peerBoxScope } from "../../peer-inbound-face.js";
38
38
  import { RESUME_RESTATABLE_KEYS, readResumeRestatement } from "../resume-restatement.js";
39
+ import { readSessionAgent } from "../../session-agent.js";
39
40
  const DECIDE_BODY_DECLARED_KEYS = [
40
41
  "answer", "boundCallId", "boundInputHash", "checkpointToken", "decidedBy", "decidedVia", "decision", "feedback", "reason", "remember", "updatedInput",
41
42
  ...RESUME_RESTATABLE_KEYS,
@@ -315,7 +316,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
315
316
  if (match.decl.id === "approvals-stream") {
316
317
  const ownerParam = operator ? (new URLSearchParams(match.search).get("owner") ?? undefined) : undefined;
317
318
  const scope = operator ? ownerParam : ownScope;
318
- await streamApprovals(req, res, cs, deps.toolApproval, peerInboundRowsFor(defaultPeerInboundFace, principal, ownerParam), scope, deps.config);
319
+ await streamApprovals(req, res, cs, deps.toolApproval, peerInboundRowsFor(defaultPeerInboundFace, principal, ownerParam), deps.sessionStorage, scope, deps.config);
319
320
  return;
320
321
  }
321
322
  if (match.decl.id === "approvals-list") {
@@ -331,7 +332,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
331
332
  sendApprovalsReadFailure(req, res, deps.logger, err);
332
333
  return;
333
334
  }
334
- const pending = [...durableRows.map((r) => projectPendingForWire(r, deps.config)), ...peerInboundRowsFor(defaultPeerInboundFace, principal, ownerParam)()];
335
+ const pending = [...(await projectDurableQueue(durableRows, deps.config, deps.sessionStorage)), ...peerInboundRowsFor(defaultPeerInboundFace, principal, ownerParam)()];
335
336
  sendJson(res, 200, {
336
337
  pending,
337
338
  serverNowMs: Date.now(),
@@ -1036,7 +1037,7 @@ function peerInboundRowsFor(face, principal, operatorOwnerParam) {
1036
1037
  const scope = peerBoxScope(principal);
1037
1038
  return () => face.list(scope);
1038
1039
  }
1039
- function projectPendingForWire(row, deployment) {
1040
+ function projectPendingForWire(row, deployment, agent) {
1040
1041
  const governance = deployment;
1041
1042
  const redacted = redactPendingDisclosures(row);
1042
1043
  const governanceForced = governanceOriginOf({ riskDescriptor: row.riskDescriptor ?? undefined }, governance);
@@ -1049,7 +1050,27 @@ function projectPendingForWire(row, deployment) {
1049
1050
  classes: row.classes,
1050
1051
  deploymentListQuestion,
1051
1052
  });
1052
- return { kind: "checkpoint", ...redacted, ...(governanceForced ? { governanceForced } : {}), ...(rememberRefused ? { rememberRefused: true } : {}) };
1053
+ return { kind: "checkpoint", ...redacted, ...(governanceForced ? { governanceForced } : {}), ...(rememberRefused ? { rememberRefused: true } : {}), ...(agent !== undefined ? { agent } : {}) };
1054
+ }
1055
+ async function projectDurableQueue(rows, deployment, sessions) {
1056
+ const agents = await sessionAgentsOf(rows.map((r) => r.sessionId), sessions);
1057
+ return rows.map((r) => projectPendingForWire(r, deployment, agents.get(r.sessionId)));
1058
+ }
1059
+ async function sessionAgentsOf(sessionIds, sessions) {
1060
+ const out = new Map();
1061
+ if (sessions?.placementOf === undefined)
1062
+ return out;
1063
+ await Promise.all([...new Set(sessionIds)].map(async (sessionId) => {
1064
+ try {
1065
+ const agent = await readSessionAgent(sessions, sessionId);
1066
+ if (agent !== undefined)
1067
+ out.set(sessionId, agent);
1068
+ }
1069
+ catch (err) {
1070
+ recordFailOpen("server.approvals.queue-row-agent-unreadable", `session ${JSON.stringify(sessionId)}: ${seatText("operator-log-text", err instanceof Error ? err.message : String(err))}`);
1071
+ }
1072
+ }));
1073
+ return out;
1053
1074
  }
1054
1075
  function createSseDeltaLane(res, spec) {
1055
1076
  let prev = new Map();
@@ -1068,7 +1089,7 @@ function createSseDeltaLane(res, spec) {
1068
1089
  },
1069
1090
  };
1070
1091
  }
1071
- export async function streamApprovals(req, res, cs, live, peerInbound, scope, governance, pollMs = APPROVALS_STREAM_POLL_MS) {
1092
+ export async function streamApprovals(req, res, cs, live, peerInbound, sessions, scope, governance, pollMs = APPROVALS_STREAM_POLL_MS) {
1072
1093
  const keyOf = (p) => {
1073
1094
  switch (p.kind) {
1074
1095
  case "checkpoint":
@@ -1118,7 +1139,7 @@ export async function streamApprovals(req, res, cs, live, peerInbound, scope, go
1118
1139
  while (!closed) {
1119
1140
  let snapshot;
1120
1141
  try {
1121
- const pending = [...(await cs.listPending(scope)).map((r) => projectPendingForWire(r, governance)), ...(peerInbound?.() ?? [])];
1142
+ const pending = [...(await projectDurableQueue(await cs.listPending(scope), governance, sessions)), ...(peerInbound?.() ?? [])];
1122
1143
  snapshot = { pending, livePending: live ? live.listLivePending(scope) : [] };
1123
1144
  }
1124
1145
  catch (err) {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * POST /v1/rules → 写一条**收紧**规则(deny/ask)进 durable 分区,单步、无票仪式
5
5
  * POST /v1/rules/cc-import/prepare → 预览 + 一张**一次性、principal 绑定、有期、载荷绑定**的票
6
- * POST /v1/rules/cc-import/redeem → 原子消费票 → confirm → 批量兑付进店
6
+ * POST /v1/rules/cc-import/redeem → 原子消费票 → confirm → 批量兑付进店 → 回执 `{ members, rev }`(与 local-import 口同一张,{@link sendImportReceipt})
7
7
  * GET /v1/rules → 列出自己名下**活着**的规则(keyset 分页,游标绑 rev)
8
8
  * DELETE /v1/rules → 按 (rule, scope) 内容撤销,**恒经** core `removePersistedRule`
9
9
  *
@@ -159,7 +159,7 @@ export async function handleRules(req, res, match, ctx) {
159
159
  }
160
160
  const redeemed = await lane.redeemLocalImport(principal, parsed.data.ticket);
161
161
  if (redeemed.ok) {
162
- sendJson(res, 200, { members: redeemed.outcome.members, rev: redeemed.outcome.rev });
162
+ sendImportReceipt(res, redeemed.outcome);
163
163
  return;
164
164
  }
165
165
  logger.info("local rule import ticket refused", { reason: redeemed.reason, detail: redeemed.detail, retryable: redeemed.reason !== "ticket-unusable" && redeemed.reason !== "in-flight" && redeemed.retryable === true });
@@ -218,7 +218,10 @@ export async function handleRules(req, res, match, ctx) {
218
218
  sendError(res, 404, "not_found.rule_ticket", "no such import ticket");
219
219
  return;
220
220
  }
221
- sendJson(res, 200, { result: redeemed.result });
221
+ sendImportReceipt(res, redeemed.outcome);
222
+ }
223
+ function sendImportReceipt(res, outcome) {
224
+ sendJson(res, 200, { members: outcome.members, rev: outcome.rev });
222
225
  }
223
226
  function resolveTargetPrincipal(requested, caller, config) {
224
227
  if (requested === undefined || requested === caller)
@@ -7,6 +7,8 @@ import * as facts from "../../capability-facts.js";
7
7
  import { classifySyncRelationshipByIds, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
8
8
  import { isUuidShape, legacyUnownedSessionOk, sessionOwnerCellOf } from "../../security.js";
9
9
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logRequestError, msg } from "../send.js";
10
+ import { StampedSessionArchiveError } from "../../session-error-wire.js";
11
+ import { readSessionAgent } from "../../session-agent.js";
10
12
  import { buildDisplaySafeUrl } from "../../trace/redact.js";
11
13
  import { storeFaultClassOf } from "../../plugins/store-fault.js";
12
14
  import { fleetViewOf, gatedPrincipal, explicitOperatorOk, sendUnauthorized, sessionCallerOf } from "../principal-gate.js";
@@ -359,6 +361,16 @@ export async function handleSessionSync(req, res, match, ctx) {
359
361
  const dstIds = (await sessionStorage.listEntryIds?.(sessionId)) ?? null;
360
362
  const relA = classifySyncRelationshipByIds(entryIds, dstIds);
361
363
  if ((relA.relation === "fork" || relA.relation === "stale") && resolution !== "overwrite-dst") {
364
+ if (sessionStorage.assertArchiveAnonymous !== undefined && (await readSessionAgent(sessionStorage, sessionId)) === undefined) {
365
+ try {
366
+ await sessionStorage.assertArchiveAnonymous(sessionId);
367
+ }
368
+ catch (e) {
369
+ if (e instanceof StampedSessionArchiveError && sendSessionError(res, e, "import"))
370
+ return;
371
+ throw e;
372
+ }
373
+ }
362
374
  sendError(res, 409, "conflict", "sync conflict", { relation: relA });
363
375
  return;
364
376
  }
@@ -5,7 +5,7 @@ import { graftParkToolCallId, markChildrenStoppedByUserOnAbort, syncRefusalHttpS
5
5
  import { atHostBoundary, sealResultForExit, outwardPlane } from "../../terminal.js";
6
6
  import { voidVerifierPauseCard } from "../../verifier-pause-card.js";
7
7
  import { withPrincipal } from "../../observability/principal-context.js";
8
- import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../../fleet/fleet-bus.js";
8
+ import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, nameFleetRowOnDelivery } from "../../fleet/fleet-bus.js";
9
9
  import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
10
10
  import { createLiveModelText, modelStreamOfTask } from "../../trace/model-segment.js";
11
11
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../../orchestration/workflow-completion-inbox.js";
@@ -17,7 +17,7 @@ import { recordResourceWindow } from "../../resource-window.js";
17
17
  import { redactSecrets } from "../../trace/redact.js";
18
18
  import { writeLiveFrame, writeLiveHeartbeat } from "../live-frame.js";
19
19
  import { contextUsageEventData, turnEndEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, compactionStartedEventData, workspaceChangedEventData, wiringManifestFaceEventData, toolRosterDeltaEventData, toolProgressEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, liveIdentityFields } from "../../trace/project.js";
20
- import { cascadeConfig, runMeta } from "../run-meta.js";
20
+ import { cascadeConfig, fillRunPreviewOnDelivery, runMeta } from "../run-meta.js";
21
21
  import { idemCallerOf, inflightKeyOf, submissionIdemOf } from "../idempotency.js";
22
22
  import { replayOfExistingRun } from "../submit-replay.js";
23
23
  import { httpErrorCode, sendJson, sendError, sseHeaders } from "../send.js";
@@ -33,8 +33,7 @@ import { failOpenTagForDroppedFrame, recordFailOpen } from "../../observability/
33
33
  import { buildSyncSubmitBody } from "../sync-submit-body.js";
34
34
  import { assertNever } from "../route-ctx.js";
35
35
  import * as facts from "../../capability-facts.js";
36
- import { persistSubmitCtx } from "../submit-ctx.js";
37
- import { serviceVersion } from "../../service-version.js";
36
+ import { persistSubmissionCtx } from "../submit-ctx.js";
38
37
  import { withModelReachScope } from "../../model-reach-scope.js";
39
38
  import { createInstructionSelectionLeg } from "../../instruction-selection.js";
40
39
  import { fieldConflictFacts } from "../../field-conflict.js";
@@ -326,9 +325,10 @@ export async function handleTasks(req, res, match, ctx) {
326
325
  return placementReject;
327
326
  }
328
327
  durableTaskId = tid;
328
+ fillRunPreviewOnDelivery(prepared.acceptance, deps.runStore, tid);
329
329
  }
330
330
  if (deps.checkpointStore) {
331
- const ctxRefusal = await persistSubmitCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope, writerVersion: serviceVersion() });
331
+ const ctxRefusal = await persistSubmissionCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared);
332
332
  if (ctxRefusal) {
333
333
  writeLiveFrame(res, { type: "done", result: doneResultOfOutcome(ctxRefusal) });
334
334
  return ctxRefusal;
@@ -361,8 +361,10 @@ export async function handleTasks(req, res, match, ctx) {
361
361
  let fleetPub;
362
362
  try {
363
363
  fleetPub = durableTaskId
364
- ? fleetRunPublisher(deps.fleetBus, { runId: durableTaskId, scope: gatedPrincipal(req, deps.config) ?? "default", rootTaskId: durableTaskId, sessionId: prepared.spec.sessionId, ...fleetRunLabels(prepared.spec.objective) })
364
+ ? fleetRunPublisher(deps.fleetBus, { runId: durableTaskId, scope: gatedPrincipal(req, deps.config) ?? "default", rootTaskId: durableTaskId, sessionId: prepared.spec.sessionId, ...fleetRunLabels(prepared.acceptance.objectiveAtSubmit) })
365
365
  : undefined;
366
+ if (fleetPub)
367
+ nameFleetRowOnDelivery(prepared.acceptance, fleetPub);
366
368
  }
367
369
  catch (e) {
368
370
  if (durableHeartbeat)
@@ -499,7 +501,7 @@ export async function handleTasks(req, res, match, ctx) {
499
501
  userMsgEntryId = promptEntryId;
500
502
  const causeAtDone = ev.type === "done" ? ac.cause() : undefined;
501
503
  await deps.userInputLedger?.observeLegEvent(liveStream, ev);
502
- prepared.acceptance.observe(ev);
504
+ await prepared.acceptance.observe(ev);
503
505
  await liveWorkspace.observe(ev);
504
506
  if (ev.type === "done") {
505
507
  finalResult = await mintSubmitExitResult(ev.result, deps.checkpointStore);
@@ -894,9 +896,10 @@ export async function handleTasks(req, res, match, ctx) {
894
896
  if (placementReject)
895
897
  return placementReject;
896
898
  durableTaskId = tid;
899
+ fillRunPreviewOnDelivery(prepared.acceptance, deps.runStore, tid);
897
900
  }
898
901
  if (deps.checkpointStore) {
899
- const ctxRefusal = await persistSubmitCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope, writerVersion: serviceVersion() });
902
+ const ctxRefusal = await persistSubmissionCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared);
900
903
  if (ctxRefusal)
901
904
  return ctxRefusal;
902
905
  }
@@ -940,7 +943,7 @@ export async function handleTasks(req, res, match, ctx) {
940
943
  const syncWorkspace = createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), prepared.legFacts?.sessionWitness);
941
944
  for await (const ev of stream) {
942
945
  observeSyncLegEvent(ev);
943
- prepared.acceptance.observe(ev);
946
+ await prepared.acceptance.observe(ev);
944
947
  await syncWorkspace.observe(ev);
945
948
  }
946
949
  return stream.result();
@@ -1,21 +1,28 @@
1
1
  /**
2
- * [ref] A9 装配缝的一片小叶子 —— 提交路径共用的两个纯函数(run 行元数据 + cascade 配置)。
2
+ * [ref] A9 装配缝的一片小叶子 —— 提交路径共用的三只小件(run 行元数据 + run 行预览的补写口 + cascade 配置)。
3
3
  *
4
- * 为什么单独成文件:`runMeta` 被 tasks 与 runs **两个**域模块调用,`cascadeConfig` 同理(还被测试直接引)。
5
- * 挂在任一域模块上都会造出 routes/* 互相值 import 的第二条边;它们又是纯函数(只依赖入参 + redact 叶子),
4
+ * 为什么单独成文件:`runMeta` 与预览补写口被 tasks 与 runs **两个**域模块调用,`cascadeConfig` 同理(还被测试直接引)。
5
+ * 挂在任一域模块上都会造出 routes/* 互相值 import 的第二条边;它们只依赖入参(受理座 / run 店两只口)+ 预览读法叶子,
6
6
  * 所以正确归处是叶子。server.ts 转出口 `cascadeConfig` 以保住既有 import 点。
7
7
  */
8
- import type { TaskSpec, CascadeConfig } from "@sema-agent/core";
8
+ import type { CascadeConfig } from "@sema-agent/core";
9
+ import type { RunAcceptance } from "../run-acceptance.js";
10
+ import type { RunStore } from "../plugins/store-backend.js";
9
11
  /** The denormalized run-row metadata captured at submit: correlation (jobId), credential-derived source,
10
- * and a redacted objective glance for lists. One place so the three submit paths can't drift. */
12
+ * and a redacted objective glance for lists. One place so the three submit paths can't drift.
13
+ *
14
+ * 预览只取受理座交出的、提交那一刻就知道的交付文本(律的属主 = `run-acceptance.ts`「交付的是哪一份」):腿上没有 prompt 屏 ⇒ 交付的就是请求原文,
15
+ * 建行即写;腿上有屏 ⇒ 建行**不写**(列 NULL = 没建过预览),引擎交付可写的那一份时经 {@link fillRunPreviewOnDelivery} 补写一次。本函数不读请求体。 */
11
16
  export declare function runMeta(prepared: {
12
17
  jobId?: string;
13
- spec: TaskSpec;
18
+ acceptance: Pick<RunAcceptance, "objectiveAtSubmit">;
14
19
  }, source: string | null): {
15
20
  jobId: string | null;
16
21
  source: string | null;
17
- objectivePreview: string;
22
+ objectivePreview: string | null;
18
23
  };
24
+ /** 腿上有屏时 run 行预览的补写口(建行之后、起跑之前登记):交付的那一份 → 同一只读法 → 店的写一次口(列已有值则不动)。腿上没有屏 ⇒ 受理座永不调它。 */
25
+ export declare function fillRunPreviewOnDelivery(acceptance: Pick<RunAcceptance, "onDelivered">, runStore: Pick<RunStore, "fillObjectivePreview">, taskId: string): void;
19
26
  /** Build the cascade config from the operator ladder. Default gate = `status==="completed"` (escalate
20
27
  * on a cheap-rung failure); cost-ceiling from the task's budget bounds the cold-rerun tax. */
21
28
  export declare function cascadeConfig(ladder: readonly string[], maxCostUsd?: number): CascadeConfig;
@@ -1,12 +1,15 @@
1
1
  import { buildRunObjectivePreview } from "../user-authored-text.js";
2
2
  export function runMeta(prepared, source) {
3
- const obj = prepared.spec.objective ?? "";
3
+ const delivered = prepared.acceptance.objectiveAtSubmit;
4
4
  return {
5
5
  jobId: prepared.jobId ?? null,
6
6
  source,
7
- objectivePreview: buildRunObjectivePreview(obj),
7
+ objectivePreview: delivered === undefined ? null : buildRunObjectivePreview(delivered),
8
8
  };
9
9
  }
10
+ export function fillRunPreviewOnDelivery(acceptance, runStore, taskId) {
11
+ acceptance.onDelivered("run-preview", (objective) => runStore.fillObjectivePreview(taskId, buildRunObjectivePreview(objective)));
12
+ }
10
13
  export function cascadeConfig(ladder, maxCostUsd) {
11
14
  return {
12
15
  ladder: ladder.map((model) => ({ model })),
@@ -2,7 +2,7 @@ import http from "node:http";
2
2
  import type { MemoryComplianceFaces, MemoryOriginFaces, SessionMemoryStatusFace } from "../memory-operator-faces.js";
3
3
  import type { SideQueryAccounting } from "../budget.js";
4
4
  import type { IncomingMessage } from "node:http";
5
- import { type Runner, type TaskSpec, type TaskResult, type WorkflowRunStore, type MemoryEntry, type MemoryExportBundle, type MemoryImportReport, type ConsolidationRunReceipt, type ConsolidationDriverRunRow, type SideQuerySpec, type SideQueryResult, type Brain, type EngineNotice } from "@sema-agent/core";
5
+ import { type Runner, type RunnerDeps, type TaskSpec, type TaskResult, type WorkflowRunStore, type MemoryEntry, type MemoryExportBundle, type MemoryImportReport, type ConsolidationRunReceipt, type ConsolidationDriverRunRow, type SideQuerySpec, type SideQueryResult, type Brain, type EngineNotice } from "@sema-agent/core";
6
6
  import type { TaskRequestBody } from "./wire-types.js";
7
7
  import type { ServiceConfig } from "../config-types.js";
8
8
  import { type SessionMirrorRuling } from "../web-admin-client/facade.js";
@@ -65,7 +65,7 @@ export interface RequestAuth {
65
65
  * 平铺形的直接后果 = main.ts 一个上千行的装配字面量,键与键之间没有任何「属于同一件事」的编译期表达,
66
66
  * 键落错家/从未接线只能靠源码钉逐个盯(1.300/1.301/1.304/1.307/1.310 五起真缺陷都是这个病族)。
67
67
  *
68
- * 药:七个**窄**接口 + 顶层必填八件。装配点按组构造(每组一个带类型标注的中间 const ⇒ 受 TS 多余
68
+ * 药:七个**窄**接口 + 顶层必填件(今天九件)。装配点按组构造(每组一个带类型标注的中间 const ⇒ 受 TS 多余
69
69
  * 属性检查保护),键放错组当场编译红。
70
70
  *
71
71
  * 兼容形(本批**不动消费点**):`ServiceDeps` = 平铺视图 ∩ 组槽。旧的平铺构造(96 个测试文件 +
@@ -73,8 +73,8 @@ export interface RequestAuth {
73
73
  * 入口摊平回平铺视图,server.ts 内的 ~700 处 `deps.x` 消费点原样保留。消费点迁移是后批的事——
74
74
  * 一步到位需要同批改 ~700 处 src + ~800 处 test 键位,与「先立形、后搬家」相比没有额外收益。
75
75
  */
76
- /** createHttpServer 的**必填八件**——引擎、部署配置、提交的身份与根会话(authorizer)、请求→TaskSpec 的映射、选路席、side-query 执行席、读口场景门、对端座的 desk。
77
- * 八者恒在场,故留在顶层(不进任何可选组):没有它们 server 根本起不来,包一层可选组只会把编译期
76
+ /** createHttpServer 的**必填九件**——引擎、部署配置、提交的身份与根会话(authorizer)、请求→TaskSpec 的映射、选路席、side-query 执行席、读口场景门、对端座的 desk、部署座的 mod 注册。
77
+ * 九者恒在场,故留在顶层(不进任何可选组):没有它们 server 根本起不来,包一层可选组只会把编译期
78
78
  * 错误推迟到运行期(本仓 E-HIGH-1 病族的成因就是「可选键从未接线、编译期无声」)。 */
79
79
  export interface ServiceCoreDeps {
80
80
  runner: Runner;
@@ -172,6 +172,14 @@ export interface ServiceCoreDeps {
172
172
  * 🔴 **必填**:没有 MCP / A2A 凭据的嵌入也给一只 `createPeerDesk()`(零 IO)—— 一个部署只有一张登记表,缺席不是「没有座」的另一种读法。
173
173
  */
174
174
  peerDesk: PeerDesk;
175
+ /**
176
+ * 部署座的 mod 注册(`RunnerDeps.mods`:这个部署每只 Runner 都带的同一份;起服第五段装配口 `assembleDeploymentMods` 交回,装配点把同一个值交给 Runner 与这里)。
177
+ * 本层只问它一件事:这条腿上有没有 prompt 屏(`prompt-screen.ts` 的 `promptScreenDeclared`,读注册**声明**的事件,连同任务座 `TaskSpec.mods`)——
178
+ * 有屏 ⇒ 受理座把目标落盘(run 行预览 · 续跑体 · 会话标题 · fleet 行名)推迟到引擎原样交付之后(读法全文 `run-acceptance.ts`「交付的是哪一份」)。
179
+ * 🔴 **必填**:缺席若读成「没有屏」,装了屏的部署会把屏前文本落进上面四处,而且谁也看不见 —— 机密轴上没有静默的那一侧。没有 mod 的嵌入显式传 `[]`;
180
+ * 与交给 Runner 的必须是同一个值(两处不同 = 嵌入方的装配错,本层读不出来)。
181
+ */
182
+ deploymentMods: NonNullable<RunnerDeps["mods"]>;
175
183
  }
176
184
  /** **durable 持久面**:每个键 present ⇔ 对应路由/能力位在场,absent ⇒ 501(诚实缺席),
177
185
  * 绝不静默降级。装配点 = main.ts 的 store backend 开箱段。 */
@@ -880,7 +888,7 @@ export interface ServiceDepGroups {
880
888
  deployment?: ServiceDeploymentDeps;
881
889
  knobs?: ServiceKnobDeps;
882
890
  }
883
- /** 平铺视图 = 必填八件 + 其余七组的全可选并集。**派生**自组接口,故与分组形永不漂。 */
891
+ /** 平铺视图 = 必填九件 + 其余七组的全可选并集。**派生**自组接口,故与分组形永不漂。 */
884
892
  export type FlatServiceDeps = ServiceCoreDeps & Partial<ServiceStoreDeps & ServiceCoordinatorDeps & ServiceSeamDeps & ServiceObservabilityDeps & ServiceGovernanceDeps & ServiceDeploymentDeps & ServiceKnobDeps>;
885
893
  /** createHttpServer 的入参:两形皆可(平铺=存量,分组=推荐)。同名键两形都给时,**顶层显式键赢**
886
894
  * (含显式 `undefined`)——一处装配点只该用一形,混用时以肉眼最近的那个为准。 */
@@ -66,6 +66,9 @@ export function createHttpServer(rawDeps) {
66
66
  if (typeof deps.authorize !== "function") {
67
67
  throw new Error("createHttpServer: the authorize seat is required — it is the only resolver of a submission's root session; pass `createAuthorizer(config, runner.sessions)` for a single-user embedding; refusing to start rather than run submissions on a root nobody attested");
68
68
  }
69
+ if (!Array.isArray(deps.deploymentMods)) {
70
+ throw new Error("createHttpServer: the deploymentMods seat is required — it is how the server learns whether a prompt screen runs on its legs (the same registrations the Runners carry as `mods`); pass `[]` for an embedding without mods; refusing to start rather than store objective text before the screen decides");
71
+ }
69
72
  {
70
73
  const retired = retiredServiceDepsRefusal(deps);
71
74
  if (retired !== undefined)
@@ -3,6 +3,9 @@ import type { Logger } from "../observability/logger.js";
3
3
  import type { Metrics } from "../observability/metrics.js";
4
4
  import { buildCheckpointCtx, type CheckpointCtxSealer, type SealedCheckpointCtx } from "./checkpoint-ctx.js";
5
5
  import { type RefusalBody } from "./refusal-body.js";
6
+ import type { CheckpointCtx, PreparedTaskSubmission } from "./route-ctx.js";
7
+ /** 这份续跑输入里体的目标是不是**不可展示**的那一份(`CheckpointCtx.promptScreened` 在场)。读续跑体目标的展示面只经这一只问;落盘的 JSON 逐位判形,不信型面。 */
8
+ export declare function objectiveWithheldIn(ctx: Pick<CheckpointCtx, "promptScreened">): boolean;
6
9
  /** 写不进的那一种可答复的结局(各腿把它交回自己的出口:同步 / durable 腿直接回,活流腿编码成终帧)。 */
7
10
  export interface SubmitCtxRefusal {
8
11
  readonly status: 413 | 409;
@@ -23,4 +26,13 @@ export declare function persistSubmitCtx(store: {
23
26
  readonly runStore: RunStore;
24
27
  readonly taskId: string;
25
28
  } | undefined, sessionId: string, fields: Parameters<typeof buildCheckpointCtx>[0]): Promise<SubmitCtxRefusal | undefined>;
29
+ /**
30
+ * 提交腿写续跑输入的入口(三个调用点同调这一只):体与标记按受理座提交时交出的那一份定(见文件头「体里的目标按哪一份写」),只写这一笔。
31
+ * 写不进的处置与答复 = {@link persistSubmitCtx}(本函数只定体、不另判)。
32
+ */
33
+ export declare function persistSubmissionCtx(store: {
34
+ putCtx(sessionId: string, ctx: SealedCheckpointCtx): Promise<void>;
35
+ }, deps: Parameters<typeof persistSubmitCtx>[1], claim: Parameters<typeof persistSubmitCtx>[2], prepared: Pick<PreparedTaskSubmission, "body" | "auth" | "verify" | "acceptance" | "bodyFromScreenedCtx"> & {
36
+ readonly spec: Pick<PreparedTaskSubmission["spec"], "sessionId" | "model">;
37
+ }): Promise<SubmitCtxRefusal | undefined>;
26
38
  //# sourceMappingURL=submit-ctx.d.ts.map