@sema-agent/server 7.143.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 (139) hide show
  1. package/MIGRATION.md +295 -0
  2. package/README.md +3 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +34 -16
  5. package/dist/approval-card.d.ts +31 -14
  6. package/dist/approval-card.js +8 -8
  7. package/dist/audit.d.ts +2 -21
  8. package/dist/audit.js +0 -9
  9. package/dist/boot/execution-env.js +1 -1
  10. package/dist/boot/runner-deps.d.ts +10 -5
  11. package/dist/boot/runner-deps.js +5 -4
  12. package/dist/boot/session-faces.d.ts +1 -1
  13. package/dist/boot/session-faces.js +3 -3
  14. package/dist/boot/shutdown.d.ts +1 -1
  15. package/dist/boot/shutdown.js +4 -2
  16. package/dist/boot/stage-01-config.js +2 -0
  17. package/dist/boot/stage-07-capability-layer.js +1 -1
  18. package/dist/boot/stage-09-leader.d.ts +1 -1
  19. package/dist/boot/stage-10-http-server.d.ts +1 -1
  20. package/dist/boot/stage-10-http-server.js +4 -9
  21. package/dist/capabilities/scenarios.d.ts +14 -18
  22. package/dist/capabilities/scenarios.js +1 -2
  23. package/dist/config-d-migration.d.ts +3 -0
  24. package/dist/delegated-session-owner.d.ts +33 -151
  25. package/dist/delegated-session-owner.js +1 -83
  26. package/dist/entry-mode-clamp-notice.d.ts +1 -1
  27. package/dist/execution-placement.d.ts +7 -4
  28. package/dist/fleet/fleet-bus.d.ts +13 -3
  29. package/dist/fleet/fleet-bus.js +17 -3
  30. package/dist/hooks/classic-stations.d.ts +3 -1
  31. package/dist/hooks/classic-stations.js +2 -0
  32. package/dist/http/admission.js +2 -1
  33. package/dist/http/checkpoint-ctx.d.ts +1 -0
  34. package/dist/http/checkpoint-ctx.js +1 -0
  35. package/dist/http/durable-run-leg.js +7 -6
  36. package/dist/http/principal-gate.d.ts +1 -1
  37. package/dist/http/refusal-body.d.ts +7 -3
  38. package/dist/http/refusal-body.js +1 -0
  39. package/dist/http/resume-legs.js +16 -7
  40. package/dist/http/route-ctx.d.ts +15 -0
  41. package/dist/http/routes/approvals-assistant.d.ts +20 -1
  42. package/dist/http/routes/approvals-assistant.js +28 -6
  43. package/dist/http/routes/rules.d.ts +1 -1
  44. package/dist/http/routes/rules.js +5 -2
  45. package/dist/http/routes/session-sync.js +12 -0
  46. package/dist/http/routes/tasks.js +12 -9
  47. package/dist/http/run-meta.d.ts +14 -7
  48. package/dist/http/run-meta.js +5 -2
  49. package/dist/http/server.d.ts +13 -5
  50. package/dist/http/server.js +3 -0
  51. package/dist/http/submit-ctx.d.ts +12 -0
  52. package/dist/http/submit-ctx.js +23 -0
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.js +1 -0
  55. package/dist/lane-home-probe.d.ts +15 -0
  56. package/dist/lane-home-probe.js +4 -0
  57. package/dist/observability/corrupt-read-seat.d.ts +5 -9
  58. package/dist/observability/corrupt-read-seat.js +1 -2
  59. package/dist/observability/fail-open.d.ts +26 -10
  60. package/dist/observability/fail-open.js +26 -10
  61. package/dist/observability/run-terminal.d.ts +1 -1
  62. package/dist/observability/run-terminal.js +2 -2
  63. package/dist/parked-revive-surface.d.ts +6 -7
  64. package/dist/peer-inbound-face.d.ts +29 -34
  65. package/dist/peer-inbound-face.js +28 -63
  66. package/dist/plugins/approval-ask-store-file.d.ts +16 -2
  67. package/dist/plugins/approval-ask-store-file.js +21 -1
  68. package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
  69. package/dist/plugins/approval-ask-store-memory.js +11 -8
  70. package/dist/plugins/caching-session-store.d.ts +3 -3
  71. package/dist/plugins/caching-session-store.js +3 -3
  72. package/dist/plugins/checkpoint-store-sql.d.ts +62 -37
  73. package/dist/plugins/checkpoint-store-sql.js +123 -40
  74. package/dist/plugins/file-run-store.d.ts +13 -1
  75. package/dist/plugins/file-run-store.js +11 -1
  76. package/dist/plugins/fork-routing-session-store.d.ts +8 -1
  77. package/dist/plugins/fork-routing-session-store.js +6 -0
  78. package/dist/plugins/local-checkpoint-store.d.ts +17 -8
  79. package/dist/plugins/local-checkpoint-store.js +26 -7
  80. package/dist/plugins/local-session-store.d.ts +105 -34
  81. package/dist/plugins/local-session-store.js +168 -65
  82. package/dist/plugins/local-session-window.d.ts +4 -2
  83. package/dist/plugins/memory-run-store.d.ts +2 -0
  84. package/dist/plugins/memory-run-store.js +6 -0
  85. package/dist/plugins/one-time-migrations.d.ts +12 -12
  86. package/dist/plugins/one-time-migrations.js +13 -13
  87. package/dist/plugins/pg-session-storage.d.ts +1 -9
  88. package/dist/plugins/pg-session-storage.js +2 -19
  89. package/dist/plugins/remote-env-host.d.ts +8 -4
  90. package/dist/plugins/remote-env-host.js +7 -9
  91. package/dist/plugins/remote-shell.js +12 -2
  92. package/dist/plugins/run-store-sql.d.ts +4 -0
  93. package/dist/plugins/run-store-sql.js +3 -0
  94. package/dist/plugins/schema-consistency.d.ts +5 -1
  95. package/dist/plugins/session-fork.d.ts +1 -1
  96. package/dist/plugins/session-ownership.d.ts +9 -0
  97. package/dist/plugins/session-ownership.js +6 -0
  98. package/dist/plugins/session-store.d.ts +13 -16
  99. package/dist/plugins/session-store.js +9 -10
  100. package/dist/plugins/sql-json-column.d.ts +24 -2
  101. package/dist/plugins/sql-json-column.js +38 -3
  102. package/dist/plugins/store-backend.js +3 -2
  103. package/dist/plugins/tidb-session-store.d.ts +2 -14
  104. package/dist/plugins/tidb-session-store.js +2 -20
  105. package/dist/prompt-screen.d.ts +21 -0
  106. package/dist/prompt-screen.js +8 -0
  107. package/dist/rules-consent.d.ts +63 -51
  108. package/dist/rules-consent.js +6 -10
  109. package/dist/run-acceptance.d.ts +40 -10
  110. package/dist/run-acceptance.js +38 -5
  111. package/dist/run-local.js +2 -1
  112. package/dist/runs.js +5 -1
  113. package/dist/security.d.ts +10 -7
  114. package/dist/session-agent.d.ts +39 -0
  115. package/dist/session-agent.js +16 -0
  116. package/dist/session-error-wire.d.ts +25 -26
  117. package/dist/session-error-wire.js +4 -5
  118. package/dist/session-incarnation.d.ts +2 -2
  119. package/dist/session-notify-door.d.ts +2 -2
  120. package/dist/session-notify-door.js +5 -1
  121. package/dist/tool-approval.d.ts +3 -1
  122. package/dist/tool-approval.js +47 -18
  123. package/dist/trace/core-keyset-guard.d.ts +1 -1
  124. package/dist/trace/engine-notice-wire.d.ts +1 -1
  125. package/dist/trace/engine-notice-wire.js +1 -1
  126. package/dist/trace/ledger-sink.js +12 -4
  127. package/dist/trace/model-segment.d.ts +26 -6
  128. package/dist/trace/model-segment.js +58 -9
  129. package/dist/trace/project.d.ts +8 -2
  130. package/dist/trace/project.js +6 -4
  131. package/dist/trace/redact.d.ts +77 -2
  132. package/dist/trace/redact.js +234 -99
  133. package/dist/trace/self-minted-wire.d.ts +160 -9
  134. package/dist/trace/self-minted-wire.js +52 -3
  135. package/package.json +3 -3
  136. package/wire-changes/server@7.144.0.json +291 -0
  137. package/wire-changes/server@7.145.0.json +146 -0
  138. package/dist/session-records-skipped-notice.d.ts +0 -21
  139. package/dist/session-records-skipped-notice.js +0 -27
