@sema-agent/client-core 0.12.1 → 0.13.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 (127) hide show
  1. package/README.md +15 -2
  2. package/dist/abortableSleep.d.ts +29 -0
  3. package/dist/abortableSleep.js +43 -0
  4. package/dist/adapt/arms.d.ts +58 -0
  5. package/dist/adapt/arms.js +613 -0
  6. package/dist/adapt/ids.d.ts +22 -0
  7. package/dist/adapt/ids.js +34 -0
  8. package/dist/adapt/instanceLedger.d.ts +36 -0
  9. package/dist/adapt/instanceLedger.js +50 -0
  10. package/dist/adapt/panelTasks.d.ts +54 -0
  11. package/dist/adapt/panelTasks.js +193 -0
  12. package/dist/adapt/textStream.d.ts +49 -0
  13. package/dist/adapt/textStream.js +141 -0
  14. package/dist/adapt/toolCards.d.ts +65 -0
  15. package/dist/adapt/toolCards.js +100 -0
  16. package/dist/adapt/turnFlags.d.ts +33 -0
  17. package/dist/adapt/turnFlags.js +55 -0
  18. package/dist/adapt/wireShapes.d.ts +93 -0
  19. package/dist/adapt/wireShapes.js +167 -0
  20. package/dist/adapt.d.ts +32 -59
  21. package/dist/adapt.js +78 -1206
  22. package/dist/adapter/downstream/eventToSdkMessage.d.ts +55 -13
  23. package/dist/adapter/downstream/eventToSdkMessage.js +166 -106
  24. package/dist/adapter/downstream/terminalToSdkResult.js +149 -160
  25. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +36 -0
  26. package/dist/adapter/downstream/turnUsageToModelUsage.js +34 -6
  27. package/dist/adapter/runStream.d.ts +29 -4
  28. package/dist/adapter/runStream.js +129 -13
  29. package/dist/adapter/types.d.ts +2 -2
  30. package/dist/agentSession/backgroundView.js +4 -15
  31. package/dist/agentsWireCaps.d.ts +10 -6
  32. package/dist/agentsWireCaps.js +21 -7
  33. package/dist/argvFlagValue.d.ts +41 -0
  34. package/dist/argvFlagValue.js +69 -0
  35. package/dist/attachmentsWireCaps.d.ts +3 -2
  36. package/dist/attachmentsWireCaps.js +5 -3
  37. package/dist/classifierVerdictWire.d.ts +8 -0
  38. package/dist/classifierVerdictWire.js +8 -0
  39. package/dist/cloudConfigWireCaps.d.ts +28 -1
  40. package/dist/cloudConfigWireCaps.js +51 -11
  41. package/dist/controlRouter.d.ts +14 -8
  42. package/dist/controlRouter.js +15 -21
  43. package/dist/detachWire.d.ts +15 -5
  44. package/dist/detachWire.js +17 -7
  45. package/dist/effortWire.d.ts +0 -21
  46. package/dist/effortWire.js +6 -20
  47. package/dist/engineInlineTaskStats.d.ts +12 -6
  48. package/dist/engineWireSdk.d.ts +12 -0
  49. package/dist/env/localeGeo.js +2 -1
  50. package/dist/envFlag.d.ts +39 -0
  51. package/dist/envFlag.js +51 -0
  52. package/dist/finalVerifyWire.d.ts +7 -5
  53. package/dist/finalVerifyWire.js +6 -5
  54. package/dist/fleet/fleetLedger.d.ts +32 -9
  55. package/dist/fleet/fleetLedger.js +119 -34
  56. package/dist/fleet/fleetProjection.d.ts +44 -6
  57. package/dist/fleet/fleetProjection.js +52 -10
  58. package/dist/fleetAgentPanelProjection.d.ts +18 -1
  59. package/dist/fleetAgentPanelProjection.js +61 -14
  60. package/dist/forkWireCaps.d.ts +2 -1
  61. package/dist/forkWireCaps.js +5 -11
  62. package/dist/goalStopHook.d.ts +142 -0
  63. package/dist/goalStopHook.js +258 -0
  64. package/dist/headlessPermissionModeWire.d.ts +10 -0
  65. package/dist/headlessPermissionModeWire.js +24 -21
  66. package/dist/headlessReconnectWire.d.ts +7 -1
  67. package/dist/headlessReconnectWire.js +20 -2
  68. package/dist/hitl/approvalsFeed.js +31 -8
  69. package/dist/hitl/askGateWire.d.ts +27 -96
  70. package/dist/hitl/askGateWire.js +69 -546
  71. package/dist/hitl/frameRouter.d.ts +86 -0
  72. package/dist/hitl/frameRouter.js +342 -0
  73. package/dist/hitl/gateLedger.d.ts +107 -0
  74. package/dist/hitl/gateLedger.js +113 -0
  75. package/dist/hitl/hitlBridge.d.ts +75 -15
  76. package/dist/hitl/hitlBridge.js +94 -22
  77. package/dist/hitl/hitlHostSurface.d.ts +49 -0
  78. package/dist/hitl/hitlHostSurface.js +155 -0
  79. package/dist/hitl/parkResolver.d.ts +74 -0
  80. package/dist/hitl/parkResolver.js +241 -0
  81. package/dist/hitl/planReviewWire.d.ts +60 -2
  82. package/dist/hitl/planReviewWire.js +152 -74
  83. package/dist/hitl/toolApprovalWire.d.ts +67 -4
  84. package/dist/hitl/toolApprovalWire.js +119 -31
  85. package/dist/hooksWireCaps.d.ts +1 -82
  86. package/dist/hooksWireCaps.js +34 -235
  87. package/dist/host.d.ts +16 -5
  88. package/dist/index.d.ts +2 -0
  89. package/dist/index.js +15 -1
  90. package/dist/interactiveToolsWire.d.ts +15 -4
  91. package/dist/interactiveToolsWire.js +24 -26
  92. package/dist/limitsWire.js +7 -40
  93. package/dist/liveInitToolFace.d.ts +51 -6
  94. package/dist/liveQuestionStore.d.ts +5 -6
  95. package/dist/model/providerPresets.js +11 -1
  96. package/dist/notifications.d.ts +48 -2
  97. package/dist/notifications.js +223 -46
  98. package/dist/retainBackgroundWireCaps.d.ts +3 -2
  99. package/dist/retainBackgroundWireCaps.js +5 -9
  100. package/dist/sandboxWire.d.ts +9 -31
  101. package/dist/sandboxWire.js +51 -50
  102. package/dist/scenarioWire.d.ts +1 -1
  103. package/dist/scenarioWire.js +25 -36
  104. package/dist/seam.d.ts +23 -6
  105. package/dist/seam.js +40 -30
  106. package/dist/seatContract.d.ts +369 -83
  107. package/dist/seatContract.js +585 -198
  108. package/dist/selfOrchestrationWireCaps.d.ts +6 -5
  109. package/dist/selfOrchestrationWireCaps.js +8 -12
  110. package/dist/sessionSlot.d.ts +8 -0
  111. package/dist/sessionSlot.js +1 -0
  112. package/dist/steering.js +2 -2
  113. package/dist/subagent/engineTaskHandleWire.d.ts +3 -0
  114. package/dist/subagent/engineTaskHandleWire.js +17 -2
  115. package/dist/subagentContentStore.d.ts +5 -5
  116. package/dist/toolResult.d.ts +89 -8
  117. package/dist/toolResult.js +99 -30
  118. package/dist/typePins.d.ts +17 -0
  119. package/dist/typePins.js +1 -0
  120. package/dist/ultracodeWireCaps.js +5 -6
  121. package/dist/unrefTimer.d.ts +19 -0
  122. package/dist/unrefTimer.js +5 -0
  123. package/dist/workflow.d.ts +3 -2
  124. package/dist/workflow.js +3 -2
  125. package/dist/workflowClient.d.ts +7 -0
  126. package/dist/workflowClient.js +47 -12
  127. package/package.json +3 -3
