@sema-agent/server 7.71.0 → 7.73.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 (78) hide show
  1. package/README.md +1 -1
  2. package/USAGE.md +12 -1
  3. package/dist/approval-card.d.ts +16 -17
  4. package/dist/approval-card.js +0 -9
  5. package/dist/boot/config-center.d.ts +6 -1
  6. package/dist/boot/config-center.js +3 -1
  7. package/dist/boot/memory-boundary.d.ts +27 -5
  8. package/dist/boot/memory-boundary.js +6 -6
  9. package/dist/boot/memory-consolidation.js +2 -1
  10. package/dist/boot/runner-deps.d.ts +32 -16
  11. package/dist/boot/runner-deps.js +3 -1
  12. package/dist/boot/stores.js +6 -4
  13. package/dist/boot/unpriced-ceiling-verdict.d.ts +15 -0
  14. package/dist/boot/unpriced-ceiling-verdict.js +39 -0
  15. package/dist/brain.js +2 -9
  16. package/dist/budget.d.ts +21 -2
  17. package/dist/budget.js +3 -3
  18. package/dist/capabilities/tool-defer.d.ts +18 -12
  19. package/dist/config-catalog.js +12 -12
  20. package/dist/config-center/apply-effective.js +2 -4
  21. package/dist/config-center/facade.d.ts +1 -1
  22. package/dist/config-center/restart-signal.js +2 -2
  23. package/dist/config-center/skills-mcp.js +15 -0
  24. package/dist/config-types.d.ts +83 -23
  25. package/dist/config-types.js +19 -1
  26. package/dist/config.d.ts +58 -0
  27. package/dist/config.js +76 -22
  28. package/dist/http/active-run-conflict.d.ts +3 -2
  29. package/dist/http/durable-run-leg.d.ts +64 -0
  30. package/dist/http/durable-run-leg.js +63 -0
  31. package/dist/http/route-ctx.d.ts +14 -0
  32. package/dist/http/route-ctx.js +3 -0
  33. package/dist/http/routes/approvals-assistant.js +3 -3
  34. package/dist/http/routes/capabilities.js +2 -2
  35. package/dist/http/routes/runs.js +23 -68
  36. package/dist/http/routes/workflows.js +1 -0
  37. package/dist/http/server.js +70 -24
  38. package/dist/main.js +4 -1
  39. package/dist/memory-bundle-engine.d.ts +4 -3
  40. package/dist/memory-bundle-engine.js +2 -1
  41. package/dist/memory-incident-sink.d.ts +37 -0
  42. package/dist/memory-incident-sink.js +13 -0
  43. package/dist/memory-operator-faces.d.ts +9 -3
  44. package/dist/memory-operator-faces.js +4 -3
  45. package/dist/memory-scope.d.ts +37 -1
  46. package/dist/memory-scope.js +12 -2
  47. package/dist/observability/fail-open.d.ts +1 -1
  48. package/dist/observability/fail-open.js +1 -1
  49. package/dist/observability/metrics.js +1 -1
  50. package/dist/observability/tool-trace.d.ts +18 -1
  51. package/dist/observability/tool-trace.js +4 -3
  52. package/dist/parked-decide.js +3 -2
  53. package/dist/plugins/e2b-orphan-reclaim.d.ts +4 -4
  54. package/dist/plugins/e2b-orphan-reclaim.js +18 -20
  55. package/dist/plugins/file-run-store.js +1 -0
  56. package/dist/plugins/memory-run-store.js +1 -0
  57. package/dist/plugins/run-store-sql.d.ts +5 -2
  58. package/dist/plugins/run-store-sql.js +5 -3
  59. package/dist/plugins/store-contracts.d.ts +41 -0
  60. package/dist/plugins/store-contracts.js +3 -2
  61. package/dist/run-cancel-context.js +1 -1
  62. package/dist/run-local.js +19 -8
  63. package/dist/runs.d.ts +16 -2
  64. package/dist/runs.js +2 -1
  65. package/dist/runtime-caps-resolver.js +5 -3
  66. package/dist/spec-fields.d.ts +4 -2
  67. package/dist/task-a2a.js +3 -2
  68. package/dist/task-mcp.js +3 -2
  69. package/dist/tool-approval.d.ts +26 -21
  70. package/dist/tool-approval.js +2 -2
  71. package/dist/trace/core-keyset-guard.d.ts +17 -5
  72. package/dist/trace/project.d.ts +55 -0
  73. package/dist/trace/project.js +45 -3
  74. package/dist/usage-analytics.d.ts +20 -2
  75. package/dist/usage-analytics.js +9 -1
  76. package/dist/wire-name-bound.d.ts +14 -0
  77. package/dist/wire-name-bound.js +13 -0
  78. package/package.json +2 -2
