@sema-agent/server 7.107.0 → 7.109.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 (101) hide show
  1. package/MIGRATION.md +72 -1
  2. package/USAGE.md +51 -25
  3. package/dist/approval-ask-audit-store.d.ts +62 -4
  4. package/dist/approval-ask-audit-store.js +67 -11
  5. package/dist/approval-ask-machine.d.ts +9 -25
  6. package/dist/approval-ask-machine.js +5 -2
  7. package/dist/approval-card.d.ts +32 -3
  8. package/dist/approval-card.js +29 -2
  9. package/dist/approval.js +16 -13
  10. package/dist/bench/s1/live-deps.d.ts +4 -5
  11. package/dist/bench/s1/live-deps.js +9 -10
  12. package/dist/boot/coordinators.d.ts +4 -0
  13. package/dist/boot/coordinators.js +3 -2
  14. package/dist/boot/leader.js +0 -2
  15. package/dist/boot/resolve-spec.d.ts +1 -1
  16. package/dist/boot/resolve-spec.js +3 -8
  17. package/dist/boot/shutdown.d.ts +8 -0
  18. package/dist/boot/shutdown.js +31 -8
  19. package/dist/boot/stage-06-runners.d.ts +1 -0
  20. package/dist/boot/stage-06-runners.js +6 -3
  21. package/dist/boot/stage-07-capability-layer.d.ts +1 -0
  22. package/dist/boot/stage-08-reapers.d.ts +1 -0
  23. package/dist/boot/stage-09-leader.d.ts +1 -0
  24. package/dist/boot/stage-10-http-server.d.ts +1 -0
  25. package/dist/boot/stage-10-http-server.js +3 -1
  26. package/dist/brain.js +2 -1
  27. package/dist/capabilities/scenarios.d.ts +3 -2
  28. package/dist/config-catalog.js +1 -3
  29. package/dist/config-center/apply-effective.js +2 -0
  30. package/dist/config-center/types.d.ts +7 -0
  31. package/dist/config-provider.js +1 -0
  32. package/dist/config-types.d.ts +2 -9
  33. package/dist/config.js +2 -5
  34. package/dist/deployment-governance.js +2 -1
  35. package/dist/fleet-client.d.ts +10 -2
  36. package/dist/fleet-client.js +14 -8
  37. package/dist/http/admission.js +8 -2
  38. package/dist/http/memory-compliance-failure-reply.d.ts +41 -0
  39. package/dist/http/memory-compliance-failure-reply.js +73 -0
  40. package/dist/http/memory-store-refusal-reply.d.ts +2 -2
  41. package/dist/http/resume-legs.js +3 -2
  42. package/dist/http/route-ctx.d.ts +3 -3
  43. package/dist/http/routes/capabilities.js +1 -0
  44. package/dist/http/routes/diagnostics.js +2 -0
  45. package/dist/http/routes/memory-compliance.js +2 -34
  46. package/dist/http/routes/memory-origin.d.ts +1 -1
  47. package/dist/http/routes/sessions.js +3 -11
  48. package/dist/http/routes/side-query.js +7 -2
  49. package/dist/http/routes/tasks.js +2 -1
  50. package/dist/http/server.d.ts +2 -2
  51. package/dist/http/wire-types.d.ts +8 -0
  52. package/dist/leader/diffup.d.ts +53 -0
  53. package/dist/leader/diffup.js +95 -1
  54. package/dist/leader/leader.d.ts +0 -6
  55. package/dist/leader/leader.js +1 -1
  56. package/dist/leader/repair-wire.js +1 -1
  57. package/dist/leader/wire.d.ts +3 -10
  58. package/dist/leader/wire.js +39 -13
  59. package/dist/memory-store-refusal.d.ts +2 -1
  60. package/dist/model-entry-headers.d.ts +41 -0
  61. package/dist/model-entry-headers.js +61 -0
  62. package/dist/model-entry-refusal.d.ts +6 -0
  63. package/dist/model-entry-refusal.js +4 -0
  64. package/dist/model-reach-scope.d.ts +55 -0
  65. package/dist/model-reach-scope.js +16 -0
  66. package/dist/model-unreachable-notice.d.ts +38 -0
  67. package/dist/model-unreachable-notice.js +72 -0
  68. package/dist/observability/fail-open.d.ts +5 -5
  69. package/dist/observability/fail-open.js +5 -5
  70. package/dist/observability/run-terminal.d.ts +2 -1
  71. package/dist/outbound-dispatcher.d.ts +69 -0
  72. package/dist/outbound-dispatcher.js +123 -3
  73. package/dist/plugins/approval-ask-store-file.d.ts +2 -1
  74. package/dist/plugins/approval-ask-store-file.js +3 -0
  75. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  76. package/dist/plugins/approval-ask-store-memory.js +7 -1
  77. package/dist/plugins/approval-ask-store-sql.d.ts +18 -0
  78. package/dist/plugins/approval-ask-store-sql.js +7 -0
  79. package/dist/plugins/park-commit-watch.d.ts +48 -0
  80. package/dist/plugins/park-commit-watch.js +87 -0
  81. package/dist/request-key-closure.js +1 -1
  82. package/dist/router/route-orchestration.js +7 -2
  83. package/dist/run-local.js +2 -3
  84. package/dist/runs.js +3 -2
  85. package/dist/runtime-governance.d.ts +3 -3
  86. package/dist/runtime-governance.js +3 -2
  87. package/dist/task-settings.js +4 -2
  88. package/dist/tool-approval.d.ts +99 -15
  89. package/dist/tool-approval.js +75 -10
  90. package/dist/tool-rule-names.d.ts +25 -0
  91. package/dist/tool-rule-names.js +5 -0
  92. package/dist/trace/core-keyset-guard.d.ts +14 -2
  93. package/dist/trace/engine-notice-wire.d.ts +3 -1
  94. package/dist/trace/engine-notice-wire.js +2 -0
  95. package/dist/trace/project.d.ts +3 -1
  96. package/dist/trace/project.js +2 -3
  97. package/dist/trace/self-minted-wire.d.ts +71 -0
  98. package/dist/trace/self-minted-wire.js +28 -0
  99. package/dist/unattended-permission-mode.d.ts +54 -93
  100. package/dist/unattended-permission-mode.js +8 -61
  101. package/package.json +3 -3
@@ -21,7 +21,7 @@
21
21
  * 没有方法、没有捕获的行为。
22
22
  */
23
23
  import { z } from "zod";