@@ -7,26 +7,41 @@ const asStr = (v) => (typeof v === 'string' && v.length > 0 ? v : undefined);
7
7
  * Project the registry's mcp domain (`config.mcp.servers`, registry-core McpServerSpec — the CENTER shape
8
8
  * with envRefs/headerRefs) → the SDK/core wire `McpServerSpec[]` (real env/header VALUES, resolved from the
9
9
  * shell env). Disabled servers are skipped; unknown transport kinds are skipped (never mis-mapped); the
10
- * result is capped at MCP_CAPS.items exactly like the local `.mcp.json` projection.
10
+ * result is capped at MCP_CAPS.items exactly like the local `.mcp.json` projection. Every skip is recorded
11
+ * in `droppedServers` (REF-CC-157) — nothing vanishes from the wire silently.
11
12
  */
12
13
  export function cloudMcpToSpecs(servers, env) {
13
14
  const specs = [];
14
15
  const missingEnv = [];
16
+ const droppedServers = [];
17
+ const drop = (name, reason) => {
18
+ droppedServers.push({ ...(name ? { name } : {}), reason });
19
+ };
15
20
  for (const raw of asArr(servers)) {
16
- if (specs.length >= MCP_CAPS.items)
17
- break;
18
21
  const s = asRec(raw);
19
- if (!s || s.enabled === false)
22
+ const name = s ? asStr(s.name) : undefined;
23
+ // cap first: everything past MCP_CAPS.items is dropped regardless of what it looks like — this is
24
+ // the reason it never got evaluated, so report it as such (not as a false 'missing-field').
25
+ if (specs.length >= MCP_CAPS.items) {
26
+ drop(name, 'capped');
20
27
  continue;
21
- const name = asStr(s.name);
28
+ }
29
+ if (!s || s.enabled === false) {
30
+ drop(name, s?.enabled === false ? 'disabled' : 'missing-field');
31
+ continue;
32
+ }
22
33
  const t = asRec(s.transport);
23
- if (!name || !t)
34
+ if (!name || !t) {
35
+ drop(name, 'missing-field');
24
36
  continue;
37
+ }
25
38
  let transport = null;
26
39
  if (t.kind === 'stdio') {
27
40
  const command = asStr(t.command);
28
- if (!command)
41
+ if (!command) {
42
+ drop(name, 'missing-field');
29
43
  continue;
44
+ }
30
45
  // envRefs: target ENV key → ENV NAME holding the value (center never stores the secret). Resolve
31
46
  // against the shell env — the engine child inherits it, so stdio spawn sees the same values.
32
47
  const envOut = {};
@@ -50,8 +65,10 @@ export function cloudMcpToSpecs(servers, env) {
50
65
  }
51
66
  else if (t.kind === 'http') {
52
67
  const url = asStr(t.url);
53
- if (!url)
68
+ if (!url) {
69
+ drop(name, 'missing-field');
54
70
  continue;
71
+ }
55
72
  const headers = {};
56
73
  for (const [headerName, ref] of Object.entries(asRec(t.headerRefs) ?? {})) {
57
74
  const refName = asStr(ref);
@@ -72,7 +89,8 @@ export function cloudMcpToSpecs(servers, env) {
72
89
  };
73
90
  }
74
91
  else {
75
- continue; // unknown transport kind — skip, never mis-map (same posture as the local sse/ws skip)
92
+ drop(name, 'unknown-transport'); // unknown transport kind — skip, never mis-map (same posture as the local sse/ws skip)
93
+ continue;
76
94
  }
77
95
  const allowTools = asArr(s.allowTools).filter((a) => typeof a === 'string');
78
96
  specs.push({
@@ -82,7 +100,7 @@ export function cloudMcpToSpecs(servers, env) {
82
100
  ...(s.elicitation === true ? { elicitation: true } : {}),
83
101
  });
84
102
  }
85
- return { specs, missingEnv: [...new Set(missingEnv)] };
103
+ return { specs, missingEnv: [...new Set(missingEnv)], droppedServers };
86
104
  }
87
105
  // ── skills: config.skills.skills (MANIFEST) + bodies → SkillSpec[] ───────────────────────────────────────
88
106
  /**
@@ -137,6 +155,26 @@ export function cloudModelsToDoc(models) {
137
155
  ...(Array.isArray(m.atModelAllowlist) ? { atModelAllowlist: m.atModelAllowlist } : {}),
138
156
  };
139
157
  }
158
+ /**
159
+ * REF-CC-157(midband-05,fix-outright):`config.models.models` 里被 `cloudModelsToDoc` 过滤掉的原始
160
+ * 条目(缺 `name` 或 `id`)——仿 `skillsMissingBody` 的形状,重扫同一份原始数组产出丢弃清单,让调用方
161
+ * 能警示用户「你的 model 配置有 N 行没生效」,而不是让一个打错字的字段静默从目录消失。
162
+ */
163
+ export function cloudModelsDropped(models) {
164
+ const m = asRec(models);
165
+ if (!m || !Array.isArray(m.models))
166
+ return [];
167
+ const out = [];
168
+ for (const e of m.models) {
169
+ const r = asRec(e);
170
+ if (r && asStr(r.name) && asStr(r.id))
171
+ continue; // kept by cloudModelsToDoc — not dropped
172
+ const id = r ? asStr(r.id) : undefined;
173
+ const name = r ? asStr(r.name) : undefined;
174
+ out.push({ ...(id ? { id } : {}), ...(name ? { name } : {}) });
175
+ }
176
+ return out;
177
+ }
140
178
  /** ENABLED model names in a models doc (banner/status display + "did the cloud catalog apply" checks). */
141
179
  export function cloudModelNames(doc) {
142
180
  if (!doc)
@@ -200,7 +238,7 @@ export function projectEffectiveBody(body, skillBodies, env, rostersValue) {
200
238
  const config = asRec(b.config) ?? {};
201
239
  const mcpDomain = asRec(config.mcp);
202
240
  const skillsDomain = asRec(config.skills);
203
- const { specs: mcp, missingEnv: mcpMissing } = cloudMcpToSpecs(mcpDomain?.servers, env);
241
+ const { specs: mcp, missingEnv: mcpMissing, droppedServers: droppedMcpServers } = cloudMcpToSpecs(mcpDomain?.servers, env);
204
242
  const manifest = asArr(skillsDomain?.skills);
205
243
  const skills = cloudSkillsToSpecs(manifest, (hash) => skillBodies[hash] ?? null);
206
244
  const skillsMissingBody = manifest
@@ -223,6 +261,8 @@ export function projectEffectiveBody(body, skillBodies, env, rostersValue) {
223
261
  missingEnv: [...new Set([...mcpMissing, ...cloudModelMissingKeyEnv(modelsDoc, env)])],
224
262
  scope: asStr(b.scope) ?? 'global',
225
263
  skillsMissingBody,
264
+ droppedMcpServers,
265
+ droppedModels: cloudModelsDropped(config.models),
226
266
  execution: cloudExecutionToPolicy(config.execution),
227
267
  };
228
268
  }
@@ -196,11 +196,17 @@ export declare class ControlRouter {
196
196
  }): Promise<RunReceipt | null>;
197
197
  private nextId;
198
198
  }
199
- /** Read a normalized error code/status off any SDK error shape variant (errorCode / code / name / status).
200
- * design/161 A 起导出:错误判型单源 —— 包内新读面(backgroundView 等)一律吃这一口,不再各自手摸
201
- * `(e as any).status`。 */
202
- export declare function errCodes(e: unknown): {
203
- code?: unknown;
204
- name?: unknown;
205
- status?: unknown;
206
- };
199
+ /** design/161 A 起导出:错误判型单源 —— 包内新读面(backgroundView )一律吃这一口,不再各自
200
+ * 手摸 `(e as any).status`。 */
201
+ export interface ErrCodes {
202
+ code?: string | undefined;
203
+ name?: string | undefined;
204
+ status?: number | undefined;
205
+ }
206
+ /**
207
+ * Read a normalized error code/status off any SDK error shape variant (errorCode / code / name / status).
208
+ * REF-CC-135(TYPESHAPE-15,P3 wave1 卡C-argv):判型单源此前返回 3 个裸 unknown(B2/B4 违规——调用方
209
+ * `code === 404`(status 才是数字位)这类拼写型 bug 能编译期全绿恒 false)。窄化成具名 `ErrCodes`,
210
+ * 非匹配类型一律降到 undefined(而不是把垃圾值原样放行)。
211
+ */
212
+ export declare function errCodes(e: unknown): ErrCodes;
@@ -1,3 +1,4 @@
1
+ import { uuid } from './adapter/types.js';
1
2
  // ── Errors the router surfaces (fail-closed; never auto-retried) ───────────────
2
3
  //
3
4
  // Mirrors hitlBridge.HitlSafetyError: every safety-relevant control error is a signal to re-present to the
@@ -60,7 +61,7 @@ export class ControlRouter {
60
61
  */
61
62
  async submit(text, opts) {
62
63
  const req = this.buildTaskRequest(text);
63
- const receipt = await this.client.runs.create(req, opts ? { signal: opts.signal } : undefined);
64
+ const receipt = await this.client.runs.create(req, opts?.signal !== undefined ? { signal: opts.signal } : undefined);
64
65
  // Adopt service-minted continuity + latch the in-flight run.
65
66
  if (this.sessionId === undefined)
66
67
  this.sessionId = receipt.sessionId;
@@ -109,7 +110,7 @@ export class ControlRouter {
109
110
  // Raw text, no pre-sanitize (the server fences). `mode` omitted → server default one-at-a-time.
110
111
  const body = mode !== undefined ? { text, mode } : { text };
111
112
  try {
112
- return await this.client.runs.steer(this.taskId, body, opts ? { signal: opts.signal } : undefined);
113
+ return await this.client.runs.steer(this.taskId, body, opts?.signal !== undefined ? { signal: opts.signal } : undefined);
113
114
  }
114
115
  catch (e) {
115
116
  if (isSteeringNotRunning(e)) {
@@ -147,7 +148,7 @@ export class ControlRouter {
147
148
  throw new ControlSafetyError('no run in flight to cancel', 'not_found');
148
149
  }
149
150
  try {
150
- return await this.client.runs.cancel(this.taskId, opts ? { signal: opts.signal } : undefined);
151
+ return await this.client.runs.cancel(this.taskId, opts?.signal !== undefined ? { signal: opts.signal } : undefined);
151
152
  }
152
153
  catch (e) {
153
154
  if (isCancelSuspendedConflict(e)) {
@@ -206,29 +207,22 @@ export class ControlRouter {
206
207
  return `q-${this.seq}-${uuid()}`;
207
208
  }
208
209
  }
209
- // ── helpers ───────────────────────────────────────────────────────────────────
210
- /** A v4-shaped uuid for the local queue handles (never on the wire). Mirrors types.ts/seamQuery's helper. */
211
- function uuid() {
212
- const g = typeof globalThis !== 'undefined'
213
- ? globalThis.crypto
214
- : undefined;
215
- if (g && typeof g.randomUUID === 'function')
216
- return g.randomUUID();
217
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
218
- const r = (Math.random() * 16) | 0;
219
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
220
- return v.toString(16);
221
- });
222
- }
223
- /** Read a normalized error code/status off any SDK error shape variant (errorCode / code / name / status).
224
- * design/161 批 A 起导出:错误判型单源 —— 包内新读面(backgroundView 等)一律吃这一口,不再各自手摸
225
- * `(e as any).status`。 */
210
+ /**
211
+ * Read a normalized error code/status off any SDK error shape variant (errorCode / code / name / status).
212
+ * REF-CC-135(TYPESHAPE-15,P3 wave1 卡C-argv):判型单源此前返回 3 个裸 unknown(B2/B4 违规——调用方
213
+ * `code === 404`(status 才是数字位)这类拼写型 bug 能编译期全绿恒 false)。窄化成具名 `ErrCodes`,
214
+ * 非匹配类型一律降到 undefined(而不是把垃圾值原样放行)。
215
+ */
226
216
  export function errCodes(e) {
227
217
  if (!e || typeof e !== 'object')
228
218
  return {};
229
219
  const o = e;
230
220
  // [1912] errorCode 恒在(server ≥1.302 canonical)——`?? o.code` 老兜底已清;status 双键保留(HTTP 层形非 wire 键)。
231
- return { code: o.errorCode, name: o.name, status: o.status ?? o.statusCode };
221
+ const code = typeof o.errorCode === 'string' ? o.errorCode : undefined;
222
+ const name = typeof o.name === 'string' ? o.name : undefined;
223
+ const rawStatus = o.status ?? o.statusCode;
224
+ const status = typeof rawStatus === 'number' ? rawStatus : undefined;
225
+ return { code, name, status };
232
226
  }
233
227
  /** 409 `steering.not_running` / `SteeringNotRunningError` — the run is suspended/terminal (runs.ts:45). */
234
228
  function isSteeringNotRunning(e) {
@@ -46,8 +46,9 @@
46
46
  * ACCEPTED uncancellable window — detach's inherent cost, no waiting introduced (design §2.2 记账).
47
47
  *
48
48
  * PRECEDENCE (off wins; otherwise DEFAULT ON):
49
- * a) `SEMA_HEADLESS_DETACH` env = 'false'/'0' (settings env lane, fail-soft any other value
50
- * including typos stays default-on, finalVerifyWire discipline) → off.
49
+ * a) `SEMA_HEADLESS_DETACH` env = an {@link envFlagOff} spelling (REF-CC-141 dup-02 单源;settings
50
+ * env lane, fail-soft — any OTHER value including typos stays default-on, finalVerifyWire
51
+ * discipline) → off.
51
52
  * b) VERSION GATE: SEMA_ENGINE_URL in play (user-supplied external engine) and `/health` CONFIRMS
52
53
  * version <1.196.0 (or no version field = pre-1.96) → off + SEMA_DEBUG log.
53
54
  * Unlike limitsWire (warn-and-send: an old server drops an unknown BODY field server-side), here
@@ -71,9 +72,10 @@ export declare const DETACH_WIRE_MIN_ENGINE: readonly [number, number, number];
71
72
  /** True when the engine version string satisfies the detach-wire minimum (≥1.196.0). Unparsable ⇒ false. */
72
73
  export declare const versionSupportsDetach: (v: string | undefined) => boolean;
73
74
  /**
74
- * The settings-lane off-switch: `SEMA_HEADLESS_DETACH` = 'false' or '0' (case-insensitive) disables
75
- * the header. Any other value (empty/'true'/typo) = default ON — fail-soft, a settings typo never
76
- * flips the lane silently to the strict-alignment (disconnect-kills-run) mode (finalVerifyWire 纪律).
75
+ * The settings-lane off-switch: `SEMA_HEADLESS_DETACH` set to an {@link envFlagOff} spelling
76
+ * (REF-CC-141 dup-02 单源:`0`/`false`/`no`/`off`/`none`,大小写不敏感)disables the header. Any other
77
+ * value (empty/'true'/typo) = default ON fail-soft, a settings typo never flips the lane silently
78
+ * to the strict-alignment (disconnect-kills-run) mode (finalVerifyWire 纪律).
77
79
  */
78
80
  export declare function headlessDetachDisabledByEnv(env?: EnvLike): boolean;
79
81
  /**
@@ -105,6 +107,14 @@ export declare function resolveHeadlessDetach(baseUrl: string, env?: EnvLike, op
105
107
  fetchImpl?: typeof fetch;
106
108
  timeoutMs?: number;
107
109
  }): Promise<DetachResolution>;
110
+ /**
111
+ * 🟢 B8 ALLOW 清单登记(REF-CC-域词表-05,属主=client-core 规范重构轮,到期复议=server 契约文案
112
+ * 变更时或 2026-Q4 复审以先到者为准):文本匹配 + 非文本结构性合取前置(`isDetachArmed()` +
113
+ * `status===400`),不是散点裸字面量。同批同表的另两处:`classifierVerdictWire.ts`
114
+ * `CLASSIFIER_DENY_SIGNATURE`(core lockstep 机器签名)、`subagent/engineTaskHandleWire.ts`
115
+ * `STOP_CONFLICT_CODES`(SDK typed 缺席时的 code-in-text 回落)——三处互指,理由逐条写在各自
116
+ * 站点,不共享一份文案(判错方向、前置合取各不相同)。
117
+ */
108
118
  /** server 400 body 判别锚(server.ts sendJson 原文:"x-detach-on-disconnect requires a durable run
109
119
  * (a run store + sessionId) — without one the detached result would be unqueryable")。 */
110
120
  export declare const DETACH_DURABLE_OFF_400_ANCHOR = "x-detach-on-disconnect requires a durable run";
@@ -46,8 +46,9 @@
46
46
  * ACCEPTED uncancellable window — detach's inherent cost, no waiting introduced (design §2.2 记账).
47
47
  *
48
48
  * PRECEDENCE (off wins; otherwise DEFAULT ON):
49
- * a) `SEMA_HEADLESS_DETACH` env = 'false'/'0' (settings env lane, fail-soft any other value
50
- * including typos stays default-on, finalVerifyWire discipline) → off.
49
+ * a) `SEMA_HEADLESS_DETACH` env = an {@link envFlagOff} spelling (REF-CC-141 dup-02 单源;settings
50
+ * env lane, fail-soft — any OTHER value including typos stays default-on, finalVerifyWire
51
+ * discipline) → off.
51
52
  * b) VERSION GATE: SEMA_ENGINE_URL in play (user-supplied external engine) and `/health` CONFIRMS
52
53
  * version <1.196.0 (or no version field = pre-1.96) → off + SEMA_DEBUG log.
53
54
  * Unlike limitsWire (warn-and-send: an old server drops an unknown BODY field server-side), here
@@ -58,6 +59,7 @@
58
59
  * c) self-spawn lane (no SEMA_ENGINE_URL): release-pinned ≥1.197.0 — trusted, no probe, header on.