@@ -0,0 +1,64 @@
1
+ /**
2
+ * **一条 durable run 的「建行 + 驱动」单一属主**(S-218 提取;此前整段住在 `routes/runs.ts` 的
3
+ * `createDurableRun` 里,只服务 HTTP/A2A 两条提交门)。
4
+ *
5
+ * 🔴 为什么提成模块:`/decide` 的 workflow 第三条车道在**宿主不在 park 上**时,同样要在宿主会话上起
6
+ * 一跑新 turn 把决定交给宿主的模型(S-218)。这条链上挂着 `createRun` 的会话 claim(以及它唯一诚实的
7
+ * 409 出路)、checkpoint ctx 落库、fleet 行发布、resume/rewind 两族锚点捕获、审批 ALS、通告投递……
8
+ * 手抄第二份 = 把这十来件事复制成两份会漂的实现,而漂开的那天没有任何东西会说话(`createDurableRun`
9
+ * 当年从路由体里提取出来时,头注写的就是同一条判词)。方向:`routes/runs.ts` 与 `http/server.ts`
10
+ * 都 import 本模块,本模块谁都不 import —— 与 `http/run-meta.ts` 同一条「两个域共用一件 ⇒ 单独成文件」。
11
+ *
12
+ * 边界(本腿**不**含的东西,留给各自的门):`body.taskId` 幂等重放、`Idempotency-Key` 内存去重、
13
+ * lease / 治理窗 pre-admission、属主门。它们是**提交面**的判据(读请求、写响应),而本腿只吃已经备好的
14
+ * `PreparedTaskSubmission` 并交回一份收据 —— 这正是它能被一条没有 `ServerResponse` 的内部车道复用的原因。
15
+ */
16
+ import { type RunInternals } from "@sema-agent/core";
17
+ import type { RunStore } from "../plugins/store-backend.js";
18
+ import type { PreparedTaskSubmission, RouteCtxBase } from "./route-ctx.js";
19
+ /**
20
+ * 🔴 **TRUSTED run internals** —— 由**部署方**(不是模型、不是请求体、不是工具参数)铸给这一跑的
21
+ * core 内部席位。今天唯一成员 `workflowParkedResume` = 人对一条 parked workflow 子代的决定
22
+ * (语义属主 = core `RunInternals.workflowParkedResume` 的 d.ts 顶注:「A host that decided a workflow
23
+ * child's parked checkpoint launches the run that re-invokes `Workflow({resumeFromRunId})` with the
24
+ * decision here」)。
25
+ *
26
+ * 为什么单独命名而不是直接摊 `RunInternals`:`RunInternals` 里绝大多数席位是**执行腿自己**铸的
27
+ * (onForwardEvent / onTaskNotification / onActivity …),把整只类型开给调用方等于邀请第二个写者;
28
+ * 本类型只开「部署方可以决定的那一格」,新增一格时**必须**在这里显式点名,编译期看得见。
29
+ * 缺席 ⇒ 对两条腿都是完整 no-op。
30
+ */
31
+ export type TrustedRunInternals = Pick<RunInternals, "workflowParkedResume">;
32
+ export interface DurableRunLegArgs {
33
+ /** `prepareSpec` 的产物,或内部车道按同一条 `resolveSpec` 装配出来的等价形。 */
34
+ prepared: PreparedTaskSubmission;
35
+ /** 调用方自带的 taskId(`POST /v1/runs` 的 `body.taskId` 幂等键);缺席 ⇒ 本腿铸 uuidv7。 */
36
+ clientTaskId?: string;
37
+ /** 🔒 这条 run 的 **fleet 行 scope** —— 必须与 `GET /v1/fleet/stream` 过滤用的是同一个身份,且绝不是
38
+ * 自报的 `x-agent-principal` 头(自报头会把行打到受害者的 scope 上,或打到 `default` 而真属主的流把它
39
+ * 过滤掉)。提交门传 `gatedPrincipal(req) ?? "default"`;`/decide` 的第三条车道传 resume 家族的
40
+ * `resumeFleetScope(req, auth)`(同一条 wire 上两条腿因此同答)。 */
41
+ fleetScope: string;
42
+ /** 🔒 这条 run 的 **elicit / question 应答方**(E23:与 respond 门读的是同一个已验身份,不是自报头)。
43
+ * 提交门传 `gatedPrincipal(req) ?? null` = 提交者本人;`/decide` 的第三条车道传**卡属主**(= 原提交者,
44
+ * 不是代为按下按钮的 operator —— 与 resume 腿那条成文口径逐字同源)。`null` = 匿名/开放部署。 */
45
+ elicitOwner: string | null;
46
+ /** run 行元数据的 `source` 列(调用方的系统身份;内部车道传自己的车道名)。 */
47
+ source: string | null;
48
+ /** 见 {@link TrustedRunInternals}。缺席 ⇒ 与本位存在之前逐字相同。 */
49
+ trustedInternals?: TrustedRunInternals;
50
+ }
51
+ /**
52
+ * 建行 + 驱动。返回 `202` = 新行已建并在跑(体带 `taskId`/`sessionId`);`409` = 会话被别的 run 占着,
53
+ * **没有**建行(体 = `buildActiveRunConflict` 的既有形,含 `errorCode: "conflict.session_active_run"`)。
54
+ * 协议渲染归调用方(HTTP `sendJson` / A2A 协议对象 / `/decide` 的受理体)。
55
+ */
56
+ export declare function mintAndDriveDurableRun(ctx: RouteCtxBase, runStore: RunStore, args: DurableRunLegArgs): Promise<{
57
+ status: number;
58
+ body: {
59
+ taskId: string;
60
+ sessionId: string;
61
+ status: string;
62
+ } | object;
63
+ }>;
64
+ //# sourceMappingURL=durable-run-leg.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { uuidv7 } from "@sema-agent/core";
2
+ import { runInBackground } from "../runs.js";
3
+ import { fleetRunPublisher, fleetRunLabels } from "../fleet/fleet-bus.js";
4
+ import { cascadeConfig, runMeta } from "./run-meta.js";
5
+ import { resolveStreamApprovalGate } from "../tool-approval.js";
6
+ import { buildActiveRunConflict } from "./active-run-conflict.js";
7
+ import { clearTurnActivity, readTurnActivityMs } from "../turn-activity.js";
8
+ import { recordResourceWindow } from "../resource-window.js";
9
+ export async function mintAndDriveDurableRun(ctx, runStore, args) {
10
+ const { deps } = ctx;
11
+ const { inflightRuns, preemptableRuns, steerableRuns } = ctx.registry;
12
+ const sessionId = args.prepared.spec.sessionId ?? uuidv7();
13
+ const taskId = args.clientTaskId ?? uuidv7();
14
+ const created = await runStore.createRun(taskId, sessionId, args.prepared.auth?.principal ?? null, deps.instanceId ?? "default", runMeta(args.prepared, args.source));
15
+ if (created.ok)
16
+ clearTurnActivity(taskId);
17
+ if (created.ok)
18
+ recordResourceWindow(taskId, args.prepared.spec.resourceSuspend);
19
+ if (created.ok)
20
+ deps.sessionTitler?.maybeTitle(sessionId, args.prepared.spec.objective, typeof args.prepared.spec.model === "string" ? args.prepared.spec.model : args.prepared.spec.model?.id);
21
+ if (!created.ok) {
22
+ if (args.clientTaskId && created.activeTaskId === args.clientTaskId) {
23
+ return { status: 202, body: { taskId: args.clientTaskId, sessionId, status: "running" } };
24
+ }
25
+ return { status: 409, body: await buildActiveRunConflict({ runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, sessionId, created.activeTaskId) };
26
+ }
27
+ if (deps.checkpointStore) {
28
+ await deps.checkpointStore.putCtx(sessionId, { body: args.prepared.body, memoryScope: args.prepared.auth?.memoryScope });
29
+ }
30
+ const anchorOwner = args.prepared.auth?.principal ?? null;
31
+ const getLeafId = deps.sessionStorage?.getLeafId?.bind(deps.sessionStorage);
32
+ const captureTurnAnchor = deps.resumeAnchorStore && getLeafId
33
+ ? async (eventId) => {
34
+ const leaf = await getLeafId(sessionId);
35
+ if (leaf)
36
+ await deps.resumeAnchorStore.put(sessionId, eventId, leaf, anchorOwner);
37
+ }
38
+ : undefined;
39
+ const captureUserMessageAnchor = deps.resumeAnchorStore
40
+ ? async (entryId) => { await deps.resumeAnchorStore.put(sessionId, taskId, entryId, anchorOwner); }
41
+ : undefined;
42
+ const fleetPub = fleetRunPublisher(deps.fleetBus, {
43
+ runId: taskId,
44
+ scope: args.fleetScope,
45
+ rootTaskId: taskId,
46
+ ...fleetRunLabels(args.prepared.spec.objective),
47
+ });
48
+ void runInBackground(args.prepared.runner, { ...args.prepared.spec, sessionId }, 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
49
+ ? {
50
+ coordinator: deps.toolApproval,
51
+ streamApprovalOn: resolveStreamApprovalGate({
52
+ toolApprovalEnabled: true,
53
+ streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
54
+ backend: deps.backend,
55
+ parkFacility: deps.checkpointStore !== undefined,
56
+ }).active,
57
+ windowMs: deps.config.streamApproval?.windowMs,
58
+ windowMarginMs: deps.config.streamAskWindowMarginMs,
59
+ }
60
+ : undefined, deps.checkpointStore, args.prepared.engineNotices, args.trustedInternals);
61
+ return { status: 202, body: { taskId, sessionId, status: "running" } };
62
+ }
63
+ //# sourceMappingURL=durable-run-leg.js.map
@@ -438,6 +438,20 @@ export interface RouteMiss {
438
438
  * 两臂逐字节同形,指路价值不丢。钉=audit-fixes-http「[ref]」格(两臂全文比对);文案变更走
439
439
  * api-error-text-freeze 基线流程。
440
440
  */
441
+ /**
442
+ * **「决定/续跑已受理」那一枚 200 收据的单一铸点**(S-218)。
443
+ *
444
+ * 两条腿投同一份体:①resume 家族的受理点(`driveResumeLeg`,宿主停在 park 上时);②`/decide` 的
445
+ * workflow 第三条车道在宿主**不在** park 上时新铸的那一跑。契约上这两条腿说的是同一句话 ——
446
+ * 「决定已被受理,拿 `taskId` 去 poll `GET /v1/runs/:id` / `/events` 跟终态」——所以消费端**一条分支
447
+ * 都不该加**;写成两份字面量,漂开的那天没有任何东西会说话。
448
+ *
449
+ * `bindingEnforced` 的语义不因铸点而变(「这次决定按绑定坐标受理,不是盲目放行」),故两腿同带。
450
+ */
451
+ export declare function acceptedResumingReceipt(taskId: string, sessionId: string): {
452
+ status: number;
453
+ body: object;
454
+ };
441
455
  export declare const RUN_NOT_FOUND_MESSAGE = "run not found \u2014 the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
442
456
  /** [ref] 前缀=域判别子既成契约:a\* 与 wa\* 形 id 给 TaskOutput/journal 指路;其余给 {@link RUN_NOT_FOUND_MESSAGE}。 */
443
457
  export declare function runNotFoundMessage(id: string): string;
@@ -17,6 +17,9 @@ export function sendResumeOutcome(res, out, logger, extraBody) {
17
17
  res.setHeader("retry-after", String(Math.ceil(wait)));
18
18
  sendJson(res, out.status, extraBody === undefined ? out.body : { ...out.body, ...extraBody });
19
19
  }
20
+ export function acceptedResumingReceipt(taskId, sessionId) {
21
+ return { status: 200, body: { taskId, sessionId, status: "resuming", bindingEnforced: true } };
22
+ }
20
23
  export const RUN_NOT_FOUND_MESSAGE = "run not found — the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
21
24
  export function runNotFoundMessage(id) {
22
25
  const aStar = /^(?:a|wa)[0-9a-f]{4,}$/i.test(id);
@@ -1,4 +1,4 @@
1
- import { screenedGate } from "../../trace/project.js";
1
+ import { screenedStoredGate } from "../../trace/project.js";
2
2
  import { personDecision } from "../../host-decision.js";
3
3
  import { sameExecutionRecord } from "@sema-agent/core";
4
4
  import { DECIDED_REPLAY_SCAN_CAP, approvalPayloadFingerprint } from "../../plugins/checkpoint-store-sql.js";
@@ -87,7 +87,7 @@ async function decideIdempotentReplay(cs, args) {
87
87
  return undefined;
88
88
  }
89
89
  }
90
- return screenedGate(first);
90
+ return screenedStoredGate(first);
91
91
  })();
