@sema-agent/client-core 0.14.0 → 0.16.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.
@@ -18,7 +18,18 @@ export type SandboxParseResult = {
18
18
  * `--sandbox=<v>` forms). No flag ⇒ `{mode:'default'}`. Repeated flag ⇒ LAST wins (commander convention).
19
19
  * Missing value / flag-shaped value / empty / over-long ⇒ fail-LOUD parse error (never a silent default —
20
20
  * a mis-typed environment choice must not quietly run on the wrong image).
21
- */
21
+ *
22
+ * 🔴 [2393] sweep-F10(2026-08-03 全窗复审)**承诺范围如实收窄**(裁定:行为不改,话改):
23
+ * 「fail-LOUD,never a silent default」这句话现在**不**覆盖一种输入 —— 同一个旗**重复出现**且
24
+ * 前一次缺值/非法、后一次合法(`--sandbox --sandbox foo`):
25
+ * 收编前是 per-occurrence eager validate,在**第一个**缺值 occurrence 上就 fail-loud;收编后走
26
+ * `lastFlagValue`('invalidate' 缺省)= 后一次合法 occurrence 覆盖前一次,整体接受。
27
+ * 为什么裁定不改回去:本模块自己写着的契约就是「Repeated flag ⇒ LAST wins(commander convention)」,
28
+ * eager-validate 违反的正是这条契约(它让**被覆盖掉的**那一次决定整条命令的成败)。
29
+ * 剩下的形(唯一一次出现却缺值 / 值是旗形 / 空 / 超长 / 带空白)**仍然全部 fail-loud** —— 那才是
30
+ * 「打错一个字就悄悄跑在别的镜像上」真正要防的那一族。
31
+ * ⚠️ `docs/REFACTOR-LEDGER.md` REF-CC-143 的处置列此前写的是「缺值策略**统一** fail-loud」,
32
+ * 与落地的两策略叶(`'invalidate'` / `'latch-previous'`)相悖,已同批改正(sweep-F11)。 */
22
33
  export declare function parseSandboxArgv(argv: readonly string[]): SandboxParseResult;
23
34
  /** The request-body stamp for a selection: EXPLICIT ⇒ `{sandboxImageProfile}`, default/auto ⇒ nothing
24
35
  * (auto is advisory-by-prompt — the submit itself never carries a profile, per clay's c) 口径). */
@@ -37,7 +37,18 @@ const USAGE_HINT = "sema: --sandbox expects a profile name or 'auto'.\n" +
37
37
  * `--sandbox=<v>` forms). No flag ⇒ `{mode:'default'}`. Repeated flag ⇒ LAST wins (commander convention).
38
38
  * Missing value / flag-shaped value / empty / over-long ⇒ fail-LOUD parse error (never a silent default —
39
39
  * a mis-typed environment choice must not quietly run on the wrong image).