@@ -11,7 +11,7 @@
11
11
  * 它是 handler 体的第二个入参 —— 触发之后 handler 不再起新事、在飞的子进程要叫停(`hooks/hook-runner.ts` 的命令钩子腿)。
12
12
  *
13
13
  * 站的集合 = 引擎事件表里有站的 `classic.*` 名(一条钉从 `MOD_EVENT_TABLE` 派生对拍,不手抄第二份)。
14
- * 观察站(`StopFailure` / `PostCompact` / `PermissionDenied`)只许观察者:答案形是「无」,core 的迁移配方要求挂 `{ observe: true }`。
14
+ * 观察站(`StopFailure` / `PostCompact` / `PermissionDenied` / `SessionStart` / `SessionEnd`)只许观察者:答案形是「无」,core 的迁移配方要求挂 `{ observe: true }`。
15
15
  */
16
16
  import type { ClassicEventMap, ClassicEventName, EngineDollar, On } from "@sema-agent/core";
17
17
  /** 引擎有站的 `classic.*` 名(core 的 `ClassicEventMap` 键集)。 */
@@ -30,6 +30,8 @@ export declare const CLASSIC_STATION_ROLE: {
30
30
  readonly "classic.PreCompact": "chain";
31
31
  readonly "classic.PostCompact": "observe";
32
32
  readonly "classic.PermissionDenied": "observe";
33
+ readonly "classic.SessionStart": "observe";
34
+ readonly "classic.SessionEnd": "observe";
33
35
  };