92
92
  return {
93
93
  status: 200,
@@ -200,7 +200,7 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
200
200
  ...(s.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
201
201
  ...(s.denialLimitFallback !== undefined ? { denialLimitFallback: s.denialLimitFallback } : {}),
202
202
  ...(s.origin !== undefined ? { origin: s.origin } : {}),
203
- ...(s.classifierUnavailable !== undefined ? { classifierUnavailable: s.classifierUnavailable } : {}),
203
+ ...(s.ruleStoreUnreadable !== undefined ? { ruleStoreUnreadable: s.ruleStoreUnreadable } : {}),
204
204
  objective: ctx?.body?.objective ?? null,
205
205
  input,
206
206
  };
@@ -1,4 +1,4 @@
1
- import { DEFAULT_EFFORT_LEVELS, expandTiers, routePairingStatus } from "@sema-agent/core";
1
+ import { expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
2
2
  import { isModelAllowlisted } from "../../model-select.js";
3
3
  import { resolveModelApiKey } from "../../key-resolver.js";
4
4
  import { isSealedKeyPoison } from "../../sealed-key.js";
@@ -194,7 +194,7 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
194
194
  vision: Array.isArray(m.input) ? m.input.includes("image") : false,
195
195
  ...(m.contextWindow ? { contextWindow: m.contextWindow } : {}),
196
196
  ...(m.maxTokens ? { maxOutputTokens: m.maxTokens } : {}),
197
- ...(m.reasoning ? { supportedEffortLevels: [...DEFAULT_EFFORT_LEVELS] } : {}),
197
+ ...((levels) => (levels !== undefined ? { supportedEffortLevels: [...levels] } : {}))(reasoningEffortLevelsOf(m)),
198
198
  ...(m.compat !== undefined ? { compat: m.compat } : {}),
199
199
  ...(atAllowlist.length > 0 ? { atMentionable: isModelAllowlisted(name, atCatalog, atAllowlist) } : {}),
200
200
  routePairing: routePairingOf(deps, name, m),
@@ -6,13 +6,10 @@ import { isTerminalRunStatus, isParkedRunStatus } from "../../plugins/store-cont
6
6
  import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-sql.js";
7
7
  import { redactSecrets } from "../../trace/redact.js";
8
8
  import { HttpError, verifiedPrincipal, isUuidV7, encodeCheckpointScope } from "../../security.js";
9
- import { runInBackground } from "../../runs.js";
10
9
  import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
11
10
  import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
12
- import { fleetRunPublisher, fleetRunLabels } from "../../fleet/fleet-bus.js";
13
11
  import { composeSupervisorCost, infraCost, infraUsageFromEvents, hasInfraPricing } from "../../observability/cost-taxonomy.js";
14
12
  import { recordFailOpen } from "../../observability/fail-open.js";
15
- import { cascadeConfig, runMeta } from "../run-meta.js";
16
13
  import { createHash } from "node:crypto";
17
14
  import { scopedIdempotencyKey } from "../idempotency.js";
18
15
  import { streamSseLog } from "../sse-log.js";
@@ -20,14 +17,14 @@ import { bindSseLifecycle } from "../sse-lifecycle.js";
20
17
  import { buildApprovalPreamble, buildApprovalPreambleSseFrames } from "../../approval-card.js";
21
18
  import { ASK_DECISION_CONSUMER_ABSENT, resolveStreamApprovalGate } from "../../tool-approval.js";
22
19
  import { MAX_DECISION_NOTE_CHARS } from "../../approval-ask-machine.js";
23
- import { normalizeRunEventType } from "../../trace/project.js";
20
+ import { normalizeRunEventType, screenedStoredEventData } from "../../trace/project.js";
24
21
  import { sendJson, sendError, httpErrorCode, sseHeaders } from "../send.js";
25
- import { buildActiveRunConflict } from "../active-run-conflict.js";
26
- import { clearTurnActivity, readTurnActivityMs } from "../../turn-activity.js";
22
+ import { readTurnActivityMs } from "../../turn-activity.js";
27
23
  import { mintCancelledResult } from "../../run-cancel-context.js";
28
- import { buildCrossSliceUsage, readResourceWindow, recordResourceWindow } from "../../resource-window.js";
24
+ import { buildCrossSliceUsage, readResourceWindow } from "../../resource-window.js";
29
25
  import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
30
26
  import { ActorAssertionWire } from "@sema-agent/settings-schema";
27
+ import { mintAndDriveDurableRun } from "../durable-run-leg.js";
31
28
  import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
32
29
  import { withPrincipal } from "../../observability/principal-context.js";
33
30
  export const RUN_ID_RE = /^\/v1\/runs\/([^/]+)(\/events)?$/;
@@ -140,9 +137,10 @@ async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
140
137
  retainedFrom: (id) => runStore.retainedFrom(id),
141
138
  formatEvent: (ev) => {
142
139
  const type = normalizeRunEventType(ev.type);
143
- const data = type === "done" && ev.data && typeof ev.data === "object" && ev.data.result && typeof ev.data.result === "object"
140
+ const redacted = type === "done" && ev.data && typeof ev.data === "object" && ev.data.result && typeof ev.data.result === "object"
144
141
  ? { ...ev.data, result: memoRedactTerminalResult("ev.done.result", ev.data.result) }
145
142
  : type === "failed" ? memoRedactedRead("ev.failed", ev.data, (d) => redactLedgerEventData(type, d)) : redactLedgerEventData(type, ev.data);
143
+ const data = screenedStoredEventData(type, redacted);
146
144
  return { id: ev.seq, event: type, data: { type, ...(data ?? {}) } };
147
145
  },
148
146
  staleFrame: () => ({ event: "failed", data: { type: "failed", errorMessage: "run stalled (instance lost?)" } }),
@@ -265,6 +263,8 @@ async function handleRunsBody(req, res, url, ctx, miss) {
265
263
  error: stale ? "run stalled (instance lost?)" : isTerminalRunStatus(run.status) ? memoRedactedRead("run.error", run.error ?? undefined, redactErrorMessage) : redactErrorMessage(run.error ?? undefined),
266
264
  jobId: run.jobId ?? undefined,
267
265
  source: run.source ?? undefined,
266
+ heldBy: run.instanceId ?? null,
267
+ cancelRequested: run.cancelRequested,
268
268
  });
269
269
  }