59
60
  */
60
61
  import { hostEnv } from './hostEnv.js';
62
+ import { envFlagOff } from './envFlag.js';
61
63
  import { versionSupportsLimits } from './limitsWire.js';
62
64
  /** The server ≥1.196 opt-in header: disconnect ⇒ detach (run continues to durable terminal). */
63
65
  export const DETACH_HEADER = 'x-detach-on-disconnect';
@@ -72,13 +74,13 @@ export const DETACH_WIRE_MIN_ENGINE = [1, 196, 0];
72
74
  /** True when the engine version string satisfies the detach-wire minimum (≥1.196.0). Unparsable ⇒ false. */
73
75
  export const versionSupportsDetach = versionSupportsLimits;
74
76
  /**
75
- * The settings-lane off-switch: `SEMA_HEADLESS_DETACH` = 'false' or '0' (case-insensitive) disables
76
- * the header. Any other value (empty/'true'/typo) = default ON — fail-soft, a settings typo never
77
- * flips the lane silently to the strict-alignment (disconnect-kills-run) mode (finalVerifyWire 纪律).
77
+ * The settings-lane off-switch: `SEMA_HEADLESS_DETACH` set to an {@link envFlagOff} spelling
78
+ * (REF-CC-141 dup-02 单源:`0`/`false`/`no`/`off`/`none`,大小写不敏感)disables the header. Any other
79
+ * value (empty/'true'/typo) = default ON fail-soft, a settings typo never flips the lane silently
80
+ * to the strict-alignment (disconnect-kills-run) mode (finalVerifyWire 纪律).
78
81
  */
