@sema-agent/server 7.55.0 → 7.57.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 (129) hide show
  1. package/MIGRATION.md +3 -3
  2. package/README.md +14 -7
  3. package/README.zh-CN.md +10 -6
  4. package/USAGE.md +207 -59
  5. package/deploy/sema-up/chart/values.yaml +1 -1
  6. package/dist/approval-ask-audit-store.d.ts +100 -1
  7. package/dist/approval-ask-audit-store.js +103 -2
  8. package/dist/approval-card.d.ts +148 -13
  9. package/dist/approval-card.js +82 -13
  10. package/dist/approval-content-kind.d.ts +22 -0
  11. package/dist/approval-content-kind.js +5 -0
  12. package/dist/approval.d.ts +31 -0
  13. package/dist/approval.js +18 -0
  14. package/dist/auto-mode-face.d.ts +111 -0
  15. package/dist/auto-mode-face.js +99 -0
  16. package/dist/bench/s1/arms.js +5 -5
  17. package/dist/bench/s1/live-deps.js +11 -11
  18. package/dist/bench/s1/run-firm.js +3 -0
  19. package/dist/bench/s1/runner-ctx.d.ts +4 -0
  20. package/dist/bench/s1/runner-ctx.js +7 -0
  21. package/dist/boot/config-center.js +23 -1
  22. package/dist/boot/coordinators.js +13 -1
  23. package/dist/boot/leader.d.ts +21 -0
  24. package/dist/boot/leader.js +6 -0
  25. package/dist/boot/parked-revive-gate.d.ts +7 -2
  26. package/dist/boot/parked-revive-gate.js +12 -1
  27. package/dist/boot/resolve-spec.d.ts +3 -0
  28. package/dist/boot/resolve-spec.js +3 -1
  29. package/dist/boot/runner-deps.d.ts +12 -0
  30. package/dist/boot/runner-deps.js +28 -3
  31. package/dist/boot/runtime-caps.d.ts +19 -8
  32. package/dist/boot/runtime-caps.js +50 -20
  33. package/dist/boot/session-shell-gate-registry.d.ts +39 -0
  34. package/dist/boot/session-shell-gate-registry.js +21 -0
  35. package/dist/config-catalog.js +14 -5
  36. package/dist/config-center/types.d.ts +2 -1
  37. package/dist/config-provider.js +1 -0
  38. package/dist/config-types.d.ts +41 -10
  39. package/dist/config.d.ts +21 -0
  40. package/dist/config.js +40 -5
  41. package/dist/http/route-ctx.d.ts +90 -3
  42. package/dist/http/routes/a2a-serve.js +5 -3
  43. package/dist/http/routes/approvals-assistant.js +21 -8
  44. package/dist/http/routes/capabilities.js +21 -4
  45. package/dist/http/routes/leader.js +2 -2
  46. package/dist/http/routes/memory-origin.d.ts +2 -2
  47. package/dist/http/routes/rules.js +3 -2
  48. package/dist/http/routes/runs.d.ts +0 -14
  49. package/dist/http/routes/runs.js +20 -8
  50. package/dist/http/routes/tasks.js +34 -8
  51. package/dist/http/routes/workflows.js +20 -5
  52. package/dist/http/server.d.ts +11 -1
  53. package/dist/http/server.js +220 -34
  54. package/dist/http/wire-types.d.ts +9 -4
  55. package/dist/leader/diffout.js +2 -1
  56. package/dist/leader/endpoint.js +49 -15
  57. package/dist/leader/fanout.js +6 -5
  58. package/dist/leader/leader.js +17 -14
  59. package/dist/leader/merge.js +17 -12
  60. package/dist/leader/planner.js +3 -2
  61. package/dist/leader/repair-oracle.js +6 -4
  62. package/dist/leader/repair-wire.js +5 -3
  63. package/dist/leader/wire.d.ts +80 -1
  64. package/dist/leader/wire.js +55 -26
  65. package/dist/main.js +44 -8
  66. package/dist/observability/err-text.d.ts +6 -0
  67. package/dist/observability/err-text.js +10 -0
  68. package/dist/observability/fail-open.d.ts +40 -0
  69. package/dist/observability/fail-open.js +19 -0
  70. package/dist/observability/metrics.js +1 -1
  71. package/dist/observability/run-terminal-log.d.ts +120 -0
  72. package/dist/observability/run-terminal-log.js +360 -0
  73. package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
  74. package/dist/orchestration/workflow-completion-inbox.js +8 -0
  75. package/dist/permission-rule-vocab.d.ts +40 -0
  76. package/dist/permission-rule-vocab.js +17 -0
  77. package/dist/plugins/checkpoint-store-sql.d.ts +26 -0
  78. package/dist/plugins/checkpoint-store-sql.js +23 -4
  79. package/dist/plugins/file-run-store.d.ts +3 -34
  80. package/dist/plugins/file-run-store.js +21 -0
  81. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  82. package/dist/plugins/local-checkpoint-store.js +5 -0
  83. package/dist/plugins/local-session-store.d.ts +9 -9
  84. package/dist/plugins/local-session-store.js +5 -3
  85. package/dist/plugins/memory-run-store.d.ts +3 -15
  86. package/dist/plugins/memory-run-store.js +21 -0
  87. package/dist/plugins/permission-rule-store-file.d.ts +22 -4
  88. package/dist/plugins/permission-rule-store-file.js +25 -11
  89. package/dist/plugins/permission-rule-store-sql.d.ts +60 -30
  90. package/dist/plugins/permission-rule-store-sql.js +39 -23
  91. package/dist/plugins/pg-session-storage.js +17 -13
  92. package/dist/plugins/remote-scratchpad.js +3 -2
  93. package/dist/plugins/run-store-sql.d.ts +3 -42
  94. package/dist/plugins/run-store-sql.js +36 -5
  95. package/dist/plugins/store-backend.d.ts +1 -1
  96. package/dist/plugins/store-contracts.d.ts +70 -1
  97. package/dist/plugins/tidb-session-store.js +18 -14
  98. package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
  99. package/dist/plugins/workflow-run-store-sql.js +10 -2
  100. package/dist/rules-consent.d.ts +5 -4
  101. package/dist/rules-consent.js +25 -46
  102. package/dist/run-cancel-context.d.ts +13 -0
  103. package/dist/run-cancel-context.js +15 -0
  104. package/dist/run-local.js +2 -2
  105. package/dist/runs.d.ts +4 -1
  106. package/dist/runs.js +41 -10
  107. package/dist/runtime-caps-resolver.d.ts +133 -17
  108. package/dist/runtime-caps-resolver.js +44 -4
  109. package/dist/security.d.ts +7 -0
  110. package/dist/task-settings.d.ts +57 -3
  111. package/dist/task-settings.js +78 -5
  112. package/dist/task-workflow.d.ts +32 -2
  113. package/dist/task-workflow.js +8 -3
  114. package/dist/tool-approval.d.ts +38 -63
  115. package/dist/tool-approval.js +102 -74
  116. package/dist/trace/core-keyset-guard.d.ts +2 -2
  117. package/dist/trace/ledger-sink.d.ts +13 -4
  118. package/dist/trace/ledger-sink.js +14 -6
  119. package/dist/trace/project.d.ts +13 -0
  120. package/dist/trace/project.js +10 -1
  121. package/dist/trace/redact.d.ts +22 -11
  122. package/dist/trace/redact.js +655 -20
  123. package/dist/trace/sema-provenance.d.ts +26 -0
  124. package/dist/trace/sema-provenance.js +11 -0
  125. package/dist/turn-activity.d.ts +32 -2
  126. package/dist/turn-activity.js +29 -4
  127. package/package.json +3 -3
  128. package/skills/find-skills.md +1 -1
  129. package/skills/loop.md +1 -1