24
- import { READ_ROOT_CANDIDATE_DIR_MAX, type AskEvidenceAbsence, type AskRequest as CoreAskRequest, type PendingAction as CorePendingAction, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
24
+ import { READ_ROOT_CANDIDATE_DIR_MAX, type AskClass, type AskEvidenceAbsence, type AskRequest as CoreAskRequest, type PendingAction as CorePendingAction, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
25
25
  import type { AskRow } from "./plugins/approval-ask-store-sql.js";
26
26
  /** 模型自由文本(`sourceAgentName` / `delegation.agentName`)的限长(设计稿 §6.2)。设计 §3.1 把
27
27
  * 「限长 + 脱敏」写成 **server 新增责任**(引擎无此层):spawning model 挑的名字是自由文本,
@@ -429,6 +429,25 @@ export declare function readClosedWordSeat<S extends ClosedWordSeat>(carrier: ob
429
429
  type ClosedWordSeat = "mandate" | "origin";
430
430
  /** 一座的值型 = core 寄存行上那一座的型(去掉可选位的 `undefined`)。 */
431
431
  type ClosedWordOf<S extends ClosedWordSeat> = NonNullable<ToolApprovalPark[S]>;
432
+ /**
433
+ * 🔴 **S-934 ②(core 7.37.0 #1214)—— ask 类对的唯一窄读**:`askClass`(一问的类 = `classes` 的首员)与 `classes`(这一问属于的全部类,core
434
+ * `ASK_CLASSES` 序)。三个载体上同名:`AskRequest`(活卡帧 / 呈卡帧的卡读它)、寄存行 `PendingAction.tool_approval`(运维队列行经
435
+ * {@link parkRowFacts} 读它);`CheckpointSummary` 不回显(core 原句「read the row」)⇒ inbox 行不带。
436
+ *
437
+ * 判据(display,不是裁决输入 —— 裁决在 core 的仲裁点,本仓无人值守腿的按类作答读的是 core 交来的 `AskRequest.classes` 本身,不经本函数):
438
+ * · **两键都缺席 ⇒ 缺席**(不计数):老引擎(core 7.37.0 之前)、老行(续跑不补,KL-1214-b)、不盖 `origin` 的站(KL-1214-a)。缺席从不编默认类;
439
+ * · **在场 ⇒ 必须是一整只好对**:`askClass` 过 core `isAskClass`;`classes` 是非空数组、每员过 `isAskClass`、首员 === `askClass`(core d.ts
440
+ * 「`classes[0] === askClass`」「Present exactly when askClass is」)。半对 / 集外词 / 首员不符 ⇒ **两键都不投**(卡上少一行类 ≪ 卡上出现生词
441
+ * 或半只对)+ 计一次 `server.approval-card.closed-word-out-of-set`(S-249 族,detail 前缀 `askClass=` / `classes=` 判别是哪一格读不出)。
442
+ * · 不自造:本仓不重算类、不排序、不去重(core 的屏 `screenAskClassMembers` 在写停车行前已经筛过;它不在包根导出面上 —— 转 core 请求导出,
443
+ * 收车档 ⑤)。投出去的 `classes` 是新数组(帧不与载体共享可变引用)。
444
+ */
445
+ export declare function readAskClassPair(carrier: object): AskClassPair | undefined;
446
+ /** 类对的投影形(帧 / 卡 / 队列行同一形):两键同在同缺。值型取 core 寄存行上的座(core 改型 ⇒ 下面的围栏编译红)。 */
447
+ export interface AskClassPair {
448
+ askClass: NonNullable<ToolApprovalPark["askClass"]>;
449
+ classes: Array<NonNullable<ToolApprovalPark["classes"]>[number]>;
450
+ }
432
451
  /**
433
452
  * 🔴 S-700 P1(client-core CC-180 / cli L-652 的根子;板 [ref] 订正 → [ref] 立案)—— **park 行事实**:
434
453
  * 运维队列行(`GET /v1/approvals` + `/stream`)顶层展开的那几位。型**从 core 寄存行派生,不手抄**(S-249 同一条
@@ -446,8 +465,10 @@ type ClosedWordOf<S extends ClosedWordSeat> = NonNullable<ToolApprovalPark[S]>;
446
465
  * S-755(cli L-695 / [ref],client-core [ref])第五位 `origin`:谁提的这一问(core `ASK_ORIGINS` 闭集一词,经 {@link readClosedWordSeat};
447
466
  * 与活卡帧 `origin` / inbox 行 `origin` 同名同义同值)。此前只有活卡帧与 inbox 行带它 ⇒ 耐久重开 / 重连时壳从队列行读不到出身词,
448
467
  * bypass 姿态下按 `ask_rule` / `hook` 照问的那条壳规则无从落地。缺席 = 老行(core 7.5.0 之前 park 的)/ 集外词(计 S-249 tag)。
468
+ * S-934 ②(core 7.37.0 #1214)第六 / 七位 `askClass` / `classes`:这一问的类对(经 {@link readAskClassPair};两位同在同缺,与活卡帧 / 呈卡帧的卡
469
+ * 同名同义同值)—— durable 审批席按类答的依据。缺席 = 老行(core 7.37.0 之前 park 的,续跑不补)/ 读不出(计 S-249 tag)。
449
470
  */
450
- export type ParkRowFacts = Pick<ToolApprovalPark, "requiresRealApproval" | "ruleOffersAbsence" | "mandated" | "mandate" | "origin">;
471
+ export type ParkRowFacts = Pick<ToolApprovalPark, "requiresRealApproval" | "ruleOffersAbsence" | "mandated" | "mandate" | "origin" | "askClass" | "classes">;
451
472
  /**
452
473
  * S-700 —— park 行事实的**唯一投影**(`pendingAction` 是寄存行上 core 铸的那一只,读自持久层 ⇒ `unknown`)。
453
474
  *
@@ -629,6 +650,8 @@ export declare const ApprovalCardSchema: z.ZodObject<{
629
650
  }, z.core.$strict>>>;
630
651
  personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
631
652
  }, z.core.$strict>>;
653
+ askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
654
+ classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
632
655
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
633
656
  sourceTaskId: z.ZodOptional<z.ZodString>;
634
657
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -742,6 +765,8 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
742
765
  }, z.core.$strict>>>;
743
766
  personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
744
767
  }, z.core.$strict>>;
768
+ askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>;
769
+ classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "operator_forced" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "ordinary">>>;
745
770
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
746
771
  sourceTaskId: z.ZodOptional<z.ZodString>;
747
772
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -787,6 +812,10 @@ export interface ApprovalCardSource {
787
812
  * `ApprovalCardSchema.ruleOffers`)。与 wire 帧**同一份素材** ⇒ live 帧 / `card_json` / 重放帧
788
813
  * 三面同源。 */
789
814
  ruleOffers?: readonly RuleOffer[];
815
+ /** S-934 ②:类对 —— 与 wire 帧**同一份素材**(发帧点经 `readAskClassPair` 窄读定;`ToolApprovalFrame` 结构上满足本接口)⇒ live 帧 /
816
+ * `approval_request` / `card_json` / 重放帧四面同源。两键同在同缺。 */
817
+ askClass?: AskClass;
818
+ classes?: readonly AskClass[];
790
819
  fromSubagent?: true;
791
820
  sourceTaskId?: string;
792
821
  /** 已 redactSecrets。 */
@@ -838,7 +867,7 @@ export interface ApprovalRequestFrame {
838
867
  /** = SSE 具名事件名(与 `tool_approval` 同约定)。 */
839
868
  type: "approval_request";
840
869
  schemaVersion: 1;
841
- /** `deriveAskId` 派生(持久层主键;五元组见 approval-ask-machine.ts)。 */
870
+ /** `deriveAskId` 派生(持久层主键;六元身份元组 —— S-944 / 7.109.0 起含这一问的类集 —— 见 approval-ask-machine.ts)。 */
842
871
  askId: string;
843
872
  /** wire run id(= 协调器 ctx 的 taskId)。 */
844
873
  taskId: string;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, isAskOrigin, isPersistedRuleMandate } from "@sema-agent/core";
2
+ import { ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, isAskClass, isAskOrigin, isPersistedRuleMandate } from "@sema-agent/core";
3
3
  import { redactSecrets } from "./trace/redact.js";
4
4
  import { recordFailOpen } from "./observability/fail-open.js";
5
5
  import { PERSISTED_RULE_MATCHES, UNCOVERED_SEGMENT_REASONS } from "./permission-rule-vocab.js";
@@ -284,18 +284,31 @@ const CLOSED_WORD_SEAT_GUARDS = {
284
284
  mandate: isPersistedRuleMandate,
285
285
  origin: isAskOrigin,
286
286
  };
287
+ export function readAskClassPair(carrier) {
288
+ const askClass = "askClass" in carrier ? carrier.askClass : undefined;
289
+ const classes = "classes" in carrier ? carrier.classes : undefined;
290
+ if (askClass === undefined && classes === undefined)
291
+ return undefined;
292
+ if (isAskClass(askClass) && Array.isArray(classes) && classes.length > 0 && classes.every(isAskClass) && classes[0] === askClass) {
293
+ return { askClass, classes: [...classes] };
294
+ }
295
+ recordFailOpen("server.approval-card.closed-word-out-of-set", isAskClass(askClass) ? `classes=${outOfSetDetail(classes)}` : `askClass=${outOfSetDetail(askClass)}`);
296
+ return undefined;
297
+ }
287
298
  export function parkRowFacts(pendingAction) {
288
299
  if (typeof pendingAction !== "object" || pendingAction === null || !("kind" in pendingAction) || pendingAction.kind !== "tool_approval")
289
300
  return {};
290
301
  const ruleOffersAbsence = readRuleOffersAbsence(pendingAction);
291
302
  const mandate = readClosedWordSeat(pendingAction, "mandate");
292
303
  const origin = readClosedWordSeat(pendingAction, "origin");
304
+ const askClassPair = readAskClassPair(pendingAction);
293
305
  return {
294
306
  ...(readParkRowTrueSeat(pendingAction, "requiresRealApproval") ? { requiresRealApproval: true } : {}),
295
307
  ...(readParkRowTrueSeat(pendingAction, "mandated") ? { mandated: true } : {}),
296
308
  ...(mandate !== undefined ? { mandate } : {}),
297
309
  ...(origin !== undefined ? { origin } : {}),
298
310
  ...(ruleOffersAbsence !== undefined ? { ruleOffersAbsence } : {}),
311
+ ...(askClassPair ?? {}),
299
312
  };
300
313
  }
301
314
  const DenialLimitFallbackSchema = z
@@ -337,6 +350,8 @@ export const ApprovalCardSchema = z
337
350
  classifierUnavailable: ClassifierUnavailableSchema.optional(),
338
351
  probeCause: ProbeCauseSchema.optional(),
339
352
  ruleEvidence: RuleEvidenceSchema.optional(),
353
+ askClass: z.custom((v) => isAskClass(v)).optional(),
354
+ classes: z.array(z.custom((v) => isAskClass(v))).min(1).optional(),
340
355
  fromSubagent: z.literal(true).optional(),
341
356
  sourceTaskId: z.string().max(MAX_IDENT).optional(),
342
357
  sourceAgentName: z.string().max(MAX_AGENT_NAME).optional(),
@@ -348,7 +363,17 @@ export const ApprovalCardSchema = z
348
363
  })