79
82
  export function headlessDetachDisabledByEnv(env = hostEnv()) {
80
- const v = env[HEADLESS_DETACH_ENV]?.trim().toLowerCase();
81
- return v === 'false' || v === '0';
83
+ return envFlagOff(env[HEADLESS_DETACH_ENV]);
82
84
  }
83
85
  /**
84
86
  * Version-gate probe: GET `${baseUrl}/health`, read `version`. Returns FALSE only when the engine is
@@ -135,6 +137,14 @@ export async function resolveHeadlessDetach(baseUrl, env = hostEnv(), opts) {
135
137
  // 也无处可查)。壳自 spawn 引擎恒 DB_BACKEND=local 永不命中;命中面 = SEMA_ENGINE_URL 外接引擎显式关
136
138
  // durable。裸 `sema: API Error: 400 · x-detach-on-disconnect requires a durable run …` 是 server 视角
137
139
  // 的契约话,用户不知道下一步 —— 追加一行人话(supplement, never mask:API Error 行字面保留)。
140
+ /**
141
+ * 🟢 B8 ALLOW 清单登记(REF-CC-域词表-05,属主=client-core 规范重构轮,到期复议=server 契约文案
142
+ * 变更时或 2026-Q4 复审以先到者为准):文本匹配 + 非文本结构性合取前置(`isDetachArmed()` +
143
+ * `status===400`),不是散点裸字面量。同批同表的另两处:`classifierVerdictWire.ts`
144
+ * `CLASSIFIER_DENY_SIGNATURE`(core lockstep 机器签名)、`subagent/engineTaskHandleWire.ts`
145
+ * `STOP_CONFLICT_CODES`(SDK typed 缺席时的 code-in-text 回落)——三处互指,理由逐条写在各自
146
+ * 站点,不共享一份文案(判错方向、前置合取各不相同)。
147
+ */
138
148
  /** server 400 body 判别锚(server.ts sendJson 原文:"x-detach-on-disconnect requires a durable run
139
149
  * (a run store + sessionId) — without one the detached result would be unqueryable")。 */