@@ -1,38 +1,97 @@
1
1
  import { z } from "zod";
2
2
  import { ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
3
3
  import { redactSecrets } from "./trace/redact.js";
4
+ import { recordFailOpen } from "./observability/fail-open.js";
5
+ import { PERSISTED_RULE_MATCHES, UNCOVERED_SEGMENT_REASONS } from "./permission-rule-vocab.js";
4
6
  export const MAX_AGENT_NAME = 200;
5
7
  const MAX_IDENT = 200;
6
8
  export const MAX_RULE_OFFERS = 4;
7
9
  export const MAX_RULE_OFFER_BATCH_MEMBERS = 8;
8
- const SegmentRuleSuggestionSchema = z
9
- .object({
10
- rule: z.string().max(MAX_RULE_TEXT_CHARS),
11
- match: z.enum(["exact", "prefix"]),
12
- command: z.string().max(MAX_RULE_TEXT_CHARS),
13
- segment: z.string().max(MAX_RULE_TEXT_CHARS),
14
- })
15
- .strict();
10
+ export const MAX_UNCOVERED_DETAIL_ROWS = 32;
11
+ const RuleMatchSchema = z.enum(PERSISTED_RULE_MATCHES);
12
+ const UncoveredReasonSchema = z.enum(UNCOVERED_SEGMENT_REASONS);
13
+ const RuleOfferBatchMemberSchema = z.union([
14
+ z
15
+ .object({
16
+ kind: z.literal("command"),
17
+ rule: z.string().max(MAX_RULE_TEXT_CHARS),
18
+ match: RuleMatchSchema,
19
+ command: z.string().max(MAX_RULE_TEXT_CHARS),
20
+ segment: z.string().max(MAX_RULE_TEXT_CHARS),
21
+ })
22
+ .strict(),
23
+ z
24
+ .object({
25
+ kind: z.literal("directoryRead"),
26
+ rule: z.string().max(MAX_RULE_TEXT_CHARS),
27
+ directory: z.string().max(MAX_RULE_TEXT_CHARS),
28
+ segment: z.string().max(MAX_RULE_TEXT_CHARS),
29
+ })
30
+ .strict(),
31
+ ]);
32
+ const UncoveredSegmentDetailSchema = z.object({ segment: z.string().max(MAX_RULE_TEXT_CHARS), reason: UncoveredReasonSchema }).strict();
33
+ const UncoveredSegmentDetailRawSchema = z.object({ segment: z.string(), reason: UncoveredReasonSchema }).strip();
16
34
  export const RuleOfferSchema = z.union([
17
- z.object({ kind: z.literal("single"), rule: z.string().max(MAX_RULE_TEXT_CHARS), match: z.enum(["exact", "prefix"]), command: z.string().max(MAX_RULE_TEXT_CHARS) }).strict(),
35
+ z.object({ kind: z.literal("single"), rule: z.string().max(MAX_RULE_TEXT_CHARS), match: RuleMatchSchema, command: z.string().max(MAX_RULE_TEXT_CHARS) }).strict(),
18
36
  z
19
37
  .object({
20
38
  kind: z.literal("batch"),
21
- rules: z.array(SegmentRuleSuggestionSchema).min(1).max(MAX_RULE_OFFER_BATCH_MEMBERS),
39
+ rules: z.array(RuleOfferBatchMemberSchema).min(1).max(MAX_RULE_OFFER_BATCH_MEMBERS),
22
40
  uncoveredSegments: z.number().int().nonnegative().safe(),
41
+ uncoveredDetail: z.array(UncoveredSegmentDetailSchema).max(MAX_UNCOVERED_DETAIL_ROWS).optional(),
23
42
  })
24
43
  .strict(),
25
44
  ]);
26
45
  export const RuleOfferRawSchema = z.union([
27
- z.object({ kind: z.literal("single"), rule: z.string(), match: z.enum(["exact", "prefix"]), command: z.string() }).strip(),
46
+ z.object({ kind: z.literal("single"), rule: z.string(), match: RuleMatchSchema, command: z.string() }).strip(),
28
47
  z
29
48
  .object({
30
49
  kind: z.literal("batch"),
31
- rules: z.array(z.object({ rule: z.string(), match: z.enum(["exact", "prefix"]), command: z.string(), segment: z.string() }).strip()).min(1),
50
+ rules: z
51
+ .array(z.union([
52
+ z.object({ kind: z.literal("command"), rule: z.string(), match: RuleMatchSchema, command: z.string(), segment: z.string() }).strip(),
53
+ z.object({ kind: z.literal("directoryRead"), rule: z.string(), directory: z.string(), segment: z.string() }).strip(),
54
+ ]))
55
+ .min(1),
32
56
  uncoveredSegments: z.number().int().nonnegative().safe(),
57
+ uncoveredDetail: z.array(UncoveredSegmentDetailRawSchema).optional(),
33
58
  })
34
59
  .strip(),
35
60
  ]);
61
+ export function screenUncoveredDetailSeat(item) {
62
+ if (typeof item !== "object" || item === null)
63
+ return item;
64
+ const candidate = item;
65
+ if (candidate.kind !== "batch" || !("uncoveredDetail" in candidate) || candidate.uncoveredDetail === undefined)
66
+ return item;
67
+ const rows = candidate.uncoveredDetail;
68
+ const wellFormed = Array.isArray(rows) &&
69
+ rows.length <= MAX_UNCOVERED_DETAIL_ROWS &&
70
+ rows.length === candidate.uncoveredSegments &&
71
+ rows.every((r) => UncoveredSegmentDetailRawSchema.safeParse(r).success);
72
+ if (wellFormed)
73
+ return item;
74
+ recordFailOpen("server.rule-offers.uncovered-detail-dropped", Array.isArray(rows) ? `rows=${rows.length} count=${String(candidate.uncoveredSegments)}` : `seat is ${typeof rows}`);
75
+ const { uncoveredDetail: _dropped, ...rest } = candidate;
76
+ return rest;
77
+ }
78
+ export function backfillLegacyBatchMemberKind(item) {
79
+ if (typeof item !== "object" || item === null)
80
+ return item;
81
+ const candidate = item;
82
+ if (candidate.kind !== "batch" || !Array.isArray(candidate.rules))
83
+ return item;
84
+ const isLegacy = (m) => typeof m === "object" &&
85
+ m !== null &&
86
+ !("kind" in m) &&
87
+ typeof m.rule === "string" &&
88
+ typeof m.match === "string" &&
89
+ typeof m.command === "string" &&
90
+ typeof m.segment === "string";
91
+ if (!candidate.rules.some(isLegacy))
92
+ return item;
93
+ return { ...candidate, rules: candidate.rules.map((m) => (isLegacy(m) ? { kind: "command", ...m } : m)) };
94
+ }
36
95
  export function copyRuleOffer(o) {
37
96
  switch (o.kind) {
38
97
  case "single":
@@ -40,8 +99,18 @@ export function copyRuleOffer(o) {
40
99
  case "batch":
41
100
  return {
42
101
  kind: "batch",
43
- rules: o.rules.map((r) => ({ rule: r.rule, match: r.match, command: r.command, segment: r.segment })),
102
+ rules: o.rules.map((r) => {
103
+ switch (r.kind) {
104
+ case "command":
105
+ return { kind: "command", rule: r.rule, match: r.match, command: r.command, segment: r.segment };
106
+ case "directoryRead":
107
+ return { kind: "directoryRead", rule: r.rule, directory: r.directory, segment: r.segment };
108
+ default:
109
+ throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(r)}`);
110
+ }
111
+ }),
44
112
  uncoveredSegments: o.uncoveredSegments,
113
+ ...(o.uncoveredDetail !== undefined ? { uncoveredDetail: o.uncoveredDetail.map((d) => ({ segment: d.segment, reason: d.reason })) } : {}),
45
114
  };
46
115
  default:
47
116
  throw new Error(`unknown rule-offer kind: ${JSON.stringify(o)}`);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * [ref]([ref]② core 点名 → [ref] 认领):`contentKind` 投影键的**单一词属主**(server 侧)。
3
+ *
4
+ * core 在 `summarizeCheckpoint`(dist/core/checkpoint-store.js)对 tool_approval 门铸
5
+ * `contentKind: "content_ask"`,判据一行:`tool.toolName === ASK_USER_QUESTION_TOOL_NAME`
6
+ * (常量声明 = core `dist/core/ask-question.d.ts:14`,值 `"AskUserQuestion"`;该常量**未从 core 包根
7
+ * 导出**(index.d.ts 零命中,exports map 无深路径口)⇒ 本仓落一份同字节常量,不做深路径导入)。
8
+ * 三条 server 投影面(SQL `listPending` 列读 / LOCAL `listPending` blob 读 / live `listLivePending`)
9
+ * 全部经 {@link contentKindOf} 派生,恒与 core 的 summarize 面同词 —— 词漂移由 parity 钉看住
10
+ * (`test/local-checkpoint-store.test.ts` [ref] parity 格:core `summarizeCheckpoint` 对 AskUserQuestion
11
+ * checkpoint 的铸值必须逐字等于本函数的答案)。
12
+ *
13
+ * 🔴 与 `hasBidiControls` 的「server 不重算」纪律**不冲突**:那一位是有界扫描的结果(重算可分家),
14
+ * 而 contentKind 是行上 core 铸的 `toolName` 的**全函数**(一行确定性判)——派生 ≠ 重扫。
15
+ * 🔴 OMIT 契约与同族键一致(`boundCallId`/`gateKind`/`hasBidiControls` 的 [ref]② 纪律):
16
+ * `"content_ask"` 或**缺席**,恒不铸 `null`/`false`;缺席 = 不是内容问句门(普通工具门 / 非工具 park /
17
+ * toolName 缺席的存量行)。展示/分诊/渲染分型用,永不参与 resume / gate / CAS。
18
+ */
19
+ export declare const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
20
+ /** 三读面共用的派生:AskUserQuestion 工具门 ⇒ `"content_ask"`;其余(含 null/缺席)⇒ undefined(键缺席)。 */
21
+ export declare function contentKindOf(toolName: string | null | undefined): "content_ask" | undefined;
22
+ //# sourceMappingURL=approval-content-kind.d.ts.map
@@ -0,0 +1,5 @@
1
+ export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
2
+ export function contentKindOf(toolName) {
3
+ return toolName === ASK_USER_QUESTION_TOOL_NAME ? "content_ask" : undefined;
4
+ }
5
+ //# sourceMappingURL=approval-content-kind.js.map
@@ -36,6 +36,37 @@ export declare function assertGateIntentServiceable(config: {
36
36
  approvalNeverAuto: readonly string[];
37
37
  durableApproval: boolean;
38
38
  }, checkpointStorePresent: boolean): void;
39
+ /**
40
+ * [ref] 件⑩ —— **center 的 `forceDurableGate` 在无 checkpoint 店的部署上会静默 inert**,补一条 boot 披露。
41
+ *
42
+ * 事实链(core 7.2.0 亲读):`RuntimeCaps.forceDurableGate:true` 让引擎**合成**一个
43
+ * `durableApproval{scope: principal}`(`dist/core/types.d.ts:5279-5283`),好让 policy 的 `ask` 挂到
44
+ * durable wire 上。但「挂得上去」的前提是这台 worker 真有 checkpoint 店 —— 而本仓的构造条件逐字是
45
+ * `backend?.checkpoint && config.durableApproval`(main.ts)。`DURABLE_APPROVAL` 关 ⇒ 店缺席 ⇒ 合成出来
46
+ * 的那把锁**没有锁孔**:core 侧退化成非 durable 的 ask 链,center 那条「fleet-wide interactive runs gate」
47
+ * 的授权在这台机器上**一个字节都没兑现**,且两侧都不出声(core 侧归 core 的 C-22)。
48
+ *
49
+ * 为什么是 warn 不是拒启:`forceDurableGate` 是 **per-principal** 的授权,boot 期没有 principal,判不出
50
+ * 「今天真的会有人被下发」——能判的只有「这台机器的下发口开着(有 entitlement 源)而承接面关着」。
51
+ * 拿一个**可能**的下发去挡死一台本来跑得好好的 worker,是本条诊断带来的新故障,不是它治的病
52
+ * (与 `noticeAutoCompactAboveWindow` 的同一条取舍)。所以:响亮一行,不拒启。
53
+ *
54
+ * 为什么在 **boot** 判而不是 resolve 时判:两个入参都是**部署级**的(entitlement 源在不在、durable 门开
55
+ * 不开),per-principal 的那一半改变不了结论;放 resolve 里就得自己造去重,而 boot 天然只跑一次。
56
+ *
57
+ * 纯函数(返回一行或 undefined,不自己写日志)= 红先测得动;调用点在 main.ts 的
58
+ * {@link assertGateIntentServiceable} 之后(同一段门装配)。
59
+ */
60
+ export declare function buildForceDurableGateInertNotice(input: {
61
+ /** center per-principal entitlement 源在不在(`centerEntitlementSourceWired` 的结果 —— 判据单源在那里)。 */
62
+ entitlementSourceWired: boolean;
63
+ /** 本进程真建出 checkpoint 店了吗(= `backend?.checkpoint && DURABLE_APPROVAL`)。 */
64
+ checkpointStorePresent: boolean;
65
+ durableApproval: boolean;
66
+ }): {
67
+ event: string;
68
+ fields: Record<string, unknown>;
69
+ } | undefined;
39
70
  /**
40
71
  * Durable approval policy (core 1.67 / [ref]): returns three-state **`ask`** for gated tools instead of
41
72
  * polling. With `TaskSpec.durableApproval` set + a `checkpointStore` on the Runner, core turns that `ask` into
package/dist/approval.js CHANGED
@@ -13,6 +13,24 @@ export function assertGateIntentServiceable(config, checkpointStorePresent) {
13
13
  }
14
14
  throw new Error("DURABLE_APPROVAL=true but no checkpoint store is available — the configured DB backend has no checkpoint face (or no backend is configured). Wire a MySQL-protocol/PostgreSQL/file backend, or unset DURABLE_APPROVAL.");
15
15
  }
16
+ export function buildForceDurableGateInertNotice(input) {
17
+ if (!input.entitlementSourceWired || input.checkpointStorePresent)
18
+ return undefined;
19
+ return {
20
+ event: "center_force_durable_gate_inert",
21
+ fields: {
22
+ durableApproval: input.durableApproval,
23
+ note: "this deployment resolves per-principal entitlements from config-center, but it has NO checkpoint store " +
24
+ (input.durableApproval
25
+ ? "(DURABLE_APPROVAL=true, yet the configured backend exposes no checkpoint face)"
26
+ : "(DURABLE_APPROVAL is off)") +
27
+ " — a center-issued RuntimeCaps.forceDurableGate=true will be INERT here: the engine synthesizes a " +
28
+ "durableApproval scope, but with no store to suspend into, gated asks fall back to the non-durable chain " +
29
+ "and the fleet-wide interactive-runs mandate is NOT enforced on this worker. Set DURABLE_APPROVAL=true with " +
30
+ "a backend that has a checkpoint face (MySQL-protocol / PostgreSQL / file), or stop issuing forceDurableGate for these principals.",
31
+ },
32
+ };
33
+ }
16
34
  function createNameSetMatcher(names) {
17
35
  const exact = new Set(names);
18
36
  const covering = names.filter(isNamespacedCoveringRuleName);
@@ -0,0 +1,111 @@
1
+ import { type Model, type ModelRoles, type RuntimeCaps } from "@sema-agent/core";
2
+ import type { SettingsPermissionMode } from "./task-settings.js";
3
+ /**
4
+ * 分类器**配置**的模型路由 —— 直接复用 core 的 `resolveTaskModel({ modelRole: "classifier" })`(prepare-task 武装时
5
+ * 走的同一函数:role 回落链 `classifier → summarize → default`,且 `{select}` 形 RoleSpec 按目录选型)。
6
+ * codex r4 [medium] 验真后改:首版按名字级自算,遇 center 下发的 `{select:{tier}}` 形会错落到 summarize/default,
7
+ * 对 `sema doctor permissions` 报另一个模型。答的是「配置了什么」;运行期 `derivedRouteFallsBack` 的回落不在射程
8
+ * (那是 core 状态座的事)。什么都解不出 ⇒ `{id:undefined, fault}`(core 的拒句当数据交出,读面不写 model 键):
9
+ * 本面是纯披露、不是执法——读面 500 掉整张能力表比少一个 model 键更伤。
10
+ */
11
+ export declare function classifierRouteFor(deps: {
12
+ models?: Record<string, Model>;
13
+ roles?: ModelRoles;
14
+ }): ClassifierRoute;
15
+ /** `classifierRouteFor` 的答:解出 ⇒ `{id}`;解不出 ⇒ `{id:undefined, fault}`(core 的拒句原文,不吞)。 */
16
+ export type ClassifierRoute = {
17
+ readonly id: string;
18
+ readonly fault?: undefined;
19
+ } | {
20
+ readonly id: undefined;
21
+ readonly fault: string;
22
+ };
23
+ /** core [ref] seam ⑤ 的未武装原因闭集(六词,[ref] 加 settings_denied;顺序 = 判序)。 */
24
+ export declare const AUTO_MODE_UNARMED_REASONS: readonly ["mode_not_auto", "deployment_incapable", "org_denied", "local_denied", "settings_denied", "resolver_fault"];
25
+ export type AutoModeUnarmedReason = (typeof AUTO_MODE_UNARMED_REASONS)[number];
26
+ /** 锁步表:词表与语义一一对应(加员/删员任一侧不同步 = tsc 红)。语义只供文档/测试,不上 wire。 */
27
+ export declare const AUTO_MODE_UNARMED_REASON_SEMANTICS: {
28
+ readonly mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\";座缺席)——只属意图武装式,旧式 core 不读模式";
29
+ readonly deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器);旧式 core 下另含「无 center 源 = 零授予路径」";
30
+ readonly org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项;旧式 core 下另含「center 在场但未授予」";
31
+ readonly local_denied: "本地 catalog 键 PERMISSIONS_DISABLE_AUTO_MODE=true(center/settings 键名 permissions.disableAutoMode)把 caps.autoMode 折成 false";
32
+ readonly settings_denied: "settings 层 permissions.disableAutoMode 取 disable 或 true(两套已发布拼写同义;用户/managed 层 kill-switch)把本 run 折成非 auto —— capabilities 查询串不带 settings,本词由 run 级面产([6128] PM 裁定第六员)";
33
+ readonly resolver_fault: "per-principal caps resolver 抛错/答非记录(core 同形全拒:autoMode:false + onError phase config)";
34
+ };
35
+ /**
36
+ * 安装包探针:**这台机器上装的 core 用哪套武装极性**。
37
+ *
38
+ * 判据([ref] 改,原先用「854b202b 同批的运行时导出 `mailboxCrossProcessMountVerdict`」作代理 —— 那是 385 片2a 的
39
+ * mailbox 面,与 auto 武装**语义无关**,只是恰好同一个 dist;core 若把 [ref] 单独发一个补丁版,代理就答错):
40
+ *
41
+ * · **主判(语义精确)= [ref] 自己的座**:武装点 `dist/core/runner/prepare-task.js` 里是否出现 `autoModeRequested`。
42
+ * 亲验(2026-09-03,`git grep autoModeRequested` 两 ref 对拍):v7.2.0 全树**零**处提及,854b202b 起 prepare-task
43
+ * 有 8 处 —— 单调、且探的就是「武装点读不读意图座」这件事本身。dist 未 mangle(属性名从不被 tsc/esbuild 改名),
44
+ * d.ts 不参与判断(类型面运行期探不到,读 js 才是「引擎真这么跑」)。
45
+ * · **佐证(只在主判为 false 时读)= 同批到货的运行时导出** `mailboxCrossProcessMountVerdict`(包内随发的
46
+ * `node_modules/@sema-agent/core/test/export-surface.snapshot.json`(core 包内随发),core `files` 字段含它 —— 消费账本门 A 的同一真源)。
47
+ * - 座缺席 ∧ 标记缺席 ⇒ 就是 npm 7.2.0 那一档 ⇒ `false`(旧式臂,兼容形);
48
+ * - 座缺席 ∧ 标记**在场** ⇒ **矛盾**:dist 已是 854b202b 之后,而武装点不再提这个座名(core 重构/换文件)
49
+ * ⇒ 判据失效,**抛**(不猜。静默回落旧式臂正是「854 之后的部署悄悄退回 org 授予极性」那一形)。
50
+ * - 座在场 ⇒ `true`,不看佐证([ref] 单发的补丁版合法)。
51
+ *
52
+ * 🔴 不用 `import * as core`(消费账本门禁命名空间导入:每个消费名必须可按名对账);两个文件都经主入口 resolve
53
+ * 定位(package `exports` 映射不暴露这些子路径)。**任何读不到 = 抛**,由 boot 装配点承接成拒启(见
54
+ * {@link coreArmsAutoOnIntent})。
55
+ *
56
+ * @param distDir 测试注入的假 `dist/` 根;生产不传(从安装的 core 主入口解)。
57
+ */
58
+ export declare function coreArmsAutoOnIntentProbe(distDir?: string): boolean;
59
+ /**
60
+ * {@link coreArmsAutoOnIntentProbe} 的**惰性记忆化**访问器 —— 生产装配点(`main.ts` 的 `autoModeFace` 组装,
61
+ * 早于 `server.listen`)调一次,探针抛就是**boot 期响亮拒启**并带指路。
62
+ *
63
+ * 🔴 为什么必须惰性([ref]):此前是模块顶层 `const … = coreArmsAutoOnIntentProbe()`,而 `main.ts` 顶部 import 本模块
64
+ * ⇒ 探针在 **import 期**求值,读不到快照就在任何日志器/诊断存在之前把整台 server 抛死(一个 npm 安装瑕疵 =
65
+ * 一台起不来且没有遗言的机器)。惰性之后,同一条失败落在 boot 装配点,遗言完整、退出码照旧。
66
+ */
67
+ export declare function coreArmsAutoOnIntent(): boolean;
68
+ /** 装配面交给读面的四件事实(`main.ts` 从 runnerDeps / 合成后的 caps resolver / 到货位取,读面不自算)。 */
69
+ export interface AutoModeFaceDeps {
70
+ /** `RunnerDeps.autoMode !== undefined`——**读**装配点那一位,不写字面 true(与 parked-revive 的
71
+ * `autoModeSeatMounted` 同一纪律:席位哪天改成有条件挂载,这里不能静默错供)。 */
72
+ readonly seatMounted: boolean;
73
+ /** {@link coreArmsAutoOnIntent}(生产装配递探针访问器;测试可递 true 走正控)。false ⇒ `deployment_incapable`。 */
74
+ readonly engineArmsOnIntent: boolean;
75
+ /** 合成后的 per-principal caps resolver(= 递给 `RunnerDeps.runtimeCapsResolver` 的同一只:center 客户端
76
+ * → observer env 基线 → memory grant → 本地 auto deny,单一属主 boot/runtime-caps.ts)。缺席 = 无任何
77
+ * per-principal 源(无 center、无本地 deny)⇒ caps undefined ⇒ 三态之「缺席=不阻」。 */
78
+ readonly resolveCaps?: ((principal: string | undefined) => RuntimeCaps | undefined | Promise<RuntimeCaps | undefined>) | undefined;
79
+ /** 分类器**配置**的模型路由(server 侧按 core 的 role 回落序 `classifier → summarize → default` 从
80
+ * `config.roles` 读到的 catalog 名)。这是「配置了什么」,不是「这次真用了什么」——core 的
81
+ * `derivedRouteFallsBack` 可能在运行期回落到主模型;真值候 core 状态座。
82
+ * 🔴 codex r3 [medium]:是 **getter** 不是快照——config-center 会原地热更新 `config.roles`(Runner 每任务从活引用
83
+ * 重解),boot 期铸的字符串会在热切换后永久陈旧;生产装配递的 getter 调 `classifierRouteFor({ models: config.models, roles: config.roles })` 并把 fault 记 debug。 */
84
+ readonly classifierModel?: (() => string | undefined) | undefined;
85
+ }
86
+ export type AutoModeVerdict = {
87
+ readonly armed: true;
88
+ readonly model?: string;
89
+ readonly fault?: undefined;
90
+ } | {
91
+ readonly armed: false;
92
+ readonly reason: AutoModeUnarmedReason;
93
+ readonly model?: string; /** 仅 resolver_fault 臂:错误原文(不上 wire,路由记 warn)。 */
94
+ readonly fault?: string;
95
+ };
96
+ /**
97
+ * 三项合取的 server 侧裁决。`requestedMode` 缺席 = 按 auto 意图答(「若请求会不会武装」);`principal` =
98
+ * 调用方经门验证的身份(匿名 ⇒ undefined ⇒ center 客户端答 undefined,本地 deny 层照样罩)。
99
+ *
100
+ * 🔴 resolver 抛错**不吞**:合成腿的每一层都自带 fail-closed/缺席臂(center 客户端 catch-all、grant 腿 onFault),
101
+ * 走到这里还在抛 = 装配缺陷,读面 500 比一句编出来的 reason 诚实。
102
+ */
103
+ export declare function judgeAutoModeArming(face: AutoModeFaceDeps | undefined, input: {
104
+ readonly requestedMode: SettingsPermissionMode | undefined;
105
+ readonly principal: string | undefined;
106
+ readonly entitlementSource: boolean;
107
+ /** 本地 `PERMISSIONS_DISABLE_AUTO_MODE`(= catalog/settings 键 `permissions.disableAutoMode`)是否置 true —— caps false 时分词
108
+ * `local_denied` / `org_denied`(seam ⑤;两者对壳的指路不同:改本机 env vs 找组织)。 */
109
+ readonly localDeny: boolean;
110
+ }): Promise<AutoModeVerdict>;
111
+ //# sourceMappingURL=auto-mode-face.d.ts.map
@@ -0,0 +1,99 @@
1
+ import { createRequire } from "node:module";
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { z } from "zod";
5
+ import { resolveTaskModel } from "@sema-agent/core";
6
+ export function classifierRouteFor(deps) {
7
+ try {
8
+ return { id: resolveTaskModel({ modelRole: "classifier" }, deps).model.id };
9
+ }
10
+ catch (err) {
11
+ return { id: undefined, fault: err instanceof Error ? err.message : String(err) };
12
+ }
13
+ }
14
+ export const AUTO_MODE_UNARMED_REASONS = ["mode_not_auto", "deployment_incapable", "org_denied", "local_denied", "settings_denied", "resolver_fault"];
15
+ export const AUTO_MODE_UNARMED_REASON_SEMANTICS = {
16
+ mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\";座缺席)——只属意图武装式,旧式 core 不读模式",
17
+ deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器);旧式 core 下另含「无 center 源 = 零授予路径」",
18
+ org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项;旧式 core 下另含「center 在场但未授予」",
19
+ local_denied: "本地 catalog 键 PERMISSIONS_DISABLE_AUTO_MODE=true(center/settings 键名 permissions.disableAutoMode)把 caps.autoMode 折成 false",
20
+ settings_denied: "settings 层 permissions.disableAutoMode 取 disable 或 true(两套已发布拼写同义;用户/managed 层 kill-switch)把本 run 折成非 auto —— capabilities 查询串不带 settings,本词由 run 级面产([6128] PM 裁定第六员)",
21
+ resolver_fault: "per-principal caps resolver 抛错/答非记录(core 同形全拒:autoMode:false + onError phase config)",
22
+ };
23
+ function armingProbeFailure(what) {
24
+ return new Error(`auto-mode arming probe cannot decide (${what}). This probe answers ONE question — does the INSTALLED @sema-agent/core arm ` +
25
+ `auto mode from the task's INTENT seat (core #521: spec.autoModeRequested ∧ seat ∧ caps !== false), or from the older ` +
26
+ `"org grants it" polarity (runtimeCaps.autoMode === true ∧ seat)? The two polarities are OPPOSITE on the permission axis, ` +
27
+ `so guessing would silently put this deployment on the wrong one — either arming runs the org never granted, or reporting ` +
28
+ `"not armed" at /v1/capabilities while the engine really is arming (#157: no silent fail-open on the permission axis). ` +
29
+ `Refusing to start instead. Fix: reinstall the engine (rm -rf node_modules && npm ci) so the package ships its full dist, ` +
30
+ `or — if core moved/renamed the arming site — update the probe in src/auto-mode-face.ts to read the new one.`);
31
+ }
32
+ export function coreArmsAutoOnIntentProbe(distDir) {
33
+ const dist = distDir ?? dirname(createRequire(import.meta.url).resolve("@sema-agent/core"));
34
+ const armingSite = join(dist, "core", "runner", "prepare-task.js");
35
+ let armingSrc;
36
+ try {
37
+ armingSrc = readFileSync(armingSite, "utf8");
38
+ }
39
+ catch (err) {
40
+ throw armingProbeFailure(`the engine's arming site is unreadable at ${armingSite}: ${err instanceof Error ? err.message : String(err)}`);
41
+ }
42
+ if (armingSrc.includes(CORE_AUTO_MODE_INTENT_SEAT))
43
+ return true;
44
+ const snapPath = join(dist, "..", "test", "export-surface.snapshot.json");
45
+ let snap;
46
+ try {
47
+ const parsed = ExportSurfaceSnapshotSchema.safeParse(JSON.parse(readFileSync(snapPath, "utf8")));
48
+ if (!parsed.success)
49
+ throw new Error(`export-surface snapshot shape mismatch: ${parsed.error.issues.map((i) => i.path.join(".") + ":" + i.message).join("; ")}`);
50
+ snap = parsed.data;
51
+ }
52
+ catch (err) {
53
+ throw armingProbeFailure(`the arming seat is absent from ${armingSite} and the corroborating export-surface snapshot is unreadable at ${snapPath}: ${err instanceof Error ? err.message : String(err)}`);
54
+ }
55
+ if (!snap.exports || typeof snap.exports !== "object")
56
+ throw armingProbeFailure(`core export-surface snapshot malformed at ${snapPath}`);
57
+ if (typeof snap.exports.mailboxCrossProcessMountVerdict === "string") {
58
+ throw armingProbeFailure(`contradiction — the installed core's export surface is at/after the batch that shipped #521, but its arming site ` +
59
+ `(${armingSite}) never names \`${CORE_AUTO_MODE_INTENT_SEAT}\`, so the engine either moved the arming site or renamed the seat`);
60
+ }
61
+ return false;
62
+ }
63
+ const CORE_AUTO_MODE_INTENT_SEAT = "autoModeRequested";
64
+ let armsOnIntentMemo;
65
+ const ExportSurfaceSnapshotSchema = z.object({ exports: z.record(z.string(), z.string()).optional() }).passthrough();
66
+ export function coreArmsAutoOnIntent() {
67
+ if (armsOnIntentMemo === undefined)
68
+ armsOnIntentMemo = coreArmsAutoOnIntentProbe();
69
+ return armsOnIntentMemo;
70
+ }
71
+ export async function judgeAutoModeArming(face, input) {
72
+ const route = face?.seatMounted ? face.classifierModel?.() : undefined;
73
+ const model = route !== undefined ? { model: route } : {};
74
+ if (face?.engineArmsOnIntent && input.requestedMode !== undefined && input.requestedMode !== "auto")
75
+ return { armed: false, reason: "mode_not_auto", ...model };
76
+ if (face === undefined || !face.seatMounted)
77
+ return { armed: false, reason: "deployment_incapable" };
78
+ let caps;
79
+ try {
80
+ caps = face.resolveCaps !== undefined ? await face.resolveCaps(input.principal) : undefined;
81
+ }
82
+ catch (err) {
83
+ return { armed: false, reason: "resolver_fault", ...model, fault: err instanceof Error ? err.message : String(err) };
84
+ }
85
+ const deniedWord = input.localDeny ? "local_denied" : "org_denied";
86
+ if (face.engineArmsOnIntent) {
87
+ if (caps?.autoMode === false)
88
+ return { armed: false, reason: deniedWord, ...model };
89
+ return { armed: true, ...model };
90
+ }
91
+ if (caps?.autoMode === true)
92
+ return { armed: true, ...model };
93
+ if (caps?.autoMode === false)
94
+ return { armed: false, reason: deniedWord, ...model };
95
+ if (!input.entitlementSource)
96
+ return { armed: false, reason: "deployment_incapable", ...model };
97
+ return { armed: false, reason: "org_denied", ...model };
98
+ }
99
+ //# sourceMappingURL=auto-mode-face.js.map
@@ -1,4 +1,4 @@
1
- import { leafBudgetFields } from "./runner-ctx.js";
1
+ import { leafBudgetFields, s1DebugGit } from "./runner-ctx.js";
2
2
  import {} from "./tasks.js";
3
3
  import { decideApproval, decidePlan } from "./reviewer.js";
4
4
  import { oracleTrulyCorrect, pickOracleVerdicts, armTag, S1_SCHEMA_VERSION, S1_CORE_VERSION, } from "./row.js";
@@ -164,7 +164,7 @@ export async function runArm(arm, trap, seed, ctx, deps, supStore) {
164
164
  vr = await deps.runWithVerification(implSpec);
165
165
  }
166
166
  catch (e) {
167
- if (process.env.S1_DEBUG_GIT)
167
+ if (s1DebugGit())
168
168
  console.error("[S1 SOLO infra throw]", e instanceof Error ? (e.stack ?? e.message) : String(e));
169
169
  return assembleRow({
170
170
  arm, trap, seed, ctx,
@@ -176,7 +176,7 @@ export async function runArm(arm, trap, seed, ctx, deps, supStore) {
176
176
  finishedAt: ctx.clock.now(),
177
177
  });
178
178
  }
179
- if (process.env.S1_DEBUG_GIT)
179
+ if (s1DebugGit())
180
180
  console.error("[S1 SOLO vr.status]", vr.status, "| stats:", JSON.stringify(vr.stats)?.slice(0, 200));
181
181
  const oracle = pickOracleVerdicts(await deps.runOracle());
182
182
  return assembleRow({
@@ -210,7 +210,7 @@ export async function runArm(arm, trap, seed, ctx, deps, supStore) {
210
210
  }
211
211
  }
212
212
  catch (e) {
213
- if (process.env.S1_DEBUG_GIT)
213
+ if (s1DebugGit())
214
214
  console.error(`[S1 SUP infra throw driver=${trap.supDriver}]`, e instanceof Error ? (e.stack ?? e.message) : String(e));
215
215
  return assembleRow({
216
216
  arm, trap, seed, ctx,
@@ -222,7 +222,7 @@ export async function runArm(arm, trap, seed, ctx, deps, supStore) {
222
222
  finishedAt: ctx.clock.now(),
223
223
  });
224
224
  }
225
- if (process.env.S1_DEBUG_GIT)
225
+ if (s1DebugGit())
226
226
  console.error(`[S1 SUP vr.status driver=${trap.supDriver}]`, vr.status, "| repairTerminal:", repairTerminal, "| verdict:", vr.verification?.verdict, "| unverifiedReason:", vr.verification?.unverifiedReason, "| checkpointGate:", JSON.stringify(vr.checkpointGate), "| error:", vr.error, "| stats:", JSON.stringify(vr.stats)?.slice(0, 180));
227
227
  const oracle = pickOracleVerdicts(await deps.runOracle());
228
228
  const runStatus = classifyRunStatus({ status: vr.status, repairTerminal });
@@ -7,7 +7,7 @@ import { createDurableAskPolicy } from "../../approval.js";
7
7
  import { createLeaderRunner } from "../../leader/wire.js";
8
8
  import { runOracle as runStandaloneOracle } from "./oracle.js";
9
9
  import { makeRepairOracle } from "./repair-oracle-adapter.js";
10
- import { assertDistinctEnvs } from "./runner-ctx.js";
10
+ import { assertDistinctEnvs, s1DebugGit } from "./runner-ctx.js";
11
11
  const W = "/home/user";
12
12
  const REPO = `${W}/repo`;
13
13
  const BASE_REF = "s1-base";
@@ -112,7 +112,7 @@ async function transferWorkerTreeToGrader(worker, grader) {
112
112
  const ext = await sh(grader)(`set -e; test -s /tmp/s1-wt.tar; rm -rf ${REPO}; mkdir -p ${REPO}; tar -xf /tmp/s1-wt.tar -C ${REPO}; echo "EXTRACTED=$(ls -A ${REPO} | wc -l)"`);
113
113
  if (ext.code !== 0 || !ext.out.includes("EXTRACTED="))
114
114
  throw new Error(`s1 grader tar extract failed: ${ext.out.slice(-400)}`);
115
- if (process.env.S1_DEBUG_GIT)
115
+ if (s1DebugGit())
116
116
  console.error(`[S1_DEBUG_TAR] head=${head.slice(0, 8)} base=${base.slice(0, 8)} tarBytes=${buf.length} ${ext.out.slice(-60)}`);
117
117
  return { head, base };
118
118
  }
@@ -141,7 +141,7 @@ async function importBundleBinaryToGrader(grader, tarBuffer, shas) {
141
141
  const ext = await sh(grader)(`set -e; test -s /tmp/s1-wt.tar; rm -rf ${REPO}; mkdir -p ${REPO}; tar -xf /tmp/s1-wt.tar -C ${REPO}; echo "EXTRACTED=$(ls -A ${REPO} | wc -l)"`);
142
142
  if (ext.code !== 0 || !ext.out.includes("EXTRACTED="))
143
143
  throw new Error(`s1 grader tar extract failed: ${ext.out.slice(-400)}`);
144
- if (process.env.S1_DEBUG_GIT)
144
+ if (s1DebugGit())
145
145
  console.error(`[S1_DEBUG_TAR team] head=${shas.head.slice(0, 8)} base=${shas.base.slice(0, 8)} ${ext.out.slice(-60)}`);
146
146
  return shas;
147
147
  }
@@ -196,7 +196,7 @@ export function buildLiveDeps(rt, trap, seed, cellId) {
196
196
  state.shas = await transferWorkerTreeToGrader(worker, grader);
197
197
  const task = { ...oracleTaskFromTrap(trap), ...(state.shas ? { deliveredShas: state.shas } : {}) };
198
198
  const v = await runStandaloneOracle(grader, graderTransport(grader), worker, task);
199
- if (process.env.S1_DEBUG_GIT)
199
+ if (s1DebugGit())
200
200
  console.error(`[S1_DEBUG_ORACLE ${trap.id}] delivered=${v.delivered} tests=${v.hiddenTestsGreen} build=${v.buildPassed} inv=${v.invariantsOk} | raw=`, JSON.stringify(v.raw)?.slice(0, 700));
201
201
  return v;
202
202
  };
@@ -250,27 +250,27 @@ export function buildLiveDeps(rt, trap, seed, cellId) {
250
250
  execFileSync("git", ["clone", "-q", "--branch", branch, remoteUrl, cloneDir], { stdio: ["ignore", "ignore", "pipe"] });
251
251
  }
252
252
  catch (e1) {
253
- if (process.env.S1_DEBUG_GIT)
253
+ if (s1DebugGit())
254
254
  console.error(`[S1_DEBUG_IMPORT] cp clone --branch ${branch} failed: ${e1.stderr?.toString().slice(-200)}; retrying default`);
255
255
  execFileSync("git", ["clone", "-q", remoteUrl, cloneDir], { stdio: ["ignore", "ignore", "pipe"] });
256
256
  }
257
257
  const baseHash = execFileSync("git", ["-C", cloneDir, "rev-list", "--max-parents=0", "HEAD"]).toString().trim().split("\n").pop().trim();
258
258
  const head = execFileSync("git", ["-C", cloneDir, "rev-parse", "HEAD"]).toString().trim();
259
259
  if (!/^[0-9a-f]{40}$/.test(baseHash) || head === baseHash) {
260
- if (process.env.S1_DEBUG_GIT)
260
+ if (s1DebugGit())
261
261
  console.error(`[S1_DEBUG_IMPORT] cp integrated tree not delivered: head=${head} base=${baseHash}`);
262
262
  return false;
263
263
  }
264
264
  const bundlePath = join(tmp, "team.bundle");
265
265
  execFileSync("git", ["-C", cloneDir, "archive", "--format=tar", "-o", bundlePath, "HEAD"], { stdio: ["ignore", "ignore", "pipe"] });
266
266
  const buffer = readFileSync(bundlePath);
267
- if (process.env.S1_DEBUG_GIT)
267
+ if (s1DebugGit())
268
268
  console.error(`[S1_DEBUG_IMPORT] cp integrated head=${head} base=${baseHash} tar bytes=${buffer.length}`);
269
269
  state.teamBundle = { buffer, head, base: baseHash };
270
270
  return true;
271
271
  }
272
272
  catch (e) {
273
- if (process.env.S1_DEBUG_GIT)
273
+ if (s1DebugGit())
274
274
  console.error(`[S1_DEBUG_IMPORT] control-plane THREW:`, e instanceof Error ? e.message : String(e));
275
275
  return false;
276
276
  }
@@ -361,7 +361,7 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId,
361
361
  process.env.LEADER_CONFLICT_ROUNDS = process.env.LEADER_CONFLICT_ROUNDS ?? "2";
362
362
  const run = createLeaderRunner({
363
363
  brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
364
- ...(process.env.S1_DEBUG_GIT ? { 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) : "") } } : {}),
364
+ ...(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) : "") } } : {}),
365
365
  });