270
270
  return;
@@ -305,7 +305,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
305
305
  sendError(res, 409, "conflict.approval_settled", "pending approval was settled concurrently (decided or expired) — re-check the run and retry cancel if it is still active", { taskId, status: st });
306
306
  }
307
307
  else {
308
- sendJson(res, 202, { taskId, status: st ?? "failed", note: "run already terminal — cancel is a no-op" });
308
+ sendJson(res, 202, { taskId, status: st ?? "failed", cancelRequested: now?.cancelRequested ?? false, note: "run already terminal — cancel is a no-op" });
309
309
  }
310
310
  return;
311
311
  }
@@ -319,7 +319,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
319
319
  sendError(res, 409, "conflict.approval_settled", "pending approval was settled concurrently (decided or expired) — re-check the run and retry cancel if it is still active", { taskId, status: st });
320
320
  }
321
321
  else {
322
- sendJson(res, 202, { taskId, status: st ?? "failed", note: "run already terminal — cancel is a no-op" });
322
+ sendJson(res, 202, { taskId, status: st ?? "failed", cancelRequested: now?.cancelRequested ?? false, note: "run already terminal — cancel is a no-op" });
323
323
  }
324
324
  return;
325
325
  }
@@ -355,10 +355,10 @@ async function handleRunsBody(req, res, url, ctx, miss) {
355
355
  }