140
150
  export const DETACH_DURABLE_OFF_400_ANCHOR = 'x-detach-on-disconnect requires a durable run';
@@ -1,24 +1,3 @@
1
- /**
2
- * ⇄ B2 批搬迁(2026-07-27,多端改造设计稿 §3 B2):cli src/sema/effortWire.ts 逐字搬入(零依赖)。
3
- */
4
- /**
5
- * src/sema/effortWire.ts — the EFFORT/REASONING-STRENGTH ENGINE WIRE (per-turn `/effort` → engine).
6
- *
7
- * WHY THIS FILE EXISTS (the parity gap it closes):
8
- * CC's `/effort` slider sets a per-turn reasoning strength; sema's picker writes it to `AppState.effortValue`
9
- * (overrides/effort.tsx:302) but the seam's `toTaskRequest` never put it on the wire, so the picked effort
10
- * reached the UI yet NOT the engine. Grounding (2026-06-29) showed the seam is ALREADY complete downstream:
11
- * - core `TaskSpec.thinking: ThinkingLevel` = off|minimal|low|medium|high|xhigh|max (7 档,covers CC effort)
12
- * — `core/src/core/types.ts:466`; consumed at `prepare-task.ts:1663` (→brain) / :1471 (xhigh/max→awareness).
13
- * - service `reasoningEffort` → `thinking`, UNKNOWN value 400 fail-loud — `server.ts:234/3576`, `main.ts:1270`.
14
- * So this is a SHELL-only wire: map the picked `EffortValue` → the wire `reasoningEffort` (a core ThinkingLevel)
15
- * and stamp it on `TaskRequest`. Pure projection here (testable); the AppState read is inline in seamQuery.
16
- *
17
- * MAPPING: sema's picker can yield low|medium|high|max (base EffortLevel) + xhigh|ultracode|auto (187 override
18
- * accept-set) + numeric. The wire `reasoningEffort` accepts a core ThinkingLevel only. So pass a value IFF it
19
- * is a ThinkingLevel (off|minimal|low|medium|high|xhigh|max); `auto` (let engine default), `ultracode` (a
20
- * separate mode, future slice), numeric and unset all OMIT — the engine then uses its own default.
21
- */
22
1
  /**
23
2
  * The core ThinkingLevel = the service `reasoningEffort` accept-set (`server.ts:3576`) = the SDK
24
3
  * `TaskRequest.reasoningEffort` union. Declared as a literal union (not bare `string`) so the projection's
@@ -1,25 +1,11 @@
1
+ const REASONING_EFFORT_TUPLE = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
1
2
  /**
2
- * B2 批搬迁(2026-07-27,多端改造设计稿 §3 B2):cli src/sema/effortWire.ts 逐字搬入(零依赖)
3
- */
4
- /**
5
- * src/sema/effortWire.ts the EFFORT/REASONING-STRENGTH ENGINE WIRE (per-turn `/effort` → engine).
6
- *
7
- * WHY THIS FILE EXISTS (the parity gap it closes):
8
- * CC's `/effort` slider sets a per-turn reasoning strength; sema's picker writes it to `AppState.effortValue`
9
- * (overrides/effort.tsx:302) but the seam's `toTaskRequest` never put it on the wire, so the picked effort
10
- * reached the UI yet NOT the engine. Grounding (2026-06-29) showed the seam is ALREADY complete downstream:
11
- * - core `TaskSpec.thinking: ThinkingLevel` = off|minimal|low|medium|high|xhigh|max (7 档,covers CC effort)
12
- * — `core/src/core/types.ts:466`; consumed at `prepare-task.ts:1663` (→brain) / :1471 (xhigh/max→awareness).
13
- * - service `reasoningEffort` → `thinking`, UNKNOWN value 400 fail-loud — `server.ts:234/3576`, `main.ts:1270`.
14
- * So this is a SHELL-only wire: map the picked `EffortValue` → the wire `reasoningEffort` (a core ThinkingLevel)
15
- * and stamp it on `TaskRequest`. Pure projection here (testable); the AppState read is inline in seamQuery.
16
- *
17
- * MAPPING: sema's picker can yield low|medium|high|max (base EffortLevel) + xhigh|ultracode|auto (187 override
18
- * accept-set) + numeric. The wire `reasoningEffort` accepts a core ThinkingLevel only. So pass a value IFF it
19
- * is a ThinkingLevel (off|minimal|low|medium|high|xhigh|max); `auto` (let engine default), `ultracode` (a
20
- * separate mode, future slice), numeric and unset all OMIT — the engine then uses its own default.
3
+ * 🔴 [2006] B-CC-2(2026-07-29):`new Set<ReasoningEffort>([…])` 只保证「元素合法」,**从不保证
4
+ * 穷举** —— 往 `ReasoningEffort` 加第八档而这里不跟,编译期一声不吭,运行期那一档被
5
+ * `effortToReasoningEffort` 当成非法值 OMIT 掉 ⇒ 用户拨到的档位静默到不了引擎。补的就是
6
+ * 「类型 常量」那半边:两个方向的 `Exclude` 都必须塌成 `never`。
7
+ * `Covers`(REF-CC-145/dup-12,P3 wave1 卡C-argv)改吃共用叶 typePins.ts——四份逐字重复钉之一。
21
8
  */