366
366
  const teamSeedWrite = (trap.seedFiles ?? [])
367
367
  .map((f) => {
@@ -415,13 +415,13 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId,
415
415
  });
416
416
  }
417
417
  catch (e) {
418
- if (process.env.S1_DEBUG_GIT)
418
+ if (s1DebugGit())
419
419
  console.error(`[S1_DEBUG_LEADER ${trap.id}] run() THREW:`, e instanceof Error ? (e.stack ?? e.message) : String(e));
420
420
  killDaemon();
421
421
  rmSync(baseDir, { recursive: true, force: true });
422
422
  return { ok: false, reports: [], infraFailed: true };
423
423
  }
424
- if (process.env.S1_DEBUG_GIT)
424
+ if (s1DebugGit())
425
425
  console.error(`[S1_DEBUG_LEADER ${trap.id}] ok=${res.ok} error=${JSON.stringify(res.error)} cancelled=${res.cancelled} merge=${JSON.stringify(res.merge)} repairTerminal=${res.repairTerminal} reports=${(res.reports ?? []).length} reportsDetail=${JSON.stringify((res.reports ?? []).map((r) => ({ w: r.workerId, ok: r.ok, err: r.error })))?.slice(0, 400)}`);
426
426
  let imported = false;
427
427
  const merged = res.merge?.ok === true;