356
356
  const finalRow = await rs.getRun(taskId).catch(() => undefined);
357
357
  if (finalRow && finalRow.errorCode !== "cancelled" && isTerminalRunStatus(finalRow.status)) {
358
- sendJson(res, 202, { taskId, status: finalRow.status, errorCode: finalRow.errorCode ?? null, note: `session unlocked; the run was terminalized concurrently (${finalRow.errorCode ?? finalRow.status}) before this cancel's write — reporting the actual ledger state` });
358
+ sendJson(res, 202, { taskId, status: finalRow.status, errorCode: finalRow.errorCode ?? null, cancelRequested: finalRow.cancelRequested, note: `session unlocked; the run was terminalized concurrently (${finalRow.errorCode ?? finalRow.status}) before this cancel's write — reporting the actual ledger state` });
359
359
  return;
360
360
  }
361
- sendJson(res, 202, { taskId, status: "failed", errorCode: "cancelled", note });
361
+ sendJson(res, 202, { taskId, status: "failed", errorCode: "cancelled", cancelRequested: false, note });
362
362
  };
363
363
  if (run.status === "running") {
364
364
  let flagged = await deps.runStore.requestCancel(taskId, run.owner);
@@ -374,23 +374,23 @@ async function handleRunsBody(req, res, url, ctx, miss) {
374
374
  continue;
375
375
  }
376
376
  if (now?.status === "running") {
377
- sendError(res, 409, "conflict.approval_settled", "pending approval was settled concurrently (decided or expired) — re-check the run and retry cancel if it is still active", { taskId, status: now.status });
377
+ sendError(res, 409, "conflict.run_not_running", "the cancel flag could not be set: this run was not in 'running' state at the moment of each attempt (it keeps flipping between running and a park) — retry cancel", { taskId, status: now.status });
378
378
  return;
379
379
  }
380
- sendJson(res, 202, { taskId, status: now?.status ?? "failed", note: "run already terminal — cancel is a no-op" });
380
+ sendJson(res, 202, { taskId, status: now?.status ?? "failed", cancelRequested: now?.cancelRequested ?? false, note: "run already terminal — cancel is a no-op" });
381
381
  return;
382
382
  }
383
383
  if (inflightRuns.has(taskId)) {
384
384
  cancelledViaVerb.add(taskId);
385
385
  inflightRuns.get(taskId).abort();
386
386
  }
387
- sendJson(res, 202, { taskId, status: "cancelling" });
387
+ sendJson(res, 202, { taskId, status: "cancelling", cancelRequested: true });
388
388
  }
389
389
  else if (isParkedRunStatus(run.status)) {
390
390
  await cancelSuspended();
391
391
  }
392
392
  else {
393
- sendJson(res, 202, { taskId, status: run.status, note: "run already terminal — cancel is a no-op" });
393
+ sendJson(res, 202, { taskId, status: run.status, cancelRequested: run.cancelRequested, note: "run already terminal — cancel is a no-op" });
394
394
  }
395
395
  return;
396
396
  }
@@ -1559,58 +1559,13 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
1559
1559
  }