349
364
  .optional(),
350
365
  })
351
- .strict();
366
+ .strict()
367
+ .superRefine((card, ctx) => {
368
+ const hasClass = card.askClass !== undefined;
369
+ const hasClasses = card.classes !== undefined;
370
+ if (hasClass !== hasClasses) {
371
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: [hasClass ? "classes" : "askClass"], message: "askClass and classes must be present together" });
372
+ }
373
+ else if (hasClass && card.classes[0] !== card.askClass) {
374
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["classes"], message: "classes[0] must equal askClass" });
375
+ }
376
+ });
352
377
  export const ApprovalCardEnvelopeSchema = z
353
378
  .object({
354
379
  schemaVersion: z.literal(1),
@@ -377,6 +402,7 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
377
402
  const ruleEvidence = readRuleEvidence(req);
378
403
  const ruleOffersAbsence = readRuleOffersAbsence(req);
379
404
  const denialLimitFallback = readDenialLimitFallback(req);
405
+ const askClassPair = readAskClassPair(source);
380
406
  const toolCallId = clip(source.toolCallId, MAX_IDENT);
381
407
  const sourceTaskId = clip(source.sourceTaskId, MAX_IDENT);
382
408
  const sourceAgentName = clip(source.sourceAgentName, MAX_AGENT_NAME);
@@ -399,6 +425,7 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
399
425
  ...(source.ruleOffers !== undefined && source.ruleOffers.length > 0 ? { ruleOffers: source.ruleOffers.map(copyRuleOffer) } : {}),
400
426
  ...(ruleOffersAbsence !== undefined ? { ruleOffersAbsence } : {}),
401
427
  ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
428
+ ...(askClassPair !== undefined ? { askClass: askClassPair.askClass, classes: askClassPair.classes } : {}),
402
429
  ...(source.fromSubagent === true ? { fromSubagent: true } : {}),
403
430
  ...(sourceTaskId !== undefined ? { sourceTaskId } : {}),
404
431
  ...(sourceAgentName !== undefined ? { sourceAgentName } : {}),
package/dist/approval.js CHANGED
@@ -3,6 +3,7 @@ import { operatorAlwaysHumanAsk, operatorAsk } from "./operator-ask.js";
3
3
  import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
4
4
  import { isExplicitPosture } from "./posture-source.js";
5
5
  import { handsOnWorkerFilePlane } from "./execution-lane-caps.js";
6
+ import { heldRuleName } from "./tool-rule-names.js";
6
7
  export function hasApprovalListIntent(config) {
7
8
  return (config.approvalRequire.length > 0 ||
8
9
  config.approvalDeny.length > 0 ||
@@ -79,49 +80,51 @@ export function createDurableAskPolicy(opts) {
79
80
  const neverAuto = createNameSetMatcher(opts.neverAuto ?? []);
80
81
  const budget = Math.max(0, Math.floor(opts.autoBudget ?? 0));
81
82
  let autoApproved = 0;
83
+ const isQuestionTool = (name) => name === ASK_USER_QUESTION_TOOL_NAME;
82
84
  return {
83
85
  check: (req) => {
84
- const toolName = req.toolName;
85
- if (deny(toolName))
86
+ if (heldRuleName(req, deny) !== undefined)
86
87
  return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
87
- if (neverAuto(toolName))
88
- return operatorAlwaysHumanAsk(`APPROVAL_NEVER_AUTO(${toolName})`, `human approval required to run "${req.toolName}"`);
89
- if (require(toolName)) {
88
+ const neverAutoHit = heldRuleName(req, neverAuto);
89
+ if (neverAutoHit !== undefined)
90
+ return operatorAlwaysHumanAsk(`APPROVAL_NEVER_AUTO(${neverAutoHit})`, `human approval required to run "${req.toolName}"`);
91
+ const requireHit = heldRuleName(req, require);
92
+ if (requireHit !== undefined) {
90
93
  const remEarly = req.budget?.resourceRemainingMicroUsd;
91
94
  if (remEarly !== undefined && remEarly <= 0) {
92
95
  return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
93
96
  }
94
- if (opts.exempt && toolName !== ASK_USER_QUESTION_TOOL_NAME) {
97
+ if (opts.exempt && heldRuleName(req, isQuestionTool) === undefined) {
95
98
  return (async () => {
96
99
  let exempted = false;
97
100
  try {
98
- exempted = (await opts.exempt(toolName)) === true;
101
+ exempted = (await opts.exempt(req.toolName)) === true;
99
102
  }
100
103
  catch {
101
104
  exempted = false;
102
105
  }
103
106
  if (exempted) {
104
- opts.onExempted?.(toolName, req.toolName);
107
+ opts.onExempted?.(req.toolName, req.toolName);
105
108
  return { action: "allow" };
106
109
  }
107
- return decideRequired(req, toolName);
110
+ return decideRequired(req, requireHit);
108
111
  })();
109
112
  }
110
- return decideRequired(req, toolName);
113
+ return decideRequired(req, requireHit);
111
114
  }
112
115
  return { action: "allow" };
113
116
  },
114
117
  };
115
- function decideRequired(req, toolName) {
118
+ function decideRequired(req, requireHit) {
116
119
  const rem = req.budget?.resourceRemainingMicroUsd;
117
120
  if (rem !== undefined && rem <= 0) {
118
121
  return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
119
122
  }
120
- if (toolName !== ASK_USER_QUESTION_TOOL_NAME && budget > 0 && autoApproved < budget) {
123
+ if (heldRuleName(req, isQuestionTool) === undefined && budget > 0 && autoApproved < budget) {
121
124
  autoApproved++;
122
125
  return { action: "allow" };
123
126
  }
124
- return operatorAsk(`APPROVAL_REQUIRE(${toolName})`, `approval required to run "${req.toolName}"`);
127
+ return operatorAsk(`APPROVAL_REQUIRE(${requireHit})`, `approval required to run "${req.toolName}"`);
125
128
  }
126
129
  }
127
130
  //# sourceMappingURL=approval.js.map
@@ -35,7 +35,6 @@
35
35
  * `infraFailed` (EXCLUDED) — NEVER a fabricated uniform delivered=false loss for the whole TEAM column.
36
36
  */
37
37
  import { type TaskSpec } from "@sema-agent/core";
38
- import { type UnattendedLeg } from "../../unattended-permission-mode.js";
39
38
  import type { ProfileDeps, SoloImplSpec, SupImplSpec } from "./arms.js";
40
39
  import type { TrapSpec } from "./tasks.js";
41
40
  /** Live runtime config — the SAME base model + budget every arm runs on (the §3.2 fairness root). */
@@ -96,11 +95,11 @@ export declare function benchBaseSpec(rt: LiveRuntimeConfig, cellId: string): (o
96
95
  /**
97
96
  * Project a `SoloImplSpec` (budget already stamped by the harness, all keys in `limits`) onto the bench base TaskSpec.
98
97
  * 🔴 7.106.0 合并复审 X3:SOLO 是**无人值守腿**(verify→fix 跑在一只不接 `onAsk` 的 Runner 上,没有审批席)⇒ 经 `withUnattendedLeg` 接
99
- * 与 TEAM 的 leader worker 同一条腿(模式 = `UNATTENDED_PERMISSION_MODE`、写面 roots、写保护拒层)。修前裸 spec ⇒ core 7.34.0「缺席 =
100
- * default」让写与非只读命令 headless 拒、终态照旧 completed —— SOLO 与 TEAM 的比较混进了权限差异。SUP 不走这里:它有耐久审批席
101
- * (checkpoint suspend),它的问要人答,bypass 会把 SUP 的门放掉。
98
+ * 与 TEAM 的 leader worker 同一只合并口(S-934 ①:`default` + 按类作答的 onAsk,无席)。修前裸 spec ⇒ core 7.34.0「缺席 =
99
+ * default」且无席 ⇒ 写与非只读命令 headless 拒、终态照旧 completed —— SOLO 与 TEAM 的比较混进了权限差异。SUP 不走这里:它有耐久审批席
100
+ * (checkpoint suspend),它的问要人答。
102
101
  */
103
- export declare function toTaskSpec(base: (objective: string) => TaskSpec, impl: SoloImplSpec, leg: UnattendedLeg): TaskSpec;
102
+ export declare function toTaskSpec(base: (objective: string) => TaskSpec, impl: SoloImplSpec): TaskSpec;
104
103
  /** Project a `SupImplSpec` onto the bench base TaskSpec PLUS the durable wiring (checkpointStore + ask policy). */
105
104
  export declare function toSupTaskSpec(base: (objective: string) => TaskSpec, impl: SupImplSpec, trap: TrapSpec): TaskSpec;
106
105
  //# sourceMappingURL=live-deps.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { outwardPlane } from "../../terminal.js";
2
2
  import { Runner, runWithVerification, resumeWithVerification, runRepairLoop, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, } from "@sema-agent/core";
3
3
  import { loadConfig } from "../../config.js";
4
- import { createUnattendedLeg, unattendedPermissionMode, withUnattendedLeg } from "../../unattended-permission-mode.js";
4
+ import { withUnattendedLeg } from "../../unattended-permission-mode.js";
5
5
  import { createBrain } from "../../brain.js";
6
6
  import { buildPricing } from "../../budget.js";
7
7
  import { RemoteContainerExecutionEnv } from "../../plugins/remote-env-e2b.js";
@@ -58,7 +58,7 @@ function buildBrainIngredients(rt) {
58
58
  if (!p || ((p.inputPer1M ?? 0) === 0 && (p.outputPer1M ?? 0) === 0)) {
59
59
  throw new Error(`s1 live: zero pricing for ${rt.modelId} — C1 cost axis would be a fabricated $0 (set MODEL_COST_INPUT/OUTPUT)`);
60
60
  }
61
- return { brain, models: config.models, roles: config.roles, pricing, unattendedPermissionMode: unattendedPermissionMode(config) };
61
+ return { brain, models: config.models, roles: config.roles, pricing };
62
62
  }
63
63
  const sh = (env) => async (cmd) => {
64
64
  const r = await env.exec(`bash -lc ${JSON.stringify(cmd)}`);
@@ -168,9 +168,8 @@ function graderTransport(grader) {
168
168
  };
169
169
  }
170
170
  export function buildLiveDeps(rt, trap, seed, cellId) {
171
- const { brain, models, roles, pricing, unattendedPermissionMode: leaderMode } = buildBrainIngredients(rt);
171
+ const { brain, models, roles, pricing } = buildBrainIngredients(rt);
172
172
  const baseSpec = benchBaseSpec(rt, cellId);
173
- const soloLeg = createUnattendedLeg(leaderMode, {});
174
173
  const state = {};
175
174
  const ensureWorker = async () => {
176
175
  if (!state.worker) {
@@ -208,7 +207,7 @@ export function buildLiveDeps(rt, trap, seed, cellId) {
208
207
  const deps = {
209
208
  runWithVerification: async (implSpec) => {
210
209
  const { runner } = await ensureWorker();
211
- return runWithVerification(runner, toTaskSpec(baseSpec, implSpec, soloLeg), { maxRounds: rt.maxRounds ?? 2 });
210
+ return runWithVerification(runner, toTaskSpec(baseSpec, implSpec), { maxRounds: rt.maxRounds ?? 2 });
212
211
  },
213
212
  runWithVerificationSup: async (implSpec) => {
214
213
  const { runner } = await ensureWorker();
@@ -284,7 +283,7 @@ export function buildLiveDeps(rt, trap, seed, cellId) {
284
283
  rmSync(tmp, { recursive: true, force: true });
285
284
  }
286
285
  };
287
- return runLeaderForTrap(rt, brain, models, roles, pricing, leaderMode, trap, cellId, captureIntegratedTreeToBundle);
286
+ return runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId, captureIntegratedTreeToBundle);
288
287
  },
289
288
  runOracle: () => runOracle(false),
290
289
  };
@@ -303,8 +302,8 @@ export function benchBaseSpec(rt, cellId) {
303
302
  function projectImplBudget(base, impl) {
304
303
  return { ...base(impl.objective), limits: { ...impl.limits } };
305
304
  }
306
- export function toTaskSpec(base, impl, leg) {
307
- return withUnattendedLeg(projectImplBudget(base, impl), leg);
305
+ export function toTaskSpec(base, impl) {
306
+ return withUnattendedLeg(projectImplBudget(base, impl));
308
307
  }
309
308
  export function toSupTaskSpec(base, impl, trap) {
310
309
  const requireApproval = trap.gatedTools ?? ["Bash"];
@@ -317,7 +316,7 @@ export function toSupTaskSpec(base, impl, trap) {
317
316
  onQuestion: QUESTION_AWAITS_RESUME,
318
317
  };
319
318
  }
320
- async function runLeaderForTrap(rt, brain, models, roles, pricing, leaderMode, trap, cellId, importIntegratedTreeToWorker) {
319
+ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId, importIntegratedTreeToWorker) {
321
320
  const { mkdtempSync, rmSync, writeFileSync, mkdirSync } = await import("node:fs");
322
321
  const { execFileSync, spawn } = await import("node:child_process");
323
322
  const { tmpdir } = await import("node:os");
@@ -370,7 +369,7 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, leaderMode, t
370
369
  process.env.LEADER_ENABLED = "true";
371
370
  process.env.LEADER_WORKER_BUDGET_USD = String((rt.maxCostUsd ?? 1.0) / 2);
372
371
  const run = createLeaderRunner({
373
- brain, models, roles, pricing, unattendedPermissionMode: leaderMode, e2bApiKey: rt.e2bApiKey, workspace: W,
372
+ brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
374
373
  ...(s1DebugGit() ? { logger: { warn: (m, x) => console.error(`[S1_DEBUG_LEADER warn] ${m}`, x ? JSON.stringify(x).slice(0, 300) : ""), info: (m, x) => console.error(`[S1_DEBUG_LEADER info] ${m}`, x ? JSON.stringify(x).slice(0, 200) : "") } } : {}),
375
374
  });
376
375
  const teamSeedWrite = (trap.seedFiles ?? [])
@@ -13,6 +13,7 @@ import type { Logger } from "../observability/logger.js";
13
13
  import { QuestionCoordinator } from "../question.js";
14
14
  import { ToolApprovalCoordinator, type ParkedAskRedeem } from "../tool-approval.js";
15
15
  import { FileApprovalAskAuditStore } from "../approval-ask-audit-store.js";
16
+ import type { ParkCommitWatch } from "../plugins/park-commit-watch.js";
16
17
  import { SendUserFileEmitter } from "../capabilities/send-user-file-tool.js";
17
18
  import { TaskEnvRegistry } from "../capabilities/sandbox-file-send.js";
18
19
  import type { StoreBackend } from "../plugins/store-backend.js";
@@ -32,6 +33,9 @@ export interface LiveCoordinatorsCtx {
32
33
  * 必须排在它之前;同款 holder 先例 = reaper 的 `getRunDenySweep`)。取到 undefined ⇒ 那条臂逐字回落
33
34
  * 修前 404(partial 部署安全)。缺席(不传本键)⇒ 协调器整条迟到腿的 PARKED 臂恒 404。 */
34
35
  getParkedRedeem?: () => ParkedAskRedeem | undefined;
36
+ /** S-929 F1:checkpoint 店的 park 提交观察层(stage-06 与店同一处铸;在场 ⇔ 有 checkpoint 店)。协调器据它判「有序关停下的 park 路由
37
+ * 提交了没有」—— 审计 settle 行等 park 提交落定才写。缺席 = 没有 park 设施(core 在 ask 回调处就收场,没有提交这一步)。 */
38
+ parkCommits?: ParkCommitWatch;
35
39
  }
36
40
  export declare function createLiveCoordinators(ctx: LiveCoordinatorsCtx): {
37
41
  elicitation: ElicitationCoordinator | undefined;
@@ -10,7 +10,7 @@ import { basename, resolve } from "node:path";
10
10
  import { toolsRunOnThisHost } from "../task-cwd.js";
11
11
  import { stat as fsStat, readFile as fsReadFile } from "node:fs/promises";
12
12
  export function createLiveCoordinators(ctx) {
13
- const { config, logger, backend, sendUserFileTaskEnvs, ruleConsent, ruleScopeRootFor, getParkedRedeem } = ctx;
13
+ const { config, logger, backend, sendUserFileTaskEnvs, ruleConsent, ruleScopeRootFor, getParkedRedeem, parkCommits } = ctx;
14
14
  const elicitation = config.mcpElicitation.enabled ? new ElicitationCoordinator(config.mcpElicitation.throttle) : undefined;
15
15
  const question = config.askQuestionEnabled ? new QuestionCoordinator(config.questionThrottle) : undefined;
16
16
  const durableEnabled = backend?.checkpoint !== undefined && config.durableApproval === true;
@@ -51,7 +51,7 @@ export function createLiveCoordinators(ctx) {
51
51
  decided: converged.decided,
52
52
  inconsistent: converged.inconsistent,
53
53
  corruptLines: converged.corruptLines,
54
- note: "orphan approval asks from a previous crash were converged to crashed_before_park; see GET /v1/approvals crashConverged",
54
+ note: "orphan approval asks from the previous process were converged (cause crashed_before_park = it crashed; cause shutdown_before_park = an orderly shutdown cut the leg off before it closed or before its park committed); see GET /v1/approvals crashConverged",
55
55
  });
56
56
  }
57
57
  }
@@ -71,6 +71,7 @@ export function createLiveCoordinators(ctx) {
71
71
  ...(getParkedRedeem !== undefined ? { parkedRedeem: getParkedRedeem } : {}),
72
72
  ...(approvalAskAudit !== undefined ? { askAudit: approvalAskAudit } : {}),
73
73
  ...(approvalExemptionStore !== undefined ? { sessionGrants: approvalExemptionStore } : {}),
74
+ ...(parkCommits !== undefined ? { parkCommits } : {}),
74
75
  })
75
76
  : undefined;
76
77
  const sendUserFileEmitter = (() => {
@@ -2,7 +2,6 @@ import { createLeaderEndpoint } from "../leader/endpoint.js";
2
2
  import { createLeaderRunner } from "../leader/wire.js";
3
3
  import { buildDeploymentPostureSeats } from "./runner-deps.js";
4
4
  import { hasApprovalListIntent } from "../approval.js";
5
- import { unattendedPermissionMode } from "../unattended-permission-mode.js";
6
5
  export function leaderK8sMinioGap(leaderEnabled, leaderProvider, hasS3) {
7
6
  if (!(leaderEnabled && leaderProvider === "k8s" && !hasS3))
8
7
  return null;
@@ -59,7 +58,6 @@ export function createLeaderFace(ctx) {
59
58
  : {}),
60
59
  ...(ctx.promptSource ? { promptSource: ctx.promptSource } : {}),
61
60
  ...(ctx.onError ? { onError: ctx.onError } : {}),
62
- unattendedPermissionMode: unattendedPermissionMode(config),
63
61
  fanoutEnabled: config.leaderFanoutEnabled,
64
62
  ...((process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID)
65
63
  ? { routerModel: (process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID) }
@@ -17,7 +17,7 @@ import type { StoreBackend } from "../plugins/store-backend.js";
17
17
  import { type TaskAttachmentStore } from "../plugins/task-attachment-store.js";
18
18
  import { type createPrincipalEntitlementsClient } from "../runtime-caps-resolver.js";
19
19
  import { type OwnerAwareSessionStore } from "../security.js";
20
- import { type RunnerDepsOnAsk } from "./runner-deps.js";
20
+ import type { RunnerDepsOnAsk } from "./runner-deps.js";
21
21
  type PrincipalCaps = ReturnType<typeof createPrincipalEntitlementsClient>;
22
22
  /** `resolveSpec` 原先从 `main()` 闭包里拿到的全部 boot 局部量。 */
23
23
  export interface ResolveSpecCtx {
@@ -40,8 +40,7 @@ import { redactSecrets } from "../trace/redact.js";
40
40
  import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
41
41
  import { executionLaneCaps, executionLaneHomeDir, executionLaneOf } from "../execution-lane-caps.js";
42
42
  import { effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
43
- import { buildDeploymentPostureSeats } from "./runner-deps.js";
44
- import { createUnattendedLeg, unattendedPermissionMode, withUnattendedLeg } from "../unattended-permission-mode.js";
43
+ import { withUnattendedLeg } from "../unattended-permission-mode.js";
45
44
  import { memoryFaceMounted } from "../memory-scope.js";
46
45
  import { createAssemblyIncarnationRead, sessionFaceReaderOf } from "./session-incarnation-seat.js";
47
46
  export function createResolveSpec(ctx) {
@@ -440,7 +439,7 @@ export function createResolveSpec(ctx) {
440
439
  const merged = extra.length > 0 ? [...(base ?? []), ...extra] : base;
441
440
  return merged && config.toolDeferLongtail ? applyLongtailDefer(merged, true) : merged;
442
441
  })(cap.tools),
443
- skills: mergeUserSkills(cap.skills, body.skills, logger),
442
+ skills: body.disableSkills === true ? [] : mergeUserSkills(cap.skills, body.skills, logger),
444
443
  ...(parsedSettings.settings?.skillListingBudgetFraction !== undefined ? { skillListingBudgetFraction: parsedSettings.settings.skillListingBudgetFraction } : {}),
445
444
  ...(parsedSettings.settings?.skillListingMaxDescChars !== undefined ? { skillListingMaxDescChars: parsedSettings.settings.skillListingMaxDescChars } : {}),
446
445
  mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger, opts?.onNotice !== undefined ? { sessionId: auth?.sessionId, emit: opts.onNotice } : undefined),
@@ -670,15 +669,11 @@ export function createResolveSpec(ctx) {
670
669
  }
671
670
  return governed;
672
671
  };
673
- const resumeRunnerSeats = () => ({
674
- ...buildDeploymentPostureSeats(config),
675
- ...(memoryEngine !== undefined ? { memoryEngineDir: memoryEngine.root } : {}),
676
- });
677
672
  const resolveSpecForLeg = async (body, req, auth, opts) => {
678
673
  const spec = await resolveSpec(body, req, auth, opts);
679
674
  if (opts?.unattendedLeg !== true)
680
675
  return spec;
681
- return withUnattendedLeg(spec, createUnattendedLeg(unattendedPermissionMode(config), resumeRunnerSeats()));
676
+ return withUnattendedLeg(spec);
682
677
  };
683
678
  return resolveSpecForLeg;
684
679
  }
@@ -29,6 +29,8 @@ import type { RateLimiter } from "../observability/rate-limit.js";
29
29
  import type { startFleetClientFromEnv } from "../fleet-client.js";
30
30
  import type { BreakerStateStore, CostQuotaStore, RateLimiterStore, StoreBackend } from "../plugins/store-backend.js";
31
31
  import type { WorkflowNotifyJournalStore } from "../orchestration/workflow-notify-journal.js";
32
+ import type { ToolApprovalCoordinator } from "../tool-approval.js";
33
+ import type { ParkCommitWatch } from "../plugins/park-commit-watch.js";
32
34
  export interface ShutdownCtx {
33
35
  config: ServiceConfig;
34
36
  /** [ref]①:崩溃遗言的 version 键(与 /v1/capabilities.version 同源=main 的 serviceVersion())。 */
@@ -87,6 +89,12 @@ export interface ShutdownCtx {
87
89
  configCenter: {
88
90
  stopRefreshLoop(): void;
89
91
  } | undefined;
92
+ /** S-929:活体审批协调器(`TOOL_APPROVAL_ENABLED` 关 ⇒ 缺席)。hardShutdown 起点调它的 `drainForShutdown`:本进程握着的活卡
93
+ * 按断连同路收场、审计留有序关停记号 —— 否则下一次 boot 把有序重启读成崩溃(`crashed_before_park`)。 */
94
+ toolApproval: ToolApprovalCoordinator | undefined;
95
+ /** S-929 F1:checkpoint 店的 park 提交观察层(`plugins/park-commit-watch.ts`;在场 ⇔ 本部署有 checkpoint 店)。hardShutdown 在
96
+ * `backend.close()` 之前等它 quiesce —— 在飞的 `put`(审批 park / 资源挂起…,与审批协调器在不在场无关)不许被抽掉连接池。 */
97
+ parkCommits: ParkCommitWatch | undefined;
90
98
  }
91
99
  /**
92
100
  * `installShutdownHandlers` 的**摘除把手**([ref] 二轮重扫件4)。
@@ -6,8 +6,9 @@ import { installProcessExitBoundary } from "./exit-boundary.js";
6
6
  import { createEngineLeaseTracker } from "./engine-lease.js";
7
7
  import { writeExitLast, readExitLast, clearExitLast } from "./exit-last.js";
8
8
  import { isScriptRealmRejection, describeRejectionReason } from "../orchestration/hardened-vm-runner.js";
9
+ const HARD_STOP_GRACE_MS = 10_000;
9
10
  export function installShutdownHandlers(ctx) {
10
- const { config, logger, server, reaper, fleetReconcile, retentionLane, releaseRetentionLease, otelExporter, breakerState, costQuota, rateLimiter, runner, subRunner, lspManager, workflowNotifyJournal, fleetClient, backend, drainState, storeLiveProbe, configCenter, version, deviceHub, } = ctx;
11
+ const { config, logger, server, reaper, fleetReconcile, retentionLane, releaseRetentionLease, otelExporter, breakerState, costQuota, rateLimiter, runner, subRunner, lspManager, workflowNotifyJournal, fleetClient, backend, drainState, storeLiveProbe, configCenter, version, deviceHub, toolApproval, parkCommits, } = ctx;
11
12
  const dataRoot = config.localDataRoot;
12
13
  if (dataRoot) {
13
14
  const prior = readCrashLast(dataRoot);
@@ -69,6 +70,16 @@ export function installShutdownHandlers(ctx) {
69
70
  closing = true;
70
71
  if (dataRoot)
71
72
  clearCrashLast(dataRoot);
73
+ const graceDeadlineMs = Date.now() + HARD_STOP_GRACE_MS;
74
+ const approvalDrain = toolApproval === undefined
75
+ ? Promise.resolve()
76
+ : toolApproval
77
+ .drainForShutdown(graceDeadlineMs)
78
+ .then(({ uncommittedParks }) => {
79
+ for (const p of uncommittedParks)
80
+ logger.warn("approval_park_uncommitted_at_shutdown", { approvalId: p.approvalId, taskId: p.taskId });
81
+ })
82
+ .catch((err) => logger.warn("approval_drain_for_shutdown_failed", { err: String(err) }));
72
83
  clearInterval(reaper);
73
84
  clearInterval(fleetReconcile.timer);
74
85
  const retentionTeardown = retentionLane === undefined
@@ -79,6 +90,7 @@ export function installShutdownHandlers(ctx) {
79
90
  .then(() => undefined)
80
91
  .catch(() => undefined);
81
92
  fleetReconcile.reconciler.stop();
93
+ const routingStopped = fleetClient?.stopRouting() ?? Promise.resolve();
82
94
  parentWatch?.stop();
83
95
  engineLease?.stop();
84
96
  storeLiveProbe?.stop();
@@ -86,23 +98,34 @@ export function installShutdownHandlers(ctx) {
86
98
  configCenter?.stopRefreshLoop();
87
99
  otelExporter?.stop();
88
100
  breakerState?.stop();
89
- if (costQuota && "stop" in costQuota)
90
- costQuota.stop();
91
- if (rateLimiter && "stop" in rateLimiter)
92
- rateLimiter.stop();
93
101
  runner.sessions.dispose();
94
102
  subRunner.sessions.dispose();
95
103
  void lspManager?.dispose?.();
96
104
  void workflowNotifyJournal?.close?.();
97
105
  exitBoundary.reapChildProcessesEarly();
98
- void fleetClient?.stop();
106
+ const drained = approvalDrain
107
+ .then(() => parkCommits?.quiesce(graceDeadlineMs))
108
+ .then((q) => {
109
+ if (q !== undefined && q.inFlight > 0)
110
+ logger.warn("checkpoint_puts_inflight_at_backend_close", { inFlight: q.inFlight });
111
+ });
112
+ const ledgerStopped = drained.then(async () => {
113
+ if (costQuota && "stop" in costQuota)
114
+ costQuota.stop();
115
+ if (rateLimiter && "stop" in rateLimiter)
116
+ rateLimiter.stop();
117
+ await fleetClient?.flushFinalUsage();
118
+ });
99
119
  server.close(() => {
100
- void retentionTeardown.then(() => (backend ? backend.close() : Promise.resolve())).finally(() => process.exit(0));
120
+ void Promise.all([retentionTeardown, ledgerStopped])
121
+ .then(() => (backend ? backend.close() : Promise.resolve()))
122
+ .then(() => routingStopped)
123
+ .finally(() => process.exit(0));
101
124
  });
102
125
  const finalExit = setTimeout(() => { logger.warn("hard_shutdown_final_deadline", { afterMs: 15_000 }); process.exit(0); }, 15_000);
103
126
  finalExit.unref?.();
104
127
  server.closeIdleConnections?.();
105
- const forceClose = setTimeout(() => server.closeAllConnections?.(), 10_000);
128
+ const forceClose = setTimeout(() => server.closeAllConnections?.(), HARD_STOP_GRACE_MS);
106
129
  forceClose.unref?.();
107
130
  };
108
131
  let draining = false;
@@ -14,6 +14,7 @@ export declare function runStage06(ctx: Awaited<ReturnType<typeof runStage05>>):
14
14
  approvalExemptionStore: import("../plugins/approval-exemption-store.js").ApprovalExemptionStore | undefined;
15
15
  centerRuntimeCapsResolver: ((principal: string | undefined) => Promise<import("@sema-agent/core").RuntimeCaps | undefined>) | undefined;
16
16
  checkpointStore: import("../plugins/store-backend.js").CheckpointStoreFull | undefined;
17
+ parkCommits: import("../plugins/park-commit-watch.js").ParkCommitWatch | undefined;
17
18
  durableEnabled: boolean;
18
19
  elicitation: import("../elicitation.js").ElicitationCoordinator | undefined;
19
20
  fleetBus: import("../fleet/fleet-bus.js").FleetEventBus;
@@ -13,6 +13,7 @@ import { buildConsolidationValveAudit, createMemoryConsolidationFaces, resolveCo
13
13
  import { createPerModelAuthSeat } from "./side-query-lane.js";
14
14
  import { createWorkflowOrchestration } from "./workflow-orchestration.js";
15
15
  import { createLiveCoordinators } from "./coordinators.js";
16
+ import { createParkCommitWatch } from "../plugins/park-commit-watch.js";
16
17
  import { createRuntimeCaps } from "./runtime-caps.js";
17
18
  import { createRunnerDeps, createSharedRunnerDeps } from "./runner-deps.js";
18
19
  import { createOrgMemoryAdmissionWiring } from "./org-memory.js";
@@ -22,8 +23,11 @@ import { parkedAskRedeem, runStore } from "./late-binds.js";
22
23
  export function runStage06(ctx) {
23
24
  const { backend, backgroundAgentStore, brain, sessionIncarnationOf, config, configCenter, consolidationValve, deploymentHooks, executionEnvFactory, fileHistoryStore, localRoot, logger, lspManager, mailboxRecipientLifecycle, mailboxStore, memoryEngine, memorySyncRunner, metrics, perSessionCwd, permissionRuleProvider, pricing, rosterStore, ruleConsent, sendUserFileTaskEnvs, sessionCaptureRecordStore, sessionCaptureRecords, sessionPolicyStore, sessionStore, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore } = ctx;
24
25
  const { sqlWorkflowRunStore, workflowNotifyJournal, workflowCompletionInbox, deliverWorkflowCompletion, workflowNotifyGate, fleetBus, workflowRunStore, workflowJournalStore, outcomeSink, workflowRecoverOpts, workflowAgentRegistry, subagentSteerRegistry, workflowAgentSessionIndex, } = createWorkflowOrchestration({ config, logger, metrics, localRoot, backend, getRunStore: () => runStore });
26
+ const parkCommitted = backend?.checkpoint && config.durableApproval ? createParkCommitWatch(backend.checkpoint(logger)) : undefined;
27
+ const checkpointStore = parkCommitted?.store;
28
+ const parkCommits = parkCommitted?.watch;
25
29
  const { elicitation, question, toolApproval, durableEnabled, streamApprovalGate, approvalAskAudit, approvalExemptionStore, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec } = createLiveCoordinators({
26
- config, logger, backend, sendUserFileTaskEnvs, ruleConsent,
30
+ config, logger, backend, sendUserFileTaskEnvs, ruleConsent, parkCommits,
27
31
  getParkedRedeem: () => parkedAskRedeem,
28
32
  ruleScopeRootFor: (sessionId) => cardRuleScopeRoot(sessionId !== undefined ? perSessionCwd.get(sessionId) : undefined, config),
29
33
  });
@@ -40,7 +44,6 @@ export function runStage06(ctx) {
40
44
  logger.info("shared_memory_stores_enabled", { backend: backend?.kind });
41
45
  const runnerOffloadStore = toolResultStore ?? new InMemoryToolResultStore({ maxTotalChars: 64_000_000 });
42
46
  const commitHands = config.configProvider === "local" ? { commitCoAuthor: "Sema <noreply@vivi-ai.com>" } : undefined;
43
- const checkpointStore = backend?.checkpoint && config.durableApproval ? backend.checkpoint(logger) : undefined;
44
47
  const retentionExecutor = backend?.retention?.({ taskAttachmentTable: taskAttachmentStore !== undefined, taskListTables: taskListLane.backend !== "memory", logger });
45
48
  const retentionLaneStore = backend?.retentionLane?.();
46
49
  assertRetentionLaneWirable({
@@ -202,6 +205,6 @@ export function runStage06(ctx) {
202
205
  };
203
206
  const subRunner = new Runner(subRunnerDeps);
204
207
  const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
205
- return { ...ctx, approvalAskAudit, approvalExemptionStore, centerRuntimeCapsResolver, checkpointStore, durableEnabled, elicitation, fleetBus, governanceSeams, handslessRunner, handslessSubRunner, hookAgent, hookAgentRunner, hookLlm, mcpRevocations, memoryConsolidationFaces, memoryOptOutGrantStore, orgMemoryAdmission, outcomeSink, principalCaps, question, retentionExecutor, retentionLaneStore, runner, runnerDeps, runtimeCapsPeekResolver, sendFileLedger, sendUserFileEmitter, sendUserFileToolSpec, sessionTitler, sharedMemoryStore, sqlWorkflowRunStore, streamApprovalGate, subRunner, subRunnerSessions, subagentSteerRegistry, toolApproval, workflowAgentRegistry, workflowAgentSessionIndex, workflowCompletionInbox, workflowJournalStore, workflowModelAllowlistSnapshot, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, workflowRunStore, workflowsCapable };
208
+ return { ...ctx, approvalAskAudit, approvalExemptionStore, centerRuntimeCapsResolver, checkpointStore, parkCommits, durableEnabled, elicitation, fleetBus, governanceSeams, handslessRunner, handslessSubRunner, hookAgent, hookAgentRunner, hookLlm, mcpRevocations, memoryConsolidationFaces, memoryOptOutGrantStore, orgMemoryAdmission, outcomeSink, principalCaps, question, retentionExecutor, retentionLaneStore, runner, runnerDeps, runtimeCapsPeekResolver, sendFileLedger, sendUserFileEmitter, sendUserFileToolSpec, sessionTitler, sharedMemoryStore, sqlWorkflowRunStore, streamApprovalGate, subRunner, subRunnerSessions, subagentSteerRegistry, toolApproval, workflowAgentRegistry, workflowAgentSessionIndex, workflowCompletionInbox, workflowJournalStore, workflowModelAllowlistSnapshot, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, workflowRunStore, workflowsCapable };
206
209
  }
207
210
  //# sourceMappingURL=stage-06-runners.js.map
@@ -27,6 +27,7 @@ export declare function runStage07(ctx: Awaited<ReturnType<typeof runStage06>>):
27
27
  approvalAskAudit: import("../approval-ask-audit-store.js").FileApprovalAskAuditStore | undefined;
28
28
  centerRuntimeCapsResolver: ((principal: string | undefined) => Promise<import("@sema-agent/core").RuntimeCaps | undefined>) | undefined;
29
29
  checkpointStore: import("../plugins/store-backend.js").CheckpointStoreFull | undefined;
30
+ parkCommits: import("../plugins/park-commit-watch.js").ParkCommitWatch | undefined;
30
31
  durableEnabled: boolean;
31
32
  elicitation: import("../elicitation.js").ElicitationCoordinator | undefined;
32
33
  fleetBus: import("../fleet/fleet-bus.js").FleetEventBus;