22
- const REASONING_EFFORT_TUPLE = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
23
9
  const _effortPins = [true, true];
24
10
  void _effortPins;
25
11
  const REASONING_EFFORTS = new Set(REASONING_EFFORT_TUPLE);
@@ -22,6 +22,17 @@
22
22
  * ticks by itself. Local (non-engine) AgentTool runs never write here, so the local path is untouched
23
23
  * (fallback only consulted when the card id has an entry).
24
24
  */
25
+ /**
26
+ * TYPESHAPE-13:inline task tick 的形状(生产侧 `seam.ts` ChromeEvent 的 `inline_task_stats`/
27
+ * `op:'tick'` 臂内嵌套 `stats` 字段 ↔ 本文件消费侧 `publishEngineInlineTaskTick` 的 `tick` 形参)
28
+ * 跨模块内联写了两份逐字相同的形状,抽成具名类型两处共用——漏改一处(比如只给 seam.ts 加
29
+ * `currentToolName?`)时,两侧编译仍全绿但字段会被静默丢弃,B4 命名 DU 豁免只护直接成员形状,
30
+ * 不护这种嵌套字段。
31
+ *
32
+ * 定义在 `seam.ts`(本包基础词表模块)、这里反向 import——本文件是零依赖的 dep-free store,
33
+ * seam.ts 反过来依赖它会把层级倒过来。
34
+ */
35
+ import type { InlineTaskTick } from './seam.js';
25
36
  export interface EngineInlineTaskStats {
26
37
  /** Cumulative tool uses off the latest tick (frozen at settle). */
27
38
  toolUses: number;
@@ -38,12 +49,7 @@ export interface EngineInlineTaskStats {
38
49
  isError: boolean;
39
50
  }
40
51
  /** Bridge write side — latest cumulative tick for the card. No-op once the card settled (frozen finals). */
41
- export declare function publishEngineInlineTaskTick(cardId: string, tick: {
42
- toolUses: number;
43
- totalTokens: number;
44
- durationMs: number;
45
- currentAction?: string;
46
- }): void;
52
+ export declare function publishEngineInlineTaskTick(cardId: string, tick: InlineTaskTick): void;
47
53
  /** Bridge write side — the delegating card closed: freeze the finals. Idempotent. */
48
54
  export declare function settleEngineInlineTaskStats(cardId: string, isError: boolean): void;
49
55
  /** Turn-end / abort sweep twin — settle every card still live (a subagent never emits a terminal tick). */
@@ -53,6 +53,18 @@ export declare function resolveWireAuth(baseUrl: string | undefined, token?: str
53
53
  * 同一函数的两个名字不是两套行为)。新代码一律用 `resolveWireAuth`。
54
54
  */
55
55
  export declare function wireAuthTokenFor(baseUrl: string, token?: string): WireAuthToken;
56
+ /**
57
+ * TYPESHAPE-12:「向引擎发一次探测请求需要哪些参数」的共用形状(agentsWireCaps.
58
+ * engineSupportsTaskAgents ↔ liveInitToolFace.probeScenarioTools 此前各写一份逐字相同的 4 成员
59
+ * 内联形)。🔴 只统一形状,不统一值:两处 `timeoutMs` 缺省值不同(3000 vs 2000)——这是既有行为
60
+ * 差异,各自在调用点保留现值,改值属于行为变更,不在本次收编授权内。
61
+ */
62
+ export type EngineProbeOpts = {
63
+ fetchImpl?: typeof fetch;
64
+ timeoutMs?: number;
65
+ authToken?: string | undefined;
66
+ principal?: string | undefined;
67
+ };
56
68
  export type EngineWireClientConfig = {
57
69
  baseUrl: string;
58
70
  /** 真 token 串;缺省经 wireAuthTokenFor 三态解析(loopback-unauthed / 'anon')。 */
@@ -73,7 +73,8 @@ const echo = (v) => (v.length > ECHO_MAX ? `${v.slice(0, ECHO_MAX)}…` : v);
73
73
  function localeTag(input) {
74
74
  const pick = (v) => (typeof v === 'string' ? v.trim() : '');
75
75
  const raw = pick(input.lcAll) || pick(input.lang);
76
- const tag = raw.split('.')[0].split('@')[0].trim();
76
+ const rawNoCodeset = raw.split('.')[0] ?? '';
77
+ const tag = (rawNoCodeset.split('@')[0] ?? '').trim();
77
78
  if (!tag || NEUTRAL_LOCALES.has(tag.toLowerCase()))
78
79
  return '';
79
80
  return LANGUAGE_TAG_SHAPE.test(tag) ? tag : '';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * envFlag.ts — single spelling-set source for boolean-shaped `SEMA_*` env knobs (REF-CC-141, P2
3
+ * 台账 dup-02,wave1 卡B,2026-08-01).
4
+ *
5
+ * WHY: ten call sites across nine `*Wire*`/`*WireCaps*` modules each hand-rolled their own
6
+ * off/on-spelling check for a `SEMA_*` boolean env knob, and the four variants DISAGREED:
7
+ * ① 4-word off-set `'0'|'false'|'no'|'off'` (6 copies: forkWireCaps.ts isFalsy,
8
+ * selfOrchestrationWireCaps.ts isFalsy, retainBackgroundWireCaps.ts isFalsy,
9
+ * ultracodeWireCaps.ts deferOptedOut + lowFreqOptedOut, hooksWireCaps.ts isGoalStopHookWireArmed);
10
+ * ② 2-word off-set `'false'|'0'` (2 copies: detachWire.ts headlessDetachDisabledByEnv,
11
+ * finalVerifyWire.ts headlessFinalVerifyDisabledByEnv) — `=off` / `=no` silently did nothing;
12
+ * ③ 4-word off-set but a DIFFERENT fourth word `'off'|'0'|'false'|'none'`
13
+ * (attachmentsWireCaps.ts attachmentsForRequest — accepted "none", not "no");
14
+ * ④ 2-word set WITHOUT lowercasing `raw==='true'||raw==='1'` / `raw==='false'||raw==='0'`
15
+ * (interactiveToolsWire.ts resolveHeadlessInteractiveTools — `SEMA_HEADLESS_INTERACTIVE_TOOLS=False`
16
+ * fell through to the "not a boolean" warn-and-default path instead of being recognized).
17
+ *
18
+ * User-visible consequence (dup-02 evidence): the SAME settings.json could have
19
+ * `SEMA_HEADLESS_DETACH=off` silently ignored (falls back to default-ON) sitting right next to
20
+ * `SEMA_RETAIN_BACKGROUND=off` which worked — no way for a user to know which spelling their knob
21
+ * actually honors.
22
+ *
23
+ * FIX: one leaf module, one spelling set PER POLARITY, each the UNION across all four prior
24
+ * variants (only-tightens-never-loosens direction inverted here on purpose — widen everywhere,
25
+ * never narrow a caller's existing accepted set). Zero dependencies (no `hostEnv`/`EnvLike` import —
26
+ * operates on the already-extracted string, so it stays trivially portable and unit-testable).
27
+ */
28
+ /**
29
+ * True iff `v` is an explicit OFF spelling ({@link OFF_SPELLINGS}: `0`/`false`/`no`/`off`/`none`,
30
+ * case-insensitive, trimmed). Unset/empty/unrecognized ⇒ `false` — the caller decides what "not
31
+ * explicitly off" defaults to (many of these knobs are default-ON escape hatches).
32
+ */
33
+ export declare function envFlagOff(v: string | undefined): boolean;
34
+ /**
35
+ * True iff `v` is an explicit ON spelling ({@link ON_SPELLINGS}: `1`/`true`/`yes`/`on`,
36
+ * case-insensitive, trimmed). Unset/empty/unrecognized ⇒ `false` — the caller decides what "not
37
+ * explicitly on" defaults to.
38
+ */
39
+ export declare function envFlagOn(v: string | undefined): boolean;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * envFlag.ts — single spelling-set source for boolean-shaped `SEMA_*` env knobs (REF-CC-141, P2
3
+ * 台账 dup-02,wave1 卡B,2026-08-01).
4
+ *
5
+ * WHY: ten call sites across nine `*Wire*`/`*WireCaps*` modules each hand-rolled their own
6
+ * off/on-spelling check for a `SEMA_*` boolean env knob, and the four variants DISAGREED:
7
+ * ① 4-word off-set `'0'|'false'|'no'|'off'` (6 copies: forkWireCaps.ts isFalsy,
8
+ * selfOrchestrationWireCaps.ts isFalsy, retainBackgroundWireCaps.ts isFalsy,
9
+ * ultracodeWireCaps.ts deferOptedOut + lowFreqOptedOut, hooksWireCaps.ts isGoalStopHookWireArmed);
10
+ * ② 2-word off-set `'false'|'0'` (2 copies: detachWire.ts headlessDetachDisabledByEnv,
11
+ * finalVerifyWire.ts headlessFinalVerifyDisabledByEnv) — `=off` / `=no` silently did nothing;
12
+ * ③ 4-word off-set but a DIFFERENT fourth word `'off'|'0'|'false'|'none'`
13
+ * (attachmentsWireCaps.ts attachmentsForRequest — accepted "none", not "no");
14
+ * ④ 2-word set WITHOUT lowercasing `raw==='true'||raw==='1'` / `raw==='false'||raw==='0'`
15
+ * (interactiveToolsWire.ts resolveHeadlessInteractiveTools — `SEMA_HEADLESS_INTERACTIVE_TOOLS=False`
16
+ * fell through to the "not a boolean" warn-and-default path instead of being recognized).
17
+ *
18
+ * User-visible consequence (dup-02 evidence): the SAME settings.json could have
19
+ * `SEMA_HEADLESS_DETACH=off` silently ignored (falls back to default-ON) sitting right next to
20
+ * `SEMA_RETAIN_BACKGROUND=off` which worked — no way for a user to know which spelling their knob
21
+ * actually honors.
22
+ *
23
+ * FIX: one leaf module, one spelling set PER POLARITY, each the UNION across all four prior
24
+ * variants (only-tightens-never-loosens direction inverted here on purpose — widen everywhere,
25
+ * never narrow a caller's existing accepted set). Zero dependencies (no `hostEnv`/`EnvLike` import —
26
+ * operates on the already-extracted string, so it stays trivially portable and unit-testable).
27
+ */
28
+ /** OFF spellings — union of all four prior variants (see file header). Case-insensitive, trimmed. */
29
+ const OFF_SPELLINGS = new Set(['0', 'false', 'no', 'off', 'none']);
30
+ /** ON spellings — union of all prior variants that had an explicit truthy branch (forkWireCaps
31
+ * isTruthy `1/true/yes/on`; interactiveToolsWire's narrower `true/1`). Case-insensitive, trimmed. */
32
+ const ON_SPELLINGS = new Set(['1', 'true', 'yes', 'on']);
33
+ function normalize(v) {
34
+ return typeof v === 'string' ? v.trim().toLowerCase() : '';
35
+ }
36
+ /**
37
+ * True iff `v` is an explicit OFF spelling ({@link OFF_SPELLINGS}: `0`/`false`/`no`/`off`/`none`,
38
+ * case-insensitive, trimmed). Unset/empty/unrecognized ⇒ `false` — the caller decides what "not
39
+ * explicitly off" defaults to (many of these knobs are default-ON escape hatches).
40
+ */
41
+ export function envFlagOff(v) {
42
+ return OFF_SPELLINGS.has(normalize(v));
43
+ }
44
+ /**
45
+ * True iff `v` is an explicit ON spelling ({@link ON_SPELLINGS}: `1`/`true`/`yes`/`on`,
46
+ * case-insensitive, trimmed). Unset/empty/unrecognized ⇒ `false` — the caller decides what "not
47
+ * explicitly on" defaults to.
48
+ */
49
+ export function envFlagOn(v) {
50
+ return ON_SPELLINGS.has(normalize(v));
51
+ }
@@ -10,8 +10,9 @@
10
10
  * PRECEDENCE(off 优先,任何一条命中即不 stamp;否则默认 true):
11
11
  * a) `--no-final-verify` flag → off(一键回上游 2.1.207 严格对齐态:上游
12
12
  * `-p` 全文 0 处 final verification,默认开属超集;上游对应扩展点是 Stop hook)。
13
- * b) `SEMA_HEADLESS_FINAL_VERIFY` env('false'/'0') → off。settings.json `env` 块 → 1a-envseed →
14
- * process.env,其它 wire 配置(SEMA_HEADLESS_SCENARIO 等)同一车道。
13
+ * b) `SEMA_HEADLESS_FINAL_VERIFY` env(envFlagOff 拼写集,REF-CC-141 dup-02 单源)→ off。
14
+ * settings.json `env` 块 → 1a-envseed → process.env,其它 wire 配置(SEMA_HEADLESS_SCENARIO 等)
15
+ * 同一车道。
15
16
  * c) 用户配置了 Stop hook(settings hooks.Stop,经 hooksForWire 投影)→ off【对抗轮硬性 amendment】:
16
17
  * 引擎 finalVerification 分支在 Stop hook 查询之前 return(runtask.js:1430-1443),默认开会把
17
18
  * 用户 Stop hook 延迟至多 2 turn——对上游迁移来的 hook 用户是真行为变化,故有 Stop hook 即让位。
@@ -39,9 +40,10 @@ export declare const HEADLESS_FINAL_VERIFY_ENV = "SEMA_HEADLESS_FINAL_VERIFY";
39
40
  */
40
41
  export declare function parseNoFinalVerifyArgv(argv: string[]): boolean;
41
42
  /**
42
- * The settings-lane off-switch:`SEMA_HEADLESS_FINAL_VERIFY` = 'false' or '0'(case-insensitive)
43
- * disables the stamp。其它任何值(含空串/'true'/typo)= 默认态 on——fail-soft:一个 settings typo
44
- * 只会落回默认开,绝不 brick 任何 run(scenarioWire settings-lane 纪律)。
43
+ * The settings-lane off-switch:`SEMA_HEADLESS_FINAL_VERIFY` 设成 {@link envFlagOff} 拼写集
44
+ * (REF-CC-141 dup-02 单源:`0`/`false`/`no`/`off`/`none`,大小写不敏感)disables the stamp
45
+ * 其它任何值(含空串/'true'/typo)= 默认态 on——fail-soft:一个 settings typo 只会落回默认开,
46
+ * 绝不 brick 任何 run(scenarioWire 的 settings-lane 纪律)。
45
47
  */
46
48
  export declare function headlessFinalVerifyDisabledByEnv(env?: EnvLike): boolean;
47
49
  /**
@@ -1,4 +1,5 @@
1
1
  import { hostEnv } from './hostEnv.js';
2
+ import { envFlagOff } from './envFlag.js';
2
3
  /** Settings-lane env knob(settings.json `env` 块 → 1a-envseed → process.env)。'false'/'0' = off。 */
3
4
  export const HEADLESS_FINAL_VERIFY_ENV = 'SEMA_HEADLESS_FINAL_VERIFY';
4
5
  /**
@@ -15,13 +16,13 @@ export function parseNoFinalVerifyArgv(argv) {
15
16
  return false;
16
17
  }
17
18
  /**
18
- * The settings-lane off-switch:`SEMA_HEADLESS_FINAL_VERIFY` = 'false' or '0'(case-insensitive)
19
- * disables the stamp。其它任何值(含空串/'true'/typo)= 默认态 on——fail-soft:一个 settings typo
20
- * 只会落回默认开,绝不 brick 任何 run(scenarioWire settings-lane 纪律)。
19
+ * The settings-lane off-switch:`SEMA_HEADLESS_FINAL_VERIFY` 设成 {@link envFlagOff} 拼写集
20
+ * (REF-CC-141 dup-02 单源:`0`/`false`/`no`/`off`/`none`,大小写不敏感)disables the stamp
21
+ * 其它任何值(含空串/'true'/typo)= 默认态 on——fail-soft:一个 settings typo 只会落回默认开,
22
+ * 绝不 brick 任何 run(scenarioWire 的 settings-lane 纪律)。
21
23
  */
22
24
  export function headlessFinalVerifyDisabledByEnv(env = hostEnv()) {
23
- const v = env[HEADLESS_FINAL_VERIFY_ENV]?.trim().toLowerCase();
24
- return v === 'false' || v === '0';
25
+ return envFlagOff(env[HEADLESS_FINAL_VERIFY_ENV]);
25
26
  }
26
27
  /**
27
28
  * 用户 Stop hook 在场判定:hooksForWire() 投影(policy→user→project→local 合并、trust/managed 门后)