1560
1560
  if ((await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
1561
1561
  return null;
1562
- const resp = await idemCache.run(idemKey, async () => {
1563
- const sessionId = prepared.spec.sessionId ?? uuidv7();
1564
- const taskId = clientTaskId ?? uuidv7();
1565
- const created = await runStore.createRun(taskId, sessionId, prepared.auth?.principal ?? null, deps.instanceId ?? "default", runMeta(prepared, source));
1566
- if (created.ok)
1567
- clearTurnActivity(taskId);
1568
- if (created.ok)
1569
- recordResourceWindow(taskId, prepared.spec.resourceSuspend);
1570
- if (created.ok)
1571
- deps.sessionTitler?.maybeTitle(sessionId, prepared.spec.objective, typeof prepared.spec.model === "string" ? prepared.spec.model : prepared.spec.model?.id);
1572
- if (!created.ok) {
1573
- if (clientTaskId && created.activeTaskId === clientTaskId) {
1574
- return { status: 202, body: { taskId: clientTaskId, sessionId, status: "running" } };
1575
- }
1576
- return { status: 409, body: await buildActiveRunConflict({ runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, sessionId, created.activeTaskId) };
1577
- }
1578
- if (deps.checkpointStore) {
1579
- await deps.checkpointStore.putCtx(sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
1580
- }
1581
- const anchorOwner = prepared.auth?.principal ?? null;
1582
- const getLeafId = deps.sessionStorage?.getLeafId?.bind(deps.sessionStorage);
1583
- const captureTurnAnchor = deps.resumeAnchorStore && getLeafId
1584
- ? async (eventId) => {
1585
- const leaf = await getLeafId(sessionId);
1586
- if (leaf)
1587
- await deps.resumeAnchorStore.put(sessionId, eventId, leaf, anchorOwner);
1588
- }
1589
- : undefined;
1590
- const captureUserMessageAnchor = deps.resumeAnchorStore
1591
- ? async (entryId) => { await deps.resumeAnchorStore.put(sessionId, taskId, entryId, anchorOwner); }
1592
- : undefined;
1593
- const fleetPub = fleetRunPublisher(deps.fleetBus, {
1594
- runId: taskId,
1595
- scope: gatedPrincipal(req, deps.config) ?? "default",
1596
- rootTaskId: taskId,
1597
- ...fleetRunLabels(prepared.spec.objective),
1598
- });
1599
- void runInBackground(prepared.runner, { ...prepared.spec, sessionId }, runStore, taskId, deps.metrics, prepared.auth?.principal, prepared.verify, prepared.cascade ? cascadeConfig(deps.config.cascadeLadder, prepared.spec.limits?.maxCostUsd) : undefined, deps.instrumentDegenerate, deps.planCacheProbe, deps.config.traceThinking, inflightRuns, preemptableRuns, steerableRuns, deps.modelUsage, deps.elicitation, gatedPrincipal(req, deps.config) ?? null, captureTurnAnchor, fleetPub, captureUserMessageAnchor, deps.question, deps.workflowCompletionInbox, deps.subagentSteerRegistry, (m, x) => deps.logger?.info?.(m, x), deps.sendUserFile, deps.promptManifests, deps.toolApproval
1600
- ? {
1601
- coordinator: deps.toolApproval,
1602
- streamApprovalOn: resolveStreamApprovalGate({
1603
- toolApprovalEnabled: true,
1604
- streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
1605
- backend: deps.backend,
1606
- parkFacility: deps.checkpointStore !== undefined,
1607
- }).active,
1608
- windowMs: deps.config.streamApproval?.windowMs,
1609
- windowMarginMs: deps.config.streamAskWindowMarginMs,
1610
- }
1611
- : undefined, deps.checkpointStore, prepared.engineNotices);
1612
- return { status: 202, body: { taskId, sessionId, status: "running" } };
1613
- }, (r) => r.status === 202);
1562
+ const resp = await idemCache.run(idemKey, async () => await mintAndDriveDurableRun({ deps, registry: ctx.registry, helpers: ctx.helpers, local: ctx.local, legs: ctx.legs }, runStore, {
1563
+ prepared,
1564
+ ...(clientTaskId !== undefined ? { clientTaskId } : {}),
1565
+ fleetScope: gatedPrincipal(req, deps.config) ?? "default",
1566
+ elicitOwner: gatedPrincipal(req, deps.config) ?? null,
1567
+ source,
1568
+ }), (r) => r.status === 202);
1614
1569
  return resp;
1615
1570
  }
1616
1571
  //# sourceMappingURL=runs.js.map
@@ -338,6 +338,7 @@ function summarizeWorkflowDetail(run) {
338
338
  durationMs: durationOf(run),
339
339
  rev: run.rev,
340
340
  ...(run.error !== undefined ? { error: redactErrorMessage(run.error) ?? "" } : {}),
341
+ ...(run.errorCode !== undefined ? { errorCode: run.errorCode } : {}),
341
342
  ...(run.result !== undefined ? { result: redactSecrets(run.result) } : {}),
342
343
  phases,
343
344
  ...(unphasedView ? { unphased: unphasedView } : {}),
@@ -43,6 +43,8 @@ import { IdempotencyCache, scopedIdempotencyKey } from "./idempotency.js";
43
43
  export { scopedIdempotencyKey };
44
44
  import { streamSseLog } from "./sse-log.js";
45
45
  import { verifyRoundsFromBody } from "./verify-rounds.js";
46
+ import { acceptedResumingReceipt } from "./route-ctx.js";
47
+ import { mintAndDriveDurableRun } from "./durable-run-leg.js";
46
48
  import { RUN_NOT_FOUND_MESSAGE } from "./route-ctx.js";
47
49
  import { handleCapabilities } from "./routes/capabilities.js";
48
50
  import { handleObservability } from "./routes/observability.js";
@@ -1218,22 +1220,14 @@ export function createHttpServer(rawDeps) {
1218
1220
  const plan = planWorkflowParkedDecide(origin, parkedReq);
1219
1221
  if (!plan.ok)
1220
1222
  return plan.deny;
1223
+ const parkedResume = { workflowParkedResume: [{ runId: plan.runId, token, outcome: plan.outcome }] };
1221
1224
  const hostToken = await cs
1222
1225
  .findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
1223
1226
  .catch(() => undefined);
1224
- if (!hostToken) {
1225
- return {
1226
- status: 409,
1227
- body: {
1228
- error: "the workflow's originating session is not parked awaiting this run — the deployment can only deliver the decision on the host's next turn; retry once the host session parks, or resume the workflow run yourself",
1229
- errorCode: "decide.workflow_host_not_parked",
1230
- runId: plan.runId,
1231
- },
1232
- };
1233
- }
1234
- return await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, [
1235
- { runId: plan.runId, token, outcome: plan.outcome },
1236
- ]);
1227
+ const admittedPayer = decodeCheckpointScope(cp.scope);
1228
+ return hostToken
1229
+ ? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, parkedResume.workflowParkedResume, admittedPayer)
1230
+ : await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req, parkedResume, plan.runId);
1237
1231
  }
1238
1232
  }
1239
1233
  let ctx;
@@ -1286,9 +1280,11 @@ export function createHttpServer(rawDeps) {
1286
1280
  noteLateSettleIfAccepted(driven);
1287
1281
  return driven;
1288
1282
  }
1283
+ const ONE_SHOT_HISTORY_KEYS = ["resumeAt", "restoreFiles", "acceptPartialRestore", "rewindFilesTo", "rewindFiles"];
1284
+ const ONE_SHOT_HISTORY_STRIP = Object.fromEntries(ONE_SHOT_HISTORY_KEYS.map((k) => [k, undefined]));
1289
1285
  const resolveResumeSpec = async (body, req, auth) => {
1290
1286
  const engineNotices = [];
1291
- const spec = await deps.resolveSpec({ ...body, resumeAt: undefined }, req, auth, { leg: "resume", onNotice: (n) => engineNotices.push(n) });
1287
+ const spec = await deps.resolveSpec({ ...body, ...ONE_SHOT_HISTORY_STRIP }, req, auth, { leg: "resume", onNotice: (n) => engineNotices.push(n) });
1292
1288
  return { spec, engineNotices };
1293
1289
  };