40
- */
40
+ *
41
+ * 🔴 [2393] sweep-F10(2026-08-03 全窗复审)**承诺范围如实收窄**(裁定:行为不改,话改):
42
+ * 「fail-LOUD,never a silent default」这句话现在**不**覆盖一种输入 —— 同一个旗**重复出现**且
43
+ * 前一次缺值/非法、后一次合法(`--sandbox --sandbox foo`):
44
+ * 收编前是 per-occurrence eager validate,在**第一个**缺值 occurrence 上就 fail-loud;收编后走
45
+ * `lastFlagValue`('invalidate' 缺省)= 后一次合法 occurrence 覆盖前一次,整体接受。
46
+ * 为什么裁定不改回去:本模块自己写着的契约就是「Repeated flag ⇒ LAST wins(commander convention)」,
47
+ * eager-validate 违反的正是这条契约(它让**被覆盖掉的**那一次决定整条命令的成败)。
48
+ * 剩下的形(唯一一次出现却缺值 / 值是旗形 / 空 / 超长 / 带空白)**仍然全部 fail-loud** —— 那才是
49
+ * 「打错一个字就悄悄跑在别的镜像上」真正要防的那一族。
50
+ * ⚠️ `docs/REFACTOR-LEDGER.md` REF-CC-143 的处置列此前写的是「缺值策略**统一** fail-loud」,
51
+ * 与落地的两策略叶(`'invalidate'` / `'latch-previous'`)相悖,已同批改正(sweep-F11)。 */
41
52
  export function parseSandboxArgv(argv) {
42
53
  // REF-CC-143(dup-07)收编:extraction 改吃共用 lastFlagValue(argvFlagValue.ts)——scan-all-then-
43
54
  // validate-once, matching this module's own documented "Repeated flag ⇒ LAST wins" contract (the
@@ -46,7 +46,18 @@ export type ScenarioParseResult = {
46
46
  * flag ⇒ LAST wins (commander convention). Missing value / flag-shaped value / name-shape violation ⇒
47
47
  * fail-LOUD parse error (never a silent default — a mis-typed routing choice must not quietly run on
48
48
  * the wrong tool surface; same stance as `--sandbox`).
49
- */
49
+ *
50
+ * 🔴 [2393] sweep-F10(2026-08-03 全窗复审)**承诺范围如实收窄**(裁定:行为不改,话改):
51
+ * 「fail-LOUD,never a silent default」这句话现在**不**覆盖一种输入 —— 同一个旗**重复出现**且
52
+ * 前一次缺值/非法、后一次合法(`--scenario --scenario foo`):
53
+ * 收编前是 per-occurrence eager validate,在**第一个**缺值 occurrence 上就 fail-loud;收编后走
54
+ * `lastFlagValue`('invalidate' 缺省)= 后一次合法 occurrence 覆盖前一次,整体接受。
55
+ * 为什么裁定不改回去:本模块自己写着的契约就是「Repeated flag ⇒ LAST wins(commander convention)」,
56
+ * eager-validate 违反的正是这条契约(它让**被覆盖掉的**那一次决定整条命令的成败)。
57
+ * 剩下的形(唯一一次出现却缺值 / 值是旗形 / 空 / 超长 / 带空白)**仍然全部 fail-loud** —— 那才是
58
+ * 「打错一个字就悄悄跑在别的镜像上」真正要防的那一族。
59
+ * ⚠️ `docs/REFACTOR-LEDGER.md` REF-CC-143 的处置列此前写的是「缺值策略**统一** fail-loud」,
60
+ * 与落地的两策略叶(`'invalidate'` / `'latch-previous'`)相悖,已同批改正(sweep-F11)。 */
50
61
  export declare function parseScenarioArgv(argv: readonly string[]): ScenarioParseResult;
51
62
  /**
52
63
  * The settings-lane default (`SEMA_HEADLESS_SCENARIO`). FAIL-SOFT on a bad value (stderr warning +
@@ -44,7 +44,18 @@ const USAGE_HINT = "sema: --scenario expects a scenario name (lowercase letters/
44
44
  * flag ⇒ LAST wins (commander convention). Missing value / flag-shaped value / name-shape violation ⇒
45
45
  * fail-LOUD parse error (never a silent default — a mis-typed routing choice must not quietly run on
46
46
  * the wrong tool surface; same stance as `--sandbox`).
47
- */
47
+ *
48
+ * 🔴 [2393] sweep-F10(2026-08-03 全窗复审)**承诺范围如实收窄**(裁定:行为不改,话改):
49
+ * 「fail-LOUD,never a silent default」这句话现在**不**覆盖一种输入 —— 同一个旗**重复出现**且
50
+ * 前一次缺值/非法、后一次合法(`--scenario --scenario foo`):
51
+ * 收编前是 per-occurrence eager validate,在**第一个**缺值 occurrence 上就 fail-loud;收编后走
52
+ * `lastFlagValue`('invalidate' 缺省)= 后一次合法 occurrence 覆盖前一次,整体接受。
53
+ * 为什么裁定不改回去:本模块自己写着的契约就是「Repeated flag ⇒ LAST wins(commander convention)」,
54
+ * eager-validate 违反的正是这条契约(它让**被覆盖掉的**那一次决定整条命令的成败)。
55
+ * 剩下的形(唯一一次出现却缺值 / 值是旗形 / 空 / 超长 / 带空白)**仍然全部 fail-loud** —— 那才是
56
+ * 「打错一个字就悄悄跑在别的镜像上」真正要防的那一族。
57
+ * ⚠️ `docs/REFACTOR-LEDGER.md` REF-CC-143 的处置列此前写的是「缺值策略**统一** fail-loud」,
58
+ * 与落地的两策略叶(`'invalidate'` / `'latch-previous'`)相悖,已同批改正(sweep-F11)。 */
48
59
  export function parseScenarioArgv(argv) {
49
60
  // REF-CC-143(dup-07)收编:extraction 改吃共用 lastFlagValue(argvFlagValue.ts)——scan-all-then-
50
61
  // validate-once, matching this function's own documented "Repeated flag ⇒ LAST wins" contract (the
@@ -73,6 +73,26 @@
73
73
  * @sema-agent/client-core), the contract shape does not.
74
74
  */
75
75
  const bad = (reason, field) => field === undefined ? { reason } : { reason, field };
76
+ /**
77
+ * 🔴 [2393] sweep-F8(2026-08-03 全窗复审):**嵌套失败的 field 路径不许在汇聚点被丢掉**。
78
+ * 修前三种写法各写各的:`chrome` 那条把已经算好的 `chrome.laneProof.lane` 整个覆盖成 `"chrome"`;
79
+ * `request`/`session`/`results[i]`/`sessions[i]` 把路径丢成父键名(reason 前缀里还留着索引,
80
+ * field 里没有)。这与 `SeatValidationFailure.field` 自己的定义(「嵌套位用点号,如
81
+ * `chrome.laneProof`」)直接相悖,也削弱 REF-CC-069 存在的理由 ——「一条被门吃掉的事件从此可诊断」:
82
+ * 宿主日志只会说「chrome 有问题」,而门明明知道是 `chrome.laneProof.lane`。
83
+ * @param prefix 父位路径(`"chrome"` / `` `results[${i}]` ``)
84
+ * @param verdict 子判据的失败(field 可能已是点号路径,也可能是裸键名,也可能缺席)
85
+ */
86
+ const nested = (prefix, verdict) => {
87
+ // 子判据自己已经带上父前缀时(checkSeatChromeEnvelope 收的就是 field 参数)不再重复拼。
88
+ const path = verdict.field === undefined
89
+ ? prefix
90
+ : verdict.field === prefix || verdict.field.startsWith(`${prefix}.`)
91
+ ? verdict.field
92
+ : `${prefix}.${verdict.field}`;
93
+ const reason = verdict.reason.startsWith(`${prefix}:`) ? verdict.reason : `${prefix}: ${verdict.reason}`;
94
+ return bad(reason, path);
95
+ };
76
96
  /** 存在性判定(§B10:`!!v` 那种真值判定会把空串/0 当缺席,对象位则会把 `{}` 当「有证」)。 */
77
97
  const isObject = (v) => typeof v === "object" && v !== null;
78
98
  const isAbsent = (v) => typeof v === "undefined";
@@ -318,7 +338,7 @@ const checkLocalSessionEvent = (v) => {
318
338
  if (!isAbsent(v.request)) {
319
339
  const verdict = checkToolPermissionRequest(v.request);
320
340
  if (verdict !== true)
321
- return bad(`request: ${verdict.reason}`, "request");
341
+ return nested("request", verdict); // sweep-F8:路径不丢
322
342
  }
323
343
  if (!optionalString(v.data))
324
344
  return bad("data must be a string when present", "data");
@@ -336,7 +356,7 @@ const checkLocalSessionEvent = (v) => {
336
356
  if (!isAbsent(v.session)) {
337
357
  const verdict = checkLocalSessionRecord(v.session);
338
358
  if (verdict !== true)
339
- return bad(`session: ${verdict.reason}`, "session");
359
+ return nested("session", verdict); // sweep-F8:路径不丢
340
360
  }
341
361
  if (!optionalString(v.userMessageUuid))
342
362
  return bad("userMessageUuid must be a string when present", "userMessageUuid");
@@ -352,7 +372,7 @@ const checkLocalSessionEvent = (v) => {
352
372
  if (!isAbsent(v.chrome)) {
353
373
  const verdict = checkSeatChromeEnvelope(v.chrome, "chrome");
354
374
  if (verdict !== true)
355
- return bad(verdict.reason, "chrome");
375
+ return nested("chrome", verdict); // sweep-F8:门算好的 chrome.laneProof.lane 不许被覆盖
356
376
  }
357
377
  return true;
358
378
  };
@@ -469,7 +489,7 @@ const checkSessionSearchEnvelope = (v) => {
469
489
  for (let i = 0; i < results.length; i += 1) {
470
490
  const verdict = checkSessionSearchResult(results[i]);
471
491
  if (verdict !== true)
472
- return bad(`results[${i}]: ${verdict.reason}`, "results");
492
+ return nested(`results[${i}]`, verdict); // sweep-F8
473
493
  }
474
494
  return true;
475
495
  };
@@ -482,7 +502,7 @@ const checkSessionsState = (v) => {
482
502
  for (let i = 0; i < sessions.length; i += 1) {
483
503
  const verdict = checkLocalSessionRecord(sessions[i]);
484
504
  if (verdict !== true)
485
- return bad(`sessions[${i}]: ${verdict.reason}`, "sessions");
505
+ return nested(`sessions[${i}]`, verdict); // sweep-F8
486
506
  }
487
507
  return true;
488
508
  };
package/dist/steering.js CHANGED
@@ -72,12 +72,21 @@ function parseBackgroundTaskLine(line) {
72
72
  const m = /^- \[([^\]]+)\] Task "([\s\S]*)$/.exec(head);
73
73
  if (!m)
74
74
  return null;
75
+ // 🔴 [2393] sweep-F16(2026-08-03 全窗复审):这里**不许**回落成空串。两个捕获组都是非可选的,
76
+ // `m` 非空时它们必在场(所以这条早退今天不可达),但 `?? ''` 的**形**是「不知道就渲空串」——
77
+ // 与同窗刚立的口径(`fleetAgentPanelProjection`「缺席就是 undefined,这里不许兜 0」/
78
+ // `seatContract`「绝不回落成 0(那会渲成 1970)」)相反,且真出问题时会静默产出一条 taskId
79
+ // 为空串的任务行(谁也定位不到)。noUncheckedIndexedAccess 适配的正解是**如实拒绝**。
80
+ const taskId = m[1];
81
+ const description = m[2];
82
+ if (taskId === undefined || description === undefined)
83
+ return null;
75
84
  return {
76
85
  type: 'task_status',
77
- taskId: m[1] ?? '',
86
+ taskId,
78
87
  taskType: 'local_bash',
79
88
  status,
80
- description: m[2] ?? '',
89
+ description,
81
90
  deltaSummary: null,
82
91
  };
83
92
  }
@@ -44,6 +44,19 @@ export type SubagentContentItem = {
44
44
  kind: 'echo';
45
45
  text: string;
46
46
  };
47
+ /**
48
+ * 🔴 [2393] sweep-F15(2026-08-03 全窗复审)**记一笔口径分叉**(裁定:留 `| undefined`,写明为什么):
49
+ * 下面五个可选位在 eopt 收官批被写成 `?: T | undefined`(= 允许显式赋 `undefined` 落键),而同窗
50
+ * 另两处的口径是相反的(`fleetAgentPanelProjection` 的「🔴 缺席即不落键(消费端据键在不在判三态)」、
51
+ * `toolResult` 的「公面键恒在形不许被 eopt 批翻成条件展开」)。两种口径都出自同一批,当时没有一处记账。
52
+ * 为什么这里走另一条:**本类型的消费端一处都不用「键在不在」判三态** —— 本文件 `apply()` 全部用
53
+ * `typeof ev.delta === 'string'` / `ev.output ?? ''` / `ev.isError === true` 这类**值级**判据
54
+ * (逐行核过,见下方 switch),所以 `{delta: undefined}` 与「不落键」在本模块里可观测等价;而生产者
55
+ * 是宿主的帧适配腿,`{ delta: maybe }` 这种直写形在 eopt 下会编译红,逼它们改写成条件展开只是把
56
+ * 噪音推给三端,换不来任何判别力。
57
+ * ⚠️ 反过来的约束:**谁想给这个类型加一个用「键在不在」判三态的位,就必须同批把这里改成条件展开**
58
+ * (那时两种口径就不能再共存了)。
59
+ */
47
60
  export interface SubagentContentEvent {
48
61
  type: 'text_delta' | 'reasoning_delta' | 'tool_start' | 'tool_end';
49
62
  taskId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/client-core",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Client-side session runtime shared by every sema human client (TUI / web / desktop): sema wire frames (AgentEvent) -> CC session vocabulary (SDKMessage) with dual-plane output (transcript/chrome), deterministic transcript ids, lane discipline as a type, and the notification/dedup ledgers. Every CC-skin shape is collected here so the wire itself stays neutral. Blackboard [1832] design axioms; [1651]/[1652]/[1653] signed seam design. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
5
5
  "license": "MIT",
6
6
  "type": "module",