34
36
  /** 本仓 handler 体对一站作的答:观察站不作答(`void`);链站 = core 的答案形,去掉本仓从不作的 `null` / `false`(PreToolUse 的两个「不表态」词)。 */
35
37
  type StationAnswer<K extends ClassicStation> = (typeof CLASSIC_STATION_ROLE)[K] extends "observe" ? void : Exclude<ClassicEventMap[K]["answer"], null | false>;
@@ -11,6 +11,8 @@ export const CLASSIC_STATION_ROLE = {
11
11
  "classic.PreCompact": "chain",
12
12
  "classic.PostCompact": "observe",
13
13
  "classic.PermissionDenied": "observe",
14
+ "classic.SessionStart": "observe",
15
+ "classic.SessionEnd": "observe",
14
16
  };
15
17
  export function hangClassic(on, station, answer) {
16
18
  if (CLASSIC_STATION_ROLE[station] === "observe") {
@@ -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,
@@ -159,7 +159,7 @@ export declare function fleetViewOf(req: IncomingMessage, w: IdentityWiring, fac
159
159
  * 有身份边界的部署上:
160
160
  * · **无主的行**经无主臂的唯一判据 `ownerlessRowOpen`(`security.ts`):普通会话照旧对任何调用方开放;委派子代的 placed 行只对 fleet 调用方
161
161
  * ({@link sessionCallerOf})开放 —— 任何具名主体、没有 service 凭据的无身份调用方都与别人的行同体(调用点答 404)([ref])。
162
- * 代价如实:属主还没定下的子代行连它将来的属主也暂时读不到(由一次性迁移 M23 / 那一行的下一次停驻定下);
162
+ * 代价如实:升级前写下、属主还没定下的存量子代行连它将来的属主也暂时读不到(SQL 两店由一次性迁移 M23 在窗内每次起服回填;本机文件店没有回填臂;7.144.0 起没有任何店在停驻时定属主 —— 属主只由建行那一笔记下引擎交来的 `placement.owner`);
163
163
  * · 有主的行就是 run 面那一条谓词({@link runOwnerReadable}),一字不变。
164
164
  * run 面(poll / events / cancel …)不读本谓词 —— 那是 run 资源的门,另一只。
165
165
  */
@@ -19,15 +19,19 @@ export type RefusalBody = {
19
19
  /** 拒体:`{ error, errorCode, ...facts }`。 */
20
20
  export declare function refusalBody(r: Refusal): RefusalBody;
21
21
  /**
22
- * 引擎拒一次续跑时的**三个机读判别位** → 拒体的同名事实键(唯一读法;续跑家族的每一只拒答都读它):
23
- * `detail.code` ⇒ `blockedBy`(挡住它的准入码)· `detail.field` ⇒ `field`(哪一侧 / 哪一格)· `detail.reason` ⇒ `reason`(同码多臂时哪一臂)。
22
+ * 引擎拒一次续跑时的**机读事实位** → 拒体的同名事实键(唯一读法;续跑家族的每一只拒答都读它):
23
+ * `detail.code` ⇒ `blockedBy`(挡住它的准入码)· `detail.field` ⇒ `field`(哪一侧 / 哪一格)· `detail.reason` ⇒ `reason`(同码多臂时哪一臂)·
24
+ * `detail.thrownName` ⇒ `thrownName`(引擎 7.62.0 起,只在 `reason: requirements_damaged` 的拒上:判官读这一行时被抛出的东西的名字,有界、不带 message ——
25
+ * 在场 = 引擎缺陷〔判官被一个读时会抛的成员绊倒〕,缺席 = 行的内容与它自述的对不上〔数据损坏〕,运维凭这一位分诊)。
24
26
  * 一员在场 ⇔ 它是非空串;其余(缺席、非串、空串)一律不铸键 —— 引擎没说就是没说,本层不编值,也不把一个非串原样推上 wire。
25
27
  * 载体有两种,读法同一只:同步腿的 `CheckpointError.detail`(`http/resume-legs.ts` `checkpointRefusalAnswer`),与后台停驻子代赎回回执的
26
- * `details.detail`(`parked-decide.ts` 的 422 `parked_resume.startup_failed`;引擎 7.54.0 起它把认领前那只失败的同三位交到回执上)。
28
+ * `details.detail`(`parked-decide.ts` 的 422 `parked_resume.startup_failed`;引擎 7.54.0 起它把认领前那只失败的 `code` / `field` / `reason` 交到回执上 ——
29
+ * 回执只带这三位,`thrownName` 今天不在回执里,所以这条腿的体上它恒缺席;回执哪天多带,同一只读法自然过境)。
27
30
  */
28
31
  export declare function resumeRefusalFacts(detail: unknown): {
29
32
  blockedBy?: string;
30
33
  field?: string;
31
34
  reason?: string;
35
+ thrownName?: string;
32
36
  };
33
37
  //# sourceMappingURL=refusal-body.d.ts.map
@@ -10,6 +10,7 @@ export function resumeRefusalFacts(detail) {
10
10
  ...(word(d.code) ? { blockedBy: d.code } : {}),
11
11
  ...(word(d.field) ? { field: d.field } : {}),
12
12
  ...(word(d.reason) ? { reason: d.reason } : {}),
13
+ ...(word(d.thrownName) ? { thrownName: d.thrownName } : {}),
13
14
  };
14
15
  }
15
16
  //# sourceMappingURL=refusal-body.js.map
@@ -32,7 +32,7 @@ import { redactSecrets } from "../trace/redact.js";
32
32
  import { buildRunObjectivePreview } from "../user-authored-text.js";
33
33
  import { createForwardAppendFailedNote, createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
34
34
  import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
35
- import { reasoningDeltaBytes, reasoningSegmentEventId } from "../trace/model-segment.js";
35
+ import { blockText, reasoningDeltaBytes, reasoningSegmentEventId } from "../trace/model-segment.js";
36
36
  import { errorText } from "../observability/err-text.js";
37
37
  import { recordFailOpen } from "../observability/fail-open.js";
38
38
  import { verifyRoundsFromBody } from "./verify-rounds.js";
@@ -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) {
@@ -912,6 +914,7 @@ export function createResumeLegs(ctx) {
912
914
  const rs = deps.runStore;
913
915
  const startSeq = rs && taskId ? await rs.maxSeq(taskId) : 0;
914
916
  let text = "";
917
+ let block = "";
915
918
  let reasoning = "";
916
919
  let reasoningEventId;
917
920
  const persistThinking = deps.config.traceThinking;
@@ -945,9 +948,11 @@ export function createResumeLegs(ctx) {
945
948
  reasoning = "";
946
949
  reasoningEventId = undefined;
947
950
  }
948
- if (text) {
949
- await append("text", modelTextEventData({ text, eventId: anchor.firstTextEventId }));
951
+ const segment = text + blockText(block);
952
+ if (segment) {
953
+ await append("text", modelTextEventData({ text: segment, eventId: anchor.firstTextEventId }));
950
954
  text = "";
955
+ block = "";
951
956
  }
952
957
  };
953
958
  const driveResume = async () => {
@@ -1037,9 +1042,13 @@ export function createResumeLegs(ctx) {
1037
1042
  await appendPromptManifest(append, deps.promptManifests, taskId);
1038
1043
  switch (ev.type) {
1039
1044
  case "text_delta":
1040
- text += ev.delta;
1045
+ block += ev.delta;
1041
1046
  anchor.onText(ev.eventId);
1042
1047
  break;
1048
+ case "text_end":
1049
+ text += blockText(block, ev);
1050
+ block = "";
1051
+ break;
1043
1052
  case "reasoning_delta": {
1044
1053
  const rb = reasoningDeltaBytes(ev);
1045
1054
  if (persistThinking && rb !== undefined) {
@@ -1422,7 +1431,7 @@ export function createResumeLegs(ctx) {
1422
1431
  const { spec, engineNotices } = resolved;
1423
1432
  const hostSpec = { ...spec, objective };
1424
1433
  const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
1425
- 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 } : {}) },
1426
1435
  fleetScope: resumeFleetScope(req, auth),
1427
1436
  elicitOwner: principal ?? null,
1428
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(),
@@ -412,6 +413,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
412
413
  ...durableRowIdentity("assistant-inbox", s.checkpointId, s.token),
413
414
  scope: s.scope,
414
415
  createdAt: s.createdAt,
416
+ rowVerdict: s.rowVerdict,
415
417
  ...(s.gateKind !== undefined ? { gateKind: s.gateKind } : {}),
416
418
  ...(s.severity !== undefined ? { severity: s.severity } : {}),
417
419
  ...(s.spentMicroUsd !== undefined ? { spentMicroUsd: s.spentMicroUsd } : {}),
@@ -1035,7 +1037,7 @@ function peerInboundRowsFor(face, principal, operatorOwnerParam) {
1035
1037
  const scope = peerBoxScope(principal);
1036
1038
  return () => face.list(scope);
1037
1039
  }
1038
- function projectPendingForWire(row, deployment) {
1040
+ function projectPendingForWire(row, deployment, agent) {
1039
1041
  const governance = deployment;
1040
1042
  const redacted = redactPendingDisclosures(row);
1041
1043
  const governanceForced = governanceOriginOf({ riskDescriptor: row.riskDescriptor ?? undefined }, governance);
@@ -1048,7 +1050,27 @@ function projectPendingForWire(row, deployment) {
1048
1050
  classes: row.classes,
1049
1051
  deploymentListQuestion,
1050
1052
  });
1051
- 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;
1052
1074
  }
1053
1075
  function createSseDeltaLane(res, spec) {
1054
1076
  let prev = new Map();
@@ -1067,7 +1089,7 @@ function createSseDeltaLane(res, spec) {
1067
1089
  },
1068
1090
  };
1069
1091
  }
1070
- 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) {
1071
1093
  const keyOf = (p) => {
1072
1094
  switch (p.kind) {
1073
1095
  case "checkpoint":
@@ -1117,7 +1139,7 @@ export async function streamApprovals(req, res, cs, live, peerInbound, scope, go
1117
1139
  while (!closed) {
1118
1140
  let snapshot;
1119
1141
  try {
1120
- const pending = [...(await cs.listPending(scope)).map((r) => projectPendingForWire(r, governance)), ...(peerInbound?.() ?? [])];
1142
+ const pending = [...(await projectDurableQueue(await cs.listPending(scope), governance, sessions)), ...(peerInbound?.() ?? [])];
1121
1143
  snapshot = { pending, livePending: live ? live.listLivePending(scope) : [] };
1122
1144
  }
1123
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 })),