1294
1290
  async function driveResumeIntoRunLog(args) {
@@ -1638,7 +1634,7 @@ export function createHttpServer(rawDeps) {
1638
1634
  steerableRuns.set(taskId, stream);
1639
1635
  if (!taskId || !rs)
1640
1636
  return await stream.result();
1641
- onAccepted?.({ status: 200, body: { taskId, sessionId, status: "resuming", bindingEnforced: true } });
1637
+ onAccepted?.(acceptedResumingReceipt(taskId, sessionId));
1642
1638
  for await (const ev of stream) {
1643
1639
  recordTurnActivity(taskId);
1644
1640
  fleetPub?.onEvent(ev);
@@ -1832,7 +1828,7 @@ export function createHttpServer(rawDeps) {
1832
1828
  (e.code === "checkpoint.unsupported_version" && checkpointRowRedeemableElsewhere(e.detail?.reason));
1833
1829
  if (claimedRow && taskId && deps.runStore) {
1834
1830
  if (retriable && freshRunClaimed) {
1835
- const c = { taskId, sessionId, terminal: { kind: "failed", code: e.code, message: e.message }, stats: { turns: 0, tokens: 0 } };
1831
+ const c = { taskId, sessionId, terminal: { kind: "failed", code: e.code, message: e.message }, stats: { turns: 0, tokens: 0, usageMissing: true } };
1836
1832
  await deps.runStore.setTerminal(taskId, "failed", c, e.message).catch((err) => {
1837
1833
  deps.logger?.warn?.("wake_synthetic_run_release_failed", { taskId, sessionId, errorCode: e.code, error: err instanceof Error ? err.message : String(err), note: "the synthetic run row for this refused fresh-run resume could not be settled — its task_active claim is still held and the next attempt on this session will 409 conflict.session_active_run until reapStale collects it (runStaleSec)" });
1838
1834
  });
@@ -1987,7 +1983,51 @@ export function createHttpServer(rawDeps) {
1987
1983
  const verifyRounds = verifyRoundsFromBody(ctx.body);
1988
1984
  return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, verifyRounds, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1989
1985
  }
1990
- async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowParkedResume) {
1986
+ async function startFreshHostTurn(sessionId, objective, principal, req, trustedInternals, runId) {
1987
+ const cs = deps.checkpointStore;
1988
+ if (!deps.runStore) {
1989
+ return { status: 501, body: { error: "delivering a workflow decision on the host's next turn requires a durable run store (DB_BACKEND=mysql|pg|local)", errorCode: "capability.run_store_required" } };
1990
+ }
1991
+ const ctx = await cs.getCtx(sessionId).catch(() => null);
1992
+ if (!ctx) {
1993
+ return {
1994
+ status: 409,
1995
+ body: {
1996
+ error: "the workflow's originating session no longer has the rebuild inputs for a new turn: a session's resume context is garbage-collected once it is stale AND that session itself has no pending checkpoint and no active run (REAP_RUN_STALE_SEC, default 120s) — a workflow CHILD's park does not hold the HOST session's row open. Raise REAP_RUN_STALE_SEC to cover how long approvals may sit before someone decides them, or resume the workflow run yourself with the runId below",
1997
+ errorCode: "conflict.resume_context_unavailable",
1998
+ runId,
1999
+ },
2000
+ };
2001
+ }
2002
+ const windowDenial = await usageWindowDenialFor(principal || GLOBAL_USAGE_KEY);
2003
+ if (windowDenial)
2004
+ return { status: windowDenial.status, body: windowDenial.body };
2005
+ const auth = { sessionId, principal, memoryScope: ctx.memoryScope };
2006
+ let spec;
2007
+ let engineNotices = [];
2008
+ try {
2009
+ ({ spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth));
2010
+ }
2011
+ catch (e) {
2012
+ if (e instanceof HttpError) {
2013
+ return { status: 409, body: { error: `resume blocked by an admission policy change: ${e.message}`, errorCode: "resume_blocked_by_policy", ...(e.code ? { blockedBy: e.code } : {}), ...(e.extra ?? {}) } };
2014
+ }
2015
+ throw e;
2016
+ }
2017
+ const hostSpec = { ...spec, objective, sessionId };
2018
+ const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
2019
+ prepared: { spec: hostSpec, runner: runnerFor(hostSpec), auth, body: ctx.body, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
2020
+ fleetScope: resumeFleetScope(req, auth),
2021
+ elicitOwner: principal ?? null,
2022
+ source: "workflow_parked_decide",
2023
+ trustedInternals,
2024
+ });
2025
+ if (out.status !== 202)
2026
+ return { status: out.status, body: out.body };
2027
+ const minted = out.body;
2028
+ return acceptedResumingReceipt(minted.taskId, minted.sessionId);
2029
+ }
2030
+ async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowParkedResume, ownerLeaseAdmittedFor) {
1991
2031
  const cs = deps.checkpointStore;
1992
2032
  const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
1993
2033
  const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
@@ -2058,6 +2098,7 @@ export function createHttpServer(rawDeps) {
2058
2098
  ...(acceptEarly === true ? { acceptEarly: true } : {}),
2059
2099
  ...(req !== undefined ? { callerInitiated: true } : {}),
2060
2100
  ...(workflowParkedResume !== undefined ? { workflowParkedResume } : {}),
2101
+ ...(ownerLeaseAdmittedFor !== undefined && auth.principal === ownerLeaseAdmittedFor ? { ownerLeaseAdmitted: true } : {}),
2061
2102
  });
2062
2103
  }
2063
2104
  async function resumePlanReview(sessionId, decision, editedPlan, reason, req, log, acceptEarly, decider) {
@@ -2222,27 +2263,32 @@ export function createHttpServer(rawDeps) {
2222
2263
  sendError(res, 429, "quota_exhausted", "budget exhausted (quota lease)", { retryAfterSec: adm.retryAfterSec, ...(adm.detail ?? {}) });
2223
2264
  return true;
2224
2265
  }
2225
- async function usageWindowDenied(req, res) {
2266
+ async function usageWindowDenialFor(key) {
2226
2267
  const store = deps.usageWindowStore;
2227
2268
  const windows = deps.config.usageWindows;
2228
2269
  if (!store || !windows || windows.length === 0)
2229
- return false;
2230
- const key = gatedPrincipal(req, deps.config) || GLOBAL_USAGE_KEY;
2270
+ return undefined;
2231
2271
  let retryAfterMs;
2232
2272
  try {
2233
2273
  retryAfterMs = usageRetryAfterMs(await store.read(key, windows, Date.now()));
2234
2274
  }
2235
2275
  catch (e) {
2236
2276
  deps.logger?.warn?.("usage_window_admission_read_failed", { error: e instanceof Error ? e.message : String(e) });
2237
- return false;
2277
+ return undefined;
2238
2278
  }
2239
2279
  if (retryAfterMs === undefined)
2240
- return false;
2280
+ return undefined;
2241
2281
  deps.metrics?.inc("usage_window_rejected_total");
2242
2282
  deps.logger?.info?.("usage_window_exhausted", { hasPrincipal: key !== GLOBAL_USAGE_KEY, retryAfterMs });
2243
2283
  const retryAfterSec = Math.max(1, Math.ceil(retryAfterMs / 1000));
2244
- res.setHeader("retry-after", String(retryAfterSec));
2245
- sendError(res, 429, "usage.window_exhausted", "usage window exhausted (deployment token governance)", { retryAfterSec });
2284
+ return { status: 429, body: { error: "usage window exhausted (deployment token governance)", errorCode: "usage.window_exhausted", retryAfterSec }, retryAfterSec };
2285
+ }
2286
+ async function usageWindowDenied(req, res) {
2287
+ const denial = await usageWindowDenialFor(gatedPrincipal(req, deps.config) || GLOBAL_USAGE_KEY);
2288
+ if (!denial)
2289
+ return false;
2290
+ res.setHeader("retry-after", String(denial.retryAfterSec));
2291
+ sendError(res, denial.status, denial.body.errorCode, denial.body.error, { retryAfterSec: denial.retryAfterSec });
2246
2292
  return true;
2247
2293
  }
2248
2294
  function rateLimited(req, res) {
package/dist/main.js CHANGED
@@ -8,6 +8,7 @@ import { selectEnvironmentTool } from "./capabilities/select-environment-tool.js
8
8
  import { subagentSendUserFileExtraTools } from "./capabilities/send-user-file-tool.js";
9
9
  import { brainSummary } from "./brain.js";
10
10
  import { loadConfig, logConfigDiagnostics, resolveBindHost, splitLocalRoots, parseCapEnv, parseNumOrFailNonNegative } from "./config.js";
11
+ import { createUnpricedCeilingVerdict } from "./boot/unpriced-ceiling-verdict.js";
11
12
  import { buildWriteProtectionPosture } from "./write-protection.js";
12
13
  import { cardRuleScopeRoot } from "./task-cwd.js";
13
14
  import { isPricingConfigured } from "./budget.js";
@@ -129,6 +130,8 @@ async function main() {
129
130
  }
130
131
  const configCenter = await createConfigCenterRuntime({ config, logger, metrics, localRoot });
131
132
  await configCenter.applyLocalRemoteExec();
133
+ const ceilingVerdict = createUnpricedCeilingVerdict({ config, logger });
134
+ ceilingVerdict.judge();
132
135
  const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
133
136
  const instanceId = uuidv7();
134
137
  const sessionCaptureRecords = backend?.sessionCaptureRecords();
@@ -561,7 +564,7 @@ async function main() {
561
564
  }
562
565
  }
563
566
  };
564
- configCenter.startRefreshLoop({ runnerTierFrozen, pricing, limitSync, swapRunnerDeps });
567
+ configCenter.startRefreshLoop({ runnerTierFrozen, pricing, limitSync, swapRunnerDeps, ceilingVerdict });
565
568
  configCenter.initKeyResolver();
566
569
  const { ownerAware, sessionAudit, sessionWatchRegistry, purgeSession, instrumentDegenerate, planCacheProbe } = createSessionFaces({
567
570
  deviceStore: deviceLane?.store,
@@ -53,9 +53,10 @@ export interface MemoryBundleFaces {
53
53
  /**
54
54
  * 在**已装配的记忆后端**上建 bundle 两面。命名照 CLAUDE.md 工厂律:返回的是带行为的活对象 ⇒ `create*`。
55
55
  *
56
- * `memoryDir` = `boot/stores.ts` 的 `memoryEngine.root`(该部署的记忆工作根)。见文件头:这两条路径
57
- * 不读它,传它是为了不发明第二个「引擎根」的概念 —— 万一 core 日后真用上,它拿到的也是本部署唯一的
58
- * 那个根,而不是一个本模块凭空编出来的路径。
56
+ * `memoryDir` = 引擎的**挂载平面**(`memoryMountRootOf(backend, root)` 单铸点;全仓自构引擎同一条规则,
57
+ * 由 `test/memory-layout-plane.test.ts` 的归一门咬住)。见文件头:这两条路径**不读它**(机器钉在
58
+ * `test/memory-bundle-engine.test.ts`:给一个不存在的 `memoryDir` 也照样工作),传对的那一只是为了
59
+ * 万一 core 日后真用上时,它拿到的是本部署真正的挂载平面,而不是高一层的配置根。
59
60
  */
60
61
  export declare function createMemoryBundleFaces(store: {
61
62
  backend: MemoryBackend;
@@ -1,9 +1,10 @@
1
1
  import { MemoryEngine } from "@sema-agent/core";
2
+ import { memoryMountRootOf } from "./memory-scope.js";
2
3
  export function createMemoryBundleFaces(store, opts) {
3
4
  const b = store.backend;
4
5
  if (typeof b.exportSnapshotOf !== "function" || typeof b.importBundleCommit !== "function")
5
6
  return undefined;
6
- const engine = new MemoryEngine({ backend: store.backend, memoryDir: store.root, ...(opts?.captureRecordStore !== undefined ? { captureRecordStore: opts.captureRecordStore } : {}) });
7
+ const engine = new MemoryEngine({ backend: store.backend, memoryDir: memoryMountRootOf(store.backend, store.root), ...(opts?.captureRecordStore !== undefined ? { captureRecordStore: opts.captureRecordStore } : {}) });
7
8
  return {
8
9
  exportScopes: (scopes) => engine.exportMemoryScopes(scopes),
9
10
  importBundle: (bundle, opts) => engine.importMemoryBundle(bundle, opts),