@@ -7,6 +7,7 @@ import { makeBenchClock } from "./runner-ctx.js";
7
7
  import { buildS1Report, S1_SCHEMA_VERSION, S1_CORE_VERSION } from "./row.js";
8
8
  import { armTag } from "./row.js";
9
9
  import { buildLiveDeps, liveRuntimeConfigFromEnv } from "./live-deps.js";
10
+ import { setS1DebugGit } from "./runner-ctx.js";
10
11
  export function enumerateCells(seeds, filter = {}) {
11
12
  const cells = [];
12
13
  const source = filter.only ? ALL_TRAPS : FIRM_TRAPS;
@@ -184,6 +185,7 @@ function parseArgs(argv) {
184
185
  const cellTimeoutMs = positiveIntArg(get("cell-timeout-ms"), 15 * 60_000, "--cell-timeout-ms");
185
186
  return {
186
187
  dryRun: argv.includes("--dry-run"),
188
+ debugGit: argv.includes("--debug-git"),
187
189
  seeds,
188
190
  cellTimeoutMs,
189
191
  ledgerPath: get("ledger") ?? path.resolve(`./s1-out/${runId}.ledger.jsonl`),
@@ -234,6 +236,7 @@ function dryRunMockFactory() {
234
236
  }
235
237
  export async function main(argv = process.argv.slice(2)) {
236
238
  const args = parseArgs(argv);
239
+ setS1DebugGit(args.debugGit);
237
240
  const liveRuntime = liveRuntimeConfigFromEnv();
238
241
  if (!args.dryRun && !liveRuntime) {
239
242
  console.error("[s1 run-firm] LIVE path refused: E2B_API_KEY + DEEPSEEK_API_KEY are required (the brain gateway). " +
@@ -17,6 +17,10 @@
17
17
  * + the clock; a deterministic shape test exercises it with a MOCK runner (no real agents, no keys).
18
18
  */
19
19
  import type { ExecutionEnv } from "@sema-agent/core";
20
+ /** 由 bench 入口(`run-firm.ts` 的 `--debug-git`)翻开;不调 = 关。 */
21
+ export declare function setS1DebugGit(on: boolean): void;
22
+ /** tar/import/oracle/leader 四族 stderr 追踪开着没有(读侧单点)。 */
23
+ export declare function s1DebugGit(): boolean;
20
24
  /**
21
25
  * The budget block the budget-match guard compares. NOT a s1.v1 RawRow field (the contract carries no budget) —
22
26
  * it is a PRODUCER-side fairness descriptor (one per emitted cell). `teamWorkerBudgetSumUsd` is the bigger-pie
@@ -1,3 +1,10 @@
1
+ let debugGit = false;
2
+ export function setS1DebugGit(on) {
3
+ debugGit = on;
4
+ }
5
+ export function s1DebugGit() {
6
+ return debugGit;
7
+ }
1
8
  export function budgetDescriptor(budget, arm, teamWorkerBudgetSumUsd) {
2
9
  return {
3
10
  modelId: budget